Files
portfolio/lib/cache.ts
denshooter 377631ee50 Copilot/setup sentry nextjs (#58)
* 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>
2026-01-22 10:05:43 +01:00

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);
}
};