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

View File

@@ -0,0 +1,101 @@
import type { ApolloServerPlugin } from '@apollo/server';
import { logger, logQuery, logError } from '../monitoring/logger.js';
import {
queryCounter,
queryDuration,
errorCounter,
queryComplexityGauge,
} from '../monitoring/metrics.js';
import { createTrace, endTrace } from '../monitoring/tracing.js';
/**
* Monitoring Plugin für Apollo Server
* Sammelt Logs, Metrics und Traces für jeden Request
*/
export const monitoringPlugin = (): ApolloServerPlugin => {
return {
async requestDidStart() {
return {
async didResolveOperation({ request, operationName }) {
// Erstelle Trace für Request
const traceId = createTrace().traceId;
(request as any).traceId = traceId;
logger.info('GraphQL operation started', {
operationName: operationName || 'unknown',
query: request.query,
variables: request.variables,
traceId,
});
},
async willSendResponse({ request, response }) {
const traceId = (request as any).traceId;
const operationName = request.operationName || 'unknown';
const duration = traceId ? endTrace(traceId) : 0;
// Log Query
logQuery(operationName, request.variables, duration);
// Metrics
const status = response.errors && response.errors.length > 0 ? 'error' : 'success';
queryCounter.inc({ operation: operationName, status });
queryDuration.observe({ operation: operationName }, duration / 1000);
// Log Errors
if (response.errors && response.errors.length > 0) {
response.errors.forEach((error) => {
logError(error as Error, {
operationName,
traceId,
});
errorCounter.inc({
type: error.extensions?.code as string || 'UNKNOWN',
operation: operationName,
});
});
}
},
async didEncounterErrors({ request, errors }) {
const traceId = (request as any).traceId;
const operationName = request.operationName || 'unknown';
errors.forEach((error) => {
logError(error as Error, {
operationName,
traceId,
});
errorCounter.inc({
type: error.extensions?.code as string || 'UNKNOWN',
operation: operationName,
});
});
},
};
},
};
};
/**
* Plugin für Query Complexity Tracking
*/
export const queryComplexityMonitoringPlugin = (): ApolloServerPlugin => {
return {
async requestDidStart() {
return {
async didResolveOperation({ request, operationName }) {
// Wird vom queryComplexityPlugin gesetzt
const complexity = (request as any).complexity;
if (complexity) {
queryComplexityGauge.set(
{ operation: operationName || 'unknown' },
complexity
);
}
},
};
},
};
};

View File

@@ -0,0 +1,67 @@
import type { ApolloServerPlugin } from "@apollo/server";
import { getComplexity, simpleEstimator } from "graphql-query-complexity";
import { GraphQLError } from "graphql";
interface QueryComplexityPluginOptions {
maxComplexity?: number;
defaultComplexity?: number;
}
/**
* Apollo Server Plugin für Query Complexity Limits
* Verhindert zu komplexe Queries, die das System überlasten könnten
*/
export const queryComplexityPlugin = (
options: QueryComplexityPluginOptions = {}
): ApolloServerPlugin => {
const maxComplexity = options.maxComplexity ?? 1000;
const defaultComplexity = options.defaultComplexity ?? 1;
return {
async requestDidStart() {
return {
async didResolveOperation({ request, document, schema }) {
if (!schema) return;
try {
const complexity = getComplexity({
schema,
operationName: request.operationName || undefined,
query: document,
variables: request.variables || {},
estimators: [
// Basis-Komplexität für jeden Field
simpleEstimator({ defaultComplexity }),
],
});
// Speichere Complexity im Request für Monitoring
(request as any).complexity = complexity;
if (complexity > maxComplexity) {
throw new GraphQLError(
`Query zu komplex (${complexity}). Maximum: ${maxComplexity}`,
{
extensions: {
code: "QUERY_TOO_COMPLEX",
complexity,
maxComplexity,
},
}
);
}
} catch (error: any) {
// Wenn es ein Schema-Realm-Problem gibt, logge es aber blockiere nicht
if (error.message?.includes("another module or realm")) {
console.warn(
"[Query Complexity] Schema-Realm-Konflikt, Complexity-Check übersprungen"
);
return;
}
throw error;
}
},
};
},
};
};

View File

@@ -0,0 +1,32 @@
import ApolloServerPluginResponseCache from "@apollo/server-plugin-response-cache";
import type { GraphQLRequestContext } from "@apollo/server";
/**
* Response Caching Plugin für Apollo Server
* Cached GraphQL Responses basierend auf Query und Variablen
*/
export const createResponseCachePlugin = () => {
return ApolloServerPluginResponseCache({
// Session-ID für User-spezifisches Caching
sessionId: async (
requestContext: GraphQLRequestContext<any>
): Promise<string | null> => {
// Optional: User-ID aus Headers oder Context
const userId = requestContext.request.http?.headers.get("x-user-id");
return userId || null;
},
// Cache nur bei erfolgreichen Queries
shouldWriteToCache: async (
requestContext: GraphQLRequestContext<any>
): Promise<boolean> => {
const query = requestContext.request.query;
if (!query) return false;
// Cache nur bestimmte Queries
const cacheableQueries = ["products", "pageSeo", "navigation", "page"];
return cacheableQueries.some((q) => query.includes(q));
},
});
};