project setup with core files including configuration, package management, and basic structure. Added .gitignore, README, and various TypeScript types for CMS components. Implemented initial components and layouts for the application.

This commit is contained in:
Peter Meier
2025-12-13 23:26:13 +01:00
parent ea288a5bbc
commit b1a556dc6d
167 changed files with 19057 additions and 131 deletions

207
middlelayer/resolvers.ts Normal file
View File

@@ -0,0 +1,207 @@
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;
},
},
};