import type { FastifyInstance, FastifyReply, FastifyRequest, preHandlerHookHandler } from 'fastify'; import type { TokenPayload } from '../auth/index.js'; import { WatchlistRepository } from './WatchlistRepository.js'; type AuthedRequest = FastifyRequest & { user: TokenPayload }; interface WatchlistControllerOptions { authGuard: preHandlerHookHandler; } export class WatchlistController { readonly #guards: preHandlerHookHandler[]; constructor( private readonly repo: WatchlistRepository, options: WatchlistControllerOptions, ) { this.#guards = [options.authGuard]; } register(app: FastifyInstance): void { const g = { preHandler: this.#guards }; app.get('/api/watchlist', g, this.list.bind(this)); app.post('/api/watchlist/:ticker', g, this.add.bind(this)); app.delete('/api/watchlist/:ticker', g, this.remove.bind(this)); } private list(req: FastifyRequest): { tickers: string[]; entries: { ticker: string; pinnedAt: string }[]; } { const userId = (req as AuthedRequest).user.sub; const entries = this.repo.list(userId); return { tickers: entries.map((e) => e.ticker), entries }; } private add(req: FastifyRequest, reply: FastifyReply): { ok: boolean } | FastifyReply { const userId = (req as AuthedRequest).user.sub; const ticker = (req.params as { ticker: string }).ticker?.toUpperCase(); if (!ticker || !/^[A-Z0-9.-]{1,12}$/.test(ticker)) { return reply.code(400).send({ error: 'Invalid ticker' }); } this.repo.add(ticker, userId); return { ok: true }; } private remove(req: FastifyRequest): { ok: boolean } { const userId = (req as AuthedRequest).user.sub; const ticker = (req.params as { ticker: string }).ticker?.toUpperCase(); this.repo.remove(ticker, userId); return { ok: true }; } }