diff --git a/middlelayer/adapters/Mock/_cms/mockProducts.ts b/middlelayer/adapters/Mock/_cms/mockProducts.ts index 531a478..398db15 100644 --- a/middlelayer/adapters/Mock/_cms/mockProducts.ts +++ b/middlelayer/adapters/Mock/_cms/mockProducts.ts @@ -36,7 +36,12 @@ const descriptions = [ ]; function getRandomElement(array: T[]): T { - return array[Math.floor(Math.random() * array.length)]; + const index = Math.floor(Math.random() * array.length); + const element = array[index]; + if (element === undefined) { + throw new Error("Array is empty"); + } + return element; } function getRandomPrice(): number { @@ -57,9 +62,14 @@ function generateProduct(id: string): Product { const promotionType = Math.random(); if (promotionType < 0.33) { // Sale mit Rabatt - const discountPercent = [10, 20, 30, 40, 50][ - Math.floor(Math.random() * 5) - ]; + const discountPercentArray = [10, 20, 30, 40, 50]; + const discountPercent = + discountPercentArray[ + Math.floor(Math.random() * discountPercentArray.length) + ]; + if (discountPercent === undefined) { + throw new Error("Discount percent array is empty"); + } originalPrice = basePrice; price = Math.round(basePrice * (1 - discountPercent / 100) * 100) / 100; promotion = { diff --git a/middlelayer/adapters/config.ts b/middlelayer/adapters/config.ts index 8952110..8796db3 100644 --- a/middlelayer/adapters/config.ts +++ b/middlelayer/adapters/config.ts @@ -6,7 +6,7 @@ import type { DataAdapter } from "./interface"; * Bestimmt welcher Adapter basierend auf Environment-Variablen verwendet wird */ export function createAdapter(): DataAdapter { - const adapterType = process.env.DATA_ADAPTER || "mock"; + const adapterType = process.env["DATA_ADAPTER"] || "mock"; switch (adapterType) { case "mock": diff --git a/middlelayer/auth/jwt.ts b/middlelayer/auth/jwt.ts index a98fe8c..ecfd1db 100644 --- a/middlelayer/auth/jwt.ts +++ b/middlelayer/auth/jwt.ts @@ -1,10 +1,11 @@ -import jwt from "jsonwebtoken"; -import type { JWTPayload, UserRole } from "../types/user.js"; +import jwt, { type SignOptions } from "jsonwebtoken"; +import type { JWTPayload } from "../types/user.js"; +import { UserRole } from "../types/user.js"; import { logger } from "../monitoring/logger.js"; -const JWT_SECRET = - process.env.JWT_SECRET || "your-secret-key-change-in-production"; -const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || "7d"; +const JWT_SECRET: string = + process.env["JWT_SECRET"] || "your-secret-key-change-in-production"; +const JWT_EXPIRES_IN: string = process.env["JWT_EXPIRES_IN"] || "7d"; /** * Erstellt ein JWT Token für einen User @@ -12,7 +13,7 @@ const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || "7d"; export function createToken(payload: JWTPayload): string { return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN, - }); + } as SignOptions); } /** @@ -42,7 +43,7 @@ export function extractTokenFromHeader( return null; } - return parts[1]; + return parts[1] ?? null; } /** diff --git a/middlelayer/auth/userService.ts b/middlelayer/auth/userService.ts index 127daaa..6a0c979 100644 --- a/middlelayer/auth/userService.ts +++ b/middlelayer/auth/userService.ts @@ -1,9 +1,5 @@ -import type { - User, - UserRole, - LoginCredentials, - RegisterData, -} from "../types/user.js"; +import type { User, LoginCredentials, RegisterData } from "../types/user.js"; +import { UserRole } from "../types/user.js"; import { hashPassword, comparePassword } from "./password.js"; import { createToken, verifyToken } from "./jwt.js"; import { logger } from "../monitoring/logger.js"; @@ -22,7 +18,7 @@ export class UserService { */ async register( data: RegisterData, - role: UserRole = "customer" + role: UserRole = UserRole.CUSTOMER ): Promise<{ user: User; token: string; diff --git a/middlelayer/config/cache.ts b/middlelayer/config/cache.ts index b709d0e..b6599ef 100644 --- a/middlelayer/config/cache.ts +++ b/middlelayer/config/cache.ts @@ -4,16 +4,15 @@ */ export const cacheConfig = { pages: { - ttl: parseInt(process.env.CACHE_PAGES_TTL || '60000'), // 60 Sekunden + ttl: parseInt(process.env["CACHE_PAGES_TTL"] || "60000"), // 60 Sekunden }, pageSeo: { - ttl: parseInt(process.env.CACHE_PAGE_SEO_TTL || '300000'), // 5 Minuten + ttl: parseInt(process.env["CACHE_PAGE_SEO_TTL"] || "300000"), // 5 Minuten }, navigation: { - ttl: parseInt(process.env.CACHE_NAVIGATION_TTL || '300000'), // 5 Minuten + ttl: parseInt(process.env["CACHE_NAVIGATION_TTL"] || "300000"), // 5 Minuten }, products: { - ttl: parseInt(process.env.CACHE_PRODUCTS_TTL || '30000'), // 30 Sekunden + ttl: parseInt(process.env["CACHE_PRODUCTS_TTL"] || "30000"), // 30 Sekunden }, } as const; - diff --git a/middlelayer/dataService.ts b/middlelayer/dataService.ts index b12a0f1..375acd4 100644 --- a/middlelayer/dataService.ts +++ b/middlelayer/dataService.ts @@ -2,7 +2,6 @@ import type { DataAdapter } from "./adapters/interface.js"; import { createAdapter } from "./adapters/config.js"; import type { PageSeo, Page, Navigation, Product } from "./types/index.js"; import type { TranslationsData } from "./adapters/Mock/_i18n/mockTranslations.js"; -import { AdapterError } from "./utils/errors.js"; import { cache } from "./utils/cache.js"; import { CacheKeyBuilder } from "./utils/cacheKeys.js"; import { DataServiceHelpers } from "./utils/dataServiceHelpers.js"; diff --git a/middlelayer/index.ts b/middlelayer/index.ts index 4608129..009c73d 100644 --- a/middlelayer/index.ts +++ b/middlelayer/index.ts @@ -15,14 +15,14 @@ import { getMetrics } from "./monitoring/metrics.js"; import { extractTokenFromHeader } from "./auth/jwt.js"; import { userService } from "./auth/userService.js"; -const PORT = process.env.PORT ? parseInt(process.env.PORT) : 4000; -const METRICS_PORT = process.env.METRICS_PORT - ? parseInt(process.env.METRICS_PORT) +const PORT = process.env["PORT"] ? parseInt(process.env["PORT"]) : 4000; +const METRICS_PORT = process.env["METRICS_PORT"] + ? parseInt(process.env["METRICS_PORT"]) : 9090; // Konfiguration aus Environment Variables -const MAX_QUERY_COMPLEXITY = process.env.MAX_QUERY_COMPLEXITY - ? parseInt(process.env.MAX_QUERY_COMPLEXITY) +const MAX_QUERY_COMPLEXITY = process.env["MAX_QUERY_COMPLEXITY"] + ? parseInt(process.env["MAX_QUERY_COMPLEXITY"]) : 1000; /** diff --git a/middlelayer/monitoring/logger.ts b/middlelayer/monitoring/logger.ts index fcc231a..56cc925 100644 --- a/middlelayer/monitoring/logger.ts +++ b/middlelayer/monitoring/logger.ts @@ -5,7 +5,7 @@ import winston from "winston"; * Erstellt JSON-Logs für bessere Analyse und Monitoring */ export const logger = winston.createLogger({ - level: process.env.LOG_LEVEL || "info", + level: process.env["LOG_LEVEL"] || "info", format: winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), @@ -13,7 +13,7 @@ export const logger = winston.createLogger({ ), defaultMeta: { service: "graphql-middlelayer", - environment: process.env.NODE_ENV || "development", + environment: process.env["NODE_ENV"] || "development", }, transports: [ // Console Output (für Development) @@ -29,7 +29,7 @@ export const logger = winston.createLogger({ ), }), // File Output (für Production) - ...(process.env.NODE_ENV === "production" + ...(process.env["NODE_ENV"] === "production" ? [ new winston.transports.File({ filename: "logs/error.log", diff --git a/middlelayer/resolvers.ts b/middlelayer/resolvers.ts index c915947..41b3ecb 100644 --- a/middlelayer/resolvers.ts +++ b/middlelayer/resolvers.ts @@ -50,7 +50,7 @@ export const resolvers = { products: async ( _: unknown, args: { limit?: number }, - context: GraphQLContext + _context: GraphQLContext ) => { return withErrorHandling(() => dataService.getProducts(args.limit)); }, @@ -71,7 +71,7 @@ export const resolvers = { pageSeo: async ( _: unknown, args: { locale?: string }, - context: GraphQLContext + _context: GraphQLContext ) => { return withErrorHandling(() => dataService.getPageSeo(args.locale)); }, @@ -96,14 +96,14 @@ export const resolvers = { pages: async ( _: unknown, args: { locale?: string }, - context: GraphQLContext + _context: GraphQLContext ) => { return withErrorHandling(() => dataService.getPages(args.locale)); }, homepage: async ( _: unknown, args: { locale?: string }, - context: GraphQLContext + _context: GraphQLContext ) => { return withErrorHandling(async () => { // Homepage ist immer die Seite mit dem Slug "/" @@ -117,7 +117,7 @@ export const resolvers = { navigation: async ( _: unknown, args: { locale?: string }, - context: GraphQLContext + _context: GraphQLContext ) => { return withErrorHandling(() => dataService.getNavigation(args.locale)); }, @@ -133,7 +133,7 @@ export const resolvers = { translations: async ( _: unknown, args: { locale?: string; namespace?: string }, - context: GraphQLContext + _context: GraphQLContext ) => { return withErrorHandling(() => dataService.getTranslations(args.locale, args.namespace) @@ -144,7 +144,7 @@ export const resolvers = { register: async ( _: unknown, args: { email: string; password: string; name: string }, - context: GraphQLContext + _context: GraphQLContext ) => { return withErrorHandling(async () => { const result = await userService.register({ @@ -167,7 +167,7 @@ export const resolvers = { login: async ( _: unknown, args: { email: string; password: string }, - context: GraphQLContext + _context: GraphQLContext ) => { return withErrorHandling(async () => { const result = await userService.login({ diff --git a/middlelayer/types/page.ts b/middlelayer/types/page.ts index 7630774..113bee7 100644 --- a/middlelayer/types/page.ts +++ b/middlelayer/types/page.ts @@ -9,6 +9,16 @@ import type { c_youtubeVideo } from "./c_youtubeVideo"; import type { c_headline } from "./c_headline"; export type { contentLayout }; +export type { + c_html, + c_markdown, + c_iframe, + c_imageGallery, + c_image, + c_quote, + c_youtubeVideo, + c_headline, +}; export type ContentItem = | c_html | c_markdown diff --git a/middlelayer/utils/cache.ts b/middlelayer/utils/cache.ts index c06cf59..3464e74 100644 --- a/middlelayer/utils/cache.ts +++ b/middlelayer/utils/cache.ts @@ -64,7 +64,7 @@ class InMemoryCache implements CacheInterface { * Cache-Instanzen * Verwendet Redis wenn aktiviert, sonst In-Memory */ -const useRedis = process.env.REDIS_ENABLED === "true"; +const useRedis = process.env["REDIS_ENABLED"] === "true"; export const cache = { pages: useRedis diff --git a/middlelayer/utils/dataloaders.ts b/middlelayer/utils/dataloaders.ts index 52dcd14..25c4426 100644 --- a/middlelayer/utils/dataloaders.ts +++ b/middlelayer/utils/dataloaders.ts @@ -33,7 +33,7 @@ export const createPageLoader = () => { keys.map((key) => { // Parse Key: Format kann "slug:locale" oder "slug" sein const parts = key.split(":"); - const slug = parts[0]; + const slug = parts[0] ?? ""; const locale = parts.length > 1 ? parts[1] : undefined; return dataService.getPage(slug, locale); }) diff --git a/middlelayer/utils/redisCache.ts b/middlelayer/utils/redisCache.ts index ca188db..b4f6826 100644 --- a/middlelayer/utils/redisCache.ts +++ b/middlelayer/utils/redisCache.ts @@ -16,7 +16,7 @@ export class RedisCache { constructor(defaultTTL: number, cacheType: string) { this.defaultTTL = defaultTTL; this.cacheType = cacheType; - this.useRedis = process.env.REDIS_ENABLED === "true"; + this.useRedis = process.env["REDIS_ENABLED"] === "true"; if (this.useRedis) { this.initRedis(); @@ -30,9 +30,9 @@ export class RedisCache { private async initRedis() { try { this.redis = new Redis({ - host: process.env.REDIS_HOST || "localhost", - port: parseInt(process.env.REDIS_PORT || "6379"), - password: process.env.REDIS_PASSWORD, + host: process.env["REDIS_HOST"] || "localhost", + port: parseInt(process.env["REDIS_PORT"] || "6379"), + password: process.env["REDIS_PASSWORD"], retryStrategy: (times) => { // Bei Fehler: Fallback auf In-Memory nach 3 Versuchen if (times > 3) { diff --git a/src/components/LoginModal.tsx b/src/components/LoginModal.tsx index 69c51a3..5becbae 100644 --- a/src/components/LoginModal.tsx +++ b/src/components/LoginModal.tsx @@ -86,8 +86,9 @@ export default function LoginModal() { const result: GraphQLResponse = await response.json(); - if (result.errors) { - setError(result.errors[0].message || t("login.error")); + if (result.errors && result.errors.length > 0) { + const firstError = result.errors[0]; + setError(firstError?.message || t("login.error")); return; } @@ -126,7 +127,9 @@ export default function LoginModal() { onClick={(e) => e.stopPropagation()} >
-

{t("login.title")}

+

+ {t("login.title")} +