feat: Gitea auto-provisioning and webhook context sync
- Add lib/gitea.ts: Gitea API client (createRepo, createWebhook, deleteRepo, verifyWebhookSignature) - Add lib/coolify.ts: Coolify API client (projects, databases, applications, deployments) - Update api/projects/create: auto-creates a private Gitea repo and registers a webhook on every new project; stores giteaRepo, giteaRepoUrl, giteaCloneUrl, giteaSshUrl, giteaWebhookId in project data; Gitea errors are non-fatal so project creation still succeeds - Add api/webhooks/gitea: handles push, pull_request, issues events; verifies HMAC signature; updates contextSnapshot on project record - Add api/webhooks/coolify: handles deployment status events; updates contextSnapshot.lastDeployment on project record Requires env vars: GITEA_API_URL, GITEA_API_TOKEN, GITEA_ADMIN_USER, GITEA_WEBHOOK_SECRET, COOLIFY_URL, COOLIFY_API_TOKEN Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
162
lib/coolify.ts
Normal file
162
lib/coolify.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* Coolify API client for Vibn project provisioning.
|
||||
*
|
||||
* Used server-side only. Credentials from env vars:
|
||||
* COOLIFY_URL — e.g. http://34.19.250.135:8000
|
||||
* COOLIFY_API_TOKEN — admin bearer token
|
||||
*/
|
||||
|
||||
const COOLIFY_URL = process.env.COOLIFY_URL ?? 'http://34.19.250.135:8000';
|
||||
const COOLIFY_API_TOKEN = process.env.COOLIFY_API_TOKEN ?? '';
|
||||
|
||||
export interface CoolifyProject {
|
||||
uuid: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface CoolifyDatabase {
|
||||
uuid: string;
|
||||
name: string;
|
||||
type: string;
|
||||
status: string;
|
||||
internal_db_url?: string;
|
||||
external_db_url?: string;
|
||||
}
|
||||
|
||||
export interface CoolifyApplication {
|
||||
uuid: string;
|
||||
name: string;
|
||||
status: string;
|
||||
fqdn?: string;
|
||||
git_repository?: string;
|
||||
git_branch?: string;
|
||||
}
|
||||
|
||||
async function coolifyFetch(path: string, options: RequestInit = {}) {
|
||||
const url = `${COOLIFY_URL}/api/v1${path}`;
|
||||
const res = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${COOLIFY_API_TOKEN}`,
|
||||
...(options.headers ?? {}),
|
||||
},
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
throw new Error(`Coolify API error ${res.status} on ${path}: ${text}`);
|
||||
}
|
||||
|
||||
if (res.status === 204) return null;
|
||||
return res.json();
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────
|
||||
// Projects
|
||||
// ──────────────────────────────────────────────────
|
||||
|
||||
export async function listProjects(): Promise<CoolifyProject[]> {
|
||||
return coolifyFetch('/projects');
|
||||
}
|
||||
|
||||
export async function createProject(name: string, description?: string): Promise<CoolifyProject> {
|
||||
return coolifyFetch('/projects', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ name, description }),
|
||||
});
|
||||
}
|
||||
|
||||
export async function getProject(uuid: string): Promise<CoolifyProject> {
|
||||
return coolifyFetch(`/projects/${uuid}`);
|
||||
}
|
||||
|
||||
export async function deleteProject(uuid: string): Promise<void> {
|
||||
await coolifyFetch(`/projects/${uuid}`, { method: 'DELETE' });
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────
|
||||
// Databases
|
||||
// ──────────────────────────────────────────────────
|
||||
|
||||
type DBType = 'postgresql' | 'mysql' | 'mariadb' | 'redis' | 'mongodb' | 'keydb';
|
||||
|
||||
export async function createDatabase(opts: {
|
||||
projectUuid: string;
|
||||
name: string;
|
||||
type: DBType;
|
||||
serverUuid?: string;
|
||||
environmentName?: string;
|
||||
}): Promise<CoolifyDatabase> {
|
||||
const { projectUuid, name, type, serverUuid = '0', environmentName = 'production' } = opts;
|
||||
|
||||
return coolifyFetch(`/databases`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
project_uuid: projectUuid,
|
||||
name,
|
||||
type,
|
||||
server_uuid: serverUuid,
|
||||
environment_name: environmentName,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
export async function getDatabase(uuid: string): Promise<CoolifyDatabase> {
|
||||
return coolifyFetch(`/databases/${uuid}`);
|
||||
}
|
||||
|
||||
export async function deleteDatabase(uuid: string): Promise<void> {
|
||||
await coolifyFetch(`/databases/${uuid}`, { method: 'DELETE' });
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────
|
||||
// Applications
|
||||
// ──────────────────────────────────────────────────
|
||||
|
||||
export async function createApplication(opts: {
|
||||
projectUuid: string;
|
||||
name: string;
|
||||
gitRepo: string; // e.g. "https://git.vibnai.com/mark/taskmaster.git"
|
||||
gitBranch?: string;
|
||||
serverUuid?: string;
|
||||
environmentName?: string;
|
||||
buildPack?: string; // nixpacks, static, dockerfile
|
||||
ports?: string; // e.g. "3000"
|
||||
}): Promise<CoolifyApplication> {
|
||||
const {
|
||||
projectUuid, name, gitRepo,
|
||||
gitBranch = 'main',
|
||||
serverUuid = '0',
|
||||
environmentName = 'production',
|
||||
buildPack = 'nixpacks',
|
||||
ports = '3000',
|
||||
} = opts;
|
||||
|
||||
return coolifyFetch(`/applications`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
project_uuid: projectUuid,
|
||||
name,
|
||||
git_repository: gitRepo,
|
||||
git_branch: gitBranch,
|
||||
server_uuid: serverUuid,
|
||||
environment_name: environmentName,
|
||||
build_pack: buildPack,
|
||||
ports_exposes: ports,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
export async function deployApplication(uuid: string): Promise<{ deployment_uuid: string }> {
|
||||
return coolifyFetch(`/applications/${uuid}/deploy`, { method: 'POST' });
|
||||
}
|
||||
|
||||
export async function getApplication(uuid: string): Promise<CoolifyApplication> {
|
||||
return coolifyFetch(`/applications/${uuid}`);
|
||||
}
|
||||
|
||||
export async function getDeploymentLogs(deploymentUuid: string): Promise<{ logs: string }> {
|
||||
return coolifyFetch(`/deployments/${deploymentUuid}/logs`);
|
||||
}
|
||||
Reference in New Issue
Block a user