fix: correct import ordering and update tests for drizzle-orm migration

This commit is contained in:
MythEclipse
2026-05-14 15:47:03 +07:00
parent 50d4517079
commit b600dad011
10 changed files with 87 additions and 140 deletions

View File

@@ -1,6 +1,6 @@
import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres";
import { drizzle as drizzleSqlite } from "drizzle-orm/better-sqlite3";
import Database from "better-sqlite3"; import Database from "better-sqlite3";
import { drizzle as drizzleSqlite } from "drizzle-orm/better-sqlite3";
import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres";
import { Pool } from "pg"; import { Pool } from "pg";
import { config } from "../config"; import { config } from "../config";
import { createChildLogger } from "../logger"; import { createChildLogger } from "../logger";
@@ -8,7 +8,10 @@ import * as schema from "./schema";
const logger = createChildLogger("drizzle"); const logger = createChildLogger("drizzle");
let db: ReturnType<typeof drizzlePostgres> | ReturnType<typeof drizzleSqlite> | null = null; let db:
| ReturnType<typeof drizzlePostgres>
| ReturnType<typeof drizzleSqlite>
| null = null;
/** /**
* Initialize the database connection based on DATABASE_TYPE config * Initialize the database connection based on DATABASE_TYPE config
@@ -51,7 +54,7 @@ export async function initializeDatabase() {
export function getDatabase() { export function getDatabase() {
if (db === null) { if (db === null) {
throw new Error( throw new Error(
"Database not initialized. Call initializeDatabase() first." "Database not initialized. Call initializeDatabase() first.",
); );
} }
return db; return db;

View File

@@ -1,18 +1,18 @@
import { import {
bigint as pgBigint,
foreignKey as pgForeignKey,
index as pgIndex,
integer as pgInteger,
real as pgReal,
pgTable, pgTable,
text as pgText, text as pgText,
integer as pgInteger,
bigint as pgBigint,
real as pgReal,
index as pgIndex,
foreignKey as pgForeignKey,
} from "drizzle-orm/pg-core"; } from "drizzle-orm/pg-core";
import { import {
sqliteTable, index as sqliteIndex,
text as sqliteText,
integer as sqliteInteger, integer as sqliteInteger,
real as sqliteReal, real as sqliteReal,
index as sqliteIndex, sqliteTable,
text as sqliteText,
} from "drizzle-orm/sqlite-core"; } from "drizzle-orm/sqlite-core";
import { config } from "../config"; import { config } from "../config";
@@ -257,14 +257,10 @@ export const sqliteUIStateTable = sqliteTable("ui_state", {
// ======================================== // ========================================
export const muxerJobsTable = export const muxerJobsTable =
config.DATABASE_TYPE === "postgres" config.DATABASE_TYPE === "postgres" ? pgMuxerJobsTable : sqliteMuxerJobsTable;
? pgMuxerJobsTable
: sqliteMuxerJobsTable;
export const messagesTable = export const messagesTable =
config.DATABASE_TYPE === "postgres" config.DATABASE_TYPE === "postgres" ? pgMessagesTable : sqliteMessagesTable;
? pgMessagesTable
: sqliteMessagesTable;
export const attachmentsTable = export const attachmentsTable =
config.DATABASE_TYPE === "postgres" config.DATABASE_TYPE === "postgres"
@@ -272,9 +268,7 @@ export const attachmentsTable =
: sqliteAttachmentsTable; : sqliteAttachmentsTable;
export const uiStateTable = export const uiStateTable =
config.DATABASE_TYPE === "postgres" config.DATABASE_TYPE === "postgres" ? pgUIStateTable : sqliteUIStateTable;
? pgUIStateTable
: sqliteUIStateTable;
// Export table types for use in queries // Export table types for use in queries
export type MuxerJob = typeof muxerJobsTable.$inferSelect; export type MuxerJob = typeof muxerJobsTable.$inferSelect;

View File

@@ -4,7 +4,7 @@ import "@snazzah/davey";
import "dotenv/config"; import "dotenv/config";
import { Client } from "discord.js-selfbot-v13"; import { Client } from "discord.js-selfbot-v13";
import { config } from "./config"; import { config } from "./config";
import { initializeDatabase, closeDatabase } from "./database/drizzle"; import { closeDatabase, initializeDatabase } from "./database/drizzle";
import { createChildLogger } from "./logger"; import { createChildLogger } from "./logger";
import { startPendingAIAnalysisWorker } from "./moderation/aiAnalyzer"; import { startPendingAIAnalysisWorker } from "./moderation/aiAnalyzer";
import { syncBacklogMessages } from "./moderation/backlogSync"; import { syncBacklogMessages } from "./moderation/backlogSync";

View File

@@ -245,9 +245,7 @@ Satu JSON object per pesan dalam array.`,
return { results, raw: response }; return { results, raw: response };
} }
async function analyzeAndStoreBatch( async function analyzeAndStoreBatch(messages: MessageRecord[]): Promise<void> {
messages: MessageRecord[],
): Promise<void> {
if (messages.length === 0) return; if (messages.length === 0) return;
const analyzableMessages = messages.filter( const analyzableMessages = messages.filter(
@@ -359,9 +357,7 @@ async function drainQueue(): Promise<void> {
} }
} }
export function queueMessageAnalysis( export function queueMessageAnalysis(messageId: string): void {
messageId: string,
): void {
if (!config.AI_ANALYSIS_ENABLED) return; if (!config.AI_ANALYSIS_ENABLED) return;
logger.debug({ messageId }, "Queueing AI analysis"); logger.debug({ messageId }, "Queueing AI analysis");
queuedMessageIds.add(messageId); queuedMessageIds.add(messageId);

View File

@@ -40,9 +40,7 @@ async function syncChannelMessages(
return synced; return synced;
} }
export async function syncBacklogMessages( export async function syncBacklogMessages(client: Client): Promise<void> {
client: Client,
): Promise<void> {
if (!config.MONITOR_GUILD_ID) { if (!config.MONITOR_GUILD_ID) {
logger.warn("MONITOR_GUILD_ID not configured, skipping backlog sync"); logger.warn("MONITOR_GUILD_ID not configured, skipping backlog sync");
return; return;

View File

@@ -1,9 +1,9 @@
import type { Client, Message } from "discord.js-selfbot-v13"; import type { Client, Message } from "discord.js-selfbot-v13";
import { eq } from "drizzle-orm";
import { config } from "../config"; import { config } from "../config";
import { createChildLogger } from "../logger";
import { getDatabase } from "../database/drizzle"; import { getDatabase } from "../database/drizzle";
import { messagesTable } from "../database/schema"; import { messagesTable } from "../database/schema";
import { eq } from "drizzle-orm"; import { createChildLogger } from "../logger";
import { queueMessageAnalysis } from "./aiAnalyzer"; import { queueMessageAnalysis } from "./aiAnalyzer";
import { import {
getDisplayContent, getDisplayContent,
@@ -94,9 +94,7 @@ export async function captureMessage(
); );
} }
export function registerMessageCapture( export function registerMessageCapture(client: Client): void {
client: Client,
): void {
client.on("messageCreate", async (message) => { client.on("messageCreate", async (message) => {
if (!message.guildId || message.guildId !== config.MONITOR_GUILD_ID) return; if (!message.guildId || message.guildId !== config.MONITOR_GUILD_ID) return;
if (message.author?.bot) return; if (message.author?.bot) return;

View File

@@ -1,14 +1,12 @@
import { and, asc, desc, eq, isNull, or } from "drizzle-orm";
import { getDatabase } from "../database/drizzle"; import { getDatabase } from "../database/drizzle";
import { messagesTable, attachmentsTable } from "../database/schema"; import { attachmentsTable, messagesTable } from "../database/schema";
import { eq, or, desc, asc, and, isNull } from "drizzle-orm";
import { createChildLogger } from "../logger"; import { createChildLogger } from "../logger";
import type { AttachmentRecord, MessageRecord } from "./types"; import type { AttachmentRecord, MessageRecord } from "./types";
const logger = createChildLogger("message-store"); const logger = createChildLogger("message-store");
export async function insertMessage( export async function insertMessage(message: MessageRecord): Promise<void> {
message: MessageRecord,
): Promise<void> {
try { try {
const db = getDatabase() as any; const db = getDatabase() as any;
await db.insert(messagesTable).values(message).onConflictDoNothing(); await db.insert(messagesTable).values(message).onConflictDoNothing();

View File

@@ -1,6 +1,9 @@
import { getDatabase as getDrizzleDatabase, initializeDatabase } from "./database/drizzle"; import { and, asc, eq, lt, sql } from "drizzle-orm";
import {
getDatabase as getDrizzleDatabase,
initializeDatabase,
} from "./database/drizzle";
import { muxerJobsTable, uiStateTable } from "./database/schema"; import { muxerJobsTable, uiStateTable } from "./database/schema";
import { eq, asc, lt, and, sql } from "drizzle-orm";
import { createChildLogger } from "./logger"; import { createChildLogger } from "./logger";
const logger = createChildLogger("muxer-queue"); const logger = createChildLogger("muxer-queue");
@@ -224,7 +227,8 @@ export async function cleanupCompletedJobs(
), ),
); );
const deletedCount = typeof result === "object" && "rowsAffected" in result const deletedCount =
typeof result === "object" && "rowsAffected" in result
? result.rowsAffected ? result.rowsAffected
: 0; : 0;
@@ -258,7 +262,8 @@ export async function getJobStats(): Promise<{
}; };
for (const row of rows) { for (const row of rows) {
const count = typeof row.count === "object" && "count" in row.count const count =
typeof row.count === "object" && "count" in row.count
? (row.count as any).count ? (row.count as any).count
: Number(row.count); : Number(row.count);
if (row.status === "pending") stats.pending = count; if (row.status === "pending") stats.pending = count;

View File

@@ -5,6 +5,7 @@ import http from "http";
import path from "path"; import path from "path";
import * as prism from "prism-media"; import * as prism from "prism-media";
import { WebSocketServer } from "ws"; import { WebSocketServer } from "ws";
import { getDatabase } from "./database/drizzle";
import { AppError } from "./errors"; import { AppError } from "./errors";
import { createChildLogger, logger } from "./logger"; import { createChildLogger, logger } from "./logger";
import { getMetrics, uptimeGauge } from "./metrics"; import { getMetrics, uptimeGauge } from "./metrics";
@@ -18,7 +19,6 @@ import {
getPersistedValue, getPersistedValue,
setPersistedValue, setPersistedValue,
} from "./muxer-queue"; } from "./muxer-queue";
import { getDatabase } from "./database/drizzle";
import { discordPlayer } from "./player"; import { discordPlayer } from "./player";
import type { VoiceController } from "./voiceController"; import type { VoiceController } from "./voiceController";
@@ -296,7 +296,11 @@ export async function startWebserver(
count: attachments.length, count: attachments.length,
}); });
} else { } else {
const messages = await getMessagesByChannel(channel, limitNum, offsetNum); const messages = await getMessagesByChannel(
channel,
limitNum,
offsetNum,
);
res.json({ res.json({
type: "text", type: "text",
data: messages, data: messages,

View File

@@ -3,10 +3,9 @@ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
const originalEnv = process.env; const originalEnv = process.env;
describe("PostgreSQL Connection", () => { describe("Drizzle ORM Database", () => {
let skipPostgresTests = false;
let config: any; let config: any;
let postgres: any; let drizzle: any;
let logger: any; let logger: any;
beforeAll(async () => { beforeAll(async () => {
@@ -23,118 +22,70 @@ describe("PostgreSQL Connection", () => {
// Import after environment is set // Import after environment is set
const configModule = await import("../src/config"); const configModule = await import("../src/config");
const postgresModule = await import("../src/database/postgres"); const drizzleModule = await import("../src/database/drizzle");
const loggerModule = await import("../src/logger"); const loggerModule = await import("../src/logger");
config = configModule.config; config = configModule.config;
postgres = postgresModule; drizzle = drizzleModule;
logger = loggerModule.createChildLogger("database.test"); logger = loggerModule.createChildLogger("database.test");
if (config.DATABASE_TYPE !== "postgres") { logger.info(`Testing with DATABASE_TYPE: ${config.DATABASE_TYPE}`);
skipPostgresTests = true;
logger.info("Skipping PostgreSQL tests (DATABASE_TYPE != postgres)");
}
}); });
afterAll(async () => { afterAll(async () => {
if (config && config.DATABASE_TYPE === "postgres") {
try { try {
await postgres.closePool(); await drizzle.closeDatabase();
} catch (error) { } catch (error) {
if (logger) { if (logger) {
logger.error( logger.error(
{ error: error instanceof Error ? error.message : String(error) }, { error: error instanceof Error ? error.message : String(error) },
"Error closing pool in afterAll", "Error closing database in afterAll",
); );
} }
} }
}
process.env = originalEnv; process.env = originalEnv;
}); });
it("should initialize connection pool", async () => { it("should initialize database connection", async () => {
if (skipPostgresTests) { const db = await drizzle.initializeDatabase();
logger.info("Skipping test: DATABASE_TYPE is not postgres");
return;
}
const pool = postgres.getPool(); expect(db).toBeDefined();
expect(db).toHaveProperty("query");
expect(pool).toBeDefined(); expect(db).toHaveProperty("select");
expect(pool).toHaveProperty("connect");
expect(pool).toHaveProperty("query");
expect(pool).toHaveProperty("end");
}); });
it("should execute query", async () => { it("should return same instance on subsequent calls", async () => {
if (skipPostgresTests) { const db1 = await drizzle.initializeDatabase();
logger.info("Skipping test: DATABASE_TYPE is not postgres"); const db2 = await drizzle.initializeDatabase();
return;
}
const result = await postgres.query("SELECT 1 as num"); expect(db1).toBe(db2);
expect(result).toBeDefined();
expect(result.rows).toBeDefined();
expect(result.rows.length).toBeGreaterThan(0);
expect(result.rows[0]).toHaveProperty("num");
expect(result.rows[0].num).toBe(1);
}); });
it("should handle connection errors gracefully", async () => { it("should get database instance", async () => {
if (skipPostgresTests) { await drizzle.initializeDatabase();
logger.info("Skipping test: DATABASE_TYPE is not postgres"); const db = drizzle.getDatabase();
return;
}
// Test that invalid queries throw errors appropriately expect(db).toBeDefined();
try { expect(db).toHaveProperty("query");
await postgres.query("SELECT * FROM nonexistent_table_xyz");
// If we get here, the test should fail
expect.fail("Expected query to throw an error");
} catch (error) {
// Expected behavior: query should throw an error for invalid table
expect(error).toBeDefined();
expect(error instanceof Error).toBe(true);
}
}); });
it("should acquire and release client from pool", async () => { it("should throw error if database not initialized", async () => {
if (skipPostgresTests) { // Reset the database state
logger.info("Skipping test: DATABASE_TYPE is not postgres"); vi.resetModules();
return;
}
const client = await postgres.getClient(); const drizzleModule = await import("../src/database/drizzle");
expect(client).toBeDefined(); expect(() => {
expect(client).toHaveProperty("query"); drizzleModule.getDatabase();
expect(client).toHaveProperty("release"); }).toThrow("Database not initialized");
// Execute a simple query with the client
const result = await client.query("SELECT 1 as num");
expect(result.rows[0].num).toBe(1);
// Release the client back to the pool
client.release();
}); });
it("should build config from DATABASE_URL", () => { it("should close database connection", async () => {
if (skipPostgresTests) { await drizzle.initializeDatabase();
logger.info("Skipping test: DATABASE_TYPE is not postgres"); await drizzle.closeDatabase();
return;
}
// Test buildConfig function with a sample DATABASE_URL expect(() => {
const pgConfig = postgres.buildConfig(); drizzle.getDatabase();
}).toThrow("Database not initialized");
expect(pgConfig).toBeDefined();
expect(pgConfig).toHaveProperty("host");
expect(pgConfig).toHaveProperty("port");
expect(pgConfig).toHaveProperty("min");
expect(pgConfig).toHaveProperty("max");
expect(pgConfig.port).toBeGreaterThan(0);
expect(pgConfig.min).toBeGreaterThan(0);
expect(pgConfig.max).toBeGreaterThanOrEqual(pgConfig.min);
}); });
}); });