refactor: split monolith into 3 microservices (frontend, backend, discord-gateway)

- Extract services into services/{frontend,backend,discord-gateway}
- Create packages/shared/ for shared logger, errors, utils, types
- Setup Modular MVC pattern in backend (controller→service→repository)
- Setup event-driven architecture in discord-gateway with Redis pub/sub
- Move Docker files to infra/docker/ with per-service Dockerfiles
- Update docker-compose.yml to use Traefik-only routing (no port exposes)
- Update GitHub Actions deploy workflow for multi-service matrix build
- Fix all import paths and resolve type errors across all services
- All 3 services pass tsc --noEmit clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-06-01 21:44:29 +07:00
co-authored by Claude Opus 4.8
parent bda8304bb9
commit c48a0c5e3b
193 changed files with 16879 additions and 1158 deletions
+85
View File
@@ -0,0 +1,85 @@
// ─── Toast notification system ──────────────────────────────────────────────
import {
createContext,
type ReactNode,
useCallback,
useContext,
useState,
} from "react";
interface Toast {
id: string;
message: string;
type: "info" | "success" | "error" | "warning";
}
interface ToastContextType {
toasts: Toast[];
addToast: (message: string, type?: Toast["type"]) => void;
removeToast: (id: string) => void;
}
const ToastContext = createContext<ToastContextType>({
toasts: [],
addToast: () => {},
removeToast: () => {},
});
export function ToastProvider({ children }: { children: ReactNode }) {
const [toasts, setToasts] = useState<Toast[]>([]);
const addToast = useCallback(
(message: string, type: Toast["type"] = "info") => {
const id = `toast-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
setToasts((prev) => [...prev, { id, message, type }]);
setTimeout(
() => setToasts((prev) => prev.filter((t) => t.id !== id)),
4000,
);
},
[],
);
const removeToast = useCallback((id: string) => {
setToasts((prev) => prev.filter((t) => t.id !== id));
}, []);
return (
<ToastContext.Provider value={{ toasts, addToast, removeToast }}>
{children}
<ToastContainer />
</ToastContext.Provider>
);
}
export function useToast() {
return useContext(ToastContext);
}
function ToastContainer() {
const { toasts, removeToast } = useContext(ToastContext);
if (toasts.length === 0) return null;
return (
<div className="fixed bottom-4 right-4 z-50 flex flex-col gap-2">
{toasts.map((toast) => (
<div
key={toast.id}
className={`rounded-lg border px-4 py-3 text-sm shadow-lg backdrop-blur-xl cursor-pointer transition-all hover:scale-[1.02] ${
toast.type === "error"
? "border-destructive/30 bg-destructive/20 text-destructive"
: toast.type === "success"
? "border-green-500/30 bg-green-500/10 text-green-300"
: toast.type === "warning"
? "border-yellow-500/30 bg-yellow-500/10 text-yellow-300"
: "border-border/30 bg-card/80 text-foreground"
}`}
onClick={() => removeToast(toast.id)}
>
{toast.message}
</div>
))}
</div>
);
}