// types.ts // FIXME: split this up into types used by app developers and types internal // to the framework. import type { Request as ExpressRequest } from "express"; import type { MatchFunction } from "path-to-regexp"; import { z } from "zod"; import type { Session } from "./auth/types"; import type { ContentType } from "./content-types"; import type { HttpCode } from "./http-codes"; import { AnonymousUser, type MaybeUser, type Permission, type User, } from "./user"; const methodParser = z.union([ z.literal("GET"), z.literal("POST"), z.literal("PUT"), z.literal("PATCH"), z.literal("DELETE"), ]); export type Method = z.infer; const massageMethod = (input: string): Method => { const r = methodParser.parse(input.toUpperCase()); return r; }; export type Call = { pattern: string; path: string; method: Method; parameters: object; request: ExpressRequest; user: MaybeUser; session: Session; }; export type InternalHandler = (req: ExpressRequest) => Promise; export type Handler = (call: Call) => Promise; export type ProcessedRoute = { matcher: MatchFunction>; method: Method; handler: InternalHandler; }; export type Result = { code: HttpCode; contentType: ContentType; result: string; }; export type Route = { path: string; methods: Method[]; handler: Handler; interruptable?: boolean; }; // Authentication error classes export class AuthenticationRequired extends Error { constructor() { super("Authentication required"); this.name = "AuthenticationRequired"; } } export class AuthorizationDenied extends Error { constructor() { super("Authorization denied"); this.name = "AuthorizationDenied"; } } // Helper for handlers to require authentication export function requireAuth(call: Call): User { if (call.user === AnonymousUser) { throw new AuthenticationRequired(); } return call.user; } // Helper for handlers to require specific permission export function requirePermission(call: Call, permission: Permission): User { const user = requireAuth(call); if (!user.hasPermission(permission)) { throw new AuthorizationDenied(); } return user; } export { methodParser, massageMethod };