chore: overhaul infrastructure and integrate @mintel packages
Some checks failed
🧪 CI (QA) / 🧪 Quality Assurance (push) Failing after 1m3s
Some checks failed
🧪 CI (QA) / 🧪 Quality Assurance (push) Failing after 1m3s
- Restructure to pnpm monorepo (site moved to apps/web) - Integrate @mintel/tsconfig, @mintel/eslint-config, @mintel/husky-config - Implement Docker service architecture (Varnish, Directus, Gatekeeper) - Setup environment-aware Gitea Actions deployment
This commit is contained in:
96
apps/web/src/utils/cache/file-adapter.ts
vendored
Normal file
96
apps/web/src/utils/cache/file-adapter.ts
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
import type { CacheAdapter, CacheConfig } from './interfaces';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as path from 'node:path';
|
||||
import { existsSync, mkdirSync } from 'node:fs';
|
||||
import * as crypto from 'node:crypto';
|
||||
|
||||
export class FileCacheAdapter implements CacheAdapter {
|
||||
private cacheDir: string;
|
||||
private prefix: string;
|
||||
private defaultTTL: number;
|
||||
|
||||
constructor(config?: CacheConfig & { cacheDir?: string }) {
|
||||
this.cacheDir = config?.cacheDir || path.resolve(process.cwd(), '.cache');
|
||||
this.prefix = config?.prefix || '';
|
||||
this.defaultTTL = config?.defaultTTL || 3600;
|
||||
|
||||
if (!existsSync(this.cacheDir)) {
|
||||
fs.mkdir(this.cacheDir, { recursive: true }).catch(err => {
|
||||
console.error(`Failed to create cache directory: ${this.cacheDir}`, err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private sanitize(key: string): string {
|
||||
const clean = key.replace(/[^a-z0-9]/gi, '_');
|
||||
if (clean.length > 64) {
|
||||
return crypto.createHash('md5').update(key).digest('hex');
|
||||
}
|
||||
return clean;
|
||||
}
|
||||
|
||||
private getFilePath(key: string): string {
|
||||
const safeKey = this.sanitize(`${this.prefix}${key}`).toLowerCase();
|
||||
return path.join(this.cacheDir, `${safeKey}.json`);
|
||||
}
|
||||
|
||||
async get<T>(key: string): Promise<T | null> {
|
||||
const filePath = this.getFilePath(key);
|
||||
try {
|
||||
if (!existsSync(filePath)) return null;
|
||||
const content = await fs.readFile(filePath, 'utf8');
|
||||
const data = JSON.parse(content);
|
||||
|
||||
if (data.expiry && Date.now() > data.expiry) {
|
||||
await this.del(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
return data.value;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async set<T>(key: string, value: T, ttl?: number): Promise<void> {
|
||||
const filePath = this.getFilePath(key);
|
||||
const effectiveTTL = ttl !== undefined ? ttl : this.defaultTTL;
|
||||
const data = {
|
||||
value,
|
||||
expiry: effectiveTTL > 0 ? Date.now() + effectiveTTL * 1000 : null,
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
try {
|
||||
await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8');
|
||||
} catch (error) {
|
||||
console.error(`Failed to write cache file: ${filePath}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
async del(key: string): Promise<void> {
|
||||
const filePath = this.getFilePath(key);
|
||||
try {
|
||||
if (existsSync(filePath)) {
|
||||
await fs.unlink(filePath);
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
try {
|
||||
if (existsSync(this.cacheDir)) {
|
||||
const files = await fs.readdir(this.cacheDir);
|
||||
for (const file of files) {
|
||||
if (file.endsWith('.json')) {
|
||||
await fs.unlink(path.join(this.cacheDir, file));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user