Files
2026-06-09 19:34:31 -04:00

54 lines
1.8 KiB
TypeScript

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