Files
Speisekarte/lib/db/code-lists.ts
T
florian.wilde cd8b0fab9f feat: Speiseplan-Inhalte variabel mehrsprachig (Teil 2)
Gerichtnamen, Essenszeiten und Allergen-/Zusatzstoff-Bezeichnungen sind
jetzt genauso dynamisch mehrsprachig wie die Bedienoberfläche aus
v1.10.0, statt fest auf Englisch/Polnisch begrenzt zu sein. Neue
Übersetzungstabellen ersetzen die alten name_en/name_pl-Spalten,
DeepL-Auto-Übersetzung und CSV-Import/-Export unterstützen jetzt
beliebig viele aktive Sprachen.
2026-07-20 11:10:06 +02:00

115 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import "server-only";
import { query, queryOne } from "@/lib/db";
import {
loadTranslations,
loadTranslationsFor,
replaceTranslations,
type TranslationTable,
} from "./translations";
import type { CodeItem } from "@/lib/types";
// "allergens"/"additives" sind identisch aufgebaute Tabellen (Kürzel +
// Bezeichnung in Deutsch, Übersetzungen in der jeweiligen
// *_translations-Tabelle). Tabellenname kommt nie aus Nutzereingaben,
// sondern immer aus dem literal getypten Parameter unten – sicher für
// String-Interpolation in der Query.
export type CodeTable = "allergens" | "additives";
const TRANSLATION_TABLE: Record<CodeTable, TranslationTable> = {
allergens: "allergen_translations",
additives: "additive_translations",
};
interface CodeRow {
code: string;
name: string;
sort: number;
}
export async function listCodes(table: CodeTable): Promise<CodeItem[]> {
const rows = await query<CodeRow>(`select code, name, sort from ${table} order by sort`);
const translations = await loadTranslations(TRANSLATION_TABLE[table]);
return rows.map((r) => ({ ...r, translations: translations.get(r.code) ?? {} }));
}
export async function createCode(
table: CodeTable,
input: { code: string; name: string; translations?: Record<string, string | null> }
): Promise<CodeItem> {
const maxRow = await queryOne<{ sort: number }>(
`select sort from ${table} order by sort desc limit 1`
);
const row = (await queryOne<CodeRow>(
`insert into ${table} (code, name, sort) values ($1, $2, $3) returning code, name, sort`,
[input.code, input.name, (maxRow?.sort ?? 0) + 1]
))!;
if (input.translations) {
await replaceTranslations(TRANSLATION_TABLE[table], row.code, input.translations);
}
const translations = await loadTranslationsFor(TRANSLATION_TABLE[table], [row.code]);
return { ...row, translations: translations.get(row.code) ?? {} };
}
export async function updateCode(
table: CodeTable,
originalCode: string,
input: { code: string; name: string; translations?: Record<string, string | null> }
): Promise<CodeItem | null> {
const row = await queryOne<CodeRow>(
`update ${table} set code = $1, name = $2 where code = $3 returning code, name, sort`,
[input.code, input.name, originalCode]
);
if (!row) return null;
if (input.translations) {
await replaceTranslations(TRANSLATION_TABLE[table], row.code, input.translations);
}
const translations = await loadTranslationsFor(TRANSLATION_TABLE[table], [row.code]);
return { ...row, translations: translations.get(row.code) ?? {} };
}
export async function deleteCode(table: CodeTable, code: string): Promise<void> {
await query(`delete from ${table} where code = $1`, [code]);
}
export interface CodeImportRow {
code: string;
name: string;
translations: Record<string, string>;
}
/** CSV-Import: legt neue Kürzel an, bestehende (gleicher Code) werden
* aktualisiert – ein doppelter Code innerhalb derselben Datei
* aktualisiert also einfach zweimal statt einen Fehler zu werfen. */
export async function importCodes(
table: CodeTable,
rows: CodeImportRow[]
): Promise<{ created: number; updated: number }> {
const existing = await query<{ code: string }>(`select code from ${table}`);
const known = new Set(existing.map((r) => r.code));
const maxRow = await queryOne<{ sort: number }>(
`select coalesce(max(sort), 0) as sort from ${table}`
);
let nextSort = (maxRow?.sort ?? 0) + 1;
let created = 0;
let updated = 0;
for (const row of rows) {
if (known.has(row.code)) {
await query(`update ${table} set name = $1 where code = $2`, [row.name, row.code]);
updated++;
} else {
await query(`insert into ${table} (code, name, sort) values ($1, $2, $3)`, [
row.code,
row.name,
nextSort++,
]);
known.add(row.code);
created++;
}
if (Object.keys(row.translations).length > 0) {
await replaceTranslations(TRANSLATION_TABLE[table], row.code, row.translations);
}
}
return { created, updated };
}