diff --git a/drizzle/db.ts b/drizzle/db.ts index f92fa5d1..84a0874c 100644 --- a/drizzle/db.ts +++ b/drizzle/db.ts @@ -2,6 +2,12 @@ import { config } from "config-manager"; import { drizzle } from "drizzle-orm/node-postgres"; import { Client } from "pg"; import * as schema from "./schema"; +import { + LogLevel, + type LogManager, + type MultiLogManager, +} from "~packages/log-manager"; +import { migrate } from "drizzle-orm/postgres-js/migrator"; export const client = new Client({ host: config.database.host, @@ -11,4 +17,38 @@ export const client = new Client({ database: config.database.database, }); +export const setupDatabase = async (logger: LogManager | MultiLogManager) => { + try { + await client.connect(); + } catch (e) { + await logger.logError(LogLevel.CRITICAL, "Database", e as Error); + + await logger.log( + LogLevel.CRITICAL, + "Database", + "Failed to connect to database. Please check your configuration.", + ); + process.exit(1); + } + + // Migrate the database + await logger.log(LogLevel.INFO, "Database", "Migrating database..."); + + try { + await migrate(db, { + migrationsFolder: "./drizzle", + }); + } catch (e) { + await logger.logError(LogLevel.CRITICAL, "Database", e as Error); + await logger.log( + LogLevel.CRITICAL, + "Database", + "Failed to migrate database. Please check your configuration.", + ); + process.exit(1); + } + + await logger.log(LogLevel.INFO, "Database", "Database migrated"); +}; + export const db = drizzle(client, { schema }); diff --git a/index.ts b/index.ts index d56b3cdf..4719d9cf 100644 --- a/index.ts +++ b/index.ts @@ -1,46 +1,31 @@ -import { exists, mkdir, writeFile } from "node:fs/promises"; -import { dirname } from "node:path"; import { connectMeili } from "@meilisearch"; -import { moduleIsEntry } from "@module"; import { config } from "config-manager"; -import { count, sql } from "drizzle-orm"; -import { migrate } from "drizzle-orm/postgres-js/migrator"; +import { count } from "drizzle-orm"; import { LogLevel, LogManager, MultiLogManager } from "log-manager"; -import { db, client as pgClient } from "~drizzle/db"; +import { db, setupDatabase } from "~drizzle/db"; import { status } from "~drizzle/schema"; import { createServer } from "~server"; -await pgClient.connect(); -await migrate(db, { - migrationsFolder: "./drizzle", -}); const timeAtStart = performance.now(); -// Create requests file if it doesnt exist -if ( - !(await exists( - `${process.cwd()}/${dirname(config.logging.storage.requests)}`, - )) -) { - await mkdir(`${process.cwd()}/${dirname(config.logging.storage.requests)}`); - await writeFile(`${process.cwd()}/${config.logging.storage.requests}`, ""); -} -const requests_log = Bun.file( - `${process.cwd()}/${config.logging.storage.requests}`, -); -const isEntry = moduleIsEntry(import.meta.url); +const requests_log = Bun.file(config.logging.storage.requests); +const isEntry = import.meta.path === Bun.main; + +const noColors = process.env.NO_COLORS === "true"; +const noFancyDates = process.env.NO_FANCY_DATES === "true"; + // If imported as a module, redirect logs to /dev/null to not pollute console (e.g. in tests) const logger = new LogManager(isEntry ? requests_log : Bun.file("/dev/null")); const consoleLogger = new LogManager( isEntry ? Bun.stdout : Bun.file("/dev/null"), + !noColors, + !noFancyDates, ); const dualLogger = new MultiLogManager([logger, consoleLogger]); await dualLogger.log(LogLevel.INFO, "Lysand", "Starting Lysand..."); -// NODE_ENV seems to be broken and output `development` even when set to production, so use the flag instead -const isProd = - process.env.NODE_ENV === "production" || process.argv.includes("--prod"); +await setupDatabase(dualLogger); if (config.meilisearch.enabled) { await connectMeili(dualLogger); @@ -63,7 +48,7 @@ try { process.exit(1); } -const server = createServer(config, dualLogger, isProd); +const server = createServer(config, dualLogger, true); await dualLogger.log( LogLevel.INFO, diff --git a/packages/log-manager/index.ts b/packages/log-manager/index.ts index 88b48e64..080478c8 100644 --- a/packages/log-manager/index.ts +++ b/packages/log-manager/index.ts @@ -1,6 +1,7 @@ import { appendFile, exists, mkdir } from "node:fs/promises"; import { dirname } from "node:path"; import type { BunFile } from "bun"; +import chalk from "chalk"; export enum LogLevel { DEBUG = "debug", @@ -15,12 +16,44 @@ export enum LogLevel { * @param output BunFile of output (can be a normal file or something like Bun.stdout) */ export class LogManager { - constructor(private output: BunFile) { + constructor( + private output: BunFile, + private enableColors = false, + private prettyDates = false, + ) { void this.write( `--- INIT LogManager at ${new Date().toISOString()} ---`, ); } + getLevelColor(level: LogLevel) { + switch (level) { + case LogLevel.DEBUG: + return chalk.blue; + case LogLevel.INFO: + return chalk.green; + case LogLevel.WARNING: + return chalk.yellow; + case LogLevel.ERROR: + return chalk.red; + case LogLevel.CRITICAL: + return chalk.bgRed; + } + } + + getFormattedDate(date: Date = new Date()) { + return this.prettyDates + ? date.toLocaleString(undefined, { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }) + : date.toISOString(); + } + /** * Logs a message to the output * @param level Importance of the log @@ -34,11 +67,23 @@ export class LogManager { message: string, showTimestamp = true, ) { - await this.write( - `${ - showTimestamp ? `${new Date().toISOString()} ` : "" - }[${level.toUpperCase()}] ${entity}: ${message}`, - ); + if (this.enableColors) { + await this.write( + `${ + showTimestamp + ? `${chalk.gray(this.getFormattedDate())} ` + : "" + }[${this.getLevelColor(level)( + level.toUpperCase(), + )}] ${chalk.bold(entity)}: ${message}`, + ); + } else { + await this.write( + `${ + showTimestamp ? `${this.getFormattedDate()} ` : "" + }[${level.toUpperCase()}] ${entity}: ${message}`, + ); + } } private async write(text: string) {