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:
@@ -1,13 +1,22 @@
|
|||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
import Database from "better-sqlite3";
|
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 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 { config } from "../config";
|
||||||
import { createChildLogger } from "../logger";
|
import { createChildLogger } from "../logger";
|
||||||
import { initializeDatabase } from "./drizzle";
|
import { initializeDatabase } from "./drizzle";
|
||||||
|
|
||||||
const logger = createChildLogger("migrate");
|
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> {
|
export async function runMigrations(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
logger.info("Starting database migrations");
|
logger.info("Starting database migrations");
|
||||||
@@ -15,14 +24,16 @@ export async function runMigrations(): Promise<void> {
|
|||||||
if (config.DATABASE_TYPE === "postgres") {
|
if (config.DATABASE_TYPE === "postgres") {
|
||||||
logger.info("Running PostgreSQL migrations");
|
logger.info("Running PostgreSQL migrations");
|
||||||
const db = await initializeDatabase();
|
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");
|
logger.info("PostgreSQL migrations completed successfully");
|
||||||
} else {
|
} else {
|
||||||
logger.info("Running SQLite migrations");
|
logger.info("Running SQLite migrations");
|
||||||
const sqlite = new Database(".muxer-queue.db");
|
const { sqlite, db } = await initializeMigrationSqliteDatabase();
|
||||||
sqlite.pragma("journal_mode = WAL");
|
|
||||||
const db = require("drizzle-orm/better-sqlite3").drizzle(sqlite);
|
|
||||||
migrateSqlite(db, { migrationsFolder: "./drizzle/migrations" });
|
migrateSqlite(db, { migrationsFolder: "./drizzle/migrations" });
|
||||||
|
// Ensure the SQLite connection is closed after migrations
|
||||||
|
sqlite.close();
|
||||||
logger.info("SQLite migrations completed successfully");
|
logger.info("SQLite migrations completed successfully");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -35,7 +46,7 @@ export async function runMigrations(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run migrations if called directly
|
// Run migrations if called directly
|
||||||
if (require.main === module) {
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||||
runMigrations()
|
runMigrations()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.info("Migrations completed");
|
logger.info("Migrations completed");
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ for (let i = 0; i < 256; i++) {
|
|||||||
CRC_TABLE[i] = r >>> 0;
|
CRC_TABLE[i] = r >>> 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Module = require("module");
|
const Module = require("node:module");
|
||||||
const originalRequire = Module.prototype.require;
|
const originalRequire = Module.prototype.require;
|
||||||
Module.prototype.require = function (id: string) {
|
Module.prototype.require = function (id: string) {
|
||||||
if (id === "node-crc") {
|
if (id === "node-crc") {
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ export async function captureMessage(
|
|||||||
guild_id: message.guildId!,
|
guild_id: message.guildId!,
|
||||||
channel_id: location.channelId,
|
channel_id: location.channelId,
|
||||||
thread_id: location.threadId,
|
thread_id: location.threadId,
|
||||||
user_id: message.author!.id,
|
user_id: message.author?.id,
|
||||||
username: message.author!.username,
|
username: message.author?.username,
|
||||||
avatar_url: message.author!.avatarURL() || null,
|
avatar_url: message.author?.avatarURL() || null,
|
||||||
content: getDisplayContent(message),
|
content: getDisplayContent(message),
|
||||||
edited_content: null,
|
edited_content: null,
|
||||||
created_at: message.createdTimestamp,
|
created_at: message.createdTimestamp,
|
||||||
@@ -67,7 +67,7 @@ export async function captureMessage(
|
|||||||
guild_id: message.guildId!,
|
guild_id: message.guildId!,
|
||||||
channel_id: location.channelId,
|
channel_id: location.channelId,
|
||||||
thread_id: location.threadId,
|
thread_id: location.threadId,
|
||||||
user_id: message.author!.id,
|
user_id: message.author?.id,
|
||||||
filename: attachment.name || "unknown",
|
filename: attachment.name || "unknown",
|
||||||
size: attachment.size,
|
size: attachment.size,
|
||||||
type: attachment.contentType || "application/octet-stream",
|
type: attachment.contentType || "application/octet-stream",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
import ffmpeg from "fluent-ffmpeg";
|
import ffmpeg from "fluent-ffmpeg";
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
const recordingsDir = process.env.RECORDINGS_DIR ?? "./recordings";
|
const recordingsDir = process.env.RECORDINGS_DIR ?? "./recordings";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
import ffmpeg from "fluent-ffmpeg";
|
import ffmpeg from "fluent-ffmpeg";
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
const recordingsDir = process.env.RECORDINGS_DIR ?? "./recordings";
|
const recordingsDir = process.env.RECORDINGS_DIR ?? "./recordings";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Transform, TransformCallback } from "stream";
|
import { Transform, TransformCallback } from "node:stream";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform stream untuk memfilter audio packets yang terlalu kecil
|
* Transform stream untuk memfilter audio packets yang terlalu kecil
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Readable } from "node:stream";
|
||||||
import {
|
import {
|
||||||
AudioPlayer,
|
AudioPlayer,
|
||||||
AudioPlayerStatus,
|
AudioPlayerStatus,
|
||||||
@@ -6,7 +7,6 @@ import {
|
|||||||
StreamType,
|
StreamType,
|
||||||
VoiceConnection,
|
VoiceConnection,
|
||||||
} from "@discordjs/voice";
|
} from "@discordjs/voice";
|
||||||
import { Readable } from "stream";
|
|
||||||
|
|
||||||
export class DiscordPlayer {
|
export class DiscordPlayer {
|
||||||
private player: AudioPlayer;
|
private player: AudioPlayer;
|
||||||
|
|||||||
@@ -1,38 +1,22 @@
|
|||||||
import { plainToClass } from "class-transformer";
|
import { z } from "zod";
|
||||||
import { IsBoolean, IsString, validate } from "class-validator";
|
|
||||||
|
|
||||||
export class UserStateUpdate {
|
export const userStateUpdateSchema = z.object({
|
||||||
@IsString()
|
userId: z.string(),
|
||||||
userId!: string;
|
username: z.string(),
|
||||||
|
avatar: z.string(),
|
||||||
|
speaking: z.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
@IsString()
|
export type UserStateUpdate = z.infer<typeof userStateUpdateSchema>;
|
||||||
username!: string;
|
|
||||||
|
|
||||||
@IsString()
|
export interface AudioMessage {
|
||||||
avatar!: string;
|
data: Buffer;
|
||||||
|
userId: string;
|
||||||
@IsBoolean()
|
|
||||||
speaking!: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AudioMessage {
|
|
||||||
data!: Buffer;
|
|
||||||
userId!: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function validateUserStateUpdate(
|
export async function validateUserStateUpdate(
|
||||||
data: unknown,
|
data: unknown,
|
||||||
): Promise<UserStateUpdate | null> {
|
): Promise<UserStateUpdate | null> {
|
||||||
if (typeof data !== "object" || data === null) {
|
const result = userStateUpdateSchema.safeParse(data);
|
||||||
return null;
|
return result.success ? result.data : null;
|
||||||
}
|
|
||||||
|
|
||||||
const obj = plainToClass(UserStateUpdate, data);
|
|
||||||
const errors = await validate(obj);
|
|
||||||
|
|
||||||
if (errors.length > 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 type { Client } from "discord.js-selfbot-v13";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import fs from "fs";
|
|
||||||
import helmet from "helmet";
|
import helmet from "helmet";
|
||||||
import http from "http";
|
|
||||||
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 { AppError } from "./errors";
|
import { AppError } from "./errors";
|
||||||
|
|||||||
30
tests/validation.test.ts
Normal file
30
tests/validation.test.ts
Normal 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();
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user