75 lines
2.5 KiB
TypeScript
75 lines
2.5 KiB
TypeScript
import { Router } from 'express';
|
|||
|
|
import { query, one } from '../db.js';
|
||
|
|
import { insertRow } from '../lib/sql.js';
|
||
|
|
import { h } from '../lib/respond.js';
|
||
|
|
import { requireAuth, type AuthedRequest } from '../middleware/auth.js';
|
||
|
|
import { triggerCloudBuild, getCloudBuildStatus } from '../functions/cloud-build.js';
|
||
|
|
|
||
|
|
const r = Router();
|
||
|
|
r.use(requireAuth);
|
||
|
|
const uid = (req: AuthedRequest) => req.userId as string;
|
||
|
|
|
||
|
|
function sanitizeSegment(segment: string, fallback: string) {
|
||
|
|
const cleaned = segment.toLowerCase().replace(/[^a-z0-9_]/g, '');
|
||
|
|
if (!cleaned) return fallback;
|
||
|
|
return /^[a-z]/.test(cleaned) ? cleaned : `app${cleaned}`;
|
||
|
|
}
|
||
|
|
function sanitizePackageName(raw: string | undefined, appName: string) {
|
||
|
|
const fallback = ['com', 'app', sanitizeSegment(appName, 'mobile')];
|
||
|
|
const src = (raw || '').split('.').filter(Boolean);
|
||
|
|
const segs = src.length ? src.map((s, i) => sanitizeSegment(s, fallback[i] || 'app')) : fallback;
|
||
|
|
while (segs.length < 3) segs.push(fallback[segs.length] || 'app');
|
||
|
|
return segs.join('.');
|
||
|
|
}
|
||
|
|
|
||
|
|
// POST /builds -> start a build
|
||
|
|
r.post('/', h(async (req) => {
|
||
|
|
const config = req.body || {};
|
||
|
|
const platform = config.platform || 'android';
|
||
|
|
const appName = config.appName || 'My App';
|
||
|
|
const packageName = sanitizePackageName(config.packageName, appName);
|
||
|
|
const normalizedConfig = { ...config, packageName };
|
||
|
|
|
||
|
|
const build = await insertRow<any>('app_builds', {
|
||
|
|
user_id: uid(req as any),
|
||
|
|
app_name: appName,
|
||
|
|
package_name: packageName,
|
||
|
|
website_url: config.websiteUrl,
|
||
|
|
config: normalizedConfig,
|
||
|
|
status: 'pending',
|
||
|
|
progress: 0,
|
||
|
|
});
|
||
|
|
|
||
|
|
try {
|
||
|
|
const result = await triggerCloudBuild({
|
||
|
|
buildId: build.id,
|
||
|
|
websiteUrl: config.websiteUrl,
|
||
|
|
appName,
|
||
|
|
platform,
|
||
|
|
packageName,
|
||
|
|
config: normalizedConfig,
|
||
|
|
});
|
||
|
|
return { buildId: build.id, cloudBuildId: result.cloudBuildId, message: result.message };
|
||
|
|
} catch (e: any) {
|
||
|
|
return { buildId: build.id, message: 'Build created but cloud trigger failed: ' + e.message };
|
||
|
|
}
|
||
|
|
}));
|
||
|
|
|
||
|
|
// GET /builds?limit=
|
||
|
|
r.get('/', h(async (req) => {
|
||
|
|
const limit = Math.min(parseInt(String(req.query.limit ?? '20'), 10) || 20, 200);
|
||
|
|
return query(
|
||
|
|
`SELECT * FROM public.app_builds WHERE user_id = $1 ORDER BY created_at DESC LIMIT $2`,
|
||
|
|
[uid(req as any), limit]
|
||
|
|
);
|
||
|
|
}));
|
||
|
|
|
||
|
|
// GET /builds/:id/status
|
||
|
|
r.get('/:id/status', h(async (req) => {
|
||
|
|
const status = await getCloudBuildStatus(req.params.id);
|
||
|
|
if (status) return status;
|
||
|
|
return one(`SELECT * FROM public.app_builds WHERE id = $1 AND user_id = $2`, [req.params.id, uid(req as any)]);
|
||
|
|
}));
|
||
|
|
|
||
|
|
export default r;
|