refactor: large codebase cleanup - consolidate schemas, migrate to Drizzle ORM, extract frontend components, modernize Docker builds
Build & Deploy / build-and-push (discord-gateway) (push) Failing after 2m22s
Build & Deploy / build-and-push (backend) (push) Failing after 3m22s
Build & Deploy / build-and-push (proxy) (push) Successful in 1m36s
Build & Deploy / deploy (push) Skipped

- Consolidate all DB schema definitions into packages/shared as single source of truth
- Migrate backend from raw SQL to Drizzle ORM across all modules
- Extract frontend inline UI into separate component files
- Refactor discord-gateway circuitBreaker into conversationState + moderationState
- Convert messageStore to Proxy singleton pattern
- Add validateBody/validateQuery middleware + Zod schemas for API endpoints
- Modernize Docker builds with multi-stage + pnpm deploy
- Migrate CI/CD from deployment to image-based pipeline
- Remove 60+ unused/dead files (~15K lines)
- Update color scheme from sky-blue to teal-cyan
- Move DB connection management to @bete/shared/database

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Developer
2026-07-27 21:54:31 +07:00
co-authored by Claude Opus 4.8
parent 63f21513bd
commit 5802d02e29
223 changed files with 11499 additions and 13350 deletions
@@ -2,4 +2,3 @@ import "dotenv/config";
import { config as sharedConfig } from "@bete/shared/config";
export const config = sharedConfig;
export type Config = typeof config;
+22 -50
View File
@@ -1,67 +1,39 @@
import {
closeDatabase as sharedCloseDb,
getDatabase as sharedGetDb,
getPool as sharedGetPool,
initializeDatabase as sharedInit,
} from "@bete/shared/database/init";
import { createChildLogger } from "@bete/shared/logger";
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import { config } from "../config/index.js";
const logger = createChildLogger("database");
let pool: Pool | null = null;
let db: ReturnType<typeof drizzle> | null = null;
const dbConfig = {
DATABASE_URL: config.DATABASE_URL,
POSTGRES_HOST: config.POSTGRES_HOST as string | undefined,
POSTGRES_PORT: config.POSTGRES_PORT,
POSTGRES_USER: config.POSTGRES_USER as string | undefined,
POSTGRES_PASSWORD: config.POSTGRES_PASSWORD as string | undefined,
POSTGRES_DB: config.POSTGRES_DB as string | undefined,
POSTGRES_POOL_MIN: config.POSTGRES_POOL_MIN,
POSTGRES_POOL_MAX: config.POSTGRES_POOL_MAX,
};
export async function initializeDatabase() {
if (db) {
logger.warn("Database already initialized");
return db;
}
const databaseUrl =
config.DATABASE_URL ||
`postgresql://${config.POSTGRES_USER}${config.POSTGRES_PASSWORD ? `:${config.POSTGRES_PASSWORD}` : ""}@${config.POSTGRES_HOST}:${config.POSTGRES_PORT}/${config.POSTGRES_DB}`;
pool = new Pool({
connectionString: databaseUrl,
});
pool.on("error", (err) => {
logger.error({ err }, "Unexpected error on idle client");
});
try {
const client = await pool.connect();
client.release();
logger.info("Database connection successful");
} catch (err) {
logger.error({ err }, "Failed to connect to database");
throw err;
}
db = drizzle(pool);
return db;
logger.info("Initializing database");
return sharedInit(dbConfig);
}
export function getDatabase() {
if (!db) {
throw new Error(
"Database not initialized. Call initializeDatabase() first.",
);
}
return db;
return sharedGetDb();
}
export function getPool() {
if (!pool) {
throw new Error(
"Database not initialized. Call initializeDatabase() first.",
);
}
return pool;
return sharedGetPool();
}
export async function closeDatabase() {
if (pool) {
await pool.end();
pool = null;
db = null;
logger.info("Database connection closed");
}
logger.info("Closing database");
return sharedCloseDb();
}
@@ -1,6 +1,7 @@
import { AppError, ValidationError } from "@bete/shared/errors";
import { createChildLogger } from "@bete/shared/logger";
import type { NextFunction, Request, Response } from "express";
import type { ZodSchema } from "zod";
const logger = createChildLogger("middleware");
@@ -90,3 +91,49 @@ export function requireParam(
}
return value;
}
/**
* Express middleware that validates `req.body` against a Zod schema.
* On success, replaces `req.body` with the parsed (and defaulted) value.
* On failure, responds with 400 and the Zod validation errors.
*/
export function validateBody<T>(schema: ZodSchema<T>) {
return (req: Request, res: Response, next: NextFunction) => {
const result = schema.safeParse(req.body);
if (!result.success) {
res.status(400).json({
error: "VALIDATION_ERROR",
message: "Request body validation failed",
details: result.error.flatten().fieldErrors,
});
return;
}
req.body = result.data;
next();
};
}
/**
* Express middleware that validates `req.query` against a Zod schema.
* On success, replaces `req.query` with the parsed (and defaulted) value.
* On failure, responds with 400 and the Zod validation errors.
*/
export function validateQuery<T>(schema: ZodSchema<T>) {
return (req: Request, res: Response, next: NextFunction) => {
const result = schema.safeParse(req.query);
if (!result.success) {
res.status(400).json({
error: "VALIDATION_ERROR",
message: "Query parameter validation failed",
details: result.error.flatten().fieldErrors,
});
return;
}
// Note: Express req.query is typed as ParsedQs — we attach parsed data
// alongside it via a custom property. For route handlers that read req.query
// directly, the middleware won't change the type; handlers should opt in by
// reading from the validated result or by using the schema's output type.
(req as Request & { validatedQuery: T }).validatedQuery = result.data;
next();
};
}