initial commit, boilerplate stuff

This commit is contained in:
khannurien
2026-03-15 17:15:46 +00:00
commit 6207a7549f
52 changed files with 4400 additions and 0 deletions

97
api/model/db.ts Normal file
View File

@@ -0,0 +1,97 @@
import { DatabaseSync, type SQLOutputValue } from "node:sqlite";
import { Dump, type User } from "./interfaces.ts";
export const db = new DatabaseSync("api/sql/gerbeur.db");
/**
* Database Row Types
*/
export interface DumpRow {
id: string;
title: string;
description: string | null;
user_id: string;
created_at: string;
[key: string]: SQLOutputValue; // Index signature
}
export interface UserRow {
id: string;
username: string;
password_hash: string;
is_admin: number;
created_at: string;
[key: string]: SQLOutputValue; // Index signature
}
/**
* Type Guards
*/
export function isDumpRow(obj: Record<string, SQLOutputValue>): obj is DumpRow {
return !!obj &&
typeof obj === "object" &&
"id" in obj && typeof obj.id === "string" &&
"title" in obj && typeof obj.title === "string" &&
"description" in obj &&
(typeof obj.description === "string" || obj.description === null) &&
"user_id" in obj && typeof obj.user_id === "string" &&
"created_at" in obj && typeof obj.created_at === "string";
}
export function isUserRow(obj: Record<string, SQLOutputValue>): obj is UserRow {
return !!obj &&
typeof obj === "object" &&
"id" in obj && typeof obj.id === "string" &&
"username" in obj && typeof obj.username === "string" &&
"password_hash" in obj && typeof obj.password_hash === "string" &&
"is_admin" in obj && typeof obj.is_admin === "number" &&
"created_at" in obj && typeof obj.created_at === "string";
}
/**
* Conversion Helpers
*/
export function dumpRowToApi(
row: DumpRow,
): Dump {
return {
id: row.id,
title: row.title,
description: row.description ?? undefined,
userId: row.user_id,
createdAt: new Date(row.created_at),
};
}
export function dumpApiToRow(dump: Dump): DumpRow {
return {
id: dump.id,
title: dump.title,
description: dump.description ?? null,
user_id: dump.userId,
created_at: dump.createdAt.toISOString(),
};
}
export function userRowToApi(row: UserRow): User {
return {
id: row.id,
username: row.username,
passwordHash: row.password_hash,
isAdmin: Boolean(row.is_admin),
createdAt: new Date(row.created_at),
};
}
export function userApiToRow(user: User): UserRow {
return {
id: user.id,
username: user.username,
password_hash: user.passwordHash,
is_admin: user.isAdmin ? 1 : 0,
created_at: user.createdAt.toISOString(),
};
}

154
api/model/interfaces.ts Normal file
View File

@@ -0,0 +1,154 @@
/**
* Backend
*/
export interface Dump {
id: string;
title: string;
description?: string;
userId: string;
createdAt: Date;
}
/**
* Authentication
*/
export interface User {
id: string;
username: string;
passwordHash: string;
isAdmin: boolean;
createdAt: Date;
}
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<T> {
success: true;
data: T;
error?: never;
}
export interface APIFailure {
success: false;
data?: never;
error: APIError;
}
export type APIResponse<T> = APISuccess<T> | 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 CreateDumpRequest {
title: string;
description?: string;
}
export function isCreateDumpRequest(obj: unknown): obj is CreateDumpRequest {
return !!obj &&
typeof obj === "object" &&
"title" in obj && typeof obj.title === "string" &&
(!("description" in obj) ||
(typeof obj.description === "string" || obj.description === null));
}
export interface UpdateDumpRequest {
title?: string;
description?: string;
}
export function isUpdateDumpRequest(obj: unknown): obj is UpdateDumpRequest {
return !!obj &&
typeof obj === "object" &&
(!("title" in obj) || typeof obj.title === "string") &&
(!("description" in obj) ||
(typeof obj.description === "string" || obj.description === null));
}