UI enhancemnts
This commit is contained in:
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user