mirror of
https://github.com/Sea-Haven-Industries/seahaven-ap.git
synced 2026-10-03 05:43:13 +00:00
90 lines
2.5 KiB
TypeScript
90 lines
2.5 KiB
TypeScript
import { randomUUID } from "node:crypto";
|
|
import type { Context } from "hono";
|
|
import type { Db } from "../db/client.js";
|
|
import type { UserRole } from "../env.js";
|
|
import { can, type RbacAction } from "../auth/rbac.js";
|
|
import { errorJson } from "../http.js";
|
|
import type { AppBindings } from "../auth/middleware.js";
|
|
|
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
|
|
export function isUuid(value: string): boolean {
|
|
return UUID_RE.test(value);
|
|
}
|
|
|
|
export function requireCan(c: Context<AppBindings>, action: RbacAction) {
|
|
const user = c.get("user");
|
|
if (!can(user.role, action)) {
|
|
return errorJson(c, 403, "FORBIDDEN", `Role ${user.role} is not allowed to ${action}.`);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function caller(c: Context<AppBindings>) {
|
|
return c.get("user");
|
|
}
|
|
|
|
export function iso(value: Date | string): string {
|
|
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
|
}
|
|
|
|
export function asString(value: unknown, fallback = ""): string {
|
|
return typeof value === "string" ? value : fallback;
|
|
}
|
|
|
|
export function optionalString(value: unknown): string | null | undefined {
|
|
if (value === undefined) return undefined;
|
|
if (value === null) return null;
|
|
if (typeof value === "string") return value;
|
|
return undefined;
|
|
}
|
|
|
|
export function newId(): string {
|
|
return randomUUID();
|
|
}
|
|
|
|
export function parseJsonBody(c: Context): Promise<Record<string, unknown>> {
|
|
return c.req.json<Record<string, unknown>>();
|
|
}
|
|
|
|
export function asMoney(value: unknown): string | null {
|
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
return value.toFixed(2);
|
|
}
|
|
if (typeof value === "string" && /^\d+(\.\d{1,2})?$/.test(value.trim())) {
|
|
return Number(value).toFixed(2);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function moneyCents(value: string): number {
|
|
return Math.round(Number(value) * 100);
|
|
}
|
|
|
|
export function isDateOnly(value: string): boolean {
|
|
return /^\d{4}-\d{2}-\d{2}$/.test(value);
|
|
}
|
|
|
|
export async function rowsOf<T>(handle: Db, table: unknown): Promise<T[]> {
|
|
return (await handle.db.select().from(table as never)) as T[];
|
|
}
|
|
|
|
export async function firstById<T extends { id: string }>(
|
|
handle: Db,
|
|
table: unknown,
|
|
id: string,
|
|
): Promise<T | undefined> {
|
|
const rows = await rowsOf<T>(handle, table);
|
|
return rows.find((row) => row.id === id);
|
|
}
|
|
|
|
export function isUniqueViolation(error: unknown): boolean {
|
|
return (
|
|
typeof error === "object" &&
|
|
error !== null &&
|
|
"code" in error &&
|
|
(error as { code: unknown }).code === "23505"
|
|
);
|
|
}
|
|
|
|
export type { UserRole };
|