refactor(api): DTO-Helper extrahieren + N+1 in marketplace/decks beheben

- `lib/dto.ts`: `toDeckDto` und `toCardDto` aus routes/decks.ts und
  routes/cards.ts extrahiert — testbar, zentrale Output-Shape-Doku
- `lib/marketplace/dto.ts`: `toPublicDeckDto`, `toOwnerDto`, `toVersionDto`
  aus routes/marketplace/decks.ts extrahiert
- `GET /:slug` in marketplace/decks.ts: Version + Owner parallel per
  `Promise.all` statt sequenziell (2 RTT → 1 RTT)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-05-10 16:30:29 +02:00
parent f2f752e9ee
commit c39bacc971
5 changed files with 87 additions and 86 deletions

33
apps/api/src/lib/dto.ts Normal file
View file

@ -0,0 +1,33 @@
import { cards, decks } from '../db/schema/index.ts';
export function toDeckDto(row: typeof decks.$inferSelect) {
return {
id: row.id,
user_id: row.userId,
name: row.name,
description: row.description,
color: row.color,
category: row.category,
visibility: row.visibility,
fsrs_settings: row.fsrsSettings,
content_hash: row.contentHash,
forked_from_marketplace_deck_id: row.forkedFromMarketplaceDeckId,
forked_from_marketplace_version_id: row.forkedFromMarketplaceVersionId,
created_at: row.createdAt.toISOString(),
updated_at: row.updatedAt.toISOString(),
};
}
export function toCardDto(row: typeof cards.$inferSelect) {
return {
id: row.id,
deck_id: row.deckId,
user_id: row.userId,
type: row.type,
fields: row.fields,
media_refs: row.mediaRefs ?? [],
content_hash: row.contentHash,
created_at: row.createdAt.toISOString(),
updated_at: row.updatedAt.toISOString(),
};
}

View file

@ -0,0 +1,43 @@
import { publicDecks, publicDeckVersions } from '../../db/schema/index.ts';
import type { AuthorRow } from '../../db/schema/marketplace/index.ts';
export function toPublicDeckDto(row: typeof publicDecks.$inferSelect) {
return {
id: row.id,
slug: row.slug,
title: row.title,
description: row.description,
language: row.language,
category: row.category,
license: row.license,
price_credits: row.priceCredits,
owner_user_id: row.ownerUserId,
latest_version_id: row.latestVersionId,
is_featured: row.isFeatured,
is_takedown: row.isTakedown,
created_at: row.createdAt.toISOString(),
};
}
export function toOwnerDto(row: AuthorRow) {
return {
slug: row.slug,
display_name: row.displayName,
verified_mana: row.verifiedMana,
verified_community: row.verifiedCommunity,
pseudonym: row.pseudonym,
};
}
export function toVersionDto(row: typeof publicDeckVersions.$inferSelect) {
return {
id: row.id,
deck_id: row.deckId,
semver: row.semver,
changelog: row.changelog,
content_hash: row.contentHash,
card_count: row.cardCount,
published_at: row.publishedAt.toISOString(),
deprecated_at: row.deprecatedAt?.toISOString() ?? null,
};
}