feat(previews): preview environments UI + fix latent insert bugs

- Service detail gains a Previews section: list, create (branch/PR/TTL),
  promote-to-production, delete — all confirmed actions. Verified e2e
  create→list→promote→delete.
- fix: preview create scanned services.type which real services leave NULL
  (writes go to service_type); every create 404'd. Now COALESCE(type,
  service_type, '') matching the rest of the codebase.
- fix: deployments.version is NOT NULL but three inserts omitted it —
  handleCreateDeployment, rollback, and preview promote all failed on any
  fresh-install schema. All three now write a version.
- Roadmap notes preview envs are bookkeeping-only; real branch deploy is
  tracked separately.

Authored By: TDvorak <[email protected]>
This commit is contained in:
Tomas Dvorak committed 2026-09-17 18:00:18 +02:00
1 parent 07ac54aa05
commit 8ecc8dcea5
5 files changed
+228 -17

No files matched your search

+33
View File
@@ -1449,3 +1449,36 @@ export async function markNotificationRead(id: string): Promise<void> {
export async function markAllNotificationsRead(): Promise<void> {
await requestJson('/notifications/read-all', { method: 'POST' });
}
export type PreviewEnvironmentEntity = components['schemas']['PreviewEnvironment'];
export async function listPreviewEnvironments(projectId: string): Promise<PreviewEnvironmentEntity[]> {
const payload = await requestJson<{ preview_environments?: PreviewEnvironmentEntity[] }>(
`/projects/${encodeURIComponent(projectId)}/preview-environments`,
);
return payload.preview_environments ?? [];
}
export async function createPreviewEnvironment(
projectId: string,
input: components['schemas']['CreatePreviewEnvironmentRequest'],
): Promise<PreviewEnvironmentEntity> {
return requestJson<PreviewEnvironmentEntity>(
`/projects/${encodeURIComponent(projectId)}/preview-environments`,
{ method: 'POST', body: JSON.stringify(input) },
);
}
export async function deletePreviewEnvironment(id: string): Promise<void> {
await requestJson(`/preview-environments/${encodeURIComponent(id)}`, { method: 'DELETE' });
}
export async function promotePreviewEnvironment(
id: string,
input: components['schemas']['PromotePreviewEnvironmentRequest'],
): Promise<void> {
await requestJson(`/preview-environments/${encodeURIComponent(id)}/promote`, {
method: 'POST',
body: JSON.stringify(input),
});
}