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>
74 lines
2.6 KiB
TypeScript
74 lines
2.6 KiB
TypeScript
import type { Tx } from "./db";
|
|
import { jsonArrayFrom, zeitstempel } from "./db/json";
|
|
import { fmtName } from "./format";
|
|
import type { OrgEb } from "./org";
|
|
import type { Database } from "./types";
|
|
|
|
export type OpenNote = Database["public"]["Tables"]["employee_notes"]["Row"] & {
|
|
employeeName: string;
|
|
};
|
|
|
|
/** Was die Teilabfrage liefert: die Notiz plus den Namen aus dem Join. */
|
|
export type NotizZeile = Omit<Database["public"]["Tables"]["employee_notes"]["Row"], "created_at" | "done_at"> & {
|
|
created_at: string;
|
|
done_at: string | null;
|
|
first_name: string | null;
|
|
last_name: string | null;
|
|
};
|
|
|
|
// „Meine Notizen" (Topbar-Glocke): das geteilte, mitarbeiterübergreifende
|
|
// Postfach aller noch nicht erledigten HR-Notizen — unabhängig davon, wer sie
|
|
// verfasst hat oder zu wem sie gehören (mit Nutzer abgestimmt).
|
|
//
|
|
// Früher zwei Abfragen, in JavaScript zusammengeführt, weil die API-Schicht
|
|
// für eine einzelne verschachtelte Abfrage keine Verknüpfung anbot. Am
|
|
// direkten Zugang ist es schlicht ein Join.
|
|
|
|
/**
|
|
* Die offenen Notizen als *Teilabfrage* — zum Einhängen in die eine Abfrage,
|
|
* die eine Seite ohnehin stellt (lib/db/json.ts).
|
|
*
|
|
* Die beiden Zeitstempel gehen durch zeitstempel(): innerhalb von JSON
|
|
* formatiert Postgres sie anders als der Treiber es sonst täte, und der
|
|
* Unterschied fällt erst beim Vergleichen auf.
|
|
*/
|
|
export function offeneNotizenAbfrage(eb: OrgEb) {
|
|
return eb
|
|
.selectFrom("employee_notes as n")
|
|
.leftJoin("employees as e", "e.id", "n.employee_id")
|
|
.select([
|
|
"n.id",
|
|
"n.employee_id",
|
|
"n.author_user_id",
|
|
"n.author_name",
|
|
"n.category",
|
|
"n.note_text",
|
|
"n.due_date",
|
|
"n.done",
|
|
"n.done_by",
|
|
"e.first_name",
|
|
"e.last_name",
|
|
])
|
|
.select((x) => [zeitstempel(x.ref("n.created_at")).as("created_at"), zeitstempel(x.ref("n.done_at")).as("done_at")])
|
|
.where("n.done", "=", false)
|
|
.orderBy("n.created_at", "desc");
|
|
}
|
|
|
|
/** Der reine Teil: aus den Zeilen die Notizen mit lesbarem Namen. */
|
|
export function baueOffeneNotizen(rows: NotizZeile[]): OpenNote[] {
|
|
return rows.map((row) => {
|
|
const { first_name, last_name, ...note } = row;
|
|
return {
|
|
...(note as Database["public"]["Tables"]["employee_notes"]["Row"]),
|
|
employeeName: first_name && last_name ? fmtName(first_name, last_name) : "Unbekannt",
|
|
};
|
|
});
|
|
}
|
|
|
|
export async function loadOpenNotes(tx: Tx): Promise<OpenNote[]> {
|
|
const { notes } = await tx
|
|
.selectNoFrom((eb) => [jsonArrayFrom(offeneNotizenAbfrage(eb)).as("notes")])
|
|
.executeTakeFirstOrThrow();
|
|
return baueOffeneNotizen(notes as NotizZeile[]);
|
|
}
|