feat(database): add automatic migration on startup and enhance text analysis cache source
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Client } from "discord.js-selfbot-v13";
|
||||
import { config } from "../config.js";
|
||||
import { closeDatabase, initializeDatabase } from "../database/drizzle.js";
|
||||
import { runMigrations } from "../database/migrate.js";
|
||||
import { createDiscordClientOptions } from "../discordClientOptions.js";
|
||||
import { createChildLogger } from "../logger.js";
|
||||
import { startPendingAIAnalysisWorker } from "../moderation/aiAnalyzer.js";
|
||||
@@ -40,6 +41,13 @@ export async function initializeApp() {
|
||||
});
|
||||
|
||||
try {
|
||||
if (config.AUTO_MIGRATE_ON_STARTUP) {
|
||||
logger.info(
|
||||
"AUTO_MIGRATE_ON_STARTUP enabled; running database migrations",
|
||||
);
|
||||
await runMigrations();
|
||||
}
|
||||
|
||||
logger.info("Initializing database");
|
||||
await initializeDatabase();
|
||||
logger.info("PostgreSQL database initialized");
|
||||
|
||||
@@ -167,6 +167,11 @@ const configSchema = z
|
||||
.optional()
|
||||
.transform((v) => v === "true")
|
||||
.default(true),
|
||||
AUTO_MIGRATE_ON_STARTUP: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((v) => v === "true")
|
||||
.default(true),
|
||||
DATABASE_URL: z.string().optional(),
|
||||
POSTGRES_HOST: z.string().default("localhost"),
|
||||
POSTGRES_PORT: z.coerce.number().int().positive().default(5432),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres";
|
||||
import { Pool } from "pg";
|
||||
import type { PoolClient } from "pg";
|
||||
import { config } from "../config.js";
|
||||
import { createChildLogger } from "../logger.js";
|
||||
import * as schema from "./schema.js";
|
||||
@@ -93,6 +94,27 @@ export async function executeGet(sql: string, params?: unknown[]) {
|
||||
return result.rows[0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a function with a dedicated PostgreSQL client from the shared pool.
|
||||
* Use this for session-scoped operations such as advisory locks.
|
||||
*/
|
||||
export async function withDatabaseClient<T>(
|
||||
callback: (client: PoolClient) => Promise<T>,
|
||||
): Promise<T> {
|
||||
if (!rawPool) {
|
||||
throw new Error(
|
||||
"Database not initialized. Call initializeDatabase() first.",
|
||||
);
|
||||
}
|
||||
|
||||
const client = await rawPool.connect();
|
||||
try {
|
||||
return await callback(client);
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the PostgreSQL connection pool.
|
||||
*/
|
||||
|
||||
+30
-5
@@ -1,19 +1,44 @@
|
||||
import "dotenv/config";
|
||||
import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres";
|
||||
import { migrate as migratePostgres } from "drizzle-orm/node-postgres/migrator";
|
||||
import { config } from "../config.js";
|
||||
import { createChildLogger } from "../logger.js";
|
||||
import { closeDatabase, initializeDatabase } from "./drizzle.js";
|
||||
import * as schema from "./schema.js";
|
||||
import {
|
||||
closeDatabase,
|
||||
initializeDatabase,
|
||||
withDatabaseClient,
|
||||
} from "./drizzle.js";
|
||||
|
||||
const logger = createChildLogger("migrate");
|
||||
const MIGRATION_LOCK_KEY_1 = 2026;
|
||||
const MIGRATION_LOCK_KEY_2 = 531;
|
||||
|
||||
export async function runMigrations(): Promise<void> {
|
||||
try {
|
||||
logger.info("Starting PostgreSQL migrations");
|
||||
const db = (await initializeDatabase()) as Parameters<
|
||||
typeof migratePostgres
|
||||
>[0];
|
||||
await initializeDatabase();
|
||||
|
||||
try {
|
||||
await migratePostgres(db, { migrationsFolder: "./drizzle/migrations" });
|
||||
await withDatabaseClient(async (client) => {
|
||||
const db = drizzlePostgres(client, { schema });
|
||||
|
||||
await client.query("SELECT pg_advisory_lock($1, $2)", [
|
||||
MIGRATION_LOCK_KEY_1,
|
||||
MIGRATION_LOCK_KEY_2,
|
||||
]);
|
||||
|
||||
try {
|
||||
await migratePostgres(db, {
|
||||
migrationsFolder: "./drizzle/migrations",
|
||||
});
|
||||
} finally {
|
||||
await client.query("SELECT pg_advisory_unlock($1, $2)", [
|
||||
MIGRATION_LOCK_KEY_1,
|
||||
MIGRATION_LOCK_KEY_2,
|
||||
]);
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
await closeDatabase();
|
||||
}
|
||||
|
||||
@@ -369,9 +369,9 @@ export const pgTextAnalysisCacheTable = pgTable(
|
||||
text: pgText("text").primaryKey(),
|
||||
/** JSON array of moderation flags detected for this text (e.g. ["vulgar_language","harassment"]). */
|
||||
flags: pgText("flags").notNull().default("[]"),
|
||||
/** Which source produced this result: "local" | "nvidia" | "primary_ai" | "groq". */
|
||||
/** Which source produced this result: "local" | "nvidia" | "primary_ai" | "groq" | "vision_llm". */
|
||||
source: pgText("source", {
|
||||
enum: ["local", "nvidia", "primary_ai", "groq"],
|
||||
enum: ["local", "nvidia", "primary_ai", "groq", "vision_llm"],
|
||||
})
|
||||
.notNull()
|
||||
.default("local"),
|
||||
|
||||
@@ -7,7 +7,7 @@ const logger = createChildLogger("text-cache-store");
|
||||
export interface TextCacheEntry {
|
||||
text: string;
|
||||
flags: string[];
|
||||
source: "local" | "nvidia" | "primary_ai" | "groq";
|
||||
source: "local" | "nvidia" | "primary_ai" | "groq" | "vision_llm";
|
||||
analyzed_at: number;
|
||||
expires_at: number;
|
||||
hit_count: number;
|
||||
@@ -53,7 +53,7 @@ export async function getCachedText(
|
||||
export async function upsertCachedText(
|
||||
text: string,
|
||||
flags: string[],
|
||||
source: "local" | "nvidia" | "primary_ai" | "groq",
|
||||
source: "local" | "nvidia" | "primary_ai" | "groq" | "vision_llm",
|
||||
expiresAt: number,
|
||||
): Promise<void> {
|
||||
const now = Date.now();
|
||||
|
||||
Reference in New Issue
Block a user