import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { apiClient } from "@/lib/api"; // ── Types ────────────────────────────────────────────────────────────── export interface EnrichedServer { id: number; name: string; description: string | null; game_type: string; status: "stopped" | "running" | "starting" | "restarting" | "crashed"; pid: number | null; exe_path: string; game_port: number; rcon_port: number | null; auto_restart: boolean; max_restarts: number; restart_count: number; last_restart_at: string | null; started_at: string | null; stopped_at: string | null; created_at: string; updated_at: string; cpu_percent: number | null; ram_mb: number | null; player_count: number; } export interface ConfigSection { [key: string]: unknown; _meta: { config_version: number; schema_version: number; }; } export interface ConfigMap { [sectionName: string]: ConfigSection; } export interface ConfigPreview { [filename: string]: string; } export interface Player { id: number; server_id: number; slot_id: number; name: string; guid: string; ip: string; ping: number; game_data: string | null; joined_at: string; updated_at: string; } export interface PlayersResponse { server_id: number; player_count: number; players: Player[]; } export interface PlayerHistoryEntry { id: number; server_id: number; name: string; guid: string; ip: string; game_data: string | null; joined_at: string; left_at: string | null; session_duration_seconds: number | null; } export interface PlayerHistoryResponse { total: number; items: PlayerHistoryEntry[]; } export interface Ban { id: number; server_id: number; guid: string; name: string; reason: string; banned_by: string; banned_at: string; expires_at: string | null; is_active: boolean; game_data: string | null; } export interface CreateBanRequest { player_uid: string; ban_type?: "GUID" | "IP"; reason?: string; duration_minutes?: number; } export interface Mission { name: string; filename: string; size_bytes: number; terrain: string; } export interface MissionRotationEntry { name: string; difficulty: string; } export interface MissionsResponse { server_id: number; missions: Mission[]; total: number; } export interface Mod { name: string; path: string; size_bytes: number; enabled: boolean; display_name: string | null; workshop_id: string | null; } export interface FieldSchema { widget: "text" | "number" | "password" | "textarea" | "select" | "toggle" | "tag-list"; label?: string; placeholder?: string; min?: number; max?: number; options?: string[]; } export interface ConfigSchema { [section: string]: { [field: string]: FieldSchema }; } export interface ModsResponse { server_id: number; mods: Mod[]; enabled_count: number; } // ── Query Hooks ──────────────────────────────────────────────────────── export function useServerConfigSchema(serverId: number) { return useQuery({ queryKey: ["servers", serverId, "config", "schema"], queryFn: async () => { const res = await apiClient.get<{ success: boolean; data: ConfigSchema }>( `/api/servers/${serverId}/config/schema`, ); return res.data.data; }, enabled: serverId > 0, }); } export function useServerConfig(serverId: number) { return useQuery({ queryKey: ["servers", serverId, "config"], queryFn: async () => { const res = await apiClient.get<{ success: boolean; data: ConfigMap }>( `/api/servers/${serverId}/config`, ); return res.data.data; }, enabled: serverId > 0, }); } export function useServerConfigSection(serverId: number, section: string) { return useQuery({ queryKey: ["servers", serverId, "config", section], queryFn: async () => { const res = await apiClient.get<{ success: boolean; data: ConfigSection; }>(`/api/servers/${serverId}/config/${section}`); return res.data.data; }, enabled: serverId > 0 && !!section, }); } export function useServerConfigPreview(serverId: number) { return useQuery({ queryKey: ["servers", serverId, "config", "preview"], queryFn: async () => { const res = await apiClient.get<{ success: boolean; data: ConfigPreview; }>(`/api/servers/${serverId}/config/preview`); return res.data.data; }, enabled: serverId > 0, }); } export function useServerPlayers(serverId: number) { return useQuery({ queryKey: ["players", serverId], queryFn: async () => { const res = await apiClient.get<{ success: boolean; data: PlayersResponse; }>(`/api/servers/${serverId}/players`); return res.data.data; }, enabled: serverId > 0, }); } export function useServerPlayerHistory( serverId: number, opts?: { limit?: number; offset?: number; search?: string }, ) { return useQuery({ queryKey: ["players", serverId, "history", opts], queryFn: async () => { const params = new URLSearchParams(); if (opts?.limit) params.set("limit", String(opts.limit)); if (opts?.offset) params.set("offset", String(opts.offset)); if (opts?.search) params.set("search", opts.search); const qs = params.toString(); const res = await apiClient.get<{ success: boolean; data: PlayerHistoryResponse; }>(`/api/servers/${serverId}/players/history${qs ? `?${qs}` : ""}`); return res.data.data; }, enabled: serverId > 0, }); } export function useServerBans(serverId: number) { return useQuery({ queryKey: ["bans", serverId], queryFn: async () => { const res = await apiClient.get<{ success: boolean; data: Ban[] }>( `/api/servers/${serverId}/bans`, ); return res.data.data; }, enabled: serverId > 0, }); } export function useServerMissions(serverId: number) { return useQuery({ queryKey: ["missions", serverId], queryFn: async () => { const res = await apiClient.get<{ success: boolean; data: MissionsResponse; }>(`/api/servers/${serverId}/missions`); return res.data.data; }, enabled: serverId > 0, }); } export function useServerMods(serverId: number) { return useQuery({ queryKey: ["mods", serverId], queryFn: async () => { const res = await apiClient.get<{ success: boolean; data: ModsResponse; }>(`/api/servers/${serverId}/mods`); return res.data.data; }, enabled: serverId > 0, }); } // ── Mutation Hooks ───────────────────────────────────────────────────── export function useUpdateConfigSection(serverId: number, section: string) { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: Record) => apiClient.put(`/api/servers/${serverId}/config/${section}`, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["servers", serverId, "config", section], }); queryClient.invalidateQueries({ queryKey: ["servers", serverId, "config"], }); }, }); } export function useCreateBan(serverId: number) { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: CreateBanRequest) => apiClient.post(`/api/servers/${serverId}/bans`, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["bans", serverId] }); }, }); } export function useRevokeBan(serverId: number) { const queryClient = useQueryClient(); return useMutation({ mutationFn: (banId: number) => apiClient.delete(`/api/servers/${serverId}/bans/${banId}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["bans", serverId] }); }, }); } export function useServerMissionRotation(serverId: number) { return useQuery({ queryKey: ["missions", serverId, "rotation"], queryFn: async () => { const res = await apiClient.get<{ success: boolean; data: { missions: MissionRotationEntry[] }; }>(`/api/servers/${serverId}/missions/rotation`); return res.data.data.missions; }, enabled: serverId > 0, }); } export function useUpdateMissionRotation(serverId: number) { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: { missions: MissionRotationEntry[]; config_version: number }) => apiClient.put(`/api/servers/${serverId}/missions/rotation`, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["missions", serverId, "rotation"] }); queryClient.invalidateQueries({ queryKey: ["servers", serverId, "config", "server"] }); }, }); } export function useUploadMission(serverId: number) { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (files: File[]) => { for (const file of files) { const formData = new FormData(); formData.append("file", file); await apiClient.post(`/api/servers/${serverId}/missions`, formData, { headers: { "Content-Type": "multipart/form-data" }, }); } }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["missions", serverId] }); }, }); } export function useDeleteMission(serverId: number) { const queryClient = useQueryClient(); return useMutation({ mutationFn: (filename: string) => apiClient.delete( `/api/servers/${serverId}/missions/${encodeURIComponent(filename)}`, ), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["missions", serverId] }); }, }); } export function useSetEnabledMods(serverId: number) { const queryClient = useQueryClient(); return useMutation({ mutationFn: (mods: string[]) => apiClient.put(`/api/servers/${serverId}/mods/enabled`, { mods }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["mods", serverId] }); }, }); } export function useSendCommand(serverId: number) { return useMutation({ mutationFn: (command: string) => apiClient.post(`/api/servers/${serverId}/rcon/command`, { command }), }); } export function useKickPlayer(serverId: number) { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ slotId, reason }: { slotId: number; reason: string }) => apiClient.post(`/api/servers/${serverId}/players/${slotId}/kick`, { reason }), onSuccess: () => queryClient.invalidateQueries({ queryKey: ["players", serverId] }), }); } export function useBanPlayer(serverId: number) { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ slotId, reason, durationMinutes }: { slotId: number; reason: string; durationMinutes?: number }) => apiClient.post(`/api/servers/${serverId}/players/${slotId}/ban`, { reason, duration_minutes: durationMinutes ?? null, }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["players", serverId] }); queryClient.invalidateQueries({ queryKey: ["bans", serverId] }); }, }); } export interface LogFile { filename: string; size_bytes: number; modified_at: number; } export function useServerLogFiles(serverId: number) { return useQuery({ queryKey: ["servers", serverId, "logfiles"], queryFn: async () => { const res = await apiClient.get<{ success: boolean; data: LogFile[] }>( `/api/servers/${serverId}/logfiles`, ); return res.data.data; }, enabled: serverId > 0, refetchInterval: 30_000, }); } export function useDeleteLogFile(serverId: number) { const queryClient = useQueryClient(); return useMutation({ mutationFn: (filename: string) => apiClient.delete(`/api/servers/${serverId}/logfiles/${encodeURIComponent(filename)}`), onSuccess: () => queryClient.invalidateQueries({ queryKey: ["servers", serverId, "logfiles"] }), }); }