fix: correct import ordering and update tests for drizzle-orm migration
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user