refactor(frontend): regen API types — migrate consumers off dropped Geschichte schema

With create/update returning GeschichteView, no endpoint serves the raw
Geschichte entity and springdoc drops its schema. Dashboard modules and the
home loader now use GeschichteSummary; GeschichteEditor takes GeschichteView
and maps persons into the displayName shape PersonMultiSelect renders —
fixing blank person chips on story edit. PersonMultiSelect/Sidebar narrow to
Pick<Person, 'id' | 'displayName'>, mirroring the DocumentOption precedent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-10 00:01:42 +02:00
parent 282c819e2a
commit 4e28f2f31b
9 changed files with 108 additions and 105 deletions

View File

@@ -728,6 +728,22 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/admin/backfill-titles": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post: operations["backfillTitles"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/admin/backfill-file-hashes": {
parameters: {
query?: never;
@@ -1464,22 +1480,6 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/documents/conversation": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["getConversation"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/dashboard/resume": {
parameters: {
query?: never;
@@ -1836,6 +1836,7 @@ export interface components {
sender?: components["schemas"]["Person"];
tags?: components["schemas"]["Tag"][];
trainingLabels?: ("KURRENT_RECOGNITION" | "KURRENT_SEGMENTATION")[];
hasTranscription: boolean;
thumbnailUrl?: string;
};
PersonMention: {
@@ -2023,25 +2024,44 @@ export interface components {
body?: string;
/** @enum {string} */
status?: "DRAFT" | "PUBLISHED";
/** @enum {string} */
type?: "STORY" | "JOURNEY";
personIds?: string[];
documentIds?: string[];
};
Geschichte: {
AuthorView: {
/** Format: uuid */
id: string;
displayName: string;
};
GeschichteView: {
/** Format: uuid */
id: string;
title: string;
body?: string;
/** @enum {string} */
status: "DRAFT" | "PUBLISHED";
author?: components["schemas"]["AppUser"];
persons?: components["schemas"]["Person"][];
documents?: components["schemas"]["Document"][];
/** @enum {string} */
type: "STORY" | "JOURNEY";
author?: components["schemas"]["AuthorView"];
persons: components["schemas"]["PersonView"][];
items: components["schemas"]["JourneyItemView"][];
/** Format: date-time */
publishedAt?: string;
/** Format: date-time */
createdAt: string;
/** Format: date-time */
updatedAt: string;
/** Format: date-time */
publishedAt?: string;
};
PersonView: {
/** Format: uuid */
id: string;
firstName?: string;
lastName?: string;
};
JourneyItemCreateDTO: {
/** Format: uuid */
documentId?: string;
note?: string;
};
CreateTranscriptionBlockDTO: {
/** Format: int32 */
@@ -2311,6 +2331,11 @@ export interface components {
color?: string;
/** Format: int32 */
documentCount: number;
/**
* Format: int32
* @description Distinct documents tagged with this tag or any descendant tag (subtree rollup)
*/
subtreeDocumentCount: number;
children?: components["schemas"]["TagTreeNodeDTO"][];
/**
* Format: uuid
@@ -2497,40 +2522,12 @@ export interface components {
type: "STORY" | "JOURNEY";
/** @enum {string} */
status: "DRAFT" | "PUBLISHED";
/** Format: date-time */
updatedAt: string;
author?: components["schemas"]["AuthorSummary"];
/** Format: date-time */
publishedAt?: string;
};
AuthorView: {
/** Format: uuid */
id: string;
displayName: string;
};
GeschichteView: {
/** Format: uuid */
id: string;
title: string;
body?: string;
/** @enum {string} */
status: "DRAFT" | "PUBLISHED";
/** @enum {string} */
type: "STORY" | "JOURNEY";
author?: components["schemas"]["AuthorView"];
persons: components["schemas"]["PersonView"][];
items: components["schemas"]["JourneyItemView"][];
/** Format: date-time */
publishedAt?: string;
/** Format: date-time */
createdAt: string;
/** Format: date-time */
updatedAt: string;
};
PersonView: {
/** Format: uuid */
id: string;
firstName?: string;
lastName?: string;
};
DocumentVersionSummary: {
/** Format: uuid */
id: string;
@@ -3733,7 +3730,7 @@ export interface operations {
[name: string]: unknown;
};
content: {
"*/*": components["schemas"]["Geschichte"][];
"*/*": components["schemas"]["GeschichteSummary"][];
};
};
};
@@ -3757,7 +3754,7 @@ export interface operations {
[name: string]: unknown;
};
content: {
"*/*": components["schemas"]["Geschichte"];
"*/*": components["schemas"]["GeschichteView"];
};
};
};
@@ -4286,6 +4283,26 @@ export interface operations {
};
};
};
backfillTitles: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"*/*": components["schemas"]["BackfillResult"];
};
};
};
};
backfillFileHashes: {
parameters: {
query?: never;
@@ -4485,7 +4502,7 @@ export interface operations {
[name: string]: unknown;
};
content: {
"*/*": components["schemas"]["Geschichte"];
"*/*": components["schemas"]["GeschichteView"];
};
};
};
@@ -5476,32 +5493,6 @@ export interface operations {
};
};
};
getConversation: {
parameters: {
query: {
senderId: string;
receiverId?: string;
from?: string;
to?: string;
dir?: string;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"*/*": components["schemas"]["Document"][];
};
};
};
};
getResume: {
parameters: {
query?: never;

View File

@@ -7,11 +7,12 @@ import { m } from '$lib/paraglide/messages.js';
import type { components } from '$lib/generated/api';
import GeschichteSidebar from '$lib/geschichte/GeschichteSidebar.svelte';
type Geschichte = components['schemas']['Geschichte'];
type GeschichteView = components['schemas']['GeschichteView'];
type Person = components['schemas']['Person'];
type PersonOption = Pick<Person, 'id' | 'displayName'>;
interface Props {
geschichte?: Geschichte | null;
geschichte?: GeschichteView | null;
initialPersons?: Person[];
onSubmit: (payload: {
title: string;
@@ -31,8 +32,13 @@ let { geschichte = null, initialPersons = [], onSubmit, submitting = false }: Pr
let title = $state(geschichte?.title ?? '');
let body = $state(geschichte?.body ?? '');
let status: 'DRAFT' | 'PUBLISHED' = $state(geschichte?.status ?? 'DRAFT');
let selectedPersons: Person[] = $state(
geschichte?.persons ? Array.from(geschichte.persons) : initialPersons
let selectedPersons: PersonOption[] = $state(
geschichte?.persons
? Array.from(geschichte.persons).map((p) => ({
id: p.id,
displayName: [p.firstName, p.lastName].filter(Boolean).join(' ')
}))
: initialPersons
);
let dirty = $state(false);

View File

@@ -4,10 +4,11 @@ import type { components } from '$lib/generated/api';
import PersonMultiSelect from '$lib/person/PersonMultiSelect.svelte';
type Person = components['schemas']['Person'];
type PersonOption = Pick<Person, 'id' | 'displayName'>;
interface Props {
status: 'DRAFT' | 'PUBLISHED';
selectedPersons: Person[];
selectedPersons: PersonOption[];
}
let { status, selectedPersons = $bindable() }: Props = $props();

View File

@@ -3,9 +3,12 @@ import type { components } from '$lib/generated/api';
import { m } from '$lib/paraglide/messages.js';
import { clickOutside } from '$lib/shared/actions/clickOutside';
type Person = components['schemas']['Person'];
// Narrow contract: only what the chips render and dedup needs. Full Person
// objects from /api/persons remain assignable; view projections fit too.
type PersonOption = Pick<Person, 'id' | 'displayName'>;
interface Props {
selectedPersons?: Person[];
selectedPersons?: PersonOption[];
}
let { selectedPersons = $bindable([]) }: Props = $props();

View File

@@ -3,10 +3,10 @@ import * as m from '$lib/paraglide/messages.js';
import { relativeTimeDe } from '$lib/shared/relativeTime';
import type { components } from '$lib/generated/api';
type Geschichte = components['schemas']['Geschichte'];
type GeschichteSummary = components['schemas']['GeschichteSummary'];
interface Props {
drafts: Geschichte[];
drafts: GeschichteSummary[];
}
const { drafts }: Props = $props();

View File

@@ -5,24 +5,25 @@ import { page } from 'vitest/browser';
import ReaderDraftsModule from './ReaderDraftsModule.svelte';
import type { components } from '$lib/generated/api';
type Geschichte = components['schemas']['Geschichte'];
type GeschichteSummary = components['schemas']['GeschichteSummary'];
afterEach(() => {
cleanup();
});
const draft1: Geschichte = {
const draft1: GeschichteSummary = {
id: 'g1',
title: 'Mein erster Entwurf',
status: 'DRAFT',
createdAt: '2025-01-01T00:00:00Z',
type: 'STORY',
updatedAt: '2025-01-02T00:00:00Z'
};
const draft2: Geschichte = {
const draft2: GeschichteSummary = {
id: 'g2',
title: 'Zweiter Entwurf',
status: 'DRAFT',
type: 'STORY',
createdAt: '2025-02-01T00:00:00Z',
updatedAt: '2025-02-01T00:00:00Z'
};

View File

@@ -3,10 +3,10 @@ import * as m from '$lib/paraglide/messages.js';
import { relativeTimeDe } from '$lib/shared/relativeTime';
import type { components } from '$lib/generated/api';
type Geschichte = components['schemas']['Geschichte'];
type GeschichteSummary = components['schemas']['GeschichteSummary'];
interface Props {
stories: Geschichte[];
stories: GeschichteSummary[];
}
const { stories }: Props = $props();

View File

@@ -5,27 +5,28 @@ import { page } from 'vitest/browser';
import ReaderRecentStories from './ReaderRecentStories.svelte';
import type { components } from '$lib/generated/api';
type Geschichte = components['schemas']['Geschichte'];
type GeschichteSummary = components['schemas']['GeschichteSummary'];
afterEach(() => {
cleanup();
});
const story1: Geschichte = {
const story1: GeschichteSummary = {
id: 'g1',
title: 'Die Familie Müller',
body: '<p>Dies ist eine sehr lange Geschichte über die Familie Müller. Sie lebten in Bayern und hatten viele Kinder. Das war früher so üblich in diesen Gebieten.</p>',
status: 'PUBLISHED',
createdAt: '2025-01-01T00:00:00Z',
type: 'STORY',
updatedAt: '2025-01-01T00:00:00Z',
publishedAt: '2025-01-01T00:00:00Z'
};
const longBodyStory: Geschichte = {
const longBodyStory: GeschichteSummary = {
id: 'g2',
title: 'Sehr lange Geschichte',
body: '<p>' + 'A'.repeat(200) + '</p>',
status: 'PUBLISHED',
type: 'STORY',
createdAt: '2025-02-01T00:00:00Z',
updatedAt: '2025-02-01T00:00:00Z',
publishedAt: '2025-02-01T00:00:00Z'

View File

@@ -11,7 +11,7 @@ type ActivityFeedItemDTO = components['schemas']['ActivityFeedItemDTO'];
type IncompleteDocumentDTO = components['schemas']['IncompleteDocumentDTO'];
type PersonSummaryDTO = components['schemas']['PersonSummaryDTO'];
type DocumentListItem = components['schemas']['DocumentListItem'];
type Geschichte = components['schemas']['Geschichte'];
type GeschichteSummary = components['schemas']['GeschichteSummary'];
type TagTreeNodeDTO = components['schemas']['TagTreeNodeDTO'];
function settled<T>(res: PromiseSettledResult<unknown> | undefined): T | null {
@@ -57,9 +57,9 @@ export async function load({ fetch, parent }) {
const topPersons = settled<{ items: PersonSummaryDTO[] }>(topPersonsRes)?.items ?? [];
const searchData = settled<{ items: DocumentListItem[] }>(recentDocsRes);
const recentDocs = searchData?.items ?? [];
const recentStories = settled<Geschichte[]>(recentStoriesRes) ?? [];
const recentStories = settled<GeschichteSummary[]>(recentStoriesRes) ?? [];
const tagTree = settled<TagTreeNodeDTO[]>(tagTreeRes) ?? [];
const drafts = settled<Geschichte[]>(draftsRes) ?? [];
const drafts = settled<GeschichteSummary[]>(draftsRes) ?? [];
return {
isReader: true as const,
@@ -179,9 +179,9 @@ export async function load({ fetch, parent }) {
readerStats: null,
topPersons: [] as PersonSummaryDTO[],
recentDocs: [] as DocumentListItem[],
recentStories: [] as Geschichte[],
recentStories: [] as GeschichteSummary[],
tagTree: [] as TagTreeNodeDTO[],
drafts: [] as Geschichte[],
drafts: [] as GeschichteSummary[],
error: 'Daten konnten nicht geladen werden.' as string | null
};
}