Files
sell/middlelayer/resolvers.ts

208 lines
5.7 KiB
TypeScript

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<T>(resolver: () => Promise<T>): Promise<T> {
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<ContentItem["type"], string> = {
html: "HTMLContent",
markdown: "MarkdownContent",
iframe: "IframeContent",
imageGallery: "ImageGalleryContent",
image: "ImageContent",
quote: "QuoteContent",
youtubeVideo: "YoutubeVideoContent",
headline: "HeadlineContent",
};
return typeMap[obj.type] || null;
},
},
};