fix: adapt code to modernized libraries

- Migrate validation.ts from class-transformer/class-validator to Zod
- Apply Biome auto-fixes (import sorting, nodejs protocol)
- Fix duplicate type identifier in validation.ts

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-05-15 04:25:06 +07:00
co-authored by Claude Opus 4.7
parent 203aa9a589
commit 29fcde69e4
10 changed files with 74 additions and 49 deletions
+17 -6
View File
@@ -1,13 +1,22 @@
import "dotenv/config";
import Database from "better-sqlite3";
import { drizzle as drizzleSqlite } from "drizzle-orm/better-sqlite3";
import { migrate as migrateSqlite } from "drizzle-orm/better-sqlite3/migrator";
import { migrate } from "drizzle-orm/node-postgres/migrator";
import { migrate as migratePostgres } from "drizzle-orm/node-postgres/migrator";
import { config } from "../config";
import { createChildLogger } from "../logger";
import { initializeDatabase } from "./drizzle";
const logger = createChildLogger("migrate");
export async function initializeMigrationSqliteDatabase(
path = ".muxer-queue.db",
) {
const sqlite = new Database(path);
sqlite.pragma("journal_mode = WAL");
return { sqlite, db: drizzleSqlite(sqlite) };
}
export async function runMigrations(): Promise<void> {
try {
logger.info("Starting database migrations");
@@ -15,14 +24,16 @@ export async function runMigrations(): Promise<void> {
if (config.DATABASE_TYPE === "postgres") {
logger.info("Running PostgreSQL migrations");
const db = await initializeDatabase();
await migrate(db as any, { migrationsFolder: "./drizzle/migrations" });
await migratePostgres(db as any, {
migrationsFolder: "./drizzle/migrations",
});
logger.info("PostgreSQL migrations completed successfully");
} else {
logger.info("Running SQLite migrations");
const sqlite = new Database(".muxer-queue.db");
sqlite.pragma("journal_mode = WAL");
const db = require("drizzle-orm/better-sqlite3").drizzle(sqlite);
const { sqlite, db } = await initializeMigrationSqliteDatabase();
migrateSqlite(db, { migrationsFolder: "./drizzle/migrations" });
// Ensure the SQLite connection is closed after migrations
sqlite.close();
logger.info("SQLite migrations completed successfully");
}
} catch (error) {
@@ -35,7 +46,7 @@ export async function runMigrations(): Promise<void> {
}
// Run migrations if called directly
if (require.main === module) {
if (import.meta.url === `file://${process.argv[1]}`) {
runMigrations()
.then(() => {
logger.info("Migrations completed");
+1 -1
View File
@@ -8,7 +8,7 @@ for (let i = 0; i < 256; i++) {
CRC_TABLE[i] = r >>> 0;
}
const Module = require("module");
const Module = require("node:module");
const originalRequire = Module.prototype.require;
Module.prototype.require = function (id: string) {
if (id === "node-crc") {
+4 -4
View File
@@ -34,9 +34,9 @@ export async function captureMessage(
guild_id: message.guildId!,
channel_id: location.channelId,
thread_id: location.threadId,
user_id: message.author!.id,
username: message.author!.username,
avatar_url: message.author!.avatarURL() || null,
user_id: message.author?.id,
username: message.author?.username,
avatar_url: message.author?.avatarURL() || null,
content: getDisplayContent(message),
edited_content: null,
created_at: message.createdTimestamp,
@@ -67,7 +67,7 @@ export async function captureMessage(
guild_id: message.guildId!,
channel_id: location.channelId,
thread_id: location.threadId,
user_id: message.author!.id,
user_id: message.author?.id,
filename: attachment.name || "unknown",
size: attachment.size,
type: attachment.contentType || "application/octet-stream",
+2 -2
View File
@@ -1,6 +1,6 @@
import fs from "node:fs";
import path from "node:path";
import ffmpeg from "fluent-ffmpeg";
import fs from "fs";
import path from "path";
const recordingsDir = process.env.RECORDINGS_DIR ?? "./recordings";
+2 -2
View File
@@ -1,6 +1,6 @@
import fs from "node:fs";
import path from "node:path";
import ffmpeg from "fluent-ffmpeg";
import fs from "fs";
import path from "path";
const recordingsDir = process.env.RECORDINGS_DIR ?? "./recordings";
+1 -1
View File
@@ -1,4 +1,4 @@
import { Transform, TransformCallback } from "stream";
import { Transform, TransformCallback } from "node:stream";
/**
* Transform stream untuk memfilter audio packets yang terlalu kecil
+1 -1
View File
@@ -1,3 +1,4 @@
import { Readable } from "node:stream";
import {
AudioPlayer,
AudioPlayerStatus,
@@ -6,7 +7,6 @@ import {
StreamType,
VoiceConnection,
} from "@discordjs/voice";
import { Readable } from "stream";
export class DiscordPlayer {
private player: AudioPlayer;
+13 -29
View File
@@ -1,38 +1,22 @@
import { plainToClass } from "class-transformer";
import { IsBoolean, IsString, validate } from "class-validator";
import { z } from "zod";
export class UserStateUpdate {
@IsString()
userId!: string;
export const userStateUpdateSchema = z.object({
userId: z.string(),
username: z.string(),
avatar: z.string(),
speaking: z.boolean(),
});
@IsString()
username!: string;
export type UserStateUpdate = z.infer<typeof userStateUpdateSchema>;
@IsString()
avatar!: string;
@IsBoolean()
speaking!: boolean;
}
export class AudioMessage {
data!: Buffer;
userId!: string;
export interface AudioMessage {
data: Buffer;
userId: string;
}
export async function validateUserStateUpdate(
data: unknown,
): Promise<UserStateUpdate | null> {
if (typeof data !== "object" || data === null) {
return null;
}
const obj = plainToClass(UserStateUpdate, data);
const errors = await validate(obj);
if (errors.length > 0) {
return null;
}
return obj;
const result = userStateUpdateSchema.safeParse(data);
return result.success ? result.data : null;
}
+3 -3
View File
@@ -1,9 +1,9 @@
import fs from "node:fs";
import http from "node:http";
import path from "node:path";
import type { Client } from "discord.js-selfbot-v13";
import express from "express";
import fs from "fs";
import helmet from "helmet";
import http from "http";
import path from "path";
import * as prism from "prism-media";
import { WebSocketServer } from "ws";
import { AppError } from "./errors";
+30
View File
@@ -0,0 +1,30 @@
import { expect, test } from "vitest";
import { UserStateUpdate, validateUserStateUpdate } from "../src/validation";
test("valid object returns typed object", async () => {
const input = {
userId: "123",
username: "testuser",
avatar: "avatar.png",
speaking: true,
};
const result = await validateUserStateUpdate(input);
expect(result).toEqual(input as UserStateUpdate);
});
test("non-object input returns null", async () => {
// @ts-expect-error testing invalid input
const result = await validateUserStateUpdate("not an object");
expect(result).toBeNull();
});
test("invalid field types return null", async () => {
const input = {
userId: "123",
username: "testuser",
avatar: "avatar.png",
speaking: "true", // invalid type
};
const result = await validateUserStateUpdate(input as unknown);
expect(result).toBeNull();
});