diff --git a/app/admin/(protected)/drafts/page.js b/app/admin/(protected)/drafts/page.js
index e22e084..cba82c8 100644
--- a/app/admin/(protected)/drafts/page.js
+++ b/app/admin/(protected)/drafts/page.js
@@ -2,14 +2,13 @@
import { useState, useEffect, useCallback } from 'react';
import { CheckCircle, RefreshCw, Pencil, Clock, ImageIcon, ChevronDown, ChevronUp, X, Save } from 'lucide-react';
-
const COVER_STYLES = [
- { id: 'tech-photo', name: 'Tech Photo — реальное железо' },
- { id: '3d-device', name: '3D Device — рендер устройства' },
- { id: 'code-screen', name: 'Code Screen — экран с кодом' },
- { id: 'data-flow', name: 'Data Flow — абстракция данных' },
- { id: 'ai-neural', name: 'AI Neural — нейросетевая эстетика' },
- { id: 'cinematic-tech', name: 'Cinematic — кинематографичный' },
+ { id: 'robot-workspace', name: '🤖 Робот за работой' },
+ { id: 'tech-hardware', name: '🖥 Железо и серверы' },
+ { id: 'code-terminal', name: '💻 Код и терминал' },
+ { id: 'cyber-security', name: '🔒 Кибербезопасность' },
+ { id: 'data-dashboard', name: '📊 Данные и аналитика' },
+ { id: 'future-tech', name: '🌆 Технологии будущего' },
];
const CATEGORY_LABELS = {
@@ -31,16 +30,10 @@ function DraftCard({ draft, onApproved, onCoverRegenerated }) {
const [coverUrl, setCoverUrl] = useState(draft.cover_url);
const [msg, setMsg] = useState('');
- const engineUrl = process.env.NEXT_PUBLIC_ENGINE_URL || '';
-
async function approve() {
- setLoading(true);
- setMsg('');
+ setLoading(true); setMsg('');
try {
- const r = await fetch(`${engineUrl}/api/drafts/${draft.id}/approve`, {
- method: 'PATCH',
- headers: { 'x-internal-secret': process.env.NEXT_PUBLIC_ENGINE_SECRET || '' },
- });
+ const r = await fetch(`/api/admin/drafts/${draft.id}/approve`, { method: 'PATCH' });
const d = await r.json();
if (d.ok) {
const slot = d.scheduled_at
@@ -48,23 +41,17 @@ function DraftCard({ draft, onApproved, onCoverRegenerated }) {
: null;
setMsg(slot ? `✅ Одобрено, выйдет в ${slot}` : '✅ Одобрено');
setTimeout(() => onApproved(draft.id), 1200);
- } else {
- setMsg('❌ ' + (d.error || 'Ошибка'));
- }
+ } else setMsg('❌ ' + (d.error || 'Ошибка'));
} catch { setMsg('❌ Ошибка сети'); }
setLoading(false);
}
async function regenCover() {
- setRegenLoading(true);
- setMsg('');
+ setRegenLoading(true); setMsg('');
try {
- const r = await fetch(`${engineUrl}/api/drafts/${draft.id}/regenerate-cover`, {
+ const r = await fetch(`/api/admin/drafts/${draft.id}/regenerate-cover`, {
method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'x-internal-secret': process.env.NEXT_PUBLIC_ENGINE_SECRET || '',
- },
+ headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ style: selectedStyle || undefined }),
});
const d = await r.json();
@@ -72,9 +59,7 @@ function DraftCard({ draft, onApproved, onCoverRegenerated }) {
setCoverUrl(d.cover_url);
setMsg('🎨 Обложка обновлена');
onCoverRegenerated(draft.id, d.cover_url);
- } else {
- setMsg('❌ ' + (d.error || 'Ошибка генерации'));
- }
+ } else setMsg('❌ ' + (d.error || 'Ошибка'));
} catch { setMsg('❌ Ошибка сети'); }
setRegenLoading(false);
}
@@ -82,12 +67,9 @@ function DraftCard({ draft, onApproved, onCoverRegenerated }) {
async function saveEdit() {
setSaveLoading(true);
try {
- const r = await fetch(`${engineUrl}/api/drafts/${draft.id}`, {
+ const r = await fetch(`/api/admin/drafts/${draft.id}`, {
method: 'PATCH',
- headers: {
- 'Content-Type': 'application/json',
- 'x-internal-secret': process.env.NEXT_PUBLIC_ENGINE_SECRET || '',
- },
+ headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: editTitle, excerpt: editExcerpt }),
});
const d = await r.json();
@@ -98,14 +80,13 @@ function DraftCard({ draft, onApproved, onCoverRegenerated }) {
}
const coverSrc = coverUrl
- ? (coverUrl.startsWith('http') ? coverUrl : `${engineUrl}${coverUrl}`)
+ ? (coverUrl.startsWith('http') ? coverUrl : `https://zeropost.ru${coverUrl}`)
: null;
return (
- {/* Обложка + основная инфо */}
-
+
{coverSrc
?

:
@@ -121,65 +102,40 @@ function DraftCard({ draft, onApproved, onCoverRegenerated }) {
{editing ? (
-
setEditTitle(e.target.value)}
- className="w-full font-semibold text-sm border border-neutral-300 dark:border-neutral-700 rounded px-2 py-1 bg-transparent mb-1"
- />
+
setEditTitle(e.target.value)}
+ className="w-full font-semibold text-sm border border-neutral-300 dark:border-neutral-700 rounded px-2 py-1 bg-transparent mb-1" />
) : (
-
- {editTitle}
-
+
{editTitle}
)}
- {/* Анонс */}
- {/* Сообщение */}
- {msg && (
-
{msg}
- )}
+ {msg &&
{msg}
}
- {/* Перегенерация обложки — разворачиваемый блок */}
-
- {msg && (
-
- {msg}
-
- )}
+ {msg &&
{msg}
}
{loading ? (
Загружаю черновики...
@@ -320,14 +234,11 @@ export default function AdminDraftsPage() {
Следующие появятся после autogen
) : (
-
+
{drafts.map(draft => (
-
+ setDrafts(prev => prev.filter(d => d.id !== id))}
+ onCoverRegenerated={(id, url) => setDrafts(prev => prev.map(d => d.id === id ? {...d, cover_url: url} : d))} />
))}
)}
diff --git a/app/admin/api/drafts/[id]/approve/route.js b/app/admin/api/drafts/[id]/approve/route.js
new file mode 100644
index 0000000..d343c3a
--- /dev/null
+++ b/app/admin/api/drafts/[id]/approve/route.js
@@ -0,0 +1,13 @@
+import { NextResponse } from 'next/server';
+import { requireAdminAuth } from '@/lib/adminAuth';
+import { engineFetch } from '@/lib/engine';
+
+export async function PATCH(req, { params }) {
+ await requireAdminAuth();
+ try {
+ const data = await engineFetch(`/api/drafts/${params.id}/approve`, { method: 'PATCH' });
+ return NextResponse.json(data);
+ } catch (err) {
+ return NextResponse.json({ error: err.message }, { status: 500 });
+ }
+}
diff --git a/app/admin/api/drafts/[id]/regenerate-cover/route.js b/app/admin/api/drafts/[id]/regenerate-cover/route.js
new file mode 100644
index 0000000..7910e5a
--- /dev/null
+++ b/app/admin/api/drafts/[id]/regenerate-cover/route.js
@@ -0,0 +1,16 @@
+import { NextResponse } from 'next/server';
+import { requireAdminAuth } from '@/lib/adminAuth';
+import { engineFetch } from '@/lib/engine';
+
+export async function POST(req, { params }) {
+ await requireAdminAuth();
+ try {
+ const body = await req.json().catch(() => ({}));
+ const data = await engineFetch(`/api/drafts/${params.id}/regenerate-cover`, {
+ method: 'POST', body: JSON.stringify(body),
+ });
+ return NextResponse.json(data);
+ } catch (err) {
+ return NextResponse.json({ error: err.message }, { status: 500 });
+ }
+}
diff --git a/app/admin/api/drafts/[id]/route.js b/app/admin/api/drafts/[id]/route.js
new file mode 100644
index 0000000..ef68a9c
--- /dev/null
+++ b/app/admin/api/drafts/[id]/route.js
@@ -0,0 +1,16 @@
+import { NextResponse } from 'next/server';
+import { requireAdminAuth } from '@/lib/adminAuth';
+import { engineFetch } from '@/lib/engine';
+
+export async function PATCH(req, { params }) {
+ await requireAdminAuth();
+ try {
+ const body = await req.json();
+ const data = await engineFetch(`/api/drafts/${params.id}`, {
+ method: 'PATCH', body: JSON.stringify(body),
+ });
+ return NextResponse.json(data);
+ } catch (err) {
+ return NextResponse.json({ error: err.message }, { status: 500 });
+ }
+}
diff --git a/app/admin/api/drafts/approve-all/route.js b/app/admin/api/drafts/approve-all/route.js
new file mode 100644
index 0000000..7a1a35f
--- /dev/null
+++ b/app/admin/api/drafts/approve-all/route.js
@@ -0,0 +1,13 @@
+import { NextResponse } from 'next/server';
+import { requireAdminAuth } from '@/lib/adminAuth';
+import { engineFetch } from '@/lib/engine';
+
+export async function POST() {
+ await requireAdminAuth();
+ try {
+ const data = await engineFetch('/api/drafts/approve-all', { method: 'POST' });
+ return NextResponse.json(data);
+ } catch (err) {
+ return NextResponse.json({ error: err.message }, { status: 500 });
+ }
+}
diff --git a/app/admin/api/drafts/route.js b/app/admin/api/drafts/route.js
new file mode 100644
index 0000000..99e253a
--- /dev/null
+++ b/app/admin/api/drafts/route.js
@@ -0,0 +1,15 @@
+import { NextResponse } from 'next/server';
+import { requireAdminAuth } from '@/lib/adminAuth';
+import { engineFetch } from '@/lib/engine';
+
+export const dynamic = 'force-dynamic';
+
+export async function GET() {
+ await requireAdminAuth();
+ try {
+ const data = await engineFetch('/api/drafts');
+ return NextResponse.json(data);
+ } catch (err) {
+ return NextResponse.json({ error: err.message }, { status: 500 });
+ }
+}
diff --git a/lib/engine.js b/lib/engine.js
index 115e3c1..96691ab 100644
--- a/lib/engine.js
+++ b/lib/engine.js
@@ -1,6 +1,8 @@
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,