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
@@ -9,6 +9,12 @@ interface DetailStatProps {
suffix?: string;
}
const valueColor = {
default: "",
danger: "text-red-400",
success: "text-emerald-400",
};
/**
* Small stat label used inside detail views.
*/
@@ -19,15 +25,13 @@ export function DetailStat({
suffix,
}: DetailStatProps) {
return (
<Card>
<Card className="bg-gradient-to-br from-cyan-500/5 to-transparent border-cyan-500/10">
<CardContent className="p-3">
<p className="text-xs text-muted-foreground">{label}</p>
<p className="text-xs text-muted-foreground/70 tracking-wide">
{label}
</p>
<p
className={cn(
"text-lg font-bold tabular-nums",
variant === "danger" && "text-destructive",
variant === "success" && "text-green-500",
)}
className={cn("text-lg font-bold tabular-nums", valueColor[variant])}
>
{formatNumber(value)}
{suffix}
@@ -1,7 +1,7 @@
"use client";
import { AlertCircle, RefreshCw } from "lucide-react";
import { useCallback, useEffect, useRef, useState } from "react";
import { useEffect, useRef } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -13,8 +13,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { Skeleton } from "@/components/ui/skeleton";
import { configApi, voiceApi } from "@/lib/api";
import type { Guild } from "@/lib/types";
import { useConfig, useGuilds } from "@/hooks";
export interface GuildSelectorProps {
/** Currently selected guild ID */
@@ -34,51 +33,22 @@ export function GuildSelector({
onChange,
autoHide = true,
}: GuildSelectorProps) {
const [guilds, setGuilds] = useState<Guild[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const { data: guilds = [], isLoading, error, refetch } = useGuilds();
const { data: config } = useConfig();
const initDone = useRef(false);
const fetchGuilds = useCallback(() => {
setLoading(true);
setError(null);
const tryAutoSelect = (list: Guild[]) => {
if (value || list.length === 0 || initDone.current) return;
initDone.current = true;
// Try config's monitorGuildId first, then fall back to first guild
configApi
.get()
.then((cfg) => {
const preferred = cfg.monitorGuildId ?? list[0].id;
if (preferred) onChange(preferred);
})
.catch(() => {
onChange(list[0].id);
});
};
voiceApi
.getGuilds()
.then((list) => {
setGuilds(list);
tryAutoSelect(list);
})
.catch((err) =>
setError(err instanceof Error ? err.message : "Failed to load guilds"),
)
.finally(() => setLoading(false));
}, [value, onChange]);
useEffect(() => {
fetchGuilds();
}, [fetchGuilds]);
if (value || guilds.length === 0 || initDone.current) return;
initDone.current = true;
const preferred = config?.monitorGuildId ?? guilds[0].id;
if (preferred) onChange(preferred);
}, [value, guilds, config, onChange]);
// Auto-hide when there's exactly one guild and autoHide is on
if (autoHide && guilds.length <= 1 && !loading && !error) return null;
if (autoHide && guilds.length <= 1 && !isLoading && !error) return null;
if (loading) {
if (isLoading) {
return (
<div className="flex items-center gap-3 rounded-xl border border-border/50 bg-card p-3">
<Skeleton className="h-8 w-36" />
@@ -93,10 +63,10 @@ export function GuildSelector({
<div className="flex items-center gap-2">
<AlertCircle className="size-4 text-destructive shrink-0" />
<p className="text-sm text-muted-foreground">
Could not load guilds: {error}
Could not load guilds: {error?.message ?? "Failed to load"}
</p>
</div>
<Button variant="outline" size="sm" onClick={fetchGuilds}>
<Button variant="outline" size="sm" onClick={() => refetch()}>
<RefreshCw className="size-3 mr-1" />
Retry
</Button>
@@ -26,7 +26,7 @@ export function LoadingSkeleton({
<div
className={cn(
"grid gap-3",
columns > 1 ? `grid-cols-1 md:grid-cols-${columns}` : "grid-cols-1",
columns > 1 ? "grid-cols-1 md:grid-cols-2" : "grid-cols-1",
className,
)}
>
@@ -8,9 +8,30 @@ interface StatCardProps {
label: string;
value: number;
icon: LucideIcon;
variant?: "default" | "danger" | "success";
variant?: "default" | "danger" | "success" | "warning";
}
const variantStyles = {
default: "from-cyan-500/10 to-teal-500/5 border-cyan-500/20",
danger: "from-red-500/10 to-rose-500/5 border-red-500/20",
success: "from-emerald-500/10 to-green-500/5 border-emerald-500/20",
warning: "from-amber-500/10 to-yellow-500/5 border-amber-500/20",
};
const iconBg = {
default: "bg-cyan-500/15 text-cyan-400",
danger: "bg-red-500/15 text-red-400",
success: "bg-emerald-500/15 text-emerald-400",
warning: "bg-amber-500/15 text-amber-400",
};
const valueColor = {
default: "",
danger: "text-red-400",
success: "text-emerald-400",
warning: "text-amber-400",
};
/**
* Metric card used across dashboard and landing pages.
*/
@@ -21,16 +42,22 @@ export function StatCard({
variant = "default",
}: StatCardProps) {
return (
<Card>
<Card
className={cn(
"border bg-gradient-to-br backdrop-blur-sm",
variantStyles[variant],
)}
>
<CardContent className="p-4">
<div className="flex items-start justify-between">
<div className="space-y-1.5">
<p className="text-xs text-muted-foreground">{label}</p>
<p className="text-xs text-muted-foreground/80 tracking-wide">
{label}
</p>
<p
className={cn(
"text-2xl font-bold tabular-nums tracking-tight",
variant === "danger" && "text-destructive",
variant === "success" && "text-green-500",
valueColor[variant],
)}
>
{formatNumber(value)}
@@ -39,11 +66,7 @@ export function StatCard({
<div
className={cn(
"flex size-9 shrink-0 items-center justify-center rounded-lg",
variant === "danger"
? "bg-destructive/10 text-destructive"
: variant === "success"
? "bg-green-500/10 text-green-500"
: "bg-primary/10 text-primary",
iconBg[variant],
)}
>
<Icon className="size-4" />