const ENGINE_URL = process.env.ENGINE_URL || 'http://127.0.0.1:3040'; const ENGINE_SECRET = process.env.ENGINE_SECRET || 'zeropost_internal_2026'; export async function engineFetch(path, options = {}) { return call(path, options); } async function call(path, options = {}) { const res = await fetch(`${ENGINE_URL}${path}`, { ...options, headers: { 'Content-Type': 'application/json', 'x-internal-secret': ENGINE_SECRET, ...(options.headers || {}), }, cache: options.cache || 'no-store', next: options.next, }); if (!res.ok) { const txt = await res.text(); throw new Error(`Engine ${res.status}: ${txt}`); } return res.json(); } export async function listCategories() { try { return await call('/api/categories', { next: { revalidate: 3600 } }); } catch { return []; } } export async function getCategoryArticles(slug, { limit = 20, offset = 0 } = {}) { try { return await call(`/api/categories/${slug}/articles?limit=${limit}&offset=${offset}`); } catch { return []; } } export async function listArticles({ limit = 20, offset = 0, tag, category } = {}) { const params = new URLSearchParams({ limit, offset }); if (tag) params.set('tag', tag); if (category) params.set('category', category); return call(`/api/articles?${params}`, { next: { revalidate: 60 } }); } export async function getArticle(slug) { try { return await call(`/api/articles/${slug}`, { next: { revalidate: 60 } }); } catch { return null; } } export async function listTags() { return call('/api/articles/tags', { next: { revalidate: 300 } }); } export async function getLive() { try { return await call('/api/stats/live', { cache: 'no-store' }); } catch { return null; } } export async function listSeries() { try { return await call('/api/series', { cache: 'no-store' }); } catch { return []; } } export async function getSeries(slug) { try { return await call(`/api/series/${slug}`, { cache: 'no-store' }); } catch { return null; } } export async function listNotes({ limit = 20 } = {}) { try { return await call(`/api/notes?limit=${limit}`, { cache: 'no-store' }); } catch { return []; } } export async function createNote(data) { return call('/api/notes', { method: 'POST', body: JSON.stringify(data) }); } export async function updateNote(id, data) { return call(`/api/notes/${id}`, { method: 'PATCH', body: JSON.stringify(data) }); } export async function deleteNote(id) { return call(`/api/notes/${id}`, { method: 'DELETE' }); } export async function getStats() { try { return await call('/api/stats', { cache: 'no-store' }); } catch { return null; } } export async function generateArticle(data) { return call('/api/articles/generate', { method: 'POST', body: JSON.stringify(data), }); } // ── Admin API ───────────────────────────────────────────────────────────────── export async function adminListArticles({ limit = 100, offset = 0 } = {}) { return call(`/api/articles/admin?limit=${limit}&offset=${offset}`); } export async function adminGetArticle(id) { return call(`/api/articles/id/${id}`); } export async function adminUpdateArticle(id, data) { return call(`/api/articles/${id}`, { method: 'PATCH', body: JSON.stringify(data), }); } export async function adminDeleteArticle(id) { return call(`/api/articles/${id}`, { method: 'DELETE' }); } export async function adminBackfillCovers(limit = 5) { return call('/api/articles/backfill-covers', { method: 'POST', body: JSON.stringify({ limit }), }); } export async function adminGenerateArticle(topic, tags = []) { return call('/api/articles/generate', { method: 'POST', body: JSON.stringify({ topic, tags, autoPublish: false }), }); } // ── Admin Channels API ──────────────────────────────────────────────────────── export async function adminListChannels() { return call('/api/channels/admin'); } export async function adminCreateChannel(data) { return call('/api/channels/admin', { method: 'POST', body: JSON.stringify(data), }); } export async function adminUpdateChannel(id, data) { return call(`/api/channels/admin/${id}`, { method: 'PATCH', body: JSON.stringify(data), }); } export async function adminDeleteChannel(id) { return call(`/api/channels/admin/${id}`, { method: 'DELETE' }); } export async function adminPublishToChannel(channelId, { article_id, custom_text } = {}) { return call(`/api/channels/admin/${channelId}/publish`, { method: 'POST', body: JSON.stringify({ article_id, custom_text }), }); } export async function adminGetChannelPosts(channelId) { return call(`/api/channels/admin/${channelId}/posts`); } // ── Admin Settings API ──────────────────────────────────────────────────────── export async function adminListSettings() { return call('/api/settings/admin'); } export async function adminUpdateSetting(key, value) { return call(`/api/settings/admin/${encodeURIComponent(key)}`, { method: 'PUT', body: JSON.stringify({ value }), }); } // ── Admin Articles search (typeahead) ───────────────────────────────────────── export async function adminSearchArticles({ q = '', status = 'published', category = '', channelId = null, limit = 20 } = {}) { const params = new URLSearchParams(); if (q) params.set('q', q); if (status) params.set('status', status); if (category) params.set('category', category); if (channelId)params.set('channel_id', String(channelId)); params.set('limit', String(limit)); return call(`/api/articles/admin/search?${params.toString()}`); } // ── Admin Scheduled posts ───────────────────────────────────────────────────── export async function adminGetScheduledQueue(channelId = null) { if (channelId) return call(`/api/channels/admin/${channelId}/scheduled`); return call('/api/scheduled-posts/queue'); } export async function adminScheduleArticle(channelId, { article_id, custom_text, scheduled_at } = {}) { return call(`/api/channels/admin/${channelId}/schedule`, { method: 'POST', body: JSON.stringify({ article_id, custom_text, scheduled_at }), }); } export async function adminCancelScheduled(scheduledPostId) { return call(`/api/scheduled-posts/${scheduledPostId}`, { method: 'DELETE' }); } export async function adminPreviewTemplate({ article_id, template }) { return call('/api/scheduled-posts/preview', { method: 'POST', body: JSON.stringify({ article_id, template }), }); } export async function adminRequeueArticle(articleId) { return call(`/api/scheduled-posts/schedule-article/${articleId}`, { method: 'POST' }); } // ── Zero notes — публичный API для сайта (zeropost.ru/zero) ────────────── export async function listZeroNotes({ limit = 12, offset = 0 } = {}) { try { return (await call(`/api/zero/notes?limit=${limit}&offset=${offset}`, { cache: 'no-store' })).items || []; } catch { return []; } } export async function getZeroCharacter() { try { return await call('/api/zero/character', { next: { revalidate: 3600 } }); } catch { return null; } } // Главная страница — собранный набор секций export async function getHomeData() { return call('/api/articles/home'); } // ── Channel stats ───────────────────────────────────────────────────────────── export async function getChannelSummary(channelId) { return call(`/api/channel-stats/${channelId}/summary`); } export async function getChannelHistory(channelId, days = 30) { return call(`/api/channel-stats/${channelId}/history?days=${days}`); }