UI enhancemnts

This commit is contained in:
saikiranvella
2026-06-09 19:34:31 -04:00
parent 5c8cd8935a
commit 662a717916
55 changed files with 6226 additions and 465 deletions
@@ -0,0 +1,35 @@
import type { DatabaseConnection } from '../shared/db/index.js';
import { WATCHLIST_QUERIES } from '../shared/db/queries.constant.js';
export interface WatchlistEntry {
ticker: string;
pinnedAt: string;
}
export class WatchlistRepository {
constructor(private readonly db: DatabaseConnection) {}
list(userId: string): WatchlistEntry[] {
const rows = this.db.rawAll<{ ticker: string; pinned_at: string }>(
WATCHLIST_QUERIES.SELECT_ALL,
[userId],
);
return rows.map((r) => ({ ticker: r.ticker, pinnedAt: r.pinned_at }));
}
add(ticker: string, userId: string): void {
this.db.rawRun(WATCHLIST_QUERIES.INSERT, [
ticker.toUpperCase(),
userId,
new Date().toISOString(),
]);
}
remove(ticker: string, userId: string): void {
this.db.rawRun(WATCHLIST_QUERIES.DELETE, [ticker.toUpperCase(), userId]);
}
has(ticker: string, userId: string): boolean {
return !!this.db.rawGet(WATCHLIST_QUERIES.EXISTS, [ticker.toUpperCase(), userId]);
}
}
+2
View File
@@ -0,0 +1,2 @@
export { WatchlistController } from './watchlist.controller.js';
export { WatchlistRepository } from './WatchlistRepository.js';
@@ -0,0 +1,53 @@
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 };
}
}