feat: initial scaffold - backend, frontend, Prisma schema, Docker

- Prisma schema: User, Session, RepoConfig, Preview, Job, WebhookToken, NoConfigComment, AdminSettings
- Backend: auth (login/logout/me/first-user setup), webhook handler with HMAC verification, EC2 service, SSH service, deploy pipeline, job queue worker, cron workers
- Frontend: Login with first-user detection, Dashboard, PreviewDetail with live log streaming, Settings, Repos config, Admin panel, SetupWizard, Privacy page
- Docker Compose and Dockerfile for self-hosted deployment
- Uses bcryptjs for Node 24 compatibility

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 00:18:08 +02:00
parent ca2efdadea
commit 40d484bede
108 changed files with 13393 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
import React, { useEffect } from "react";
import { Routes, Route, Navigate, useNavigate } from "react-router-dom";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { AuthContext, useAuthProvider } from "./hooks/useAuth";
import { useTheme } from "./hooks/useTheme";
import { Layout } from "./components/Layout";
import { Login } from "./pages/Login";
import { Dashboard } from "./pages/Dashboard";
import { PreviewDetail } from "./pages/PreviewDetail";
import { Settings } from "./pages/Settings";
import { Repos } from "./pages/Repos";
import { Admin } from "./pages/Admin";
import { SetupWizard } from "./pages/SetupWizard";
import { Privacy } from "./pages/Privacy";
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { user, loading } = React.useContext(AuthContext);
const navigate = useNavigate();
useEffect(() => {
if (!loading && !user) navigate("/login");
}, [user, loading, navigate]);
if (loading) return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-2xl animate-spin"></div>
</div>
);
if (!user) return null;
return <>{children}</>;
}
function SetupCheck({ children }: { children: React.ReactNode }) {
const { user } = React.useContext(AuthContext);
const navigate = useNavigate();
useEffect(() => {
if (user && !user.setupComplete) {
const path = window.location.pathname;
if (path !== "/setup" && path !== "/settings" && path !== "/privacy") {
navigate("/setup");
}
}
}, [user, navigate]);
return <>{children}</>;
}
export default function App() {
const auth = useAuthProvider();
useTheme();
return (
<AuthContext.Provider value={auth}>
<ToastContainer
position="top-right"
autoClose={4000}
hideProgressBar={false}
newestOnTop
closeOnClick
theme="colored"
/>
<Routes>
<Route path="/login" element={
auth.user ? <Navigate to="/" replace /> : <Login />
} />
<Route path="/privacy" element={<Layout><Privacy /></Layout>} />
<Route path="/*" element={
<ProtectedRoute>
<SetupCheck>
<Layout>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/previews/:id" element={<PreviewDetail />} />
<Route path="/repos" element={<Repos />} />
<Route path="/settings" element={<Settings />} />
<Route path="/admin" element={<Admin />} />
<Route path="/setup" element={<SetupWizard />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</Layout>
</SetupCheck>
</ProtectedRoute>
} />
</Routes>
</AuthContext.Provider>
);
}