Структура БД схема v1

PostgreSQL. Центр модели: workspace платит подписку, внутри projects, каждый audit_run = один этап с вердиктом и issues.

Принцип Пользователь может состоять в нескольких workspace. Подписка и лимиты — на workspace, не на user. Разовый аудит может жить без workspace до апсейла.

Сущности

ТаблицаНазначениеКлючевые поля
usersЧеловек: вход, профиль, Telegramid, email, telegram_chat_id, name, locale
workspacesПлатящая сущность: компания / командаid, name, slug, owner_user_id, plan_id, status
workspace_membersУчастники и ролиworkspace_id, user_id, role
plansСправочник тарифовid, code, name, price_rub, limits_json
subscriptionsАктивная подписка workspaceworkspace_id, plan_id, status, period_start/end
usage_countersЛимиты прогонов за периодworkspace_id, period_ym, runs_used, runs_limit
projectsСайт / доменworkspace_id, name, base_url, last_verdict
audit_runsОдин прогон этапаproject_id, stage, status, verdict, summary_json
audit_issuesНаходки прогонаrun_id, severity, code, message, tip_json
paymentsОплатыworkspace_id, type, amount_rub, provider, status
one_off_ordersРазовый аудитuser_id, product_code, target_url, payment_id, run_id
ci_integrationsGitHub / GitLab / webhookproject_id, provider, repo, enabled

DDL (ключевые таблицы)

users

CREATE TABLE users (
  id               UUID PRIMARY KEY,
  email            TEXT UNIQUE,
  telegram_chat_id BIGINT UNIQUE,
  name             TEXT,
  locale           TEXT DEFAULT 'ru',
  created_at       TIMESTAMPTZ NOT NULL,
  updated_at       TIMESTAMPTZ NOT NULL
);

workspaces

CREATE TABLE workspaces (
  id            UUID PRIMARY KEY,
  name          TEXT NOT NULL,
  slug          TEXT UNIQUE NOT NULL,
  owner_user_id UUID REFERENCES users(id),
  plan_id       UUID REFERENCES plans(id),
  status        TEXT NOT NULL, -- active|suspended
  created_at    TIMESTAMPTZ NOT NULL
);

workspace_members

CREATE TABLE workspace_members (
  workspace_id UUID REFERENCES workspaces(id),
  user_id      UUID REFERENCES users(id),
  role         TEXT NOT NULL, -- owner|admin|seo|developer|viewer
  invited_at   TIMESTAMPTZ,
  joined_at    TIMESTAMPTZ,
  PRIMARY KEY (workspace_id, user_id)
);

plans.limits_json (пример)

{
  "max_projects": 5,
  "max_runs_per_month": 150,
  "max_members": 5,
  "stages": ["base", "deep", "prod"],
  "history_days": 365,
  "ci_enabled": true,
  "ai_credits": 50
}

subscriptions

CREATE TABLE subscriptions (
  id            UUID PRIMARY KEY,
  workspace_id  UUID REFERENCES workspaces(id),
  plan_id       UUID REFERENCES plans(id),
  status        TEXT NOT NULL, -- trialing|active|past_due|canceled
  billing_cycle TEXT NOT NULL, -- monthly|yearly
  period_start  DATE NOT NULL,
  period_end    DATE NOT NULL,
  provider      TEXT, -- yookassa|invoice|manual
  external_id   TEXT,
  created_at    TIMESTAMPTZ NOT NULL
);

usage_counters

CREATE TABLE usage_counters (
  workspace_id UUID REFERENCES workspaces(id),
  period_ym    TEXT NOT NULL, -- '2026-07'
  runs_used    INT NOT NULL DEFAULT 0,
  runs_limit   INT NOT NULL,
  ai_used      INT NOT NULL DEFAULT 0,
  PRIMARY KEY (workspace_id, period_ym)
);
-- Инкремент при старте audit_run; бот проверяет перед запуском.

projects

CREATE TABLE projects (
  id           UUID PRIMARY KEY,
  workspace_id UUID REFERENCES workspaces(id),
  name         TEXT NOT NULL,
  base_url     TEXT NOT NULL,
  status       TEXT DEFAULT 'active',
  last_run_id  UUID,
  last_verdict TEXT, -- ci_ok|ci_block
  created_at   TIMESTAMPTZ NOT NULL,
  UNIQUE (workspace_id, base_url)
);

audit_runs + audit_issues

CREATE TABLE audit_runs (
  id           UUID PRIMARY KEY,
  project_id   UUID REFERENCES projects(id),
  workspace_id UUID NOT NULL, -- денорм. для дашборда
  stage        TEXT NOT NULL, -- base|deep|prod
  triggered_by UUID REFERENCES users(id),
  source       TEXT NOT NULL, -- telegram|web|cli|ci
  status       TEXT NOT NULL, -- queued|running|done|failed
  verdict      TEXT, -- ci_ok|ci_block
  summary_json JSONB,
  report_text  TEXT,
  started_at   TIMESTAMPTZ,
  finished_at  TIMESTAMPTZ,
  created_at   TIMESTAMPTZ NOT NULL
);

CREATE TABLE audit_issues (
  id       UUID PRIMARY KEY,
  run_id   UUID REFERENCES audit_runs(id) ON DELETE CASCADE,
  severity TEXT NOT NULL,
  code     TEXT NOT NULL,
  message  TEXT NOT NULL,
  url      TEXT,
  tip_json JSONB
);

payments

CREATE TABLE payments (
  id           UUID PRIMARY KEY,
  workspace_id UUID REFERENCES workspaces(id),
  user_id      UUID REFERENCES users(id),
  type         TEXT NOT NULL, -- subscription|one_off
  amount_rub   INT NOT NULL,
  currency     TEXT DEFAULT 'RUB',
  provider     TEXT NOT NULL,
  status       TEXT NOT NULL, -- pending|paid|failed|refunded
  external_id  TEXT,
  paid_at      TIMESTAMPTZ,
  created_at   TIMESTAMPTZ NOT NULL
);

one_off_orders

CREATE TABLE one_off_orders (
  id           UUID PRIMARY KEY,
  user_id      UUID REFERENCES users(id),
  product_code TEXT NOT NULL, -- audit_stage1|audit_full|pre_release
  target_url   TEXT NOT NULL,
  payment_id   UUID REFERENCES payments(id),
  run_id       UUID REFERENCES audit_runs(id),
  upsell_coupon TEXT,
  created_at   TIMESTAMPTZ NOT NULL
);
-- run без project до апсейла; затем привязка к project

Роли участников

РольПрава
ownerВладелец workspace, billing, удаление
adminПроекты, участники, CI, без смены тарифа
seoЗапуск этапов, просмотр отчётов
developerCI, статусы, без billing
viewerТолько чтение отчётов

Этапы аудита (stage)

stageОписание
baseЭтап 1 — базовая проверка
deepЭтап 2 — детальная
prodЭтап 3 — production

Связи (ER)

users ──< workspace_members >── workspaces
workspaces ──< projects
workspaces ──< subscriptions >── plans
workspaces ──< usage_counters
workspaces ──< payments
projects ──< audit_runs ──< audit_issues
projects ──< ci_integrations
users ──< one_off_orders >── payments
audit_runs ── (optional) one_off_orders.run_id

Индексы

Поток: запуск из Telegram

  1. Бот получает /stage2 site.ru → lookup user по telegram_chat_id
  2. Найти workspace (default) → project по base_url
  3. Проверить usage_counters.runs_used < runs_limit
  4. INSERT audit_runs (queued) → worker → done + audit_issues
  5. UPDATE projects.last_run_id, last_verdict; increment usage_counters
MVP-упрощение На первом этапе можно без ci_integrations и one_off_orders: один workspace на user, plans в конфиге, usage в Redis. Таблицы выше — целевая схема под кабинет и billing.

Миграция от текущего бота

Сейчас: chat_id + last_url в памяти.
Шаг 1: users + projects + audit_runs в Postgres.
Шаг 2: workspaces + subscriptions.
Шаг 3: кабинет читает те же таблицы.