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>
102 lines
4.4 KiB
Markdown
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.
|