chore: germantax customer project
This commit is contained in:
63
db/sqlite/0001-init.sql
Normal file
63
db/sqlite/0001-init.sql
Normal file
@@ -0,0 +1,63 @@
|
||||
CREATE TABLE "user" (
|
||||
user_id TEXT PRIMARY KEY DEFAULT (lower(substr(hex(randomblob(4)), 1, 8) || '-' || substr(hex(randomblob(2)), 1, 4) || '-4' || substr(hex(randomblob(2)), 2, 3) || '-' || substr('89ab', abs(random()) % 4 + 1, 1) || substr(hex(randomblob(2)), 2, 3) || '-' || hex(randomblob(6)))),
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
display_name TEXT NOT NULL,
|
||||
password_hash TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP -- @date
|
||||
);
|
||||
|
||||
CREATE TABLE company (
|
||||
company_id TEXT PRIMARY KEY DEFAULT (lower(substr(hex(randomblob(4)), 1, 8) || '-' || substr(hex(randomblob(2)), 1, 4) || '-4' || substr(hex(randomblob(2)), 2, 3) || '-' || substr('89ab', abs(random()) % 4 + 1, 1) || substr(hex(randomblob(2)), 2, 3) || '-' || hex(randomblob(6)))),
|
||||
name TEXT NOT NULL,
|
||||
registry_no TEXT,
|
||||
address_line1 TEXT,
|
||||
address_line2 TEXT,
|
||||
postcode TEXT,
|
||||
town TEXT,
|
||||
country TEXT,
|
||||
total_shares INTEGER NOT NULL DEFAULT 0,
|
||||
fiscal_year_end TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP -- @date
|
||||
);
|
||||
|
||||
CREATE TABLE company_member (
|
||||
member_id TEXT PRIMARY KEY DEFAULT (lower(substr(hex(randomblob(4)), 1, 8) || '-' || substr(hex(randomblob(2)), 1, 4) || '-4' || substr(hex(randomblob(2)), 2, 3) || '-' || substr('89ab', abs(random()) % 4 + 1, 1) || substr(hex(randomblob(2)), 2, 3) || '-' || hex(randomblob(6)))),
|
||||
company_id TEXT NOT NULL REFERENCES company(company_id) ON DELETE CASCADE,
|
||||
user_id TEXT NOT NULL REFERENCES "user"(user_id) ON DELETE CASCADE,
|
||||
role TEXT NOT NULL CHECK (role IN ('managing_director', 'shareholder', 'angel')),
|
||||
shares INTEGER,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, -- @date
|
||||
UNIQUE (company_id, user_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_company_member_company ON company_member(company_id);
|
||||
CREATE INDEX idx_company_member_user ON company_member(user_id);
|
||||
|
||||
CREATE TABLE resolution (
|
||||
resolution_id TEXT PRIMARY KEY DEFAULT (lower(substr(hex(randomblob(4)), 1, 8) || '-' || substr(hex(randomblob(2)), 1, 4) || '-4' || substr(hex(randomblob(2)), 2, 3) || '-' || substr('89ab', abs(random()) % 4 + 1, 1) || substr(hex(randomblob(2)), 2, 3) || '-' || hex(randomblob(6)))),
|
||||
company_id TEXT NOT NULL REFERENCES company(company_id) ON DELETE CASCADE,
|
||||
template_key TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'published', 'archived')),
|
||||
field_values TEXT NOT NULL DEFAULT '{}', -- @json:Record<string, unknown>
|
||||
pdf_key TEXT,
|
||||
created_by TEXT NOT NULL REFERENCES "user"(user_id),
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, -- @date
|
||||
published_at TEXT -- @date
|
||||
);
|
||||
|
||||
CREATE INDEX idx_resolution_company ON resolution(company_id);
|
||||
CREATE INDEX idx_resolution_status ON resolution(status);
|
||||
|
||||
CREATE TABLE audit_log (
|
||||
audit_id TEXT PRIMARY KEY DEFAULT (lower(substr(hex(randomblob(4)), 1, 8) || '-' || substr(hex(randomblob(2)), 1, 4) || '-4' || substr(hex(randomblob(2)), 2, 3) || '-' || substr('89ab', abs(random()) % 4 + 1, 1) || substr(hex(randomblob(2)), 2, 3) || '-' || hex(randomblob(6)))),
|
||||
table_name TEXT NOT NULL,
|
||||
record_id TEXT NOT NULL,
|
||||
action TEXT NOT NULL CHECK (action IN ('INSERT', 'UPDATE', 'DELETE')),
|
||||
user_id TEXT REFERENCES "user"(user_id),
|
||||
changed_fields TEXT, -- @json:Record<string, unknown>
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP -- @date
|
||||
);
|
||||
|
||||
CREATE INDEX idx_audit_log_record ON audit_log(table_name, record_id);
|
||||
CREATE INDEX idx_audit_log_created ON audit_log(created_at);
|
||||
107
db/sqlite/0002-better-auth.sql
Normal file
107
db/sqlite/0002-better-auth.sql
Normal file
@@ -0,0 +1,107 @@
|
||||
-- Migrate from Auth.js / custom user table to Better Auth.
|
||||
-- Better Auth owns user, session, account, verification tables.
|
||||
-- Drop tables in FK dependency order, then recreate with new FKs pointing to user(id).
|
||||
|
||||
-- Drop dependent tables first
|
||||
DROP TABLE IF EXISTS audit_log;
|
||||
DROP TABLE IF EXISTS resolution;
|
||||
DROP TABLE IF EXISTS company_member;
|
||||
DROP TABLE IF EXISTS "user";
|
||||
|
||||
-- Better Auth user table with displayName as additionalField
|
||||
CREATE TABLE "user" (
|
||||
"id" text not null primary key,
|
||||
"name" text not null,
|
||||
"email" text not null unique,
|
||||
"email_verified" integer not null,
|
||||
"image" text,
|
||||
"display_name" text not null default '',
|
||||
"created_at" date not null,
|
||||
"updated_at" date not null
|
||||
);
|
||||
|
||||
-- Recreate company_member referencing user(id)
|
||||
CREATE TABLE company_member (
|
||||
member_id TEXT PRIMARY KEY DEFAULT (lower(substr(hex(randomblob(4)), 1, 8) || '-' || substr(hex(randomblob(2)), 1, 4) || '-4' || substr(hex(randomblob(2)), 2, 3) || '-' || substr('89ab', abs(random()) % 4 + 1, 1) || substr(hex(randomblob(2)), 2, 3) || '-' || hex(randomblob(6)))),
|
||||
company_id TEXT NOT NULL REFERENCES company(company_id) ON DELETE CASCADE,
|
||||
user_id TEXT NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
|
||||
role TEXT NOT NULL CHECK (role IN ('managing_director', 'shareholder', 'angel')),
|
||||
shares INTEGER,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE (company_id, user_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_company_member_company ON company_member(company_id);
|
||||
CREATE INDEX idx_company_member_user ON company_member(user_id);
|
||||
|
||||
-- Recreate resolution referencing user(id)
|
||||
CREATE TABLE resolution (
|
||||
resolution_id TEXT PRIMARY KEY DEFAULT (lower(substr(hex(randomblob(4)), 1, 8) || '-' || substr(hex(randomblob(2)), 1, 4) || '-4' || substr(hex(randomblob(2)), 2, 3) || '-' || substr('89ab', abs(random()) % 4 + 1, 1) || substr(hex(randomblob(2)), 2, 3) || '-' || hex(randomblob(6)))),
|
||||
company_id TEXT NOT NULL REFERENCES company(company_id) ON DELETE CASCADE,
|
||||
template_key TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'published', 'archived')),
|
||||
field_values TEXT NOT NULL DEFAULT '{}',
|
||||
pdf_key TEXT,
|
||||
created_by TEXT NOT NULL REFERENCES "user"(id),
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
published_at TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX idx_resolution_company ON resolution(company_id);
|
||||
CREATE INDEX idx_resolution_status ON resolution(status);
|
||||
|
||||
-- Recreate audit_log referencing user(id)
|
||||
CREATE TABLE audit_log (
|
||||
audit_id TEXT PRIMARY KEY DEFAULT (lower(substr(hex(randomblob(4)), 1, 8) || '-' || substr(hex(randomblob(2)), 1, 4) || '-4' || substr(hex(randomblob(2)), 2, 3) || '-' || substr('89ab', abs(random()) % 4 + 1, 1) || substr(hex(randomblob(2)), 2, 3) || '-' || hex(randomblob(6)))),
|
||||
table_name TEXT NOT NULL,
|
||||
record_id TEXT NOT NULL,
|
||||
action TEXT NOT NULL CHECK (action IN ('INSERT', 'UPDATE', 'DELETE')),
|
||||
user_id TEXT REFERENCES "user"(id),
|
||||
changed_fields TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_audit_log_record ON audit_log(table_name, record_id);
|
||||
CREATE INDEX idx_audit_log_created ON audit_log(created_at);
|
||||
|
||||
-- Better Auth session/account/verification tables
|
||||
CREATE TABLE IF NOT EXISTS "session" (
|
||||
"id" text not null primary key,
|
||||
"expires_at" date not null,
|
||||
"token" text not null unique,
|
||||
"created_at" date not null,
|
||||
"updated_at" date not null,
|
||||
"ip_address" text,
|
||||
"user_agent" text,
|
||||
"user_id" text not null references "user" ("id") on delete cascade
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "account" (
|
||||
"id" text not null primary key,
|
||||
"account_id" text not null,
|
||||
"provider_id" text not null,
|
||||
"user_id" text not null references "user" ("id") on delete cascade,
|
||||
"access_token" text,
|
||||
"refresh_token" text,
|
||||
"id_token" text,
|
||||
"access_token_expires_at" date,
|
||||
"refresh_token_expires_at" date,
|
||||
"scope" text,
|
||||
"password" text,
|
||||
"created_at" date not null,
|
||||
"updated_at" date not null
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "verification" (
|
||||
"id" text not null primary key,
|
||||
"identifier" text not null,
|
||||
"value" text not null,
|
||||
"expires_at" date not null,
|
||||
"created_at" date not null,
|
||||
"updated_at" date not null
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "session_user_id_idx" ON "session" ("user_id");
|
||||
CREATE INDEX IF NOT EXISTS "account_user_id_idx" ON "account" ("user_id");
|
||||
CREATE INDEX IF NOT EXISTS "verification_identifier_idx" ON "verification" ("identifier");
|
||||
33
db/sqlite/0003-ba-organization.sql
Normal file
33
db/sqlite/0003-ba-organization.sql
Normal file
@@ -0,0 +1,33 @@
|
||||
-- Better Auth organization plugin tables.
|
||||
-- Adds organization, member, invitation tables used by the organization() plugin.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "organization" (
|
||||
"id" text not null primary key,
|
||||
"name" text not null,
|
||||
"slug" text unique,
|
||||
"logo" text,
|
||||
"created_at" date not null,
|
||||
"metadata" text
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "member" (
|
||||
"id" text not null primary key,
|
||||
"organization_id" text not null references "organization" ("id") on delete cascade,
|
||||
"user_id" text not null references "user" ("id") on delete cascade,
|
||||
"role" text not null,
|
||||
"created_at" date not null
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "invitation" (
|
||||
"id" text not null primary key,
|
||||
"organization_id" text not null references "organization" ("id") on delete cascade,
|
||||
"email" text not null,
|
||||
"role" text,
|
||||
"status" text not null,
|
||||
"expires_at" date not null,
|
||||
"inviter_id" text not null references "user" ("id") on delete cascade
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "member_organization_id_idx" ON "member" ("organization_id");
|
||||
CREATE INDEX IF NOT EXISTS "member_user_id_idx" ON "member" ("user_id");
|
||||
CREATE INDEX IF NOT EXISTS "invitation_organization_id_idx" ON "invitation" ("organization_id");
|
||||
29
db/sqlite/0004-fabric-audit.sql
Normal file
29
db/sqlite/0004-fabric-audit.sql
Normal file
@@ -0,0 +1,29 @@
|
||||
-- ============================================================
|
||||
-- Fabric audit event sink — the table Pikku Fabric writes AuditEvent
|
||||
-- records to. This is the framework-level event stream (distinct from
|
||||
-- any domain-level change history).
|
||||
-- Columns mirror the AuditEvent shape (camelCase → snake_case for the
|
||||
-- CamelCasePlugin kysely).
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS audit (
|
||||
event_id TEXT PRIMARY KEY,
|
||||
type TEXT NOT NULL,
|
||||
source TEXT,
|
||||
outcome TEXT,
|
||||
occurred_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
function_id TEXT,
|
||||
wire_type TEXT,
|
||||
wire_id TEXT,
|
||||
trace_id TEXT,
|
||||
transaction_id TEXT,
|
||||
query_id TEXT,
|
||||
actor TEXT,
|
||||
input TEXT,
|
||||
metadata TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_type ON audit(type);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_occurred ON audit(occurred_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_function ON audit(function_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_trace ON audit(trace_id);
|
||||
11
db/sqlite/0005-user-actor.sql
Normal file
11
db/sqlite/0005-user-actor.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
-- =============================================================================
|
||||
-- BETTER AUTH ACTOR PLUGIN — user-flow actors
|
||||
-- Adds the `actor` boolean the Better Auth `actor()` plugin declares so its
|
||||
-- schema is covered by an explicit migration and the db drift check passes.
|
||||
-- Actor rows are synthetic users signed in by pikkuUserFlow via
|
||||
-- POST /api/auth/sign-in/actor with the server-held USER_FLOW_ACTOR_SECRET;
|
||||
-- the flag rides into the pikku core session so audits/analytics can address
|
||||
-- synthetic traffic. A non-actor user can never be signed in this way.
|
||||
-- =============================================================================
|
||||
|
||||
ALTER TABLE "user" ADD COLUMN "actor" integer NOT NULL DEFAULT 0;
|
||||
16
db/sqlite/0006-admin.sql
Normal file
16
db/sqlite/0006-admin.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
-- =============================================================================
|
||||
-- BETTER AUTH ADMIN PLUGIN — user administration
|
||||
-- Adds the columns the Better Auth `admin()` plugin declares so its schema is
|
||||
-- covered by an explicit migration and the db drift check passes. The plugin
|
||||
-- exposes /api/auth/admin/* (listUsers, setRole, ban, impersonateUser, …);
|
||||
-- `impersonated_by` marks a session created via "view as user" so it can be
|
||||
-- reverted with admin.stopImpersonating. All fields are optional — a user with
|
||||
-- `role` = 'admin' is an administrator; everyone else stays a normal user.
|
||||
-- =============================================================================
|
||||
|
||||
ALTER TABLE "user" ADD COLUMN "role" text;
|
||||
ALTER TABLE "user" ADD COLUMN "banned" integer DEFAULT 0;
|
||||
ALTER TABLE "user" ADD COLUMN "ban_reason" text;
|
||||
ALTER TABLE "user" ADD COLUMN "ban_expires" date;
|
||||
|
||||
ALTER TABLE "session" ADD COLUMN "impersonated_by" text;
|
||||
13
db/sqlite/0007-fabric.sql
Normal file
13
db/sqlite/0007-fabric.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
-- =============================================================================
|
||||
-- BETTER AUTH FABRIC PLUGIN — Fabric operator admin sessions
|
||||
-- Adds the `fabric` boolean the Better Auth `fabric()` plugin declares so its
|
||||
-- schema is covered by an explicit migration and the db drift check passes.
|
||||
-- Fabric rows are synthetic operator users minted by POST /api/auth/sign-in/fabric
|
||||
-- after verifying a short-lived RS256 token the Fabric control plane signed
|
||||
-- (checked against FABRIC_AUTH_PUBLIC_KEY). They are created with role 'admin' so
|
||||
-- the console Users tab can list/impersonate real end-users WITHOUT the operator
|
||||
-- being one of them; these rows are filtered out of any end-user listing. A real
|
||||
-- user can never be signed in this way.
|
||||
-- =============================================================================
|
||||
|
||||
ALTER TABLE "user" ADD COLUMN "fabric" integer NOT NULL DEFAULT 0;
|
||||
Reference in New Issue
Block a user