24bf4706e1
- Environment/Deployment XORM models + migration 010 - Full CRUD API: GET/POST/PATCH/DELETE /environments + /deployments - Deployment status update endpoint, publishes deployment.* NATS events - EnvironmentsPage with deploy cards, history accordion, deploy modal - Sidebar Environments nav item between Pipelines and Settings - Repo page deployment status badges (env name + SHA pill per environment) - Environment/Deployment types in types/api.ts + environments.ts query hooks
158 lines
5.5 KiB
TypeScript
158 lines
5.5 KiB
TypeScript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
import { z } from 'zod'
|
|
import { api } from '../client'
|
|
import type { Environment, EnvironmentWithLatest, Deployment, DeployStatus } from '../../types/api'
|
|
|
|
// ── Schemas ───────────────────────────────────────────────────────────────────
|
|
|
|
const deployStatusSchema = z.enum(['pending', 'in_progress', 'success', 'failure', 'cancelled'])
|
|
|
|
const deploymentSchema = z.object({
|
|
id: z.number(),
|
|
envId: z.number(),
|
|
repoId: z.number(),
|
|
sha: z.string(),
|
|
ref: z.string(),
|
|
status: deployStatusSchema,
|
|
triggeredBy: z.string(),
|
|
description: z.string(),
|
|
runId: z.number().nullable(),
|
|
startedAt: z.string().nullable(),
|
|
finishedAt: z.string().nullable(),
|
|
createdAt: z.string(),
|
|
})
|
|
|
|
const environmentSchema = z.object({
|
|
id: z.number(),
|
|
repoId: z.number(),
|
|
name: z.string(),
|
|
url: z.string(),
|
|
protectionRules: z.string(),
|
|
createdAt: z.string(),
|
|
updatedAt: z.string(),
|
|
})
|
|
|
|
const environmentWithLatestSchema = environmentSchema.extend({
|
|
latestDeployment: deploymentSchema.nullable(),
|
|
})
|
|
|
|
// ── Queries ───────────────────────────────────────────────────────────────────
|
|
|
|
export function useEnvironments(owner: string, repo: string) {
|
|
return useQuery({
|
|
queryKey: ['repos', owner, repo, 'environments'],
|
|
queryFn: () =>
|
|
api.get<EnvironmentWithLatest[]>(
|
|
`/api/v1/repos/${owner}/${repo}/environments`,
|
|
z.array(environmentWithLatestSchema),
|
|
),
|
|
enabled: Boolean(owner && repo),
|
|
refetchInterval: 15_000,
|
|
})
|
|
}
|
|
|
|
export function useEnvironment(owner: string, repo: string, envName: string) {
|
|
return useQuery({
|
|
queryKey: ['repos', owner, repo, 'environments', envName],
|
|
queryFn: () =>
|
|
api.get<EnvironmentWithLatest>(
|
|
`/api/v1/repos/${owner}/${repo}/environments/${envName}`,
|
|
environmentWithLatestSchema,
|
|
),
|
|
enabled: Boolean(owner && repo && envName),
|
|
refetchInterval: 10_000,
|
|
})
|
|
}
|
|
|
|
export function useDeployments(owner: string, repo: string, envName: string, limit = 30) {
|
|
return useQuery({
|
|
queryKey: ['repos', owner, repo, 'environments', envName, 'deployments', limit],
|
|
queryFn: () =>
|
|
api.get<Deployment[]>(
|
|
`/api/v1/repos/${owner}/${repo}/environments/${envName}/deployments?limit=${limit}`,
|
|
z.array(deploymentSchema),
|
|
),
|
|
enabled: Boolean(owner && repo && envName),
|
|
refetchInterval: 10_000,
|
|
})
|
|
}
|
|
|
|
// ── Mutations ─────────────────────────────────────────────────────────────────
|
|
|
|
export function useCreateEnvironment(owner: string, repo: string) {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (body: { name: string; url?: string; protectionRules?: string }) =>
|
|
api.post<Environment>(
|
|
`/api/v1/repos/${owner}/${repo}/environments`,
|
|
environmentSchema,
|
|
body,
|
|
),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['repos', owner, repo, 'environments'] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useUpdateEnvironment(owner: string, repo: string, envName: string) {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (body: { url?: string; protectionRules?: string }) =>
|
|
api.patch<Environment>(
|
|
`/api/v1/repos/${owner}/${repo}/environments/${envName}`,
|
|
environmentSchema,
|
|
body,
|
|
),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['repos', owner, repo, 'environments'] })
|
|
qc.invalidateQueries({ queryKey: ['repos', owner, repo, 'environments', envName] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useDeleteEnvironment(owner: string, repo: string) {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (envName: string) =>
|
|
api.delete<void>(
|
|
`/api/v1/repos/${owner}/${repo}/environments/${envName}`,
|
|
z.unknown() as z.ZodType<void>,
|
|
),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['repos', owner, repo, 'environments'] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useCreateDeployment(owner: string, repo: string, envName: string) {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: (body: { sha: string; ref?: string; description?: string; runId?: number }) =>
|
|
api.post<Deployment>(
|
|
`/api/v1/repos/${owner}/${repo}/environments/${envName}/deployments`,
|
|
deploymentSchema,
|
|
body,
|
|
),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['repos', owner, repo, 'environments'] })
|
|
qc.invalidateQueries({ queryKey: ['repos', owner, repo, 'environments', envName] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useUpdateDeploymentStatus(owner: string, repo: string, envName: string) {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: ({ deployId, status, description }: { deployId: number; status: DeployStatus; description?: string }) =>
|
|
api.patch<Deployment>(
|
|
`/api/v1/repos/${owner}/${repo}/environments/${envName}/deployments/${deployId}/status`,
|
|
deploymentSchema,
|
|
{ status, description },
|
|
),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['repos', owner, repo, 'environments'] })
|
|
qc.invalidateQueries({ queryKey: ['repos', owner, repo, 'environments', envName] })
|
|
},
|
|
})
|
|
}
|