first commit

This commit is contained in:
Витковских Евгений 2026-07-30 18:45:43 +05:00
commit a306e27a18
32 changed files with 1571 additions and 0 deletions

7
Kit.Db.slnx Normal file
View File

@ -0,0 +1,7 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/Kit.Db.Api/Kit.Db.Api.csproj" />
<Project Path="src/Kit.Db.Data/Kit.Db.Data.csproj" />
<Project Path="src/Kit.Db.Domain/Kit.Db.Domain.csproj" />
</Folder>
</Solution>

26
docs/architecture.md Normal file
View File

@ -0,0 +1,26 @@
# Kit.Db - Structure
Файловая струтура
- rules/
- common/
- pg/
- sqlite/
- skills/
- projects/
- pg/
- kit_auth_pg/
- README.md
- settings/
- connection.json
- auth/
- tables/
- functions/
- scripts/
- init/
- deploy/
- auth.psql
- _docs/
- diagrams/
- openapi.yaml
- architecture.md

View File

@ -0,0 +1,100 @@
# Kit.Auth.Token.Db — Описание БД
## Общее описание
База данных для управления токенами авторизации. Хранит владельцев токенов, типы токенов и сами токены с возможностью валидации, отзыва и удаления.
- **Движок:** PostgreSQL
- **Схема:** `token`
---
## Структура проекта (по шаблону)
```
projects/pg/kit_auth_token_pg/
readme.md
settings/
connection.json
token/
tables/
owner.psql
token_type.psql
token.psql
functions/
owner/
owner_insert.psql
owner_select.psql
token/
token_consume.psql
token_delete.psql
token_insert.psql
token_revoke_by_owner.psql
token_revoke.psql
token_select.psql
token_update.psql
token_validate.psql
token_type/
token_type_select.psql
scripts/
init/
deploy/
token.psql # Точка входа модуля
token.post-deploy.psql # Post-deploy скрипты
_docs/
diagrams/
openapi.yaml
architecture.md
```
---
## Таблицы
### owner
Владельцы токенов (пользователи/системы).
### token_type
Справочник типов токенов (access, refresh и т.д.).
### token
Токены авторизации с привязкой к владельцу и типу.
---
## Функции
### owner/
| Функция | Назначение |
|---------|-----------|
| `owner_insert` | Создание владельца |
| owner_select` | Получение владельца |
### token/
| Функция | Назначение |
|---------|-----------|
| `token_insert` | Создание токена |
| `token_select` | Получение токена |
| `token_update` | Обновление токена |
| `token_delete` | Удаление токена |
| `token_consume` | Использование токена |
| `token_validate` | Валидация токена |
| `token_revoke` | Отзыв токена |
| `token_revoke_by_owner` | Отзыв всех токенов владельца |
### token_type/
| Функция | Назначение |
|---------|-----------|
| `token_type_select` | Получение типа токена |
---
## Миграции
- `2026-07-23` — Полная схема (create_full)
---
## Связи с другими БД
- Связана с `Kit.Auth.Db` (модуль `auth`) — владельцы токенов могут быть пользователями из auth.users

103
docs/structure-diagram.md Normal file
View File

@ -0,0 +1,103 @@
# Kit.Db — Структура проекта (ASCII)
```
Kit.Db/
├── rules/ # Правила для LLM по движкам
│ ├── common/ # Общие правила для всех СУБД
│ │ └── ...
│ ├── pg/ # Правила PostgreSQL
│ │ └── db-project-rule.md
│ └── sqlite/ # Правила SQLite
│ └── db-project-rule.md
├── skills/ # LLM промпты (описания таблиц, функций)
│ └── ...
└── projects/ # Проекты БД
├── pg/ # ── PostgreSQL проекты ──
│ │
│ └── kit_auth_pg/ # Проект: БД авторизации
│ │
│ ├── readme.md # Описание БД
│ │
│ ├── settings/ # Настройки подключения
│ │ └── connection.json
│ │
│ ├── auth/ # Модуль: авторизация
│ │ ├── tables/ # DDL таблиц
│ │ │ ├── user.psql
│ │ │ ├── cabinet.psql
│ │ │ └── user_cabinet.psql
│ │ │
│ │ ├── functions/ # SQL функции
│ │ │ ├── user_select_by_ids/
│ │ │ │ └── user_select_by_ids.psql
│ │ │ └── user_create/
│ │ │ └── user_create.psql
│ │ │
│ │ ├── scripts/ # Произвольные скрипты
│ │ │ └── seed_roles.sql
│ │ │
│ │ ├── init/ # Инициализация
│ │ │ └── create_schema.psql
│ │ │
│ │ ├── deploy/ # Миграции
│ │ │ ├── 2026-07-08_user-select-by-ids.sql
│ │ │ └── 2026-07-09-user-seed-merge.sql
│ │ │
│ │ ├── auth.psql # Точка входа модуля
│ │ └── auth.post-deploy.psql
│ │
│ └── _docs/ # Документация проекта
│ ├── diagrams/
│ ├── openapi.yaml
│ └── architecture.md
└── sl3/ # ── SQLite проекты ──
└── kit_example_sl3/ # Проект: пример
├── readme.md
├── settings/
│ └── connection.json
├── main/ # Модуль: основной
│ ├── tables/
│ ├── scripts/
│ ├── init/
│ ├── deploy/
│ └── main.sql
└── _docs/
├── diagrams/
├── openapi.yaml
└── architecture.md
```
---
## Связи между уровнями
```
rules/ ──────────► LLM использует при формировании проектов
common/ │
pg/ │
sqlite/ ▼
projects/
pg/ ─── PostgreSQL проекты
sl3/ ── SQLite проекты
skills/ ─────────► LLM использует при генерации описаний
projects/{engine}/{project}/
readme.md ──────────► Описание БД
settings/ ──────────► Подключение
{module}/ ──────────► SQL исходники
tables/ ──────────► DDL таблиц
functions/ ───────► SQL функции (только PG)
scripts/ ─────────► Произвольные SQL
init/ ────────────► Инициализация
deploy/ ──────────► Миграции
_docs/ ─────────────► Документация

198
docs/tz.md Normal file
View File

@ -0,0 +1,198 @@
# ТЗ — Формирование проекта для БД
## Общее описание
На основании файловой структуры (см. architecture.md) формируется проект для конкретной базы данных. Каждый проект — автономная единица с собственными настройками, исходниками и документацией.
---
## Шаблон структуры
```
Kit.Db/
├── rules/ # Правила для LLM по движкам
│ ├── common/ # Общие правила для всех СУБД
│ │ └── ...
│ ├── pg/ # Правила PostgreSQL
│ │ └── db-project-rule.md
│ └── sqlite/ # Правила SQLite
│ └── db-project-rule.md
├── skills/ # LLM промпты (описания таблиц, функций)
│ └── ...
└── projects/ # Проекты БД
├── pg/ # ── PostgreSQL проекты ──
│ │
│ └── kit_auth_pg/ # Проект: БД авторизации
│ │
│ ├── readme.md # Описание БД
│ │
│ ├── settings/ # Настройки подключения
│ │ └── connection.json
│ │
│ ├── auth/ # Модуль: авторизация
│ │ ├── tables/ # DDL таблиц
│ │ │ ├── user.psql
│ │ │ ├── cabinet.psql
│ │ │ └── user_cabinet.psql
│ │ │
│ │ ├── functions/ # SQL функции
│ │ │ ├── user_select_by_ids/
│ │ │ │ └── user_select_by_ids.psql
│ │ │ └── user_create/
│ │ │ └── user_create.psql
│ │ │
│ │ ├── scripts/ # Произвольные скрипты
│ │ │ └── seed_roles.sql
│ │ │
│ │ ├── init/ # Инициализация
│ │ │ └── create_schema.psql
│ │ │
│ │ ├── deploy/ # Миграции
│ │ │ ├── 2026-07-08_user-select-by-ids.sql
│ │ │ └── 2026-07-09-user-seed-merge.sql
│ │ │
│ │ ├── auth.psql # Точка входа модуля
│ │ └── auth.post-deploy.psql
│ │
│ └── _docs/ # Документация проекта
│ ├── diagrams/
│ ├── openapi.yaml
│ └── architecture.md
└── sl3/ # ── SQLite проекты ──
└── kit_example_sl3/ # Проект: пример
├── readme.md
├── settings/
│ └── connection.json
├── main/ # Модуль: основной
│ ├── tables/
│ ├── scripts/
│ ├── init/
│ ├── deploy/
│ └── main.sql
└── _docs/
├── diagrams/
├── openapi.yaml
└── architecture.md
```
---
## Структура проекта
Каждый проект БД создаётся по пути:
```
projects/{engine}/{project_name}/
```
Где:
- `{engine}` — тип СУБД (pg, sqlite, mysql, mssql и т.д.)
- `{project_name}` — имя проекта в формате `имя_БД_движок` (например `kit_auth_pg`)
---
## Обязательные компоненты проекта
### 1. readme.md
Описание проекта:
- Назначение БД
- Движок и версия
- Краткое описание модулей
- Связи с другими БД (если есть)
### 2. settings/
Папка с настройками подключения:
- `connection.json` — параметры подключения к БД (хост, порт, БД, пользователь)
Пример `connection.json`:
```json
{
"host": "localhost",
"port": 5432,
"database": "kit_auth",
"username": "postgres",
"password": ""
}
```
### 3. Модули (auth/, payments/, и т.д.)
Каждый модуль содержит:
- `tables/` — DDL-скрипты таблиц
- `functions/` — SQL-функции и хранимые процедуры
- `scripts/` — SQL-скрипты (миграции, seed-данные, утилиты)
- `init/` — скрипты инициализации (создание схемы, начальные данные)
- `deploy/` — инфраструктура развёртывания (docker-compose, bat, sh)
- `{module}.psql` — главный файл модуля (точка входа)
### 4. _docs/
Документация проекта:
- `diagrams/` — диаграммы (ER, зависимости, потоки)
- `openapi.yaml` — OpenAPI спецификация (если БД предоставляет API)
- `architecture.md` — описание архитектуры конкретной БД
---
## Правила именования
- Папки — в нижнем регистре, через подчёркивание: `kit_auth_pg`
- SQL-файлы — в нижнем регистре: `create_users_table.sql`
- Функции — по шаблону: `{module}_{action}.sql` (например `auth_get_user_by_id.sql`)
- Таблицы — по шаблону: `{module}_{entity}.sql` (например `auth_users.sql`)
---
## Правила (rules/)
При формировании проекта LLM использует правила из:
- `rules/common/` — общие правила для всех СУБД
- `rules/{engine}/` — правила для конкретного движка
Правила определяют:
- Стиль написания SQL
- Шаблоны описания таблиц и функций
- Формат комментариев
- Стандарты именования
---
## Skills
При формировании проекта LLM использует промпты из `skills/` для:
- Генерации описаний таблиц
- Генерации описаний функций
- Формирования документации
- Создания диаграмм
---
## Порядок создания проекта
1. Создать папку проекта `projects/{engine}/{project_name}/`
2. Создать `readme.md` с описанием
3. Создать `settings/connection.json`
4. Для каждого модуля БД создать папку модуля с подпапками (tables, functions, scripts, init, deploy)
5. Создать `_docs/` с диаграммами и спецификацией
6. Заполнить SQL-файлы на основании требований
7. Проверить структуру на соответствие шаблону

View File

@ -0,0 +1,25 @@
name: kit-auth-token-db
networks:
default:
external: true
name: sdi_server
services:
token-db:
image: postgres
restart: always
ports:
- 6749:5432
container_name: kit-auth-token-db
environment:
POSTGRES_DB: kit-auth-token
POSTGRES_USER: user_auth_token
POSTGRES_PASSWORD: 9b1d4c7a-2e63-4f08-a5d1-7c0e3b9f2a64
PGDATA: "/data"
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- ./pg_data:/var/lib/postgresql/data
- ./init:/docker-entrypoint-initdb.d

View File

@ -0,0 +1,36 @@
@echo off
setlocal
set reset_data=0
if "%~1"=="" (
goto end
)
:checkArguments
if "%~1"=="" (
goto end
)
if "%~1"=="--reset-data" (
set reset_data=1
)
if "%~1"=="-r" (
set reset_data=1
)
shift
goto checkArguments
:end
@echo on
docker compose down
@echo off
if "%reset_data%"=="1" (
rmdir /s /q .\pg_data
echo.
echo volume removed
echo.
)
endlocal

View File

@ -0,0 +1,42 @@
file_publish="/docker-entrypoint-initdb.d/scripts/publish.psql"
files=(
##########################################################################
# schemas
"/docker-entrypoint-initdb.d/schemas/token/token.psql"
##########################################################################
# tables
# token_type и owner — раньше token (на них ссылаются type_id / owner_id)
"/docker-entrypoint-initdb.d/schemas/token/tables/token_type.psql"
"/docker-entrypoint-initdb.d/schemas/token/tables/owner.psql"
"/docker-entrypoint-initdb.d/schemas/token/tables/token.psql"
##########################################################################
# functions
# token_type
"/docker-entrypoint-initdb.d/schemas/token/functions/token_type/token_type_select.psql"
# owner
"/docker-entrypoint-initdb.d/schemas/token/functions/owner/owner_select.psql"
"/docker-entrypoint-initdb.d/schemas/token/functions/owner/owner_insert.psql"
# token
"/docker-entrypoint-initdb.d/schemas/token/functions/token/token_select.psql"
"/docker-entrypoint-initdb.d/schemas/token/functions/token/token_insert.psql"
"/docker-entrypoint-initdb.d/schemas/token/functions/token/token_update.psql"
"/docker-entrypoint-initdb.d/schemas/token/functions/token/token_consume.psql"
"/docker-entrypoint-initdb.d/schemas/token/functions/token/token_validate.psql"
"/docker-entrypoint-initdb.d/schemas/token/functions/token/token_revoke.psql"
"/docker-entrypoint-initdb.d/schemas/token/functions/token/token_revoke_by_owner.psql"
"/docker-entrypoint-initdb.d/schemas/token/functions/token/token_delete.psql"
##########################################################################
# post-deploy (seed справочников)
"/docker-entrypoint-initdb.d/schemas/token/token.post-deploy.psql"
)
> ${file_publish}
for item in "${files[@]}"; do
cat "${item}" >> "${file_publish}" && printf "\n\n\n" >> "${file_publish}"
done
# run publish.psql
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -f "${file_publish}"

View File

@ -0,0 +1,7 @@
@echo off
setlocal
CALL "down.bat" %*
CALL "up.bat"
endlocal

View File

@ -0,0 +1,7 @@
@echo off
setlocal
docker network create -d bridge sdi_server
docker compose up -d
endlocal

View File

@ -0,0 +1,22 @@
-- Явная регистрация владельца по естественному ключу key (НЕ авто-создание при выпуске).
-- Идемпотентна: существует — возвращаем его id (title не перетираем); нет — создаём.
-- ON CONFLICT DO UPDATE (no-op по key) нужен, чтобы RETURNING вернул id и при конфликте;
-- это же делает функцию устойчивой к гонке двух параллельных выпусков (правило 06).
CREATE OR REPLACE FUNCTION token.owner_insert(
_key varchar,
_title varchar,
_date_created timestamptz
)
RETURNS int
LANGUAGE plpgsql
AS $$
DECLARE
_id int;
BEGIN
INSERT INTO token.owner(key, title, date_created)
VALUES (_key, _title, _date_created)
ON CONFLICT (key) DO UPDATE SET key = EXCLUDED.key
RETURNING id INTO _id;
RETURN _id;
END;
$$;

View File

@ -0,0 +1,23 @@
-- Выборка владельцев: фильтры по id/key/title, без пейджинга (правило 03).
-- key сравнивается на равенство (естественный ключ), title — текстовый ILIKE.
CREATE OR REPLACE FUNCTION token.owner_select(
_id int DEFAULT NULL,
_key varchar DEFAULT NULL,
_title varchar DEFAULT NULL
)
RETURNS SETOF refcursor
LANGUAGE plpgsql
AS $$
DECLARE
_data refcursor := 'data';
BEGIN
OPEN _data FOR
SELECT id, key, title, date_created
FROM token.owner
WHERE (_id IS NULL OR id = _id)
AND (_key IS NULL OR key = _key)
AND (_title IS NULL OR title ILIKE '%' || _title || '%')
ORDER BY id;
RETURN NEXT _data;
END;
$$;

View File

@ -0,0 +1,37 @@
-- Атомарное потребление токена за одно обращение (правило 06: логика записи — в БД).
-- В одной транзакции: проверка валидности, инкремент read_used, фиксация date_last_read,
-- продление срока при скользящей экспирации, авто-погашение при достижении лимита.
-- Возвращает курсор с одной строкой: data и итоговый признак is_valid обращения.
-- Невалиден (нет токена / неактивен / истёк / исчерпан лимит) → is_valid=false, data=NULL.
CREATE OR REPLACE FUNCTION token.token_consume(_hash varchar)
RETURNS SETOF refcursor
LANGUAGE plpgsql
AS $$
DECLARE
_result refcursor := 'data';
_now timestamptz := now();
_data text;
_valid boolean := false;
BEGIN
UPDATE token.token t
SET read_used = t.read_used + 1,
date_last_read = _now,
date_expired = CASE WHEN t.is_slide_expiration
THEN _now + make_interval(secs => t.lifetime_seconds)
ELSE t.date_expired END,
-- гасим, если этим обращением исчерпали лимит (0 = без лимита)
is_active = CASE WHEN t.read_limit > 0 AND t.read_used + 1 >= t.read_limit
THEN false ELSE t.is_active END
WHERE t.hash = _hash
AND t.is_active
AND t.date_expired > _now
AND (t.read_limit = 0 OR t.read_used < t.read_limit)
RETURNING t.data INTO _data;
_valid := FOUND;
OPEN _result FOR
SELECT _valid AS is_valid, _data AS data;
RETURN NEXT _result;
END;
$$;

View File

@ -0,0 +1,9 @@
-- Физическое удаление токена по hash (мягкое удаление таблицей не предусмотрено).
CREATE OR REPLACE FUNCTION token.token_delete(_hash varchar)
RETURNS void
LANGUAGE plpgsql
AS $$
BEGIN
DELETE FROM token.token WHERE hash = _hash;
END;
$$;

View File

@ -0,0 +1,28 @@
-- Вставка токена. PK — текстовый hash (не serial), возвращается он же.
-- read_used/date_last_read/date_revoked при выпуске не задаются (значения по умолчанию).
-- owner_id должен существовать заранее (резолв/создание владельца — см. owner_insert).
CREATE OR REPLACE FUNCTION token.token_insert(
_hash varchar,
_date_created timestamptz,
_date_expired timestamptz,
_data text,
_lifetime_seconds int,
_is_slide_expiration boolean,
_read_limit int,
_is_active boolean,
_type_id int,
_owner_id int
)
RETURNS varchar
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO token.token(
hash, date_created, date_expired, data, lifetime_seconds,
is_slide_expiration, read_limit, is_active, type_id, owner_id)
VALUES (
_hash, _date_created, _date_expired, _data, _lifetime_seconds,
_is_slide_expiration, _read_limit, _is_active, _type_id, _owner_id);
RETURN _hash;
END;
$$;

View File

@ -0,0 +1,23 @@
-- Мягкий отзыв одного токена по hash: is_active=false + фиксация момента и причины.
-- Отзываются только ещё активные токены (date_revoked/revoke_reason не перетираются
-- повторным вызовом). Возвращает число фактически отозванных (0 или 1).
CREATE OR REPLACE FUNCTION token.token_revoke(
_hash varchar,
_reason varchar
)
RETURNS int
LANGUAGE plpgsql
AS $$
DECLARE
_count int;
BEGIN
UPDATE token.token
SET is_active = false,
date_revoked = now(),
revoke_reason = _reason
WHERE hash = _hash
AND is_active;
GET DIAGNOSTICS _count = ROW_COUNT;
RETURN _count;
END;
$$;

View File

@ -0,0 +1,23 @@
-- Массовый отзыв всех активных токенов владельца (logout всех сессий приложения,
-- реакция на компрометацию источника). Опирается на индекс ix_token_owner.
-- Возвращает число фактически отозванных токенов.
CREATE OR REPLACE FUNCTION token.token_revoke_by_owner(
_owner_id int,
_reason varchar
)
RETURNS int
LANGUAGE plpgsql
AS $$
DECLARE
_count int;
BEGIN
UPDATE token.token
SET is_active = false,
date_revoked = now(),
revoke_reason = _reason
WHERE owner_id = _owner_id
AND is_active;
GET DIAGNOSTICS _count = ROW_COUNT;
RETURN _count;
END;
$$;

View File

@ -0,0 +1,28 @@
-- Выборка токенов: фильтры по полям, без пейджинга (правило 03).
-- hash/owner_id/type_id — равенство; is_active — равенство; владельца фильтруем по id
-- (его естественный ключ резолвится через owner_select на стороне вызывающего кода).
CREATE OR REPLACE FUNCTION token.token_select(
_hash varchar DEFAULT NULL,
_type_id int DEFAULT NULL,
_owner_id int DEFAULT NULL,
_is_active boolean DEFAULT NULL
)
RETURNS SETOF refcursor
LANGUAGE plpgsql
AS $$
DECLARE
_data refcursor := 'data';
BEGIN
OPEN _data FOR
SELECT hash, type_id, owner_id, date_created, date_expired, date_last_read,
data, lifetime_seconds, is_slide_expiration, read_limit, read_used,
is_active, date_revoked, revoke_reason
FROM token.token
WHERE (_hash IS NULL OR hash = _hash)
AND (_type_id IS NULL OR type_id = _type_id)
AND (_owner_id IS NULL OR owner_id = _owner_id)
AND (_is_active IS NULL OR is_active = _is_active)
ORDER BY date_created DESC;
RETURN NEXT _data;
END;
$$;

View File

@ -0,0 +1,31 @@
-- Обновление токена по hash. date_created неизменяем и не обновляется.
-- read_used/date_last_read/date_revoked обновляются специализированными операциями
-- (потребление токена / отзыв), не общим update.
CREATE OR REPLACE FUNCTION token.token_update(
_hash varchar,
_date_expired timestamptz,
_data text,
_lifetime_seconds int,
_is_slide_expiration boolean,
_read_limit int,
_is_active boolean,
_type_id int,
_owner_id int
)
RETURNS varchar
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE token.token
SET date_expired = _date_expired,
data = _data,
lifetime_seconds = _lifetime_seconds,
is_slide_expiration = _is_slide_expiration,
read_limit = _read_limit,
is_active = _is_active,
type_id = _type_id,
owner_id = _owner_id
WHERE hash = _hash;
RETURN _hash;
END;
$$;

View File

@ -0,0 +1,28 @@
-- Проверка токена БЕЗ расхода обращения (peek): read_used/date_last_read не меняются.
-- Критерий валидности тот же, что в token_consume: активен, не истёк, лимит не исчерпан.
-- Используется там, где проверка не должна «съедать» обращение (middleware, предпроверки).
-- Возвращает курсор с одной строкой: is_valid и data (data=NULL, если невалиден).
CREATE OR REPLACE FUNCTION token.token_validate(_hash varchar)
RETURNS SETOF refcursor
LANGUAGE plpgsql
AS $$
DECLARE
_result refcursor := 'data';
_now timestamptz := now();
_data text;
_valid boolean := false;
BEGIN
SELECT t.data INTO _data
FROM token.token t
WHERE t.hash = _hash
AND t.is_active
AND t.date_expired > _now
AND (t.read_limit = 0 OR t.read_used < t.read_limit);
_valid := FOUND;
OPEN _result FOR
SELECT _valid AS is_valid, _data AS data;
RETURN NEXT _result;
END;
$$;

View File

@ -0,0 +1,15 @@
-- Справочник: простой select без фильтров и пейджинга (правило 03).
CREATE OR REPLACE FUNCTION token.token_type_select()
RETURNS SETOF refcursor
LANGUAGE plpgsql
AS $$
DECLARE
_data refcursor := 'data';
BEGIN
OPEN _data FOR
SELECT id, title, key
FROM token.token_type
ORDER BY id;
RETURN NEXT _data;
END;
$$;

View File

@ -0,0 +1,32 @@
# Kit.Auth.Token.Db
## Назначение
База данных для управления токенами авторизации. Хранит владельцев токенов, типы токенов и сами токены с возможностью валидации, отзыва и удаления.
## Движок
PostgreSQL
## Схема
`token`
## Модули
### token
Основной модуль. Содержит таблицы и функции для работы с токенами.
**Таблицы:**
- `owner` — владельцы токенов
- `token_type` — справочник типов токенов
- `token` — токены авторизации
**Функции:**
- Управление владельцами: insert, select
- Управление токенами: insert, select, update, delete, consume, validate, revoke, revoke_by_owner
- Справочники: token_type_select
## Связи
- Связана с `Kit.Auth.Db` (модуль `auth`)

View File

@ -0,0 +1,382 @@
-- Схема домена Token (собственная токен-БД Auth-сервиса: kit-auth-token).
-- Схема идентична Kit.Token.Db — её ожидает встроенный серверный модуль Kit.Token.Token.
-- Таблицы и функции (CRUD через хранимки) подключаются в init.sh (правила 03–04).
CREATE SCHEMA IF NOT EXISTS token;
-- Справочник типов токена. id НЕ автоинкремент (на него ссылается token.type_id).
-- title обязателен (правило 03); key — естественный ключ для поиска типа из кода
-- (справочники ищутся по key/title, не по хардкод-id — правила 05–06).
CREATE TABLE token.token_type
(
id integer not null primary key,
title varchar(250) not null,
key varchar(250) not null unique
);
-- Владелец/источник токена (ранее денормализованное поле token.app_name).
-- Обычная таблица: владельцы создаются динамически по ходу выпуска токенов.
-- key — естественный уникальный ключ для идемпотентного get-or-create (см. owner_insert).
CREATE TABLE token.owner
(
id serial not null primary key,
key varchar(250) not null unique,
title varchar(250) not null default '',
date_created timestamptz not null
);
-- Токен: PK — текстовый hash (хеш токена, не сам токен в открытом виде).
-- owner_id — владелец/источник (ранее денормализованное поле app_name).
-- read_limit — разрешённое число обращений (0 = без лимита); read_used — уже израсходовано.
CREATE TABLE token.token
(
hash varchar(256) not null primary key,
type_id int not null references token.token_type (id),
owner_id int not null references token.owner (id),
date_created timestamptz not null,
date_expired timestamptz not null,
date_last_read timestamptz null,
data text not null,
lifetime_seconds int not null,
is_slide_expiration boolean not null,
read_limit int not null default 0,
read_used int not null default 0,
is_active boolean not null,
date_revoked timestamptz null,
revoke_reason varchar(250) null,
CONSTRAINT chk_token_lifetime CHECK (lifetime_seconds > 0),
CONSTRAINT chk_token_reads CHECK (read_used >= 0 AND (read_limit = 0 OR read_used <= read_limit)),
CONSTRAINT chk_token_expired CHECK (date_expired >= date_created)
);
-- Индексы под фильтры token_select и фоновую чистку просроченных.
CREATE INDEX ix_token_owner ON token.token (owner_id);
CREATE INDEX ix_token_expired ON token.token (date_expired);
CREATE INDEX ix_token_active_type ON token.token (is_active, type_id);
-- Справочник: простой select без фильтров и пейджинга (правило 03).
CREATE OR REPLACE FUNCTION token.token_type_select()
RETURNS SETOF refcursor
LANGUAGE plpgsql
AS $$
DECLARE
_data refcursor := 'data';
BEGIN
OPEN _data FOR
SELECT id, title, key
FROM token.token_type
ORDER BY id;
RETURN NEXT _data;
END;
$$;
-- Выборка владельцев: фильтры по id/key/title, без пейджинга (правило 03).
-- key сравнивается на равенство (естественный ключ), title — текстовый ILIKE.
CREATE OR REPLACE FUNCTION token.owner_select(
_id int DEFAULT NULL,
_key varchar DEFAULT NULL,
_title varchar DEFAULT NULL
)
RETURNS SETOF refcursor
LANGUAGE plpgsql
AS $$
DECLARE
_data refcursor := 'data';
BEGIN
OPEN _data FOR
SELECT id, key, title, date_created
FROM token.owner
WHERE (_id IS NULL OR id = _id)
AND (_key IS NULL OR key = _key)
AND (_title IS NULL OR title ILIKE '%' || _title || '%')
ORDER BY id;
RETURN NEXT _data;
END;
$$;
-- Явная регистрация владельца по естественному ключу key (НЕ авто-создание при выпуске).
-- Идемпотентна: существует — возвращаем его id (title не перетираем); нет — создаём.
-- ON CONFLICT DO UPDATE (no-op по key) нужен, чтобы RETURNING вернул id и при конфликте;
-- это же делает функцию устойчивой к гонке двух параллельных выпусков (правило 06).
CREATE OR REPLACE FUNCTION token.owner_insert(
_key varchar,
_title varchar,
_date_created timestamptz
)
RETURNS int
LANGUAGE plpgsql
AS $$
DECLARE
_id int;
BEGIN
INSERT INTO token.owner(key, title, date_created)
VALUES (_key, _title, _date_created)
ON CONFLICT (key) DO UPDATE SET key = EXCLUDED.key
RETURNING id INTO _id;
RETURN _id;
END;
$$;
-- Выборка токенов: фильтры по полям, без пейджинга (правило 03).
-- hash/owner_id/type_id — равенство; is_active — равенство; владельца фильтруем по id
-- (его естественный ключ резолвится через owner_select на стороне вызывающего кода).
CREATE OR REPLACE FUNCTION token.token_select(
_hash varchar DEFAULT NULL,
_type_id int DEFAULT NULL,
_owner_id int DEFAULT NULL,
_is_active boolean DEFAULT NULL
)
RETURNS SETOF refcursor
LANGUAGE plpgsql
AS $$
DECLARE
_data refcursor := 'data';
BEGIN
OPEN _data FOR
SELECT hash, type_id, owner_id, date_created, date_expired, date_last_read,
data, lifetime_seconds, is_slide_expiration, read_limit, read_used,
is_active, date_revoked, revoke_reason
FROM token.token
WHERE (_hash IS NULL OR hash = _hash)
AND (_type_id IS NULL OR type_id = _type_id)
AND (_owner_id IS NULL OR owner_id = _owner_id)
AND (_is_active IS NULL OR is_active = _is_active)
ORDER BY date_created DESC;
RETURN NEXT _data;
END;
$$;
-- Вставка токена. PK — текстовый hash (не serial), возвращается он же.
-- read_used/date_last_read/date_revoked при выпуске не задаются (значения по умолчанию).
-- owner_id должен существовать заранее (резолв/создание владельца — см. owner_insert).
CREATE OR REPLACE FUNCTION token.token_insert(
_hash varchar,
_date_created timestamptz,
_date_expired timestamptz,
_data text,
_lifetime_seconds int,
_is_slide_expiration boolean,
_read_limit int,
_is_active boolean,
_type_id int,
_owner_id int
)
RETURNS varchar
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO token.token(
hash, date_created, date_expired, data, lifetime_seconds,
is_slide_expiration, read_limit, is_active, type_id, owner_id)
VALUES (
_hash, _date_created, _date_expired, _data, _lifetime_seconds,
_is_slide_expiration, _read_limit, _is_active, _type_id, _owner_id);
RETURN _hash;
END;
$$;
-- Обновление токена по hash. date_created неизменяем и не обновляется.
-- read_used/date_last_read/date_revoked обновляются специализированными операциями
-- (потребление токена / отзыв), не общим update.
CREATE OR REPLACE FUNCTION token.token_update(
_hash varchar,
_date_expired timestamptz,
_data text,
_lifetime_seconds int,
_is_slide_expiration boolean,
_read_limit int,
_is_active boolean,
_type_id int,
_owner_id int
)
RETURNS varchar
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE token.token
SET date_expired = _date_expired,
data = _data,
lifetime_seconds = _lifetime_seconds,
is_slide_expiration = _is_slide_expiration,
read_limit = _read_limit,
is_active = _is_active,
type_id = _type_id,
owner_id = _owner_id
WHERE hash = _hash;
RETURN _hash;
END;
$$;
-- Атомарное потребление токена за одно обращение (правило 06: логика записи — в БД).
-- В одной транзакции: проверка валидности, инкремент read_used, фиксация date_last_read,
-- продление срока при скользящей экспирации, авто-погашение при достижении лимита.
-- Возвращает курсор с одной строкой: data и итоговый признак is_valid обращения.
-- Невалиден (нет токена / неактивен / истёк / исчерпан лимит) → is_valid=false, data=NULL.
CREATE OR REPLACE FUNCTION token.token_consume(_hash varchar)
RETURNS SETOF refcursor
LANGUAGE plpgsql
AS $$
DECLARE
_result refcursor := 'data';
_now timestamptz := now();
_data text;
_valid boolean := false;
BEGIN
UPDATE token.token t
SET read_used = t.read_used + 1,
date_last_read = _now,
date_expired = CASE WHEN t.is_slide_expiration
THEN _now + make_interval(secs => t.lifetime_seconds)
ELSE t.date_expired END,
-- гасим, если этим обращением исчерпали лимит (0 = без лимита)
is_active = CASE WHEN t.read_limit > 0 AND t.read_used + 1 >= t.read_limit
THEN false ELSE t.is_active END
WHERE t.hash = _hash
AND t.is_active
AND t.date_expired > _now
AND (t.read_limit = 0 OR t.read_used < t.read_limit)
RETURNING t.data INTO _data;
_valid := FOUND;
OPEN _result FOR
SELECT _valid AS is_valid, _data AS data;
RETURN NEXT _result;
END;
$$;
-- Проверка токена БЕЗ расхода обращения (peek): read_used/date_last_read не меняются.
-- Критерий валидности тот же, что в token_consume: активен, не истёк, лимит не исчерпан.
-- Используется там, где проверка не должна «съедать» обращение (middleware, предпроверки).
-- Возвращает курсор с одной строкой: is_valid и data (data=NULL, если невалиден).
CREATE OR REPLACE FUNCTION token.token_validate(_hash varchar)
RETURNS SETOF refcursor
LANGUAGE plpgsql
AS $$
DECLARE
_result refcursor := 'data';
_now timestamptz := now();
_data text;
_valid boolean := false;
BEGIN
SELECT t.data INTO _data
FROM token.token t
WHERE t.hash = _hash
AND t.is_active
AND t.date_expired > _now
AND (t.read_limit = 0 OR t.read_used < t.read_limit);
_valid := FOUND;
OPEN _result FOR
SELECT _valid AS is_valid, _data AS data;
RETURN NEXT _result;
END;
$$;
-- Мягкий отзыв одного токена по hash: is_active=false + фиксация момента и причины.
-- Отзываются только ещё активные токены (date_revoked/revoke_reason не перетираются
-- повторным вызовом). Возвращает число фактически отозванных (0 или 1).
CREATE OR REPLACE FUNCTION token.token_revoke(
_hash varchar,
_reason varchar
)
RETURNS int
LANGUAGE plpgsql
AS $$
DECLARE
_count int;
BEGIN
UPDATE token.token
SET is_active = false,
date_revoked = now(),
revoke_reason = _reason
WHERE hash = _hash
AND is_active;
GET DIAGNOSTICS _count = ROW_COUNT;
RETURN _count;
END;
$$;
-- Массовый отзыв всех активных токенов владельца (logout всех сессий приложения,
-- реакция на компрометацию источника). Опирается на индекс ix_token_owner.
-- Возвращает число фактически отозванных токенов.
CREATE OR REPLACE FUNCTION token.token_revoke_by_owner(
_owner_id int,
_reason varchar
)
RETURNS int
LANGUAGE plpgsql
AS $$
DECLARE
_count int;
BEGIN
UPDATE token.token
SET is_active = false,
date_revoked = now(),
revoke_reason = _reason
WHERE owner_id = _owner_id
AND is_active;
GET DIAGNOSTICS _count = ROW_COUNT;
RETURN _count;
END;
$$;
-- Физическое удаление токена по hash (мягкое удаление таблицей не предусмотрено).
CREATE OR REPLACE FUNCTION token.token_delete(_hash varchar)
RETURNS void
LANGUAGE plpgsql
AS $$
BEGIN
DELETE FROM token.token WHERE hash = _hash;
END;
$$;
-- Стартовые данные схемы token (правило 03: seed справочников в post-deploy).
-- Типы токенов: id фиксированы (на них ссылается token.type_id), key — для поиска из кода.
INSERT INTO token.token_type(id, title, key) VALUES
(1, 'Сессия', 'session'),
(2, 'Подтверждение e-mail', 'email_confirm'),
(3, 'Сброс пароля', 'password_reset'),
(4, 'Magic-link (вход)', 'magic_link'),
(5, 'Одноразовый код', 'one_time_code')
ON CONFLICT (id) DO NOTHING;
-- Владелец токенов этого сервиса (whitelist token.owner). Совпадает с Auth:TokenOwnerKey="auth"
-- (Program.cs дополнительно регистрирует его идемпотентно при старте). token_insert требует
-- существующий owner_id — неизвестный источник токен не получит.
INSERT INTO token.owner(key, title, date_created) VALUES
('auth', 'Сервис авторизации', now())
ON CONFLICT (key) DO NOTHING;

View File

@ -0,0 +1,7 @@
{
"host": "localhost",
"port": 5432,
"database": "kit_auth_token",
"username": "postgres",
"password": ""
}

View File

@ -0,0 +1,10 @@
-- Владелец/источник токена (ранее денормализованное поле token.app_name).
-- Обычная таблица: владельцы создаются динамически по ходу выпуска токенов.
-- key — естественный уникальный ключ для идемпотентного get-or-create (см. owner_insert).
CREATE TABLE token.owner
(
id serial not null primary key,
key varchar(250) not null unique,
title varchar(250) not null default '',
date_created timestamptz not null
);

View File

@ -0,0 +1,28 @@
-- Токен: PK — текстовый hash (хеш токена, не сам токен в открытом виде).
-- owner_id — владелец/источник (ранее денормализованное поле app_name).
-- read_limit — разрешённое число обращений (0 = без лимита); read_used — уже израсходовано.
CREATE TABLE token.token
(
hash varchar(256) not null primary key,
type_id int not null references token.token_type (id),
owner_id int not null references token.owner (id),
date_created timestamptz not null,
date_expired timestamptz not null,
date_last_read timestamptz null,
data text not null,
lifetime_seconds int not null,
is_slide_expiration boolean not null,
read_limit int not null default 0,
read_used int not null default 0,
is_active boolean not null,
date_revoked timestamptz null,
revoke_reason varchar(250) null,
CONSTRAINT chk_token_lifetime CHECK (lifetime_seconds > 0),
CONSTRAINT chk_token_reads CHECK (read_used >= 0 AND (read_limit = 0 OR read_used <= read_limit)),
CONSTRAINT chk_token_expired CHECK (date_expired >= date_created)
);
-- Индексы под фильтры token_select и фоновую чистку просроченных.
CREATE INDEX ix_token_owner ON token.token (owner_id);
CREATE INDEX ix_token_expired ON token.token (date_expired);
CREATE INDEX ix_token_active_type ON token.token (is_active, type_id);

View File

@ -0,0 +1,9 @@
-- Справочник типов токена. id НЕ автоинкремент (на него ссылается token.type_id).
-- title обязателен (правило 03); key — естественный ключ для поиска типа из кода
-- (справочники ищутся по key/title, не по хардкод-id — правила 0506).
CREATE TABLE token.token_type
(
id integer not null primary key,
title varchar(250) not null,
key varchar(250) not null unique
);

View File

@ -0,0 +1,16 @@
-- Стартовые данные схемы token (правило 03: seed справочников в post-deploy).
-- Типы токенов: id фиксированы (на них ссылается token.type_id), key — для поиска из кода.
INSERT INTO token.token_type(id, title, key) VALUES
(1, 'Сессия', 'session'),
(2, 'Подтверждение e-mail', 'email_confirm'),
(3, 'Сброс пароля', 'password_reset'),
(4, 'Magic-link (вход)', 'magic_link'),
(5, 'Одноразовый код', 'one_time_code')
ON CONFLICT (id) DO NOTHING;
-- Владелец токенов этого сервиса (whitelist token.owner). Совпадает с Auth:TokenOwnerKey="auth"
-- (Program.cs дополнительно регистрирует его идемпотентно при старте). token_insert требует
-- существующий owner_id — неизвестный источник токен не получит.
INSERT INTO token.owner(key, title, date_created) VALUES
('auth', 'Сервис авторизации', now())
ON CONFLICT (key) DO NOTHING;

View File

@ -0,0 +1,4 @@
-- Схема домена Token (собственная токен-БД Auth-сервиса: kit-auth-token).
-- Схема идентична Kit.Token.Db — её ожидает встроенный серверный модуль Kit.Token.Token.
-- Таблицы и функции (CRUD через хранимки) подключаются в init.sh (правила 0304).
CREATE SCHEMA IF NOT EXISTS token;

138
rules/pg/db-project-rule.md Normal file
View File

@ -0,0 +1,138 @@
# Правило — Формирование проекта PostgreSQL БД
---
## Структура проекта
```
projects/pg/{project_name}/
readme.md
settings/
connection.json
{schema}/
tables/
functions/
scripts/
init/
{schema}.psql
deploy/
_docs/
diagrams/
openapi.yaml
architecture.md
```
---
## Правила
### 1. Схема = модуль
Каждая схема PostgreSQL — это отдельная папка-модуль в корне проекта.
### 2. Таблицы
- Путь: `{schema}/tables/`
- Формат: `.psql`
- Одна таблица = один файл
- Именование: `{table_name}.psql` (например `user.psql`, `cabinet.psql`)
- Содержимое: `CREATE TABLE` с комментариями
### 3. Функции
- Путь: `{schema}/functions/{function_name}/`
- Каждая функция — отдельная папка
- Внутри: файл `.psql` с телом функции
- Именование папки: по имени функции (например `user_select_by_ids/`)
### 4. Скрипты
- Путь: `{schema}/scripts/`
- SQL-скрипты (миграции, seed-данные, утилиты)
- Именование миграций: `YYYY-MM-DD_description.sql`
### 5. Init
- Путь: `{schema}/init/`
- Скрипты инициализации: создание схемы, расширений, начальных данных
- Выполняются один раз при первом развёртывании
### 6. Deploy
- Путь: `deploy/` (в корне проекта)
- Инфраструктура развёртывания: docker-compose, bat-файлы, shell-скрипты
- НЕ SQL-миграции
### 7. Главный файл модуля
- Путь: `{schema}/{schema}.psql`
- Точка входа — подключает все таблицы и функции модуля
- Порядок: сначала таблицы, потом функции
### 8. Post-deploy
- Путь: `{schema}/{schema}.post-deploy.psql`
- Скрипты, выполняемые после деплоя (seed-данные, обновление зависимостей)
---
## Пример: проект kit_auth_pg
```
projects/pg/kit_auth_pg/
readme.md
settings/
connection.json
auth/
tables/
user.psql
cabinet.psql
user_cabinet.psql
functions/
user_select_by_ids/
user_select_by_ids.psql
user_create/
user_create.psql
scripts/
seed_roles.sql
2026-07-08_user-select-by-ids.sql
init/
create_schema.psql
auth.psql
auth.post-deploy.psql
deploy/
docker-compose.yml
up.bat
down.bat
restart.bat
init.sh
_docs/
diagrams/
openapi.yaml
architecture.md
```
---
## Настройки подключения
Файл: `settings/connection.json`
```json
{
"host": "localhost",
"port": 5432,
"database": "{database_name}",
"username": "postgres",
"password": ""
}
```
---
## Инструменты
- `apply-psql.sh` — применить SQL к БД
- `up.bat` / `down.bat` — запуск/остановка контейнера
- `docker-compose.yml` — конфигурация контейнера
- `export.bat` / `import.bat` — экспорт/импорт дампа

View File

@ -0,0 +1,127 @@
# Правило — Формирование проекта SQLite БД
---
## Структура проекта
```
projects/sl3/{project_name}/
readme.md
settings/
connection.json
{module}/
tables/
scripts/
init/
{module}.sql
deploy/
_docs/
diagrams/
openapi.yaml
architecture.md
```
---
## Правила
### 1. Модуль
Каждый функциональный модуль — отдельная папка в корне проекта.
SQLite не поддерживает схемы, поэтому модуль = логическая группа объектов.
### 2. Таблицы
- Путь: `{module}/tables/`
- Формат: `.sql`
- Одна таблица = один файл
- Именование: `{table_name}.sql` (например `users.sql`, `sessions.sql`)
- Содержимое: `CREATE TABLE` с комментариями
### 3. Скрипты
- Путь: `{module}/scripts/`
- Произвольные SQL-скрипты (запросы, утилиты, seed-данные)
- SQLite не поддерживает хранимые процедуры — вся логика в скриптах
### 4. Init
- Путь: `{module}/init/`
- Скрипты инициализации: создание таблиц, индексов, начальных данных
- Выполняются один раз при первом развёртывании
### 5. Deploy
- Путь: `deploy/` (в корне проекта)
- Инфраструктура развёртывания: docker-compose, bat-файлы, shell-скрипты
- НЕ SQL-миграции
### 6. Главный файл модуля
- Путь: `{module}/{module}.sql`
- Точка входа — подключает все таблицы и скрипты модуля
- Порядок: сначала таблицы, потом скрипты
---
## Особенности SQLite
- Нет хранимых функций и процедур — папка `functions/` не используется
- Нет схем — все объекты в одном пространстве имён
- Один файл БД = один проект
- Типы данных ограничены: INTEGER, TEXT, REAL, BLOB, NUMERIC
- `PRAGMA` настройки размещаются в `init/`
---
## Пример: проект kit_example_sl3
```
projects/sl3/kit_example_sl3/
readme.md
settings/
connection.json
main/
tables/
users.sql
sessions.sql
roles.sql
scripts/
seed_roles.sql
2026-07-08_add-email-column.sql
init/
create_indexes.sql
set_pragmas.sql
main.sql
deploy/
docker-compose.yml
up.bat
down.bat
_docs/
diagrams/
openapi.yaml
architecture.md
```
---
## Настройки подключения
Файл: `settings/connection.json`
```json
{
"database": "{database_name}.db",
"journal_mode": "WAL",
"foreign_keys": true
}
```
---
## Инструменты
- `sqlite3 {database}.db < {script}.sql` — выполнить скрипт
- `.dump` — экспорт всей БД
- `.read` — выполнить файл SQL

Binary file not shown.