141 lines
4.3 KiB
TypeScript
141 lines
4.3 KiB
TypeScript
import pg from 'pg';
|
|
import { serverConfig } from './config';
|
|
import type { Deployment, DeploymentStatus } from './deployments';
|
|
|
|
const { Pool } = pg;
|
|
|
|
let pool: pg.Pool | null = null;
|
|
|
|
function parseDbUrl(url: string): pg.PoolConfig {
|
|
// Manual parsing to handle special chars in password (/, =, etc.)
|
|
const match = url.match(/^postgres(?:ql)?:\/\/([^:]+):(.+)@([^:]+):(\d+)\/(.+)$/);
|
|
if (match) {
|
|
return { user: match[1], password: match[2], host: match[3], port: parseInt(match[4]), database: match[5] };
|
|
}
|
|
return { connectionString: url };
|
|
}
|
|
|
|
function getPool(): pg.Pool {
|
|
if (!pool) {
|
|
pool = new Pool({
|
|
...parseDbUrl(serverConfig.coolifyDbUrl),
|
|
max: 3,
|
|
idleTimeoutMillis: 30000,
|
|
connectionTimeoutMillis: 5000,
|
|
});
|
|
pool.on('error', (err) => {
|
|
console.error('Coolify DB pool error:', err);
|
|
});
|
|
}
|
|
return pool;
|
|
}
|
|
|
|
function mapStatus(status: string): DeploymentStatus {
|
|
if (status === 'failed') return 'error';
|
|
if (status === 'cancelled-by-user') return 'cancelled';
|
|
return status as DeploymentStatus;
|
|
}
|
|
|
|
function rowToDeployment(row: Record<string, unknown>): Deployment {
|
|
const createdAt = row.created_at as string;
|
|
const updatedAt = row.updated_at as string;
|
|
let duration: number | undefined;
|
|
if (createdAt && updatedAt) {
|
|
const start = new Date(createdAt).getTime();
|
|
const end = new Date(updatedAt).getTime();
|
|
duration = Math.max(0, Math.floor((end - start) / 1000));
|
|
}
|
|
|
|
return {
|
|
deployment_uuid: row.deployment_uuid as string,
|
|
application_uuid: (row.application_uuid as string) || 'unknown',
|
|
application_name: (row.application_name as string) || 'Unknown App',
|
|
application_fqdn: (row.application_fqdn as string) || undefined,
|
|
status: mapStatus(row.status as string),
|
|
created_at: createdAt,
|
|
updated_at: updatedAt,
|
|
git_branch: (row.git_branch as string) || 'main',
|
|
git_commit_sha: (row.commit as string) || undefined,
|
|
commit_message: (row.commit_message as string) || undefined,
|
|
is_webhook: row.is_webhook as boolean | undefined,
|
|
is_api: row.is_api as boolean | undefined,
|
|
logs: (row.logs as string) || undefined,
|
|
duration,
|
|
};
|
|
}
|
|
|
|
export async function fetchDeployments(limit = 50): Promise<Deployment[]> {
|
|
const db = getPool();
|
|
const { rows } = await db.query(`
|
|
SELECT
|
|
q.deployment_uuid,
|
|
a.uuid AS application_uuid,
|
|
COALESCE(q.application_name, a.name) AS application_name,
|
|
a.fqdn AS application_fqdn,
|
|
q.status,
|
|
q.created_at,
|
|
q.updated_at,
|
|
a.git_branch,
|
|
q.commit,
|
|
q.commit_message,
|
|
q.is_webhook,
|
|
q.is_api
|
|
FROM application_deployment_queues q
|
|
LEFT JOIN applications a ON a.id = q.application_id::bigint
|
|
ORDER BY q.created_at DESC
|
|
LIMIT $1
|
|
`, [limit]);
|
|
|
|
const deployments = rows.map(rowToDeployment);
|
|
|
|
// Mark latest finished deployment per app as current
|
|
const latestByApp = new Map<string, string>();
|
|
for (const d of deployments) {
|
|
if (d.status === 'finished' && !latestByApp.has(d.application_uuid)) {
|
|
latestByApp.set(d.application_uuid, d.deployment_uuid);
|
|
}
|
|
}
|
|
for (const d of deployments) {
|
|
d.is_current = latestByApp.get(d.application_uuid) === d.deployment_uuid;
|
|
}
|
|
|
|
return deployments;
|
|
}
|
|
|
|
export async function fetchDeploymentDetail(uuid: string): Promise<Deployment | null> {
|
|
const db = getPool();
|
|
const { rows } = await db.query(`
|
|
SELECT
|
|
q.deployment_uuid,
|
|
a.uuid AS application_uuid,
|
|
COALESCE(q.application_name, a.name) AS application_name,
|
|
a.fqdn AS application_fqdn,
|
|
q.status,
|
|
q.created_at,
|
|
q.updated_at,
|
|
a.git_branch,
|
|
q.commit,
|
|
q.commit_message,
|
|
q.is_webhook,
|
|
q.is_api,
|
|
q.logs
|
|
FROM application_deployment_queues q
|
|
LEFT JOIN applications a ON a.id = q.application_id::bigint
|
|
WHERE q.deployment_uuid = $1
|
|
`, [uuid]);
|
|
|
|
if (rows.length === 0) return null;
|
|
return rowToDeployment(rows[0]);
|
|
}
|
|
|
|
export async function fetchActiveDeploymentLogs(): Promise<Array<{ uuid: string; logs: string; status: string }>> {
|
|
const db = getPool();
|
|
const { rows } = await db.query(`
|
|
SELECT deployment_uuid AS uuid, logs, status
|
|
FROM application_deployment_queues
|
|
WHERE status IN ('in_progress', 'queued')
|
|
ORDER BY created_at DESC
|
|
`);
|
|
return rows as Array<{ uuid: string; logs: string; status: string }>;
|
|
}
|