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,175 @@
import Redis from "ioredis";
import { cacheHits, cacheMisses } from "../monitoring/metrics.js";
import { logCacheHit, logCacheMiss } from "../monitoring/logger.js";
import { logger } from "../monitoring/logger.js";
/**
* Redis-basierter Cache mit Fallback auf In-Memory
*/
export class RedisCache<T> {
private redis: Redis | null = null;
private memoryCache: Map<string, { data: T; expiresAt: number }> = new Map();
private defaultTTL: number;
private cacheType: string;
private useRedis: boolean;
constructor(defaultTTL: number, cacheType: string) {
this.defaultTTL = defaultTTL;
this.cacheType = cacheType;
this.useRedis = process.env.REDIS_ENABLED === "true";
if (this.useRedis) {
this.initRedis();
} else {
logger.info(
`Redis Cache deaktiviert für ${cacheType}, verwende In-Memory Cache`
);
}
}
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,
retryStrategy: (times) => {
// Bei Fehler: Fallback auf In-Memory nach 3 Versuchen
if (times > 3) {
logger.warn(
"Redis-Verbindung fehlgeschlagen, verwende In-Memory Cache"
);
this.useRedis = false;
return null; // Stoppe Retry
}
return Math.min(times * 50, 2000);
},
maxRetriesPerRequest: 3,
});
this.redis.on("error", (error) => {
logger.error("Redis-Fehler", { error, cacheType: this.cacheType });
// Fallback auf In-Memory bei Fehler
this.useRedis = false;
});
this.redis.on("connect", () => {
logger.info(`Redis verbunden für ${this.cacheType}`);
});
// Test-Verbindung
await this.redis.ping();
logger.info(`Redis Cache aktiviert für ${this.cacheType}`);
} catch (error) {
logger.warn(
"Redis-Initialisierung fehlgeschlagen, verwende In-Memory Cache",
{
error,
cacheType: this.cacheType,
}
);
this.useRedis = false;
}
}
async set(key: string, data: T, ttl?: number): Promise<void> {
const ttlMs = ttl || this.defaultTTL;
const serialized = JSON.stringify(data);
if (this.useRedis && this.redis) {
try {
// Redis TTL in Sekunden
const ttlSeconds = Math.ceil(ttlMs / 1000);
await this.redis.setex(key, ttlSeconds, serialized);
return;
} catch (error) {
logger.warn("Redis set fehlgeschlagen, verwende In-Memory", {
error,
key,
});
this.useRedis = false;
}
}
// Fallback: In-Memory
const expiresAt = Date.now() + ttlMs;
this.memoryCache.set(key, { data, expiresAt });
}
async get(key: string): Promise<T | null> {
if (this.useRedis && this.redis) {
try {
const value = await this.redis.get(key);
if (value) {
// Cache Hit
cacheHits.inc({ cache_type: this.cacheType });
logCacheHit(key, this.cacheType);
return JSON.parse(value) as T;
}
// Cache Miss
cacheMisses.inc({ cache_type: this.cacheType });
logCacheMiss(key, this.cacheType);
return null;
} catch (error) {
logger.warn("Redis get fehlgeschlagen, verwende In-Memory", {
error,
key,
});
this.useRedis = false;
}
}
// Fallback: In-Memory
const entry = this.memoryCache.get(key);
if (!entry) {
cacheMisses.inc({ cache_type: this.cacheType });
logCacheMiss(key, this.cacheType);
return null;
}
if (Date.now() > entry.expiresAt) {
this.memoryCache.delete(key);
cacheMisses.inc({ cache_type: this.cacheType });
logCacheMiss(key, this.cacheType);
return null;
}
cacheHits.inc({ cache_type: this.cacheType });
logCacheHit(key, this.cacheType);
return entry.data;
}
async clear(): Promise<void> {
if (this.useRedis && this.redis) {
try {
// Lösche alle Keys mit unserem Prefix
const keys = await this.redis.keys(`${this.cacheType}:*`);
if (keys.length > 0) {
await this.redis.del(...keys);
}
} catch (error) {
logger.warn("Redis clear fehlgeschlagen", { error });
}
}
this.memoryCache.clear();
}
async delete(key: string): Promise<void> {
if (this.useRedis && this.redis) {
try {
await this.redis.del(key);
return;
} catch (error) {
logger.warn("Redis delete fehlgeschlagen", { error, key });
}
}
this.memoryCache.delete(key);
}
async disconnect(): Promise<void> {
if (this.redis) {
await this.redis.quit();
this.redis = null;
}
}
}