feat: initial zeropost-engine structure

- AI service with Anthropic claude-sonnet-4-6
- Bull queue for async generation jobs
- Routes: /api/generate, /api/channels, /api/posts
- PostgreSQL schema: users, channels, posts, generation_jobs
- Supports: post, article, topics generation types
This commit is contained in:
Alexey Pavlov
2026-05-30 21:29:00 +03:00
parent 1b9767f269
commit 612053b93d
12 changed files with 1907 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
const Queue = require('bull');
const config = require('../config');
const ai = require('../services/ai');
const { query } = require('../config/db');
const generationQueue = new Queue('generation', {
redis: config.redis,
defaultJobOptions: {
attempts: 3,
backoff: { type: 'exponential', delay: 5000 },
removeOnComplete: 100,
removeOnFail: 200,
},
});
generationQueue.process(async (job) => {
const { jobId, type, topic, tone, language, channelContext, keywords } = job.data;
await query(`UPDATE generation_jobs SET status='processing', updated_at=NOW() WHERE id=$1`, [jobId]);
try {
let result;
if (type === 'post') {
result = await ai.generatePost({ topic, tone, language, channelContext });
} else if (type === 'article') {
result = await ai.generateArticle({ topic, language, keywords });
} else if (type === 'topics') {
const topics = await ai.generateTopics({ channelContext: topic, language });
result = JSON.stringify(topics);
} else {
throw new Error(`Unknown job type: ${type}`);
}
await query(
`UPDATE generation_jobs SET status='done', result=$1, updated_at=NOW() WHERE id=$2`,
[result, jobId]
);
console.log(`[Worker] Job ${jobId} (${type}) done`);
return { jobId, result };
} catch (err) {
await query(
`UPDATE generation_jobs SET status='failed', error=$1, updated_at=NOW() WHERE id=$2`,
[err.message, jobId]
);
throw err;
}
});
generationQueue.on('failed', (job, err) => {
console.error(`[Worker] Job ${job.data.jobId} failed:`, err.message);
});
console.log('[Worker] Generation queue started');
module.exports = generationQueue;