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>
4.4 KiB
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 (declarative schema-as- code, language-agnostic).
The model: dev DB as desired state
Atlas's "diff-driven migration" workflow is a clean fit:
- Desired state — a schema produced by running
SchemaBuilderonce against an empty SQLite. The output of allCREATE TABLE/CREATE INDEXstatements is the desired state. - Current state — what the production database actually contains.
- Migration —
atlas migrate diffcompares (1) and (2) and emits versioned SQL files. - Apply — at deploy time,
atlas migrate applyruns the new migration files against prod.
Decorator code never runs ALTER at runtime. It only:
- declares
kSchema(compile-time); - runs
SchemaBuilderagainst an empty DB (CI) — produces desired state; - runs
SchemaContract::verifyat 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:
- Build a small standalone tool (
tools/schema_snapshot.cpp) that instantiates the full set ofSchemaBuilder<…>::createcalls for every entity in the app, writing all DDL to a temporary SQLite. - Atlas inspects the resulting SQLite:
atlas schema inspect --url "sqlite://./tmp_schema.db" --format '{{ hcl . }}' > schema.hcl - Commit
schema.hclto the repo. Diffs are reviewable per change. - 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:
#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.