feat: add auth and cache to currency route

This commit is contained in:
Vasily Zubarev
2025-04-04 14:01:43 +02:00
parent 1bb6447141
commit 1b1d72b22d
2 changed files with 139 additions and 1 deletions

View File

@@ -1,3 +1,5 @@
import { getSession } from "@/lib/auth"
import { PoorManCache } from "@/lib/cache"
import { format } from "date-fns"
import { NextRequest, NextResponse } from "next/server"
@@ -7,7 +9,23 @@ type HistoricRate = {
inverse: number
}
const currencyCache = new PoorManCache<number>(24 * 60 * 60 * 1000) // 24 hours
function generateCacheKey(fromCurrency: string, toCurrency: string, date: string): string {
return `${fromCurrency},${toCurrency},${date}`
}
const CLEANUP_INTERVAL = 90 * 60 * 1000
if (typeof setInterval !== "undefined") {
setInterval(() => currencyCache.cleanup(), CLEANUP_INTERVAL)
}
export async function GET(request: NextRequest) {
const session = await getSession()
if (!session || !session.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}
try {
const searchParams = request.nextUrl.searchParams
const fromCurrency = searchParams.get("from")
@@ -24,6 +42,15 @@ export async function GET(request: NextRequest) {
}
const formattedDate = format(date, "yyyy-MM-dd")
// Check cache first
const cacheKey = generateCacheKey(fromCurrency, toCurrency, formattedDate)
const cachedRate = currencyCache.get(cacheKey)
if (cachedRate !== undefined) {
return NextResponse.json({ rate: cachedRate, cached: true })
}
const url = `https://www.xe.com/currencytables/?from=${fromCurrency}&date=${formattedDate}`
const response = await fetch(url)
@@ -58,7 +85,10 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: `Currency rate not found for ${toCurrency}` }, { status: 404 })
}
return NextResponse.json({ rate: rate.rate })
// Store in cache
currencyCache.set(cacheKey, rate.rate)
return NextResponse.json({ rate: rate.rate, cached: false })
} catch (error) {
console.error("Currency API error:", error)
return NextResponse.json({ error: "Internal server error" }, { status: 500 })

108
lib/cache.ts Normal file
View File

@@ -0,0 +1,108 @@
export type CacheKey = string
export type CacheEntry<T> = {
value: T
timestamp: number
}
export class PoorManCache<T> {
private cache: Map<CacheKey, CacheEntry<T>>
private duration: number
/**
* Create a new cache instance
* @param duration Cache duration in milliseconds
*/
constructor(duration: number) {
this.cache = new Map<CacheKey, CacheEntry<T>>()
this.duration = duration
}
/**
* Get a value from the cache
* @param key Cache key
* @returns The cached value or undefined if not found or expired
*/
get(key: CacheKey): T | undefined {
const entry = this.cache.get(key)
if (!entry) {
return undefined
}
// Check if entry is expired
if (Date.now() - entry.timestamp > this.duration) {
this.cache.delete(key)
return undefined
}
return entry.value
}
/**
* Set a value in the cache
* @param key Cache key
* @param value Value to cache
*/
set(key: CacheKey, value: T): void {
this.cache.set(key, {
value,
timestamp: Date.now(),
})
}
/**
* Check if a key exists in the cache and is not expired
* @param key Cache key
* @returns True if the key exists and is not expired
*/
has(key: CacheKey): boolean {
const entry = this.cache.get(key)
if (!entry) {
return false
}
// Check if entry is expired
if (Date.now() - entry.timestamp > this.duration) {
this.cache.delete(key)
return false
}
return true
}
/**
* Remove a key from the cache
* @param key Cache key
*/
delete(key: CacheKey): void {
this.cache.delete(key)
}
/**
* Clear all expired entries from the cache
*/
cleanup(): void {
const now = Date.now()
for (const [key, entry] of this.cache.entries()) {
if (now - entry.timestamp > this.duration) {
this.cache.delete(key)
}
}
}
/**
* Get the current size of the cache
* @returns Number of entries in the cache
*/
size(): number {
return this.cache.size
}
/**
* Clear all entries from the cache
*/
clear(): void {
this.cache.clear()
}
}