import { dataService } from "./dataService.js"; import { AdapterError, NotFoundError } from "./utils/errors.js"; import type { GraphQLContext } from "./utils/dataloaders.js"; import { userService } from "./auth/userService.js"; import { logger } from "./monitoring/logger.js"; import type { ContentItem } from "./types/page.js"; import type { User } from "./types/user.js"; /** * Fehlerbehandlung für GraphQL Resolver */ function handleError(error: unknown): never { if (error instanceof NotFoundError) { throw error; } if (error instanceof AdapterError) { logger.error("Adapter-Fehler", { message: error.message, originalError: error.originalError, }); throw new Error(`Datenfehler: ${error.message}`); } logger.error("Unerwarteter Fehler", { error }); throw new Error("Ein unerwarteter Fehler ist aufgetreten"); } /** * Wrapper-Funktion für GraphQL Resolver mit automatischem Error Handling */ async function withErrorHandling(resolver: () => Promise): Promise { try { return await resolver(); } catch (error) { handleError(error); } } /** * Formatiert einen User für GraphQL (konvertiert Date zu ISO String) */ function formatUserForGraphQL(user: User) { return { ...user, createdAt: user.createdAt.toISOString(), }; } export const resolvers = { Query: { products: async ( _: unknown, args: { limit?: number }, context: GraphQLContext ) => { return withErrorHandling(() => dataService.getProducts(args.limit)); }, product: async ( _: unknown, args: { id: string }, context: GraphQLContext ) => { return withErrorHandling(async () => { // Verwende Dataloader für Batch-Loading const product = await context.loaders.product.load(args.id); if (!product) { throw new NotFoundError("Produkt", args.id); } return product; }); }, pageSeo: async ( _: unknown, args: { locale?: string }, context: GraphQLContext ) => { return withErrorHandling(() => dataService.getPageSeo(args.locale)); }, page: async ( _: unknown, args: { slug: string; locale?: string }, context: GraphQLContext ) => { return withErrorHandling(async () => { // Verwende Dataloader für Batch-Loading (mit Locale im Key) // Format: "slug:locale" oder "slug" (ohne "page:" Präfix) const cacheKey = args.locale ? `${args.slug}:${args.locale}` : args.slug; const page = await context.loaders.page.load(cacheKey); if (!page) { throw new NotFoundError("Seite", args.slug); } return page; }); }, pages: async ( _: unknown, args: { locale?: string }, context: GraphQLContext ) => { return withErrorHandling(() => dataService.getPages(args.locale)); }, homepage: async ( _: unknown, args: { locale?: string }, context: GraphQLContext ) => { return withErrorHandling(async () => { // Homepage ist immer die Seite mit dem Slug "/" const homepage = await dataService.getPage("/", args.locale); if (!homepage) { throw new NotFoundError("Homepage", "/"); } return homepage; }); }, navigation: async ( _: unknown, args: { locale?: string }, context: GraphQLContext ) => { return withErrorHandling(() => dataService.getNavigation(args.locale)); }, me: async (_: unknown, __: unknown, context: GraphQLContext) => { return withErrorHandling(async () => { // Gibt aktuellen User zurück (null wenn nicht authentifiziert) if (!context.user) { return null; } return formatUserForGraphQL(context.user); }); }, translations: async ( _: unknown, args: { locale?: string; namespace?: string }, context: GraphQLContext ) => { return withErrorHandling(() => dataService.getTranslations(args.locale, args.namespace) ); }, }, Mutation: { register: async ( _: unknown, args: { email: string; password: string; name: string }, context: GraphQLContext ) => { return withErrorHandling(async () => { const result = await userService.register({ email: args.email, password: args.password, name: args.name, }); logger.info("User registered via GraphQL", { userId: result.user.id, email: result.user.email, }); return { user: formatUserForGraphQL(result.user), token: result.token, }; }); }, login: async ( _: unknown, args: { email: string; password: string }, context: GraphQLContext ) => { return withErrorHandling(async () => { const result = await userService.login({ email: args.email, password: args.password, }); logger.info("User logged in via GraphQL", { userId: result.user.id, email: result.user.email, }); return { user: formatUserForGraphQL(result.user), token: result.token, }; }); }, }, ContentItem: { __resolveType(obj: ContentItem): string | null { // Map für Type-Resolution (Strategy Pattern) const typeMap: Record = { html: "HTMLContent", markdown: "MarkdownContent", iframe: "IframeContent", imageGallery: "ImageGalleryContent", image: "ImageContent", quote: "QuoteContent", youtubeVideo: "YoutubeVideoContent", headline: "HeadlineContent", }; return typeMap[obj.type] || null; }, }, };