import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { config } from '../../../config'; import { useAuthStore } from '../../../auth/auth-store'; export interface App { id: string; environmentId: string; slug: string; displayName: string; createdAt: string; } export interface AppVersion { id: string; appId: string; version: number; jarPath: string; jarChecksum: string; jarFilename: string; jarSizeBytes: number; uploadedAt: string; } export interface Deployment { id: string; appId: string; appVersionId: string; environmentId: string; status: 'STARTING' | 'RUNNING' | 'FAILED' | 'STOPPED'; containerId: string | null; containerName: string | null; errorMessage: string | null; deployedAt: string | null; stoppedAt: string | null; createdAt: string; } async function appFetch(path: string, options?: RequestInit): Promise { const token = useAuthStore.getState().accessToken; const res = await fetch(`${config.apiBaseUrl}/apps${path}`, { ...options, headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}), 'X-Cameleer-Protocol-Version': '1', ...options?.headers, }, }); if (res.status === 401 || res.status === 403) { useAuthStore.getState().logout(); throw new Error('Unauthorized'); } if (!res.ok) throw new Error(`API error: ${res.status}`); if (res.status === 204) return undefined as T; const text = await res.text(); if (!text) return undefined as T; return JSON.parse(text); } // --- Apps --- export function useApps(environmentId: string | undefined) { return useQuery({ queryKey: ['apps', environmentId], queryFn: () => appFetch(`?environmentId=${environmentId}`), enabled: !!environmentId, }); } export function useCreateApp() { const qc = useQueryClient(); return useMutation({ mutationFn: (req: { environmentId: string; slug: string; displayName: string }) => appFetch('', { method: 'POST', body: JSON.stringify(req) }), onSuccess: () => qc.invalidateQueries({ queryKey: ['apps'] }), }); } export function useDeleteApp() { const qc = useQueryClient(); return useMutation({ mutationFn: (id: string) => appFetch(`/${id}`, { method: 'DELETE' }), onSuccess: () => qc.invalidateQueries({ queryKey: ['apps'] }), }); } // --- Versions --- export function useAppVersions(appId: string | undefined) { return useQuery({ queryKey: ['apps', appId, 'versions'], queryFn: () => appFetch(`/${appId}/versions`), enabled: !!appId, }); } export function useUploadJar() { const qc = useQueryClient(); return useMutation({ mutationFn: async ({ appId, file }: { appId: string; file: File }) => { const token = useAuthStore.getState().accessToken; const form = new FormData(); form.append('file', file); const res = await fetch(`${config.apiBaseUrl}/apps/${appId}/versions`, { method: 'POST', headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}), 'X-Cameleer-Protocol-Version': '1', }, body: form, }); if (!res.ok) throw new Error(`Upload failed: ${res.status}`); return res.json() as Promise; }, onSuccess: (_data, { appId }) => qc.invalidateQueries({ queryKey: ['apps', appId, 'versions'] }), }); } // --- Deployments --- export function useDeployments(appId: string | undefined) { return useQuery({ queryKey: ['apps', appId, 'deployments'], queryFn: () => appFetch(`/${appId}/deployments`), enabled: !!appId, refetchInterval: 5000, }); } export function useCreateDeployment() { const qc = useQueryClient(); return useMutation({ mutationFn: ({ appId, ...req }: { appId: string; appVersionId: string; environmentId: string }) => appFetch(`/${appId}/deployments`, { method: 'POST', body: JSON.stringify(req) }), onSuccess: (_data, { appId }) => qc.invalidateQueries({ queryKey: ['apps', appId, 'deployments'] }), }); } export function useStopDeployment() { const qc = useQueryClient(); return useMutation({ mutationFn: ({ appId, deploymentId }: { appId: string; deploymentId: string }) => appFetch(`/${appId}/deployments/${deploymentId}/stop`, { method: 'POST' }), onSuccess: (_data, { appId }) => qc.invalidateQueries({ queryKey: ['apps', appId, 'deployments'] }), }); }