Skip to content

Commit 00d9ad6

Browse files
Annotate Server Helper Functions for Clarity (#57)
1 parent 7fbe661 commit 00d9ad6

3 files changed

Lines changed: 91 additions & 41 deletions

File tree

apps/api/src/lib/functions/database.ts

Lines changed: 70 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,51 +5,33 @@ import { type Context } from "hono";
55
import { isInDevMode } from ".";
66

77
/**
8-
* Fetches a database dump from a Turso database instance.
9-
* @param databseName The name of the database.
10-
* @param organizationSlug The organization slug associated with the database.
8+
* Checks if a user has site admin privileges.
9+
* @param permissionEnum - The user's site role
10+
* @returns True if the user is an ADMIN or SUPER_ADMIN, false otherwise
1111
*/
12-
export async function getDatabaseDumpTurso(
13-
databseName: string,
14-
organizationSlug: string,
15-
) {
16-
const res = await fetch(
17-
`https://${databseName}-${organizationSlug}.turso.io/dump`,
18-
{
19-
method: "GET",
20-
headers: new Headers({
21-
// Authorization: `Bearer ${env.BACKUPS_DB_BEARER}`,
22-
}),
23-
},
24-
);
25-
26-
if (!res.ok) {
27-
throw new Error(
28-
`Failed to get database dump: ${res.status} ${res.statusText}`,
29-
);
30-
}
31-
32-
return res.text();
33-
}
34-
35-
/**
36-
* Checks the database connection by pinging it and querying for it's table count.
37-
* Function will take in an database information and the type to make the appropriate query.
38-
*/
39-
export async function pingDatabase() {}
40-
4112
export function isSiteAdminUser(
4213
permissionEnum: NonNullable<UserType>["siteRole"],
4314
): boolean {
4415
return ["ADMIN", "SUPER_ADMIN"].some((role) => role === permissionEnum);
4516
}
4617

18+
/**
19+
* Retrieves a team by its ID from the database.
20+
* @param teamId - The unique identifier of the team
21+
* @returns The team object if found, undefined otherwise
22+
*/
4723
export async function findTeam(teamId: string) {
4824
return db.query.team.findFirst({
4925
where: eq(team.id, teamId),
5026
});
5127
}
5228

29+
/**
30+
* Removes a user from a team by deleting their userToTeam relationship.
31+
* @param userId - The unique identifier of the user
32+
* @param teamId - The unique identifier of the team
33+
* @returns The teamId that was deleted
34+
*/
5335
export async function leaveTeam(userId: string, teamId: string) {
5436
return db
5537
.delete(userToTeam)
@@ -59,6 +41,12 @@ export async function leaveTeam(userId: string, teamId: string) {
5941
.returning({ teamId: userToTeam.teamId });
6042
}
6143

44+
/**
45+
* Retrieves the admin relationship between a user and a team.
46+
* @param userId - The unique identifier of the user
47+
* @param teamId - The unique identifier of the team
48+
* @returns The userToTeam record if the user is an admin of the team, undefined otherwise
49+
*/
6250
export async function getAdminUserForTeam(userId: string, teamId: string) {
6351
return db.query.userToTeam.findFirst({
6452
where: and(
@@ -69,6 +57,13 @@ export async function getAdminUserForTeam(userId: string, teamId: string) {
6957
});
7058
}
7159

60+
/**
61+
* Retrieves a specific team join request by ID, user ID, and team ID.
62+
* @param requestId - The unique identifier of the join request
63+
* @param userId - The unique identifier of the user who made the request
64+
* @param teamId - The unique identifier of the team
65+
* @returns The join request record if found, undefined otherwise
66+
*/
7267
export async function getJoinTeamRequest(
7368
requestId: string,
7469
userId: string,
@@ -83,6 +78,13 @@ export async function getJoinTeamRequest(
8378
});
8479
}
8580

81+
/**
82+
* Retrieves a team join request by ID and team ID (admin view).
83+
* Does not require user ID validation.
84+
* @param requestId - The unique identifier of the join request
85+
* @param teamId - The unique identifier of the team
86+
* @returns The join request record if found, undefined otherwise
87+
*/
8688
export async function getJoinTeamRequestAdmin(
8789
requestId: string,
8890
teamId: string,
@@ -95,7 +97,14 @@ export async function getJoinTeamRequestAdmin(
9597
});
9698
}
9799

98-
// TODO: This function is lowkey pivotal so we should ensure it is WAI.
100+
/**
101+
* Checks if a user is a site admin OR if a provided query returns a truthy result.
102+
* Useful for authorization checks that can shortcut if the user is already a site admin.
103+
*
104+
* @param userSiteRole - The site role of the user
105+
* @param query - Either a Promise that resolves to a permission check result, or a function that returns such a Promise
106+
* @returns True if the user is a site admin or if the query resolves to a truthy value, false otherwise
107+
*/
99108
export async function isUserSiteAdminOrQueryHasPermissions<T = unknown>(
100109
userSiteRole: SiteRoleType,
101110
// Accept either a Promise (already invoked query) or a function that returns a Promise
@@ -109,21 +118,43 @@ export async function isUserSiteAdminOrQueryHasPermissions<T = unknown>(
109118
return !!result;
110119
}
111120

121+
/**
122+
* Logs an error message to the database with context information.
123+
* @param message - The error message to log
124+
* @param c - Optional Hono context for extracting request metadata
125+
*/
112126
export async function logError(message: string, c?: Context) {
113127
const options = getAllContextValues(c);
114128
await logToDb("ERROR", message, options);
115129
}
116130

131+
/**
132+
* Logs an info message to the database with context information.
133+
* @param message - The info message to log
134+
* @param c - Optional Hono context for extracting request metadata
135+
*/
117136
export async function logInfo(message: string, c?: Context) {
118137
const options = getAllContextValues(c);
119138
await logToDb("INFO", message, options);
120139
}
121140

141+
/**
142+
* Logs a warning message to the database with context information.
143+
* @param message - The warning message to log
144+
* @param c - Optional Hono context for extracting request metadata
145+
*/
122146
export async function logWarning(message: string, c?: Context) {
123147
const options = getAllContextValues(c);
124148
await logToDb("WARNING", message, options);
125149
}
126150

151+
/**
152+
* Inserts a log record into the database. In development mode, logs to console instead.
153+
* Silently fails if database insertion fails to prevent cascading errors.
154+
* @param loggingType - The type of log (ERROR, INFO, WARNING, etc.)
155+
* @param message - The log message
156+
* @param options - Optional logging metadata (user ID, team ID, route, request ID)
157+
*/
127158
export async function logToDb(
128159
loggingType: LoggingType,
129160
message: string,
@@ -145,6 +176,11 @@ export async function logToDb(
145176
}
146177
}
147178

179+
/**
180+
* Extracts relevant context values from a Hono request context for logging purposes.
181+
* @param c - Optional Hono context
182+
* @returns An object containing route, userId, teamId, and requestId, or undefined if no context provided
183+
*/
148184
function getAllContextValues(c?: Context): LoggingOptions | undefined {
149185
if (!c) {
150186
return undefined;
@@ -161,8 +197,7 @@ function getAllContextValues(c?: Context): LoggingOptions | undefined {
161197
/**
162198
* Safely extract an error code string from an unknown thrown value from a db error.
163199
* Returns the code as a string when present, otherwise null.
164-
*
165-
* This function can handle it being passed as either a number or string and will convert if need be
200+
* @param e - The unknown error object thrown from a database operation
166201
*/
167202
export function maybeGetDbErrorCode(e: unknown): string | null {
168203
if (e == null) return null;

apps/api/src/lib/functions/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import type { BlankEnv } from "hono/types";
44
import type { ApiContextVariables } from "../types";
55

66
/**
7-
* @description Wrapper for the Hono constructor that includes the BetterAuth types
7+
* Wrapper for the Hono constructor that includes the BetterAuth types
88
* @param options Hono options
9+
* @returns A new Hono instance with the BetterAuth types included
910
*/
1011
export function HonoBetterAuth(options?: HonoOptions<BlankEnv> | undefined) {
1112
return new Hono<{
@@ -16,6 +17,10 @@ export function HonoBetterAuth(options?: HonoOptions<BlankEnv> | undefined) {
1617
}
1718

1819
// TODO(https://github.com/acmutsa/Fallback/issues/38): Come back and find out what proper value needs to be here
20+
/**
21+
* Utility function to check if the application is running in development mode.
22+
* @returns True if in development mode, false otherwise.
23+
*/
1924
export function isInDevMode() {
2025
return process.env.NODE_ENV === "development";
2126
}

apps/api/src/lib/functions/middleware.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ import type { ApiContext } from "../types";
66
import { API_ERROR_MESSAGES } from "shared";
77

88
export const MIDDLEWARE_PUBLIC_ROUTES = ["/health", "/api/auth"];
9-
9+
/**
10+
* 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.
11+
* @param c - The Hono context object
12+
* @param next - The next middleware function in the chain
13+
*/
1014
export async function setUserSessionContextMiddleware(c: Context, next: Next) {
1115
const session = await auth.api.getSession({ headers: c.req.raw.headers });
1216
const userString = session
13-
? `Authenticated user (id: ${session?.user.id})`
17+
? `Authenticated user (id: ${session.user.id})`
1418
: "Unauthenticated User";
1519

1620
const requestId = nanoid();
@@ -30,8 +34,12 @@ export async function setUserSessionContextMiddleware(c: Context, next: Next) {
3034
await next();
3135
}
3236

37+
/**
38+
* 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.
39+
* @param c - The Hono context object
40+
* @param next - The next middleware function in the chain
41+
*/
3342
export async function authenticatedMiddleware(c: ApiContext, next: Next) {
34-
// First check if it is a public route and if so we will return (make sure this works)
3543
const isPublicRoute = MIDDLEWARE_PUBLIC_ROUTES.some((route) =>
3644
c.req.path.startsWith(route),
3745
);
@@ -53,8 +61,10 @@ export async function authenticatedMiddleware(c: ApiContext, next: Next) {
5361
return next();
5462
}
5563

56-
/*
57-
* Middleware to handle logging the request and results of request afterwards. Context object is apparently stateful
64+
/**
65+
* Middleware to perform actions after the main route logic has executed. This can be used for logging, cleanup, or other post-processing tasks.
66+
* @param c - The Hono context object
67+
* @param next - The next middleware function in the chain
5868
*/
5969
export async function afterRouteLogicMiddleware(c: ApiContext, next: Next) {
6070
// TODO(https://github.com/acmutsa/Fallback/issues/26): Come back and finish logging function

0 commit comments

Comments
 (0)