From 29667256859167465843de28f98bf380c9cbb8fc Mon Sep 17 00:00:00 2001 From: Peter Meier Date: Sun, 14 Dec 2025 00:05:47 +0100 Subject: [PATCH] Update TypeScript configuration for stricter type checks and modify environment variable access. Exclude additional directories from compilation, enhance error handling in resolvers, and improve random element selection in mock data generation. Refactor locale handling in middleware and update GraphQL client to use bracket notation for environment variables. --- .../adapters/Mock/_cms/mockProducts.ts | 18 ++++++++++++++---- middlelayer/adapters/config.ts | 2 +- middlelayer/auth/jwt.ts | 15 ++++++++------- middlelayer/auth/userService.ts | 10 +++------- middlelayer/config/cache.ts | 9 ++++----- middlelayer/dataService.ts | 1 - middlelayer/index.ts | 10 +++++----- middlelayer/monitoring/logger.ts | 6 +++--- middlelayer/resolvers.ts | 16 ++++++++-------- middlelayer/types/page.ts | 10 ++++++++++ middlelayer/utils/cache.ts | 2 +- middlelayer/utils/dataloaders.ts | 2 +- middlelayer/utils/redisCache.ts | 8 ++++---- src/components/LoginModal.tsx | 10 ++++++---- src/components/RegisterModal.tsx | 5 +++-- src/lib/alpine/loginModal.ts | 5 +++-- src/lib/alpine/registerModal.ts | 5 +++-- src/lib/graphql/client.ts | 2 +- src/lib/i18n/i18n.ts | 12 +++++++----- src/middleware.ts | 12 +++++++----- src/pages/[locale]/404.astro | 2 +- src/pages/[locale]/500.astro | 2 +- src/pages/[locale]/[...slug].astro | 4 ++-- src/pages/[locale]/index.astro | 2 +- src/pages/[locale]/products/index.astro | 2 +- tsconfig.json | 19 +++++++++++++++++-- 26 files changed, 115 insertions(+), 76 deletions(-) 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")} +