* Revise portfolio: warm brown theme, elegant typography, optimized analytics tracking (#55) * Initial plan * Update color theme to warm brown and off-white, add elegant fonts, fix analytics tracking Co-authored-by: denshooter <44590296+denshooter@users.noreply.github.com> * Fix 404 page integration with warm theme, update admin console colors, fix font loading Co-authored-by: denshooter <44590296+denshooter@users.noreply.github.com> * Address code review feedback: fix navigation, add utils, improve tracking Co-authored-by: denshooter <44590296+denshooter@users.noreply.github.com> * Fix accessibility and memory leak issues from code review Co-authored-by: denshooter <44590296+denshooter@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: denshooter <44590296+denshooter@users.noreply.github.com> * chore: Code cleanup, add Sentry.io monitoring, and documentation (#56) * Initial plan * Remove unused code and clean up console statements Co-authored-by: denshooter <44590296+denshooter@users.noreply.github.com> * Remove unused components and fix type issues Co-authored-by: denshooter <44590296+denshooter@users.noreply.github.com> * Wrap console.warn in development check Co-authored-by: denshooter <44590296+denshooter@users.noreply.github.com> * Integrate Sentry.io monitoring and add text editing documentation Co-authored-by: denshooter <44590296+denshooter@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: denshooter <44590296+denshooter@users.noreply.github.com> * Initial plan * feat: Add Sentry configuration files and example pages - Add sentry.server.config.ts and sentry.edge.config.ts - Update instrumentation.ts with onRequestError export - Update instrumentation-client.ts with onRouterTransitionStart export - Update global-error.tsx to capture exceptions with Sentry - Create Sentry example page at app/sentry-example-page/page.tsx - Create Sentry example API route at app/api/sentry-example-api/route.ts Co-authored-by: denshooter <44590296+denshooter@users.noreply.github.com> * feat: Update middleware to allow Sentry example page and fix deprecated API - Update middleware to exclude /sentry-example-page from locale routing - Remove deprecated startTransaction API from Sentry example page - Use consistent DSN configuration with fallback values Co-authored-by: denshooter <44590296+denshooter@users.noreply.github.com> * refactor: Improve Sentry configuration with environment-based sampling - Add comments explaining DSN fallback values - Use environment-based tracesSampleRate (10% in production, 100% in dev) - Address code review feedback for production-safe configuration Co-authored-by: denshooter <44590296+denshooter@users.noreply.github.com> --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
111 lines
3.4 KiB
TypeScript
111 lines
3.4 KiB
TypeScript
import { cache } from './redis';
|
|
|
|
// API Response caching
|
|
export const apiCache = {
|
|
// Generate cache key based on query parameters
|
|
generateProjectsKey(params: Record<string, string | null> = {}) {
|
|
const { page = '1', limit = '50', category, featured, published, difficulty, search } = params;
|
|
const keyParts = ['api:projects'];
|
|
|
|
if (page !== '1') keyParts.push(`page:${page}`);
|
|
if (limit !== '50') keyParts.push(`limit:${limit}`);
|
|
if (category) keyParts.push(`cat:${category}`);
|
|
// Avoid cache fragmentation like `feat:undefined` when params omit the field
|
|
if (featured != null) keyParts.push(`feat:${featured}`);
|
|
if (published != null) keyParts.push(`pub:${published}`);
|
|
if (difficulty) keyParts.push(`diff:${difficulty}`);
|
|
if (search) keyParts.push(`search:${search}`);
|
|
|
|
return keyParts.join(':');
|
|
},
|
|
|
|
async getProjects(params: Record<string, string | null> = {}) {
|
|
const key = this.generateProjectsKey(params);
|
|
return await cache.get(key);
|
|
},
|
|
|
|
async setProjects(params: Record<string, string | null> = {}, projects: unknown, ttlSeconds = 300) {
|
|
const key = this.generateProjectsKey(params);
|
|
return await cache.set(key, projects, ttlSeconds);
|
|
},
|
|
|
|
async getProject(id: number) {
|
|
return await cache.get(`api:project:${id}`);
|
|
},
|
|
|
|
async setProject(id: number, project: unknown, ttlSeconds = 300) {
|
|
return await cache.set(`api:project:${id}`, project, ttlSeconds);
|
|
},
|
|
|
|
async invalidateProject(id: number) {
|
|
await cache.del(`api:project:${id}`);
|
|
// Invalidate all project list caches
|
|
await this.invalidateAllProjectLists();
|
|
},
|
|
|
|
async invalidateAllProjectLists() {
|
|
// Clear all project list caches by pattern
|
|
// This is a simplified approach - in production you'd use Redis SCAN
|
|
const commonKeys = [
|
|
'api:projects',
|
|
'api:projects:pub:true',
|
|
'api:projects:feat:true:pub:true:limit:6',
|
|
'api:projects:page:1:limit:50',
|
|
'api:projects:pub:true:page:1:limit:50'
|
|
];
|
|
|
|
for (const key of commonKeys) {
|
|
await cache.del(key);
|
|
}
|
|
},
|
|
|
|
async invalidateAll() {
|
|
// Invalidate all project lists
|
|
await this.invalidateAllProjectLists();
|
|
// Note: Individual project caches are invalidated via invalidateProject()
|
|
// when specific projects are updated
|
|
}
|
|
};
|
|
|
|
// Performance metrics caching
|
|
export const performanceCache = {
|
|
async getMetrics(url: string) {
|
|
return await cache.get(`perf:${url}`);
|
|
},
|
|
|
|
async setMetrics(url: string, metrics: unknown, ttlSeconds = 600) {
|
|
return await cache.set(`perf:${url}`, metrics, ttlSeconds);
|
|
},
|
|
|
|
async getWebVitals() {
|
|
return await cache.get('perf:webvitals');
|
|
},
|
|
|
|
async setWebVitals(vitals: unknown, ttlSeconds = 300) {
|
|
return await cache.set('perf:webvitals', vitals, ttlSeconds);
|
|
}
|
|
};
|
|
|
|
// User session caching
|
|
export const userCache = {
|
|
async getSession(sessionId: string) {
|
|
return await cache.get(`user:session:${sessionId}`);
|
|
},
|
|
|
|
async setSession(sessionId: string, data: unknown, ttlSeconds = 86400) {
|
|
return await cache.set(`user:session:${sessionId}`, data, ttlSeconds);
|
|
},
|
|
|
|
async deleteSession(sessionId: string) {
|
|
return await cache.del(`user:session:${sessionId}`);
|
|
},
|
|
|
|
async getUserPreferences(userId: string) {
|
|
return await cache.get(`user:prefs:${userId}`);
|
|
},
|
|
|
|
async setUserPreferences(userId: string, prefs: unknown, ttlSeconds = 86400) {
|
|
return await cache.set(`user:prefs:${userId}`, prefs, ttlSeconds);
|
|
}
|
|
};
|