feat(zero): Zero notes — AI persona for @zeropostru daily posts

Adds character-driven AI-generated notes pipeline parallel to articles:
  - migration: zero_notes table (channel-scoped, status flow draft→approved→published)
  - services/zeroPrompt.js — 12 theme buckets, few-shots, anti-repeat by bucket,
    sha256 theme_hash for dedup
  - services/zeroNotes.js — generateDraft/approve/skip/edit/autoApproveOldDrafts
  - services/zeroNotesRunner.js — TG publication with multipart pose images,
    FOR UPDATE SKIP LOCKED claim, retry up to 3 attempts
  - workers/zeroNotesScheduler.js — 13:00 MSK generate, 07:00 MSK auto-approve,
    publish runner every 60s
  - routes/zero.js (public, no secret) — character bio + published notes for site
  - routes/zeroAdmin.js — full CRUD + manual generate button + regenerate

Settings (app_settings):
  ZERO_NOTES_CHANNEL_IDS  — csv int, channels to post for (required to enable)
  ZERO_NOTES_MODEL        — defaults to AI_MODEL_POST
  ZERO_SITE_URL_BASE      — optional, adds 'Open on site' inline button
This commit is contained in:
Aleksei Pavlov
2026-06-19 10:52:22 +03:00
committed by Alexey Pavlov
parent 5b5f703078
commit 29788a8f9d
8 changed files with 1245 additions and 0 deletions
+9
View File
@@ -75,6 +75,9 @@ app.post('/api/billing/webhook',
const inboxRoutes = require('./src/routes/inbox');
app.use('/api', inboxRoutes); // включает /api/tg-webhook/:channelId
// Заметки Зеро — публичная часть (для сайта zeropost.ru/zero)
app.use('/api/zero', require('./src/routes/zero'));
// Simple internal auth middleware
app.use((req, res, next) => {
const secret = req.headers['x-internal-secret'];
@@ -126,6 +129,9 @@ app.use('/api/channels', require('./src/routes/polls'));
app.use('/api', inboxRoutes);
app.use('/api', require('./src/routes/drafts'));
// Заметки Зеро — админская часть (за internal-secret middleware)
app.use('/api/admin/zero', require('./src/routes/zeroAdmin'));
app.get('/health', (req, res) => {
res.json({ ok: true, service: 'zeropost-engine', time: new Date() });
});
@@ -162,6 +168,9 @@ const start = async () => {
// Первый запуск через 5 мин после старта
setTimeout(() => draftSvc.generateDailyDrafts().catch(() => {}), 5 * 60 * 1000);
// Заметки Зеро — генерация в 13:00 МСК + авто-одобрение в 07:00 МСК
require('./src/workers/zeroNotesScheduler').start();
app.listen(config.port, () => {
console.log(`[Engine] Running on port ${config.port}`);
});