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

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:

  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. Migrationatlas 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:

#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.