first commit
This commit is contained in:
commit
a306e27a18
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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/ ─────────────► Документация
|
||||
|
|
@ -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. Проверить структуру на соответствие шаблону
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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}"
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
@echo off
|
||||
setlocal
|
||||
|
||||
CALL "down.bat" %*
|
||||
CALL "up.bat"
|
||||
|
||||
endlocal
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
@echo off
|
||||
setlocal
|
||||
|
||||
docker network create -d bridge sdi_server
|
||||
docker compose up -d
|
||||
|
||||
endlocal
|
||||
|
|
@ -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;
|
||||
$$;
|
||||
|
|
@ -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;
|
||||
$$;
|
||||
|
|
@ -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;
|
||||
$$;
|
||||
|
|
@ -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;
|
||||
$$;
|
||||
|
|
@ -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;
|
||||
$$;
|
||||
|
|
@ -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;
|
||||
$$;
|
||||
|
|
@ -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;
|
||||
$$;
|
||||
|
|
@ -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;
|
||||
$$;
|
||||
|
|
@ -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;
|
||||
$$;
|
||||
|
|
@ -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;
|
||||
$$;
|
||||
|
|
@ -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;
|
||||
$$;
|
||||
|
|
@ -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`)
|
||||
|
|
@ -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;
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"host": "localhost",
|
||||
"port": 5432,
|
||||
"database": "kit_auth_token",
|
||||
"username": "postgres",
|
||||
"password": ""
|
||||
}
|
||||
|
|
@ -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
|
||||
);
|
||||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
-- Справочник типов токена. 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
|
||||
);
|
||||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
-- Схема домена Token (собственная токен-БД Auth-сервиса: kit-auth-token).
|
||||
-- Схема идентична Kit.Token.Db — её ожидает встроенный серверный модуль Kit.Token.Token.
|
||||
-- Таблицы и функции (CRUD через хранимки) подключаются в init.sh (правила 03–04).
|
||||
CREATE SCHEMA IF NOT EXISTS token;
|
||||
|
|
@ -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` — экспорт/импорт дампа
|
||||
|
|
@ -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.
Loading…
Reference in New Issue