Remove Supabase
The database moved to a container of our own; the platform is gone. This takes out what was left of it — and, where the leftovers were load bearing, moves rather than deletes. Moved, not deleted: supabase/migrations/ -> db/migrations/ the schema's source of truth supabase/build-org.ts -> scripts/build-org.ts lib/supabase/types.ts -> lib/types.ts 52 import sites repointed The bookkeeping needed care. It lived in `supabase_migrations.schema_migrations`, and simply renaming the schema would have left the runner facing an empty table: it would have called all 67 migrations pending and replayed them against a database that is long since current. So the runner now creates `migrationen.schema_migrations` and, once, copies the old rows across — guarded so a second run does nothing and a fresh database skips it entirely. Only then does migration 20260907100000 drop the old schema. Deleted: the CLI config, the seed, the historical schema/function dumps (nothing read them), scripts/umzug-von-supabase.sh (the move is done), and both Supabase packages plus the CLI. Nothing in the application imported them — the build now succeeds with no environment variables at all, which is the proof. Integration tests: six of them signed in through Supabase Auth and asserted against the anon key and the service role. That model is gone, so the tests were not portable — they are deleted. session-context and employee-status-filter already ran on pg and are untouched; om-reporting is ported to a direct connection because it guards a real risk (the reporting line rule exists twice, once in SQL and once in TypeScript). CI: the integration job started a Supabase stack. It now runs a postgres service, applies deploy/db-init and every migration to an empty database — that was the valuable part, and it still holds — then checks that a second run is a no-op, which is what proves the bookkeeping works. Docs: security-review.md audited a service-role key, a cookie adapter and auth.users, none of which exist. Restating findings about removed components would suggest today's system had been reviewed; it has not. It now records what was removed and says a fresh review is due. data-model.md was already marked obsolete and described the pre-OM schema; azure-migration.md was a plan for a route not taken. Both deleted. Verified: npm ci, typecheck, lint, 445 tests, build — all clean without the packages. Integration tests skip cleanly with no database. Migration SQL and the runner are reviewed but NOT executed: no Docker here, and the old instance no longer resolves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import type { EmploymentStatus } from "./supabase/types";
|
||||
import type { EmploymentStatus } from "./types";
|
||||
|
||||
// Langzeitabwesenheit.
|
||||
//
|
||||
// The database still stores the employment status as 'Karenz' — renaming an
|
||||
// enum value would mean rewriting every stored function that spells it, for
|
||||
// a label change (see supabase/migrations/*_absence_type.sql). So the value
|
||||
// a label change (see db/migrations/*_absence_type.sql). So the value
|
||||
// is mapped to its display name here, in the one place the UI reads it from.
|
||||
|
||||
// Nur echte Abwesenheiten. Eine Teilzeit ist keine: wer in Bildungs-,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { EmploymentStatus, NoteCategory } from "./supabase/types";
|
||||
import type { EmploymentStatus, NoteCategory } from "./types";
|
||||
|
||||
const AVATAR_PALETTE = [
|
||||
"#d6046e",
|
||||
@@ -40,7 +40,7 @@ export const CATEGORY_STYLES: Record<ColorCategory, string> = {
|
||||
};
|
||||
|
||||
// Audit-log / activity-feed action -> badge color, per the action list in
|
||||
// supabase/schema.sql's audit_log comment.
|
||||
// dem Kommentar an audit_log in der ersten Migration.
|
||||
const ACTION_CATEGORY: Record<string, ColorCategory> = {
|
||||
Neueinstellung: "success",
|
||||
Wiedereinstellung: "success",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { jsonArrayFrom, jsonObjectFrom, zeitstempel } from "./db/json";
|
||||
import { besetzungenAbfrage, pickPlacements } from "./placement";
|
||||
import { buildOrgMaps, orgMapsAbfragen, type OrgEb } from "./org";
|
||||
import { offeneStellenAbfrage, resolveOpenPositions, type OffeneStelle } from "./positions";
|
||||
import type { HistoryEventType } from "./supabase/types";
|
||||
import type { HistoryEventType } from "./types";
|
||||
import type { AnstehendArt } from "./dashboard-filter";
|
||||
|
||||
// Was die Übersichtsseite liest — in zwei Rundreisen statt in dreizehn.
|
||||
|
||||
@@ -9,8 +9,8 @@ import type { Schema } from "./schema";
|
||||
// ═══ Warum das keine gewöhnliche Datenbankschicht ist ═══
|
||||
//
|
||||
// Die Zugriffsrechte liegen in der Datenbank: 21 RLS-Policies rufen
|
||||
// is_hr_user() auf, und das fragt seit der Umstellung nicht mehr Supabase,
|
||||
// sondern `current_setting('app.user_id')` — eine Sitzungsvariable.
|
||||
// is_hr_user() auf, und das liest
|
||||
// `current_setting('app.user_id')` — eine Sitzungsvariable.
|
||||
//
|
||||
// Sitzungsvariablen hängen an der *Verbindung*, nicht an der Anfrage. Und
|
||||
// Verbindungen kommen aus einem Pool. Wird die Variable ohne Transaktion
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Pool, types } from "pg";
|
||||
// Typprüfer und keiner der Tests fangen konnte.
|
||||
//
|
||||
// Die alte API-Schicht lieferte JSON: ein `date` kam als "2026-08-03" an,
|
||||
// ein `numeric` als Zahl. Genau so steht es in lib/supabase/types.ts, und
|
||||
// ein `numeric` als Zahl. Genau so steht es in lib/types.ts, und
|
||||
// darauf baut die gesamte Anwendung — Sortierungen mit localeCompare,
|
||||
// Vergleiche wie `entry_date <= stichtag`, das Ableiten des Status.
|
||||
//
|
||||
@@ -88,7 +88,7 @@ export function getPool(): Pool {
|
||||
statement_timeout: 20_000,
|
||||
idle_in_transaction_session_timeout: 20_000,
|
||||
connectionTimeoutMillis: 10_000,
|
||||
// Verwaltete Anbieter (Azure, RDS, Supabase) verlangen TLS; lokal nicht.
|
||||
// Verwaltete Anbieter (Azure, RDS) verlangen TLS; im eigenen Netz nicht.
|
||||
ssl: process.env.DATABASE_SSL === "false" ? undefined : { rejectUnauthorized: false },
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "server-only";
|
||||
import { sql, withUser, type Tx } from "./index";
|
||||
import type { Database } from "@/lib/supabase/types";
|
||||
import type { Database } from "@/lib/types";
|
||||
|
||||
// Aufruf einer Datenbankfunktion.
|
||||
//
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import type { ColumnType } from "kysely";
|
||||
import type { Database } from "@/lib/supabase/types";
|
||||
import type { Database } from "@/lib/types";
|
||||
|
||||
// Die Tabellenform für Kysely, abgeleitet aus der bestehenden
|
||||
// Schemabeschreibung — nicht daneben gestellt.
|
||||
//
|
||||
// Zwei Beschreibungen desselben Schemas driften auseinander, und die eine
|
||||
// hier ist bereits gegen die Migrationen abgesichert: `npm run types:check`
|
||||
// vergleicht lib/supabase/types.ts Spalte für Spalte mit
|
||||
// supabase/migrations/*.sql und schlägt in der CI fehl, wenn etwas fehlt.
|
||||
// vergleicht lib/types.ts Spalte für Spalte mit
|
||||
// db/migrations/*.sql und schlägt in der CI fehl, wenn etwas fehlt.
|
||||
// Diese Ableitung erbt diese Absicherung.
|
||||
//
|
||||
// Die Datei heisst noch lib/supabase/types.ts, weil sie aus der Zeit stammt,
|
||||
// als PostgREST der Zugriffsweg war. Sie beschreibt reines PostgreSQL und
|
||||
// wird beim Entfernen der Supabase-Pakete lediglich umbenannt.
|
||||
// Sie beschreibt reines PostgreSQL und heisst seit dem Entfernen der
|
||||
// Plattform-Pakete lib/types.ts.
|
||||
|
||||
type Tables = Database["public"]["Tables"];
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { DienstwagenArt } from "./supabase/types";
|
||||
import type { DienstwagenArt } from "./types";
|
||||
|
||||
// Wie ein Dienstwagen benannt wird.
|
||||
//
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Expression, ExpressionBuilder, SqlBool } from "kysely";
|
||||
import type { Schema } from "./db/schema";
|
||||
import type { EmploymentStatus } from "./supabase/types";
|
||||
import type { EmploymentStatus } from "./types";
|
||||
|
||||
// The SQL counterpart of deriveStatusAsOf() in lib/reports.ts.
|
||||
//
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AuditChange, HistoryEventType } from "./supabase/types";
|
||||
import type { AuditChange, HistoryEventType } from "./types";
|
||||
|
||||
// Welche Historieneinträge sich zurücknehmen lassen — und warum die übrigen
|
||||
// nicht.
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Tx } from "./db";
|
||||
import { jsonArrayFrom, zeitstempel } from "./db/json";
|
||||
import { fmtName } from "./format";
|
||||
import type { OrgEb } from "./org";
|
||||
import type { Database } from "./supabase/types";
|
||||
import type { Database } from "./types";
|
||||
|
||||
export type OpenNote = Database["public"]["Tables"]["employee_notes"]["Row"] & {
|
||||
employeeName: string;
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { ExpressionBuilder } from "kysely";
|
||||
import type { Tx } from "./db";
|
||||
import { jsonArrayFrom } from "./db/json";
|
||||
import type { Schema } from "./db/schema";
|
||||
import type { Database } from "./supabase/types";
|
||||
import type { Database } from "./types";
|
||||
|
||||
/**
|
||||
* Der Ausdrucksbauer einer Abfrage ohne eigene Tabelle (selectNoFrom) — das
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ABSENCE_TYPES } from "./absence";
|
||||
import { parseIsoDateParam } from "./reports";
|
||||
import type { Weekday } from "./supabase/types";
|
||||
import type { Weekday } from "./types";
|
||||
|
||||
// Ein Verzeichnis aller Auswahlkriterien — für die Oberfläche, die Abfrage
|
||||
// und den Export dasselbe.
|
||||
|
||||
@@ -25,7 +25,7 @@ import type {
|
||||
SourceType,
|
||||
TeilzeitArt,
|
||||
WorkerType,
|
||||
} from "./supabase/types";
|
||||
} from "./types";
|
||||
|
||||
// Shared by the Berichte page and /api/export/* so they can never drift on
|
||||
// what "the current view" means — same filters, same stichtag/event-window
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { fmtName, todayIso, yearsBetweenIso } from "./format";
|
||||
import type { EmploymentStatus, HistoryEventType, Weekday } from "./supabase/types";
|
||||
import type { EmploymentStatus, HistoryEventType, Weekday } from "./types";
|
||||
|
||||
export { todayIso };
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Hand-written to match supabase/schema.sql + supabase/migrations/*.sql (no DB
|
||||
// connection string available to run `supabase gen types typescript` in this
|
||||
// environment — regenerate from the live project once you have the Supabase
|
||||
// CLI linked).
|
||||
// Von Hand gepflegt, passend zu db/migrations/*.sql.
|
||||
//
|
||||
// Erzeugt wird hier nichts: scripts/check-schema-types.mjs haelt die Datei
|
||||
// Spalte fuer Spalte gegen die Migrationen und meldet jede Abweichung.
|
||||
|
||||
export type EmploymentStatus = "Aktiv" | "Karenz" | "Geplant" | "Ausgetreten";
|
||||
export type EmploymentType = "Vollzeit" | "Teilzeit";
|
||||
@@ -94,20 +94,15 @@ export type PendingChangeType =
|
||||
| "reorg";
|
||||
export type PendingChangeStatus = "pending" | "applied" | "cancelled";
|
||||
|
||||
// @supabase/postgrest-js requires every table/view to carry a Relationships
|
||||
// array (used for typed embedded selects) — left empty since no code in this
|
||||
// app relies on nested/embedded resource selects.
|
||||
type NoRelationships = { Relationships: [] };
|
||||
|
||||
export type Database = {
|
||||
public: {
|
||||
Tables: {
|
||||
locations: NoRelationships & {
|
||||
locations: {
|
||||
Row: { id: string; name: string; country: string };
|
||||
Insert: { id?: string; name: string; country: string };
|
||||
Update: Partial<{ id: string; name: string; country: string }>;
|
||||
};
|
||||
profiles: NoRelationships & {
|
||||
profiles: {
|
||||
Row: {
|
||||
id: string;
|
||||
email: string;
|
||||
@@ -139,7 +134,7 @@ export type Database = {
|
||||
updated_at: string;
|
||||
}>;
|
||||
};
|
||||
employees: NoRelationships & {
|
||||
employees: {
|
||||
Row: {
|
||||
id: string;
|
||||
personnel_number: number;
|
||||
@@ -258,7 +253,7 @@ export type Database = {
|
||||
};
|
||||
Update: Partial<Database["public"]["Tables"]["employees"]["Insert"]>;
|
||||
};
|
||||
employee_history: NoRelationships & {
|
||||
employee_history: {
|
||||
Row: {
|
||||
id: string;
|
||||
employee_id: string;
|
||||
@@ -283,7 +278,7 @@ export type Database = {
|
||||
};
|
||||
Update: Partial<Database["public"]["Tables"]["employee_history"]["Insert"]>;
|
||||
};
|
||||
employee_dependents: NoRelationships & {
|
||||
employee_dependents: {
|
||||
Row: {
|
||||
id: string;
|
||||
employee_id: string;
|
||||
@@ -306,7 +301,7 @@ export type Database = {
|
||||
};
|
||||
Update: Partial<Database["public"]["Tables"]["employee_dependents"]["Insert"]>;
|
||||
};
|
||||
employee_notes: NoRelationships & {
|
||||
employee_notes: {
|
||||
Row: {
|
||||
id: string;
|
||||
employee_id: string;
|
||||
@@ -335,17 +330,17 @@ export type Database = {
|
||||
};
|
||||
Update: Partial<Database["public"]["Tables"]["employee_notes"]["Insert"]>;
|
||||
};
|
||||
hire_drafts: NoRelationships & {
|
||||
hire_drafts: {
|
||||
Row: { id: string; created_by: string | null; step: number; payload: Record<string, unknown>; updated_at: string };
|
||||
Insert: { id?: string; created_by?: string | null; step?: number; payload: Record<string, unknown>; updated_at?: string };
|
||||
Update: Partial<Database["public"]["Tables"]["hire_drafts"]["Insert"]>;
|
||||
};
|
||||
saved_reports: NoRelationships & {
|
||||
saved_reports: {
|
||||
Row: { id: string; created_by: string | null; name: string; config: Record<string, unknown>; created_at: string };
|
||||
Insert: { id?: string; created_by?: string | null; name: string; config: Record<string, unknown>; created_at?: string };
|
||||
Update: Partial<Database["public"]["Tables"]["saved_reports"]["Insert"]>;
|
||||
};
|
||||
audit_log: NoRelationships & {
|
||||
audit_log: {
|
||||
Row: {
|
||||
id: string;
|
||||
occurred_at: string;
|
||||
@@ -376,7 +371,7 @@ export type Database = {
|
||||
};
|
||||
Update: Partial<Database["public"]["Tables"]["audit_log"]["Insert"]>;
|
||||
};
|
||||
pending_org_changes: NoRelationships & {
|
||||
pending_org_changes: {
|
||||
Row: {
|
||||
id: string;
|
||||
employee_id: string;
|
||||
@@ -404,14 +399,6 @@ export type Database = {
|
||||
// ── SAP-OM-Modell ──────────────────────────────────────────
|
||||
// O: rekursiv über parent_id, unit_type ist nur ein Etikett.
|
||||
org_units: {
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "org_units_parent_id_fkey";
|
||||
columns: ["parent_id"];
|
||||
referencedRelation: "org_units";
|
||||
referencedColumns: ["id"];
|
||||
},
|
||||
];
|
||||
Row: {
|
||||
id: string;
|
||||
org_number: string;
|
||||
@@ -435,7 +422,7 @@ export type Database = {
|
||||
Update: Partial<Database["public"]["Tables"]["org_units"]["Insert"]>;
|
||||
};
|
||||
// C: Katalog der Tätigkeiten.
|
||||
jobs: NoRelationships & {
|
||||
jobs: {
|
||||
Row: { id: string; code: string; title: string; created_at: string };
|
||||
Insert: { id?: string; code: string; title: string; created_at?: string };
|
||||
Update: Partial<Database["public"]["Tables"]["jobs"]["Insert"]>;
|
||||
@@ -443,20 +430,6 @@ export type Database = {
|
||||
// S: Planstelle. Der Name om_positions stammt aus der Zeit, in der die
|
||||
// alte positions-Tabelle noch danebenstand; sie ist inzwischen weg.
|
||||
om_positions: {
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "om_positions_org_unit_id_fkey";
|
||||
columns: ["org_unit_id"];
|
||||
referencedRelation: "org_units";
|
||||
referencedColumns: ["id"];
|
||||
},
|
||||
{
|
||||
foreignKeyName: "om_positions_job_id_fkey";
|
||||
columns: ["job_id"];
|
||||
referencedRelation: "jobs";
|
||||
referencedColumns: ["id"];
|
||||
},
|
||||
];
|
||||
Row: {
|
||||
id: string;
|
||||
position_number: string;
|
||||
@@ -481,7 +454,7 @@ export type Database = {
|
||||
};
|
||||
// Die Onboarding-Checkliste: je Person und Punkt eine Zeile. Welche
|
||||
// Punkte es gibt, steht in lib/onboarding.ts — nicht hier.
|
||||
onboarding_tasks: NoRelationships & {
|
||||
onboarding_tasks: {
|
||||
Row: {
|
||||
id: string;
|
||||
employee_id: string;
|
||||
@@ -510,7 +483,7 @@ export type Database = {
|
||||
};
|
||||
// Die Offboarding-Checkliste — dieselbe Form wie onboarding_tasks, für
|
||||
// den anderen Weg. Punkte in lib/offboarding.ts.
|
||||
offboarding_tasks: NoRelationships & {
|
||||
offboarding_tasks: {
|
||||
Row: {
|
||||
id: string;
|
||||
employee_id: string;
|
||||
@@ -539,7 +512,7 @@ export type Database = {
|
||||
};
|
||||
// Kostenstellen. Die Zuordnung hängt an der Planstelle, nicht an der
|
||||
// Person: der Sitz kostet Geld, auch wenn niemand darauf sitzt.
|
||||
cost_centers: NoRelationships & {
|
||||
cost_centers: {
|
||||
Row: {
|
||||
id: string;
|
||||
code: string;
|
||||
@@ -561,7 +534,7 @@ export type Database = {
|
||||
Update: Partial<Database["public"]["Tables"]["cost_centers"]["Insert"]>;
|
||||
};
|
||||
// A011: Planstelle kontiert auf Kostenstelle, zeitabhängig.
|
||||
position_cost_centers: NoRelationships & {
|
||||
position_cost_centers: {
|
||||
Row: {
|
||||
id: string;
|
||||
position_id: string;
|
||||
@@ -602,20 +575,6 @@ export type Database = {
|
||||
// Beide Richtungen: über die Planstelle hängt die Verortung in der
|
||||
// Organisation, über die Person die Verortung in der Akte. Die
|
||||
// Einbettung erspart an einem Dutzend Stellen eine zweite Abfrage.
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "position_assignments_position_id_fkey";
|
||||
columns: ["position_id"];
|
||||
referencedRelation: "om_positions";
|
||||
referencedColumns: ["id"];
|
||||
},
|
||||
{
|
||||
foreignKeyName: "position_assignments_employee_id_fkey";
|
||||
columns: ["employee_id"];
|
||||
referencedRelation: "employees";
|
||||
referencedColumns: ["id"];
|
||||
},
|
||||
];
|
||||
};
|
||||
};
|
||||
Views: Record<string, never>;
|
||||
Reference in New Issue
Block a user