diff --git a/.github/workflows/presubmits.yml b/.github/workflows/presubmits.yml index fbae8f4..6e39a3d 100644 --- a/.github/workflows/presubmits.yml +++ b/.github/workflows/presubmits.yml @@ -7,6 +7,7 @@ on: [pull_request] jobs: prettier-check: runs-on: ubuntu-latest + if: ${{ !github.event.pull_request.draft }} steps: - name: Checkout code @@ -28,6 +29,7 @@ jobs: run: pnpm run format-check builder: runs-on: ubuntu-latest + if: ${{ !github.event.pull_request.draft }} steps: - name: Checkout code diff --git a/apps/api/src/lib/functions/database.ts b/apps/api/src/lib/functions/database.ts index c9f47e6..375b40b 100644 --- a/apps/api/src/lib/functions/database.ts +++ b/apps/api/src/lib/functions/database.ts @@ -1,7 +1,6 @@ import { userToTeam, db, and, eq, log, team, teamJoinRequest } from "db"; import type { UserType, SiteRoleType } from "db/types"; -import type { LoggingOptions, LoggingType } from "../types"; -import { type Context } from "hono"; +import type { LoggingOptions, LoggingType, LoggingSource } from "../types"; import { isInDevMode } from "."; /** @@ -125,7 +124,8 @@ export async function isUserSiteAdminOrQueryHasPermissions( */ export async function logError(message: string, c?: Context) { const options = getAllContextValues(c); - await logToDb("ERROR", message, options); + const source = getLoggingSourceFromContext(c); + await logToDb("ERROR", message, source, options); } /** @@ -135,7 +135,8 @@ export async function logError(message: string, c?: Context) { */ export async function logInfo(message: string, c?: Context) { const options = getAllContextValues(c); - await logToDb("INFO", message, options); + const source = getLoggingSourceFromContext(c); + await logToDb("INFO", message, source, options); } /** @@ -145,7 +146,8 @@ export async function logInfo(message: string, c?: Context) { */ export async function logWarning(message: string, c?: Context) { const options = getAllContextValues(c); - await logToDb("WARNING", message, options); + const source = getLoggingSourceFromContext(c); + await logToDb("WARNING", message, source, options); } /** @@ -156,19 +158,24 @@ export async function logWarning(message: string, c?: Context) { * @param options - Optional logging metadata (user ID, team ID, route, request ID) */ export async function logToDb( - loggingType: LoggingType, + logType: LoggingType, message: string, + source: LoggingSource, options?: LoggingOptions, ) { if (isInDevMode()) { - console.log(`[${loggingType}] - ${message} - Options: `, options); + console.log( + `[${logType}] from ${source} - ${message} - Options: `, + options, + ); return; } try { await db.insert(log).values({ ...options, - logType: loggingType, + logType, message, + source, }); } catch (e) { // Silently fail if logging to the db fails. @@ -194,6 +201,14 @@ function getAllContextValues(c?: Context): LoggingOptions | undefined { }; } +function getLoggingSourceFromContext(c?: Context): LoggingSource { + if (!c) { + return "SERVER"; + } + + return "SERVER"; +} + /** * 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. diff --git a/apps/api/src/lib/functions/middleware.ts b/apps/api/src/lib/functions/middleware.ts index d3a0e55..9480ecc 100644 --- a/apps/api/src/lib/functions/middleware.ts +++ b/apps/api/src/lib/functions/middleware.ts @@ -6,6 +6,7 @@ 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 diff --git a/apps/api/src/lib/types.ts b/apps/api/src/lib/types.ts index 33ef5c7..e99b23e 100644 --- a/apps/api/src/lib/types.ts +++ b/apps/api/src/lib/types.ts @@ -1,6 +1,7 @@ import { log } from "db"; import type { SessionType, UserType } from "db/types"; -import type { Context } from "hono"; +import type { Context as HonoContext } from "hono"; +import { LambdaContext } from "hono/aws-lambda"; // Match the Variables shape declared in HonoBetterAuth export type ApiContextVariables = { @@ -9,13 +10,17 @@ export type ApiContextVariables = { teamId: string | null; requestId: string | null; }; -export type ApiContext = Context<{ +export type ApiContext = HonoContext<{ Variables: ApiContextVariables; }>; export type LoggingOptions = Omit< typeof log.$inferInsert, - "id" | "occurredAt" | "logType" | "message" + "id" | "occurredAt" | "logType" | "message" | "source" >; // Single type representing the logType value (e.g. "INFO" | "WARNING" | "ERROR") export type LoggingType = (typeof log.$inferSelect)["logType"]; + +export type LoggingSource = (typeof log.$inferSelect)["source"]; + +export type FallbackContext = {honoContext?: HonoContext; lambdaContext?: LambdaContext }; diff --git a/apps/api/src/routes/log.ts b/apps/api/src/routes/log.ts index 477431e..e0711d2 100644 --- a/apps/api/src/routes/log.ts +++ b/apps/api/src/routes/log.ts @@ -1,7 +1,6 @@ import { zValidator } from "@hono/zod-validator"; import { HonoBetterAuth } from "../lib/functions"; import { logSchema } from "../lib/zod"; -import { logToDb } from "../lib/functions/database"; import type { LoggingType } from "../lib/types"; import { teamIdSchema } from "shared"; import { db, eq, log } from "db"; @@ -11,6 +10,7 @@ import { getAdminUserForTeam, isSiteAdminUser, } from "../lib/functions/database"; +import { logToDb } from "../lib/functions/database"; // TODO(https://github.com/acmutsa/Fallback/issues/36): We need to allow authenticated users to log client errors, but we should rethink this a bit to add extra protections against abuse. const logHandler = HonoBetterAuth() @@ -18,11 +18,11 @@ const logHandler = HonoBetterAuth() const logData = c.req.valid("form"); const { message, logType, ...optionals } = logData; - await logToDb(logType as LoggingType, message, { + await logToDb(logType as LoggingType, message, "CLIENT",{ ...optionals, }); - return c.json({ message: "Log endpoint hit" }, 200); + return c.json({ message: "Log successful" }, 200); }) .get("/admin/all", async (c) => { const user = c.get("user"); diff --git a/packages/db/drizzle/0012_lumpy_the_anarchist.sql b/packages/db/drizzle/0012_lumpy_the_anarchist.sql new file mode 100644 index 0000000..1cc3aeb --- /dev/null +++ b/packages/db/drizzle/0012_lumpy_the_anarchist.sql @@ -0,0 +1 @@ +ALTER TABLE `log` ADD `source` text NOT NULL; \ No newline at end of file diff --git a/packages/db/drizzle/meta/0012_snapshot.json b/packages/db/drizzle/meta/0012_snapshot.json new file mode 100644 index 0000000..8e926bb --- /dev/null +++ b/packages/db/drizzle/meta/0012_snapshot.json @@ -0,0 +1,842 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "3e77dc7c-fc5e-4778-b47a-608216379cf9", + "prevId": "39b0cf06-8d6d-489a-875e-cc3b19d55c00", + "tables": { + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "backup_job": { + "name": "backup_job", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "authentication_data": { + "name": "authentication_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "database_type": { + "name": "database_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cron_string": { + "name": "cron_string", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "backup_job_team_id_team_id_fk": { + "name": "backup_job_team_id_team_id_fk", + "tableFrom": "backup_job", + "tableTo": "team", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "backup_job_run": { + "name": "backup_job_run", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "invocation_type": { + "name": "invocation_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "backup_job_id": { + "name": "backup_job_id", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(current_timestamp)" + }, + "completed_at": { + "name": "completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "result": { + "name": "result", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "backup_job_run_backup_job_id_backup_job_id_fk": { + "name": "backup_job_run_backup_job_id_backup_job_id_fk", + "tableFrom": "backup_job_run", + "tableTo": "backup_job", + "columnsFrom": [ + "backup_job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "log": { + "name": "log", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "log_type": { + "name": "log_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(current_timestamp)" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "route": { + "name": "route", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "request_id": { + "name": "request_id", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_elapsed_ms": { + "name": "time_elapsed_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "session_token_unique": { + "name": "session_token_unique", + "columns": [ + "token" + ], + "isUnique": true + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "team": { + "name": "team", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(current_timestamp)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(current_timestamp)" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "team_invite": { + "name": "team_invite", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(current_timestamp)" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'MEMBER'" + } + }, + "indexes": {}, + "foreignKeys": { + "team_invite_team_id_team_id_fk": { + "name": "team_invite_team_id_team_id_fk", + "tableFrom": "team_invite", + "tableTo": "team", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "team_join_request": { + "name": "team_join_request", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(current_timestamp)" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'PENDING'" + } + }, + "indexes": {}, + "foreignKeys": { + "team_join_request_team_id_team_id_fk": { + "name": "team_join_request_team_id_team_id_fk", + "tableFrom": "team_join_request", + "tableTo": "team", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_join_request_user_id_user_id_fk": { + "name": "team_join_request_user_id_user_id_fk", + "tableFrom": "team_join_request", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "first_name": { + "name": "first_name", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_name": { + "name": "last_name", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(current_timestamp)" + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_seen": { + "name": "last_seen", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(current_timestamp)" + }, + "site_role": { + "name": "site_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'USER'" + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_to_team": { + "name": "user_to_team", + "columns": { + "user_id": { + "name": "user_id", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'MEMBER'" + }, + "joined_on": { + "name": "joined_on", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(current_timestamp)" + } + }, + "indexes": {}, + "foreignKeys": { + "user_to_team_user_id_user_id_fk": { + "name": "user_to_team_user_id_user_id_fk", + "tableFrom": "user_to_team", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_to_team_team_id_team_id_fk": { + "name": "user_to_team_team_id_team_id_fk", + "tableFrom": "user_to_team", + "tableTo": "team", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_to_team_user_id_team_id_pk": { + "columns": [ + "user_id", + "team_id" + ], + "name": "user_to_team_user_id_team_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verification": { + "name": "verification", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json index 3395974..8c83481 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -85,6 +85,13 @@ "when": 1771399307185, "tag": "0011_robust_sabretooth", "breakpoints": true + }, + { + "idx": 12, + "version": "6", + "when": 1771710466128, + "tag": "0012_lumpy_the_anarchist", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 192479d..2e2d328 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -6,6 +6,7 @@ import { primaryKey, } from "drizzle-orm/sqlite-core"; import { nanoid } from "nanoid"; +import { db } from "."; const STANDARD_NANOID_SIZE = 12; const STANDARD_VARCHAR_LENGTH = 255; @@ -43,6 +44,9 @@ const siteRoleType = text({ enum: ["SUPER_ADMIN", "ADMIN", "USER"] }); const teamJoinRequestStatusType = text({ enum: ["PENDING", "APPROVED", "REJECTED", "RESCINDED"], }); +const loggingSourceType = text({ + enum: ["SERVER", "BACKUP_SERVICE", "CLIENT"], +}); // User Table - Partially generated based on Better Auth requirements. Modify with extreme caution. export const user = sqliteTable("user", { @@ -194,6 +198,7 @@ export const log = sqliteTable("log", { logType: logType.notNull(), message: standardVarcharFactory(), occurredAt: standardDateFactory(), + source: loggingSourceType.notNull(), // TODO(https://github.com/acmutsa/Fallback/issues/39): All of these fields are nullable because not all logs have the same info. There might be a better approach. teamId: standardVarcharFactoryNullable(), userId: standardVarcharFactoryNullable(),