Compare commits
3 commits
feat/trade
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 876538c57f | |||
| e703d64a7f | |||
|
|
0c5246913a |
1 changed files with 88 additions and 0 deletions
88
tradein-mvp/backend/data/sql/192_tradein_users_auth.sql
Normal file
88
tradein-mvp/backend/data/sql/192_tradein_users_auth.sql
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
-- 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;
|
||||
Loading…
Add table
Reference in a new issue