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:
@@ -0,0 +1,74 @@
|
||||
const BASE = "/api";
|
||||
|
||||
async function request<T = any>(
|
||||
method: string,
|
||||
path: string,
|
||||
body?: any,
|
||||
): Promise<{ ok: boolean; data?: T; message?: string; status: number }> {
|
||||
const res = await fetch(`${BASE}${path}`, {
|
||||
method,
|
||||
headers: body ? { "Content-Type": "application/json" } : undefined,
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
let json: any = {};
|
||||
try {
|
||||
json = await res.json();
|
||||
} catch {}
|
||||
|
||||
return { ok: res.ok, data: json.data, message: json.message, status: res.status };
|
||||
}
|
||||
|
||||
export const api = {
|
||||
auth: {
|
||||
login: (username: string, password: string) => request("POST", "/auth/login", { username, password }),
|
||||
logout: () => request("POST", "/auth/logout"),
|
||||
me: () => request("GET", "/auth/me"),
|
||||
setupStatus: () => request("GET", "/auth/setup-status"),
|
||||
firstUser: (username: string, password: string) => request("POST", "/auth/first-user", { username, password }),
|
||||
},
|
||||
user: {
|
||||
settings: () => request("GET", "/user/settings"),
|
||||
updateUsername: (username: string) => request("PATCH", "/user/username", { username }),
|
||||
updatePassword: (currentPassword: string, newPassword: string) =>
|
||||
request("PATCH", "/user/password", { currentPassword, newPassword }),
|
||||
updateGitea: (data: { giteaInstanceUrl: string; giteaUsername: string; giteaPAT?: string }) =>
|
||||
request("PUT", "/user/gitea", data),
|
||||
updateAws: (data: { awsAccessKeyId: string; awsSecretAccessKey: string; awsRegion: string }) =>
|
||||
request("PUT", "/user/aws", data),
|
||||
getWebhookSecret: () => request("GET", "/user/webhook-secret"),
|
||||
regenerateWebhookSecret: () => request("POST", "/user/webhook-secret/regenerate"),
|
||||
},
|
||||
repos: {
|
||||
list: () => request("GET", "/repos"),
|
||||
getConfig: (owner: string, repo: string) => request("GET", `/repos/${owner}/${repo}/config`),
|
||||
saveConfig: (data: any) => request("POST", "/repos/config", data),
|
||||
toggle: (owner: string, repo: string, enabled: boolean) => request("POST", "/repos/toggle", { owner, repo, enabled }),
|
||||
},
|
||||
previews: {
|
||||
list: () => request("GET", "/previews"),
|
||||
get: (id: number) => request("GET", `/previews/${id}`),
|
||||
stop: (id: number) => request("POST", `/previews/${id}/stop`),
|
||||
},
|
||||
admin: {
|
||||
listUsers: () => request("GET", "/admin/users"),
|
||||
createUser: (username: string, password: string) => request("POST", "/admin/users", { username, password }),
|
||||
updateUser: (id: number, data: any) => request("PATCH", `/admin/users/${id}`, data),
|
||||
deleteUser: (id: number) => request("DELETE", `/admin/users/${id}`),
|
||||
getSettings: () => request("GET", "/admin/settings"),
|
||||
updateSettings: (data: any) => request("PUT", "/admin/settings", data),
|
||||
listPreviews: () => request("GET", "/admin/previews"),
|
||||
stopPreview: (id: number) => request("POST", `/admin/previews/${id}/stop`),
|
||||
},
|
||||
};
|
||||
|
||||
export function openLogsWs(previewId: number, onMessage: (msg: any) => void, onClose?: () => void): WebSocket {
|
||||
const protocol = location.protocol === "https:" ? "wss:" : "ws:";
|
||||
const ws = new WebSocket(`${protocol}//${location.host}/api/previews/${previewId}/logs`);
|
||||
ws.onmessage = (e) => {
|
||||
try { onMessage(JSON.parse(e.data)); } catch {}
|
||||
};
|
||||
ws.onclose = onClose || (() => {});
|
||||
return ws;
|
||||
}
|
||||
Reference in New Issue
Block a user