oatpp-authkit/docs/MIGRATIONS.md
Uwe Schuster 3ccc25f231 #14 PR 1: relocate role_templates module + Atlas migration docs
Lifts role_templates / role_template_fields / user_role_assignments from
fewo-webapp into oatpp-authkit, exposed via the declarative SchemaContract
introduced in PR 0.

New files (all in oatpp-authkit):
- dto/RoleTemplateDto.hpp — RoleTemplateDto, RoleTemplateFieldDto,
  UserRoleAssignmentDto. UserWithPermissionsDto stays in fewo (fewo-
  specific /api/auth/me response shape).
- db/RoleTemplateDb.hpp — DbClient with all queries (CRUD + cascade
  soft-delete + getEffectiveFieldPermissions). RoleTemplateSchema struct
  declares the three tables' columns/indexes/sidecar tables in the new
  declarative form. TemporalRepository overlays valid_until + the
  composite UNIQUE(entity_id, valid_until) index.
- repo/ConcreteRoleTemplateRepository.hpp — Repository<RoleTemplateDto>
  inner adapter; makeRoleTemplateRepository helper composes the stack.
- docs/MIGRATIONS.md — Atlas workflow for consumers (atlasgo.io as the
  diff-driven migration tool; SchemaBuilder produces desired state, Atlas
  generates versioned SQL, SchemaContract::verify asserts at runtime).
- test/test_role_template_schema.cpp — verifies SchemaBuilder<
  RoleTemplateSchema, TemporalRepository<RoleTemplateDto>> emits the
  expected 5 DDL statements (2 sidecars + entity table + 2 indexes) with
  composite-FK + ON UPDATE CASCADE on both sidecars.

11 of 11 tests pass. RoleTemplateDto is registered as temporal via
OATPP_AUTHKIT_REGISTER_TEMPORAL so TemporalRepository compiles cleanly.

Atlas binary integration in CI is documented but not yet wired — owner
deferred to a follow-up after the first concrete consumer migration. The
shipped role_templates stack itself is fully consumable today; fewo-
webapp's switch from local copies to oatpp-authkit-shipped headers is
the natural next PR.

Bumped 0.9.0 → 0.10.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 12:36:18 +02:00

102 lines
4.4 KiB
Markdown

# Schema migrations with oatpp-authkit
oatpp-authkit (since v0.9.0) ships a declarative schema model: each
decorator in a `Repository<TDto>` stack exposes a static
`DecoratorSchema kSchema` listing the columns/indexes/sidecar tables it
needs. `SchemaBuilder<…>::create(table, exec)` composes the contributions
into a single `CREATE TABLE` per entity table. `SchemaContract::verify`
asserts the live DB matches at runtime.
This document covers the **deploy-time** companion: how to evolve a live
database between releases when the decorator stack changes. The
recommended tool is [Atlas](https://atlasgo.io) (declarative schema-as-
code, language-agnostic).
## The model: dev DB as desired state
Atlas's "diff-driven migration" workflow is a clean fit:
1. **Desired state** — a schema produced by running `SchemaBuilder` once
against an empty SQLite. The output of all `CREATE TABLE` /
`CREATE INDEX` statements *is* the desired state.
2. **Current state** — what the production database actually contains.
3. **Migration**`atlas migrate diff` compares (1) and (2) and emits
versioned SQL files.
4. **Apply** — at deploy time, `atlas migrate apply` runs the new
migration files against prod.
Decorator code never runs ALTER at runtime. It only:
- declares `kSchema` (compile-time);
- runs `SchemaBuilder` against an empty DB (CI) — produces desired state;
- runs `SchemaContract::verify` at app startup against the live DB — fails
loud if a column/sidecar required by the stack is missing (i.e. the
migration didn't run).
## Wiring it into a consumer's CI
A consumer of oatpp-authkit (e.g. fewo-webapp, palibu, …) has its own DB
schema that combines the authkit-shipped contributions with its own
local tables. The schema-snapshot workflow:
1. **Build a small standalone tool** (`tools/schema_snapshot.cpp`) that
instantiates the full set of `SchemaBuilder<…>::create` calls for every
entity in the app, writing all DDL to a temporary SQLite.
2. **Atlas inspects** the resulting SQLite:
```
atlas schema inspect --url "sqlite://./tmp_schema.db" --format '{{ hcl . }}' > schema.hcl
```
3. **Commit `schema.hcl`** to the repo. Diffs are reviewable per change.
4. **At deploy**:
```
atlas migrate diff --to "file://schema.hcl" --dir "file://migrations" \
--dev-url "sqlite://file?mode=memory" \
--format atlas
atlas migrate apply --url "sqlite://prod.db" --dir "file://migrations"
```
The first `migrate diff` emits a versioned migration file; subsequent
schema changes (decorator-level or app-level) regenerate the migration
list. Each release includes the new migration files; deploy applies them.
## Atlas-free fallback
For consumers that don't want Atlas as a dependency, `SchemaBuilder`'s
output is plain SQL — pipe it into any migration tool (Flyway, goose,
hand-rolled scripts). The C++ side stays unchanged.
The runtime guarantee — `SchemaContract::verify` throwing on missing
columns/sidecars — works regardless of which migration tool you used.
## Example: a consumer using oatpp-authkit's role_templates module
The `dto::RoleTemplateDto` + `db::RoleTemplateSchema` +
`repo::ConcreteRoleTemplateRepository` + `repo::TemporalRepository<…>`
stack ships in oatpp-authkit since v0.10.0. A consumer wires it up like:
```cpp
#include "oatpp-authkit/db/RoleTemplateDb.hpp"
#include "oatpp-authkit/repo/ConcreteRoleTemplateRepository.hpp"
// One-shot at CI: produce desired-state DDL.
oatpp_authkit::repo::SchemaBuilder<
oatpp_authkit::db::RoleTemplateSchema,
oatpp_authkit::repo::TemporalRepository<oatpp_authkit::dto::RoleTemplateDto>
>::create("role_templates", exec);
// Every app startup: assert the live DB matches.
oatpp_authkit::repo::SchemaContract<
oatpp_authkit::db::RoleTemplateSchema,
oatpp_authkit::repo::TemporalRepository<oatpp_authkit::dto::RoleTemplateDto>
>::verify("role_templates", probe);
// Routine repository use.
auto rtdb = std::make_shared<oatpp_authkit::db::RoleTemplateDb>(executor);
auto repo = oatpp_authkit::repo::makeRoleTemplateRepository(rtdb);
auto liveTemplate = repo->findByEntityId("seed00000000000000000000role01rt");
```
Atlas treats the `CREATE TABLE` output of `SchemaBuilder::create` as the
desired state for those three tables (`role_templates` +
`role_template_fields` + `user_role_assignments`); the consumer's own
schema-snapshot tool aggregates these alongside its app-specific tables.