Implement tenant isolation for Entries Service: switch transaction handling to tenantTransaction, introduce Flyway-based migrations for tenant schemas, and add JdbcTenantRegistry with control schema support. Include migration tests, schema initializations, and E2E tenant isolation. Update configuration and roadmap with completed A-1 tasks.

This commit is contained in:
2026-04-02 21:56:00 +02:00
parent b787504474
commit 9902b2bb44
19 changed files with 591 additions and 43 deletions
@@ -17,9 +17,23 @@ spring:
health-check-path: /actuator/health
health-check-interval: 10s
flyway:
enabled: ${SPRING_FLYWAY_ENABLED:true}
# Control-Schema Migrationen (Registry)
locations: classpath:db/control
# Default-Schema für Control-Registry
schemas: control
server:
port: ${SERVER_PORT:${ENTRIES_SERVICE_PORT:8083}}
# Multitenancy Bootstrap (temporär): Liste erlaubter Schemas (kommagetrennt)
multitenancy:
defaultSchemas: ${MULTITENANCY_DEFAULT_SCHEMAS:public}
# Umschalten zwischen in-memory (bootstrap) und jdbc (produktiv)
registry:
type: ${MULTITENANCY_REGISTRY_TYPE:jdbc} # jdbc | inmem
management:
endpoints:
web:
@@ -0,0 +1,14 @@
-- Create control schema and tenants registry
CREATE SCHEMA IF NOT EXISTS control;
CREATE TABLE IF NOT EXISTS control.tenants (
event_id TEXT PRIMARY KEY,
schema_name TEXT NOT NULL,
db_url TEXT NULL,
status TEXT NOT NULL DEFAULT 'ACTIVE', -- ACTIVE | READ_ONLY | LOCKED
version INT NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Index to speed up lookups by status when we add list operations later
CREATE INDEX IF NOT EXISTS idx_tenants_status ON control.tenants(status);
@@ -0,0 +1,47 @@
-- Tenant schema: core tables for Entries Service
-- NOTE: This script must be executed with Flyway configured to target the tenant's schema (schemas=<tenant_schema>)
-- nennungen
CREATE TABLE IF NOT EXISTS nennungen (
id UUID PRIMARY KEY,
abteilung_id UUID NOT NULL,
bewerb_id UUID NOT NULL,
turnier_id UUID NOT NULL,
reiter_id UUID NOT NULL,
pferd_id UUID NOT NULL,
zahler_id UUID NULL,
status VARCHAR(50) NOT NULL,
startwunsch VARCHAR(50) NOT NULL,
ist_nachnennung BOOLEAN NOT NULL DEFAULT FALSE,
nachnenngebuehr_erlassen BOOLEAN NOT NULL DEFAULT FALSE,
bemerkungen TEXT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_nennungen_turnier_id ON nennungen(turnier_id);
CREATE INDEX IF NOT EXISTS idx_nennungen_bewerb_id ON nennungen(bewerb_id);
CREATE INDEX IF NOT EXISTS idx_nennungen_abteilung_id ON nennungen(abteilung_id);
CREATE INDEX IF NOT EXISTS idx_nennungen_reiter_id ON nennungen(reiter_id);
CREATE INDEX IF NOT EXISTS idx_nennungen_pferd_id ON nennungen(pferd_id);
CREATE INDEX IF NOT EXISTS idx_nennungen_status ON nennungen(status);
-- nennungs_transfers
CREATE TABLE IF NOT EXISTS nennungs_transfers (
id UUID PRIMARY KEY,
ursprungs_nennung_id UUID NOT NULL,
neue_nennung_id UUID NOT NULL,
alter_reiter_id UUID NULL,
neuer_reiter_id UUID NULL,
altes_pferd_id UUID NULL,
neues_pferd_id UUID NULL,
ist_nach_nennschluss BOOLEAN NOT NULL DEFAULT FALSE,
nachnenngebuehr_erlassen BOOLEAN NOT NULL DEFAULT FALSE,
autorisiert_von UUID NOT NULL,
grund TEXT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_ntr_ursprungs_nennung_id ON nennungs_transfers(ursprungs_nennung_id);
CREATE INDEX IF NOT EXISTS idx_ntr_neue_nennung_id ON nennungs_transfers(neue_nennung_id);
CREATE INDEX IF NOT EXISTS idx_ntr_autorisiert_von ON nennungs_transfers(autorisiert_von);