gendesign/tradein-mvp/backend/data/sql/192_tradein_users_auth.sql
bot-backend 0c5246913a
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 1m10s
feat(tradein/db): tradein_users + tradein_sessions schema (#2551)
2026-07-30 09:40:05 +03:00

88 lines
5.5 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- Migration 192: tradein_users + tradein_sessions — DB-backed auth (issue #2551, эпик #2549)
--
-- WHY:
-- Trade-in auth сейчас держится на legacy Caddy basic-auth fallback (см. auth/roles.yaml,
-- упомянут в 191_account_quota_unlimited_flag.sql как "хардкод username в коде"). Эпик #2549
-- переводит auth на DB-backed модель: пользователи + сессии как данные, роли admin/manager/
-- employee с иерархией manager -> employee. Эта миграция — только схема (Foundation),
-- без seed-данных (seed — отдельная задача #2557) и без Python-кода (backend wiring — отдельно).
--
-- WHAT:
-- 1. tradein_users — identity + role + org-иерархия.
-- - password_hash NULL допустим: переходный период, когда логин ещё идёт через
-- legacy Caddy fallback, а не через password verify в приложении.
-- - role CHECK ('admin','manager','employee') — три уровня доступа.
-- - manager_id — self-FK, ON DELETE SET NULL (увольнение/удаление manager'а не должно
-- каскадно сносить его employees, они просто остаются без привязки).
-- - CHECK role_manager_hierarchy: admin/manager обязаны иметь manager_id IS NULL
-- (это top-level роли, у них нет "начальника" в этой модели); employee — manager_id
-- любой, включая NULL (свободный слот employee без организации допустим).
-- 2. tradein_sessions — токен-based сессии, привязаны к user_id, ON DELETE CASCADE
-- (удалили пользователя — его сессии теряют смысл, каскадная очистка корректна).
-- last_seen_at отдельно от created_at — для idle-timeout / активности сессии.
-- 3. Индексы: expires_at (уборка протухших сессий), user_id (список сессий юзера),
-- partial на manager_id (иерархия) — WHERE manager_id IS NOT NULL, т.к. большинство
-- admin/manager строк это NULL и не участвуют в lookup "employees этого manager'а".
--
-- IDEMPOTENCY:
-- CREATE TABLE IF NOT EXISTS + CREATE INDEX IF NOT EXISTS. Повторный прогон — no-op.
-- CHECK-констрейнты добавлены inline в CREATE TABLE (не через ALTER) — при повторном
-- запуске CREATE TABLE IF NOT EXISTS не выполнится вообще, констрейнт не задублируется.
--
-- Dependencies: нет (новые таблицы, ничего существующего не меняем).
-- Deploy order: эта миграция — Foundation эпика #2549. Seed (#2557) и backend auth-код —
-- отдельные PR'ы ПОСЛЕ этой (SQL-схема первой, см. .claude/rules/sql.md "Migration order").
BEGIN;
CREATE TABLE IF NOT EXISTS tradein_users (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
username text NOT NULL UNIQUE,
password_hash text NULL,
role text NOT NULL CHECK (role IN ('admin', 'manager', 'employee')),
manager_id bigint NULL REFERENCES tradein_users(id) ON DELETE SET NULL,
display_name text NULL,
org_name text NULL,
email text NULL,
is_active boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT tradein_users_role_manager_hierarchy_ck CHECK (
role NOT IN ('admin', 'manager') OR manager_id IS NULL
)
);
COMMENT ON TABLE tradein_users IS
'Trade-in DB-backed auth — пользователи (issue #2551, эпик #2549). password_hash NULL '
'допустим в переходный период (логин через legacy Caddy fallback). Seed — отдельно (#2557).';
COMMENT ON COLUMN tradein_users.password_hash IS
'NULL = логин только через legacy Caddy basic-auth fallback, не через password verify.';
COMMENT ON COLUMN tradein_users.manager_id IS
'Self-FK на tradein_users(id). NULL для admin/manager (top-level, CHECK ниже) или для '
'employee без назначенной организации.';
CREATE TABLE IF NOT EXISTS tradein_sessions (
token text PRIMARY KEY,
user_id bigint NOT NULL REFERENCES tradein_users(id) ON DELETE CASCADE,
created_at timestamptz NOT NULL DEFAULT now(),
expires_at timestamptz NOT NULL,
last_seen_at timestamptz NOT NULL DEFAULT now(),
ip_address inet NULL,
user_agent text NULL
);
COMMENT ON TABLE tradein_sessions IS
'Trade-in DB-backed auth — активные сессии (issue #2551, эпик #2549). '
'ON DELETE CASCADE от tradein_users: удалённый пользователь теряет все сессии.';
CREATE INDEX IF NOT EXISTS tradein_sessions_expires_at_idx
ON tradein_sessions (expires_at);
CREATE INDEX IF NOT EXISTS tradein_sessions_user_id_idx
ON tradein_sessions (user_id);
CREATE INDEX IF NOT EXISTS tradein_users_manager_id_idx
ON tradein_users (manager_id)
WHERE manager_id IS NOT NULL;
COMMIT;