From 4a056b3acc404ad3e3796250498ffaf53eb9b024 Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Sat, 21 Feb 2026 13:03:25 -0700 Subject: [PATCH 1/5] Removes unneeded functions. Can be added back later, but dead code for now --- apps/api/src/lib/functions/database.ts | 34 -------------------------- 1 file changed, 34 deletions(-) diff --git a/apps/api/src/lib/functions/database.ts b/apps/api/src/lib/functions/database.ts index 1201c4d..ed61d29 100644 --- a/apps/api/src/lib/functions/database.ts +++ b/apps/api/src/lib/functions/database.ts @@ -4,40 +4,6 @@ import type { LoggingOptions, LoggingType } from "../types"; import { type Context } from "hono"; import { isInDevMode } from "."; -/** - * Fetches a database dump from a Turso database instance. - * @param databseName The name of the database. - * @param organizationSlug The organization slug associated with the database. - */ -export async function getDatabaseDumpTurso( - databseName: string, - organizationSlug: string, -) { - const res = await fetch( - `https://${databseName}-${organizationSlug}.turso.io/dump`, - { - method: "GET", - headers: new Headers({ - // Authorization: `Bearer ${env.BACKUPS_DB_BEARER}`, - }), - }, - ); - - if (!res.ok) { - throw new Error( - `Failed to get database dump: ${res.status} ${res.statusText}`, - ); - } - - return res.text(); -} - -/** - * Checks the database connection by pinging it and querying for it's table count. - * Function will take in an database information and the type to make the appropriate query. - */ -export async function pingDatabase() {} - export function isSiteAdminUser( permissionEnum: NonNullable["siteRole"], ): boolean { From 83b3584dcc8d371e038e1afaf8f46ce87c2fcf3d Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Sat, 21 Feb 2026 13:07:59 -0700 Subject: [PATCH 2/5] Annotates DB functions --- apps/api/src/lib/functions/database.ts | 73 +++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/apps/api/src/lib/functions/database.ts b/apps/api/src/lib/functions/database.ts index ed61d29..05043c1 100644 --- a/apps/api/src/lib/functions/database.ts +++ b/apps/api/src/lib/functions/database.ts @@ -4,18 +4,34 @@ import type { LoggingOptions, LoggingType } from "../types"; import { type Context } from "hono"; import { isInDevMode } from "."; +/** + * Checks if a user has site admin privileges. + * @param permissionEnum - The user's site role + * @returns True if the user is an ADMIN or SUPER_ADMIN, false otherwise + */ export function isSiteAdminUser( permissionEnum: NonNullable["siteRole"], ): boolean { return ["ADMIN", "SUPER_ADMIN"].some((role) => role === permissionEnum); } +/** + * Retrieves a team by its ID from the database. + * @param teamId - The unique identifier of the team + * @returns The team object if found, undefined otherwise + */ export async function findTeam(teamId: string) { return db.query.team.findFirst({ where: eq(team.id, teamId), }); } +/** + * Removes a user from a team by deleting their userToTeam relationship. + * @param userId - The unique identifier of the user + * @param teamId - The unique identifier of the team + * @returns The teamId that was deleted + */ export async function leaveTeam(userId: string, teamId: string) { return db .delete(userToTeam) @@ -25,6 +41,12 @@ export async function leaveTeam(userId: string, teamId: string) { .returning({ teamId: userToTeam.teamId }); } +/** + * Retrieves the admin relationship between a user and a team. + * @param userId - The unique identifier of the user + * @param teamId - The unique identifier of the team + * @returns The userToTeam record if the user is an admin of the team, undefined otherwise + */ export async function getAdminUserForTeam(userId: string, teamId: string) { return db.query.userToTeam.findFirst({ where: and( @@ -35,6 +57,13 @@ export async function getAdminUserForTeam(userId: string, teamId: string) { }); } +/** + * Retrieves a specific team join request by ID, user ID, and team ID. + * @param requestId - The unique identifier of the join request + * @param userId - The unique identifier of the user who made the request + * @param teamId - The unique identifier of the team + * @returns The join request record if found, undefined otherwise + */ export async function getJoinTeamRequest( requestId: string, userId: string, @@ -49,6 +78,13 @@ export async function getJoinTeamRequest( }); } +/** + * Retrieves a team join request by ID and team ID (admin view). + * Does not require user ID validation. + * @param requestId - The unique identifier of the join request + * @param teamId - The unique identifier of the team + * @returns The join request record if found, undefined otherwise + */ export async function getJoinTeamRequestAdmin( requestId: string, teamId: string, @@ -61,7 +97,15 @@ export async function getJoinTeamRequestAdmin( }); } -// TODO: This function is lowkey pivotal so we should ensure it is WAI. +/** + * Checks if a user is a site admin OR if a provided query returns a truthy result. + * Useful for authorization checks that can shortcut if the user is already a site admin. + * + * @todo This function is pivotal so we should ensure it is working as intended. + * @param userSiteRole - The site role of the user + * @param query - Either a Promise that resolves to a permission check result, or a function that returns such a Promise + * @returns True if the user is a site admin or if the query resolves to a truthy value, false otherwise + */ export async function isUserSiteAdminOrQueryHasPermissions( userSiteRole: SiteRoleType, // Accept either a Promise (already invoked query) or a function that returns a Promise @@ -75,21 +119,43 @@ export async function isUserSiteAdminOrQueryHasPermissions( return !!result; } +/** + * Logs an error message to the database with context information. + * @param message - The error message to log + * @param c - Optional Hono context for extracting request metadata + */ export async function logError(message: string, c?: Context) { const options = getAllContextValues(c); await logToDb("ERROR", message, options); } +/** + * Logs an info message to the database with context information. + * @param message - The info message to log + * @param c - Optional Hono context for extracting request metadata + */ export async function logInfo(message: string, c?: Context) { const options = getAllContextValues(c); await logToDb("INFO", message, options); } +/** + * Logs a warning message to the database with context information. + * @param message - The warning message to log + * @param c - Optional Hono context for extracting request metadata + */ export async function logWarning(message: string, c?: Context) { const options = getAllContextValues(c); await logToDb("WARNING", message, options); } +/** + * Inserts a log record into the database. In development mode, logs to console instead. + * Silently fails if database insertion fails to prevent cascading errors. + * @param loggingType - The type of log (ERROR, INFO, WARNING, etc.) + * @param message - The log message + * @param options - Optional logging metadata (user ID, team ID, route, request ID) + */ export async function logToDb( loggingType: LoggingType, message: string, @@ -111,6 +177,11 @@ export async function logToDb( } } +/** + * Extracts relevant context values from a Hono request context for logging purposes. + * @param c - Optional Hono context + * @returns An object containing route, userId, teamId, and requestId, or undefined if no context provided + */ function getAllContextValues(c?: Context): LoggingOptions | undefined { if (!c) { return undefined; From 54972af2271c0fa987b1b0e8ffc12797fc167512 Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Sat, 21 Feb 2026 14:16:32 -0700 Subject: [PATCH 3/5] Annotates middleware and index --- apps/api/src/lib/functions/index.ts | 4 ++++ apps/api/src/lib/functions/middleware.ts | 17 ++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/api/src/lib/functions/index.ts b/apps/api/src/lib/functions/index.ts index a3389e5..4d1302c 100644 --- a/apps/api/src/lib/functions/index.ts +++ b/apps/api/src/lib/functions/index.ts @@ -16,6 +16,10 @@ export function HonoBetterAuth(options?: HonoOptions | undefined) { } // TODO(https://github.com/acmutsa/Fallback/issues/38): Come back and find out what proper value needs to be here +/** + * @description Utility function to check if the application is running in development mode. + * @returns True if in development mode, false otherwise. + */ export function isInDevMode() { return process.env.NODE_ENV === "development"; } diff --git a/apps/api/src/lib/functions/middleware.ts b/apps/api/src/lib/functions/middleware.ts index 0397b0b..2c5a5ba 100644 --- a/apps/api/src/lib/functions/middleware.ts +++ b/apps/api/src/lib/functions/middleware.ts @@ -6,7 +6,11 @@ import type { ApiContext } from "../types"; import { API_ERROR_MESSAGES } from "shared"; export const MIDDLEWARE_PUBLIC_ROUTES = ["/health", "/api/auth"]; - +/** + * Middleware to set user and session context for each request. This middleware checks the authentication status of the incoming request, retrieves the user session if it exists, and sets relevant information in the context for downstream handlers to use. It also logs the request path and authentication status for monitoring purposes. + * @param c - The Hono context object + * @param next - The next middleware function in the chain + */ export async function setUserSessionContextMiddleware(c: Context, next: Next) { const session = await auth.api.getSession({ headers: c.req.raw.headers }); const userString = session @@ -30,6 +34,11 @@ export async function setUserSessionContextMiddleware(c: Context, next: Next) { await next(); } +/** + * Middleware to enforce authentication on protected routes. This middleware checks if the incoming request is targeting a public route, and if not, it verifies that the user is authenticated by checking the context for user and session information. If the user is not authenticated, it logs an unauthorized access attempt and returns a 401 response. If the user is authenticated or if the route is public, it allows the request to proceed to the next handler. + * @param c - The Hono context object + * @param next - The next middleware function in the chain + */ export async function authenticatedMiddleware(c: ApiContext, next: Next) { // First check if it is a public route and if so we will return (make sure this works) const isPublicRoute = MIDDLEWARE_PUBLIC_ROUTES.some((route) => @@ -53,8 +62,10 @@ export async function authenticatedMiddleware(c: ApiContext, next: Next) { return next(); } -/* - * Middleware to handle logging the request and results of request afterwards. Context object is apparently stateful +/** + * Middleware to perform actions after the main route logic has executed. This can be used for logging, cleanup, or other post-processing tasks. + * @param c - The Hono context object + * @param next - The next middleware function in the chain */ export async function afterRouteLogicMiddleware(c: ApiContext, next: Next) { // TODO(https://github.com/acmutsa/Fallback/issues/26): Come back and finish logging function From 59145dfa831e8f3a840c77c12689b48054a4b636 Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Sat, 21 Feb 2026 14:31:26 -0700 Subject: [PATCH 4/5] Updates --- apps/api/src/lib/functions/database.ts | 6 ++---- apps/api/src/lib/functions/index.ts | 5 +++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/api/src/lib/functions/database.ts b/apps/api/src/lib/functions/database.ts index 05043c1..c9f47e6 100644 --- a/apps/api/src/lib/functions/database.ts +++ b/apps/api/src/lib/functions/database.ts @@ -100,8 +100,7 @@ export async function getJoinTeamRequestAdmin( /** * Checks if a user is a site admin OR if a provided query returns a truthy result. * Useful for authorization checks that can shortcut if the user is already a site admin. - * - * @todo This function is pivotal so we should ensure it is working as intended. + * * @param userSiteRole - The site role of the user * @param query - Either a Promise that resolves to a permission check result, or a function that returns such a Promise * @returns True if the user is a site admin or if the query resolves to a truthy value, false otherwise @@ -198,8 +197,7 @@ function getAllContextValues(c?: Context): LoggingOptions | undefined { /** * Safely extract an error code string from an unknown thrown value from a db error. * Returns the code as a string when present, otherwise null. - * - * This function can handle it being passed as either a number or string and will convert if need be + * @param e - The unknown error object thrown from a database operation */ export function maybeGetDbErrorCode(e: unknown): string | null { if (e == null) return null; diff --git a/apps/api/src/lib/functions/index.ts b/apps/api/src/lib/functions/index.ts index 4d1302c..c9a7cd8 100644 --- a/apps/api/src/lib/functions/index.ts +++ b/apps/api/src/lib/functions/index.ts @@ -4,8 +4,9 @@ import type { BlankEnv } from "hono/types"; import type { ApiContextVariables } from "../types"; /** - * @description Wrapper for the Hono constructor that includes the BetterAuth types + * Wrapper for the Hono constructor that includes the BetterAuth types * @param options Hono options + * @returns A new Hono instance with the BetterAuth types included */ export function HonoBetterAuth(options?: HonoOptions | undefined) { return new Hono<{ @@ -17,7 +18,7 @@ export function HonoBetterAuth(options?: HonoOptions | undefined) { // TODO(https://github.com/acmutsa/Fallback/issues/38): Come back and find out what proper value needs to be here /** - * @description Utility function to check if the application is running in development mode. + * Utility function to check if the application is running in development mode. * @returns True if in development mode, false otherwise. */ export function isInDevMode() { From deb6910ec8b84e68a1259b10282a8d9d50258bb1 Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Sat, 21 Feb 2026 14:42:30 -0700 Subject: [PATCH 5/5] Update middleware function --- apps/api/src/lib/functions/middleware.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/api/src/lib/functions/middleware.ts b/apps/api/src/lib/functions/middleware.ts index 2c5a5ba..d3a0e55 100644 --- a/apps/api/src/lib/functions/middleware.ts +++ b/apps/api/src/lib/functions/middleware.ts @@ -14,7 +14,7 @@ export const MIDDLEWARE_PUBLIC_ROUTES = ["/health", "/api/auth"]; export async function setUserSessionContextMiddleware(c: Context, next: Next) { const session = await auth.api.getSession({ headers: c.req.raw.headers }); const userString = session - ? `Authenticated user (id: ${session?.user.id})` + ? `Authenticated user (id: ${session.user.id})` : "Unauthenticated User"; const requestId = nanoid(); @@ -40,7 +40,6 @@ export async function setUserSessionContextMiddleware(c: Context, next: Next) { * @param next - The next middleware function in the chain */ export async function authenticatedMiddleware(c: ApiContext, next: Next) { - // First check if it is a public route and if so we will return (make sure this works) const isPublicRoute = MIDDLEWARE_PUBLIC_ROUTES.some((route) => c.req.path.startsWith(route), );