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.
This commit is contained in:
@@ -36,7 +36,12 @@ const descriptions = [
|
||||
];
|
||||
|
||||
function getRandomElement<T>(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 = {
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -64,7 +64,7 @@ class InMemoryCache<T> implements CacheInterface<T> {
|
||||
* 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
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
|
||||
@@ -16,7 +16,7 @@ export class RedisCache<T> {
|
||||
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<T> {
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user