/** * Backend */ export interface RichContent { type: string; url: string; siteName?: string; title?: string; description?: string; thumbnailUrl?: string; videoId?: string; } export interface Dump { id: string; kind: "url" | "file"; title: string; comment?: string; userId: string; createdAt: Date; url?: string; richContent?: RichContent; fileName?: string; fileMime?: string; fileSize?: number; voteCount: number; } /** * Authentication */ export interface User { id: string; username: string; passwordHash: string; isAdmin: boolean; createdAt: Date; avatarMime?: string; } export interface LoginUserRequest { username: string; password: string; } export interface RegisterUserRequest { username: string; password: string; } export interface UpdateUserRequest { username?: string; password?: string; isAdmin?: boolean; } export function isLoginUserRequest(obj: unknown): obj is LoginUserRequest { return !!obj && typeof obj === "object" && "username" in obj && typeof obj.username === "string" && "password" in obj && typeof obj.password === "string"; } export function isRegisterUserRequest( obj: unknown, ): obj is RegisterUserRequest { return !!obj && typeof obj === "object" && "username" in obj && typeof obj.username === "string" && "password" in obj && typeof obj.password === "string"; } export function isUpdateUserRequest(obj: unknown): obj is UpdateUserRequest { return !!obj && typeof obj === "object" && (!("username" in obj) || typeof obj.username === "string") && (!("password" in obj) || typeof obj.password === "string") && (!("isAdmin" in obj) || typeof obj.isAdmin === "boolean"); } export interface AuthResponse { token: string; user: User; } export interface AuthPayload { userId: string; username: string; isAdmin: boolean; exp: number; } export function isAuthPayload(obj: unknown): obj is AuthPayload { return !!obj && typeof obj === "object" && "userId" in obj && typeof obj.userId === "string" && "username" in obj && typeof obj.username === "string" && "isAdmin" in obj && typeof obj.isAdmin === "boolean" && "exp" in obj && typeof obj.exp === "number"; } /** * API */ export enum APIErrorCode { BAD_REQUEST = "BAD_REQUEST", NOT_FOUND = "NOT_FOUND", SERVER_ERROR = "SERVER_ERROR", TIMEOUT = "TIMEOUT", UNAUTHORIZED = "UNAUTHORIZED", VALIDATION_ERROR = "VALIDATION_ERROR", } export interface APIError { code: APIErrorCode; message: string; } export interface APISuccess { success: true; data: T; error?: never; } export interface APIFailure { success: false; data?: never; error: APIError; } export type APIResponse = APISuccess | APIFailure; export class APIException extends Error { readonly code: APIErrorCode; readonly status: number; constructor(code: APIErrorCode, status: number, message: string) { super(message); this.code = code; this.status = status; } } /** * Request DTOs */ export interface CreateUrlDumpRequest { url: string; comment?: string; } export function isCreateUrlDumpRequest( obj: unknown, ): obj is CreateUrlDumpRequest { return !!obj && typeof obj === "object" && "url" in obj && typeof obj.url === "string" && (!("comment" in obj) || typeof obj.comment === "string" || obj.comment === null); } export interface UpdateDumpRequest { url?: string; comment?: string; } export function isUpdateDumpRequest(obj: unknown): obj is UpdateDumpRequest { return !!obj && typeof obj === "object" && (!("url" in obj) || typeof obj.url === "string" || obj.url === null) && (!("comment" in obj) || typeof obj.comment === "string" || obj.comment === null); } /** * WebSockets */ export interface VoteCastMessage { type: "vote_cast"; dumpId: string; userId: string; } export interface VoteAckMessageFailure { type: "vote_ack"; dumpId: string; success: false; error: APIError; } export interface VoteAckMessageSuccess { type: "vote_ack"; dumpId: string; action: "cast" | "remove"; success: true; voteCount: number; error?: never; } export type VoteAckMessage = VoteAckMessageSuccess | VoteAckMessageFailure; export interface VoteRemoveMessage { type: "vote_remove"; dumpId: string; } export interface VotesUpdateMessage { type: "votes_update"; dumpId: string; voteCount: number; } export interface OnlineUser { userId: string; username: string; hasAvatar: boolean; } export interface WelcomeMessage { type: "welcome"; users: OnlineUser[]; myVotes: string[]; } export interface PresenceUpdateMessage { type: "presence_update"; users: OnlineUser[]; } export interface PingMessage { type: "ping"; } export interface PongMessage { type: "pong"; }