fix(geschichte): stop exposing author email in the list projection

GET /api/geschichten shipped every author's AppUser email to all readers via
GeschichteSummary.AuthorSummary — contradicting the documented rule that
author projections never expose email or group memberships. The frontend
only used it as a display-name fallback; it now falls back to [Unbekannt],
matching the server-side rule in GeschichteService.toView.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-10 07:25:11 +02:00
parent b3d54a12c4
commit e077dba595
9 changed files with 28 additions and 24 deletions

View File

@@ -12,7 +12,7 @@ const baseRow = (overrides = {}) => ({
body: '<p>Im Jahr 1923...</p>',
type: 'STORY' as 'STORY' | 'JOURNEY',
status: 'PUBLISHED' as 'PUBLISHED' | 'DRAFT',
author: { firstName: 'Anna', lastName: 'Schmidt', email: 'a@x' },
author: { firstName: 'Anna', lastName: 'Schmidt' },
publishedAt: '2026-04-15T10:00:00Z',
...overrides
});

View File

@@ -27,7 +27,7 @@ function authorName(g: GeschichteSummary): string {
const a = g.author;
if (!a) return '';
const full = [a.firstName, a.lastName].filter(Boolean).join(' ').trim();
return full || a.email || '';
return full || '[Unbekannt]';
}
</script>

View File

@@ -16,7 +16,6 @@ const makeStory = (id: string, title: string, body: string | undefined = '<p>Bod
items: [],
author: {
id: 'u1',
email: 'marcel@example.com',
firstName: 'Marcel',
lastName: 'Raddatz',
enabled: true,

View File

@@ -3,21 +3,19 @@ import { formatAuthorName, formatAuthorDisplayName, formatPublishedAt } from './
describe('formatAuthorName', () => {
it('joins firstName and lastName with a space', () => {
expect(formatAuthorName({ firstName: 'Anna', lastName: 'Schmidt', email: 'a@x' })).toBe(
'Anna Schmidt'
);
expect(formatAuthorName({ firstName: 'Anna', lastName: 'Schmidt' })).toBe('Anna Schmidt');
});
it('returns firstName alone when lastName is absent', () => {
expect(formatAuthorName({ firstName: 'Anna', email: 'a@x' })).toBe('Anna');
expect(formatAuthorName({ firstName: 'Anna' })).toBe('Anna');
});
it('returns lastName alone when firstName is absent', () => {
expect(formatAuthorName({ lastName: 'Schmidt', email: 'a@x' })).toBe('Schmidt');
expect(formatAuthorName({ lastName: 'Schmidt' })).toBe('Schmidt');
});
it('falls back to email when both names are absent', () => {
expect(formatAuthorName({ email: 'fallback@example.com' })).toBe('fallback@example.com');
it('falls back to [Unbekannt] when both names are absent', () => {
expect(formatAuthorName({})).toBe('[Unbekannt]');
});
it('returns empty string for null input', () => {

View File

@@ -1,12 +1,13 @@
import { formatDate } from '$lib/shared/utils/date';
type AuthorSummary = { firstName?: string; lastName?: string; email: string };
type AuthorSummary = { firstName?: string; lastName?: string };
type AuthorView = { displayName: string };
export function formatAuthorName(author: AuthorSummary | null | undefined): string {
if (!author) return '';
const full = [author.firstName, author.lastName].filter(Boolean).join(' ').trim();
return full || author.email || '';
// Mirrors the server-side fallback in GeschichteService.toView — email is no longer exposed.
return full || '[Unbekannt]';
}
export function formatAuthorDisplayName(author: AuthorView | null | undefined): string {