const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000/api"; async function request(path: string, options?: RequestInit): Promise { const token = typeof window !== "undefined" ? localStorage.getItem("bbe_token") : null; const headers: HeadersInit = { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), ...options?.headers, }; const res = await fetch(`${API_URL}${path}`, { ...options, headers }); if (!res.ok) { const error = await res.json().catch(() => ({ message: "Request failed" })); throw new Error(error.message || `HTTP ${res.status}`); } return res.json(); } export const api = { // Auth getChallenge: (pubkey: string) => request<{ challenge: string }>("/auth/challenge", { method: "POST", body: JSON.stringify({ pubkey }), }), verify: (pubkey: string, signedEvent: any) => request<{ token: string; user: { pubkey: string; role: string; username?: string } }>("/auth/verify", { method: "POST", body: JSON.stringify({ pubkey, signedEvent }), }), // Posts getPosts: (params?: { category?: string; page?: number; limit?: number; all?: boolean }) => { const searchParams = new URLSearchParams(); if (params?.category) searchParams.set("category", params.category); if (params?.page) searchParams.set("page", String(params.page)); if (params?.limit) searchParams.set("limit", String(params.limit)); if (params?.all) searchParams.set("all", "true"); return request<{ posts: any[]; total: number }>(`/posts?${searchParams}`); }, getPost: (slug: string) => request(`/posts/${slug}`), getPostReactions: (slug: string) => request<{ count: number; reactions: any[] }>(`/posts/${slug}/reactions`), getPostReplies: (slug: string) => request<{ count: number; replies: any[] }>(`/posts/${slug}/replies`), importPost: (data: { eventId?: string; naddr?: string }) => request("/posts/import", { method: "POST", body: JSON.stringify(data) }), updatePost: (id: string, data: any) => request(`/posts/${id}`, { method: "PATCH", body: JSON.stringify(data) }), deletePost: (id: string) => request(`/posts/${id}`, { method: "DELETE" }), // Meetups getMeetups: (params?: { status?: string; admin?: boolean }) => { const searchParams = new URLSearchParams(); if (params?.status) searchParams.set("status", params.status); if (params?.admin) searchParams.set("admin", "true"); const qs = searchParams.toString(); return request(`/meetups${qs ? `?${qs}` : ""}`); }, getMeetup: (id: string) => request(`/meetups/${id}`), createMeetup: (data: any) => request("/meetups", { method: "POST", body: JSON.stringify(data) }), updateMeetup: (id: string, data: any) => request(`/meetups/${id}`, { method: "PATCH", body: JSON.stringify(data) }), deleteMeetup: (id: string) => request(`/meetups/${id}`, { method: "DELETE" }), duplicateMeetup: (id: string) => request(`/meetups/${id}/duplicate`, { method: "POST" }), bulkMeetupAction: (action: string, ids: string[]) => request("/meetups/bulk", { method: "POST", body: JSON.stringify({ action, ids }) }), // Moderation getHiddenContent: () => request("/moderation/hidden"), hideContent: (nostrEventId: string, reason?: string) => request("/moderation/hide", { method: "POST", body: JSON.stringify({ nostrEventId, reason }) }), unhideContent: (id: string) => request(`/moderation/unhide/${id}`, { method: "DELETE" }), getBlockedPubkeys: () => request("/moderation/blocked"), blockPubkey: (pubkey: string, reason?: string) => request("/moderation/block", { method: "POST", body: JSON.stringify({ pubkey, reason }) }), unblockPubkey: (id: string) => request(`/moderation/unblock/${id}`, { method: "DELETE" }), // Users getUsers: () => request("/users"), promoteUser: (pubkey: string) => request("/users/promote", { method: "POST", body: JSON.stringify({ pubkey }) }), demoteUser: (pubkey: string) => request("/users/demote", { method: "POST", body: JSON.stringify({ pubkey }) }), // Categories getCategories: () => request("/categories"), createCategory: (data: { name: string; slug: string }) => request("/categories", { method: "POST", body: JSON.stringify(data) }), updateCategory: (id: string, data: any) => request(`/categories/${id}`, { method: "PATCH", body: JSON.stringify(data) }), deleteCategory: (id: string) => request(`/categories/${id}`, { method: "DELETE" }), // Relays getRelays: () => request("/relays"), addRelay: (data: { url: string; priority?: number }) => request("/relays", { method: "POST", body: JSON.stringify(data) }), updateRelay: (id: string, data: any) => request(`/relays/${id}`, { method: "PATCH", body: JSON.stringify(data) }), deleteRelay: (id: string) => request(`/relays/${id}`, { method: "DELETE" }), testRelay: (id: string) => request<{ success: boolean }>(`/relays/${id}/test`, { method: "POST" }), // Settings getSettings: () => request>("/settings"), getPublicSettings: () => request>("/settings/public"), updateSetting: (key: string, value: string) => request("/settings", { method: "PATCH", body: JSON.stringify({ key, value }) }), // Nostr tools fetchNostrEvent: (data: { eventId?: string; naddr?: string }) => request("/nostr/fetch", { method: "POST", body: JSON.stringify(data) }), refreshCache: () => request("/nostr/cache/refresh", { method: "POST" }), debugEvent: (eventId: string) => request(`/nostr/debug/${eventId}`), // Media uploadMedia: async (file: File) => { const token = typeof window !== "undefined" ? localStorage.getItem("bbe_token") : null; const formData = new FormData(); formData.append("file", file); const res = await fetch(`${API_URL}/media/upload`, { method: "POST", headers: token ? { Authorization: `Bearer ${token}` } : {}, body: formData, }); if (!res.ok) { const error = await res.json().catch(() => ({ message: "Upload failed" })); throw new Error(error.error || error.message || `HTTP ${res.status}`); } return res.json() as Promise<{ id: string; slug: string; url: string }>; }, getMediaList: () => request("/media"), getMedia: (id: string) => request(`/media/${id}`), deleteMedia: (id: string) => request(`/media/${id}`, { method: "DELETE" }), updateMedia: (id: string, data: { title?: string; description?: string; altText?: string }) => request(`/media/${id}`, { method: "PATCH", body: JSON.stringify(data) }), // FAQs getFaqs: () => request('/faqs'), getFaqsAll: () => request('/faqs?all=true'), getAllFaqs: () => request('/faqs/all'), createFaq: (data: { question: string; answer: string; showOnHomepage?: boolean }) => request('/faqs', { method: 'POST', body: JSON.stringify(data) }), updateFaq: (id: string, data: { question?: string; answer?: string; showOnHomepage?: boolean }) => request(`/faqs/${id}`, { method: 'PATCH', body: JSON.stringify(data) }), deleteFaq: (id: string) => request(`/faqs/${id}`, { method: 'DELETE' }), reorderFaqs: (items: { id: string; order: number }[]) => request('/faqs/reorder', { method: 'POST', body: JSON.stringify({ items }) }), // Profile (self) updateProfile: (data: { username?: string }) => request('/users/me', { method: 'PATCH', body: JSON.stringify(data) }), checkUsername: (username: string) => request<{ available: boolean; reason?: string }>( `/users/me/username-check?username=${encodeURIComponent(username)}` ), // Submissions createSubmission: (data: { eventId?: string; naddr?: string; title: string }) => request("/submissions", { method: "POST", body: JSON.stringify(data) }), getMySubmissions: () => request("/submissions/mine"), getSubmissions: (status?: string) => { const params = status ? `?status=${status}` : ""; return request(`/submissions${params}`); }, reviewSubmission: (id: string, data: { status: string; reviewNote?: string }) => request(`/submissions/${id}`, { method: "PATCH", body: JSON.stringify(data) }), };