Add NestJS Admin Facebook Publishing API with Image Support

This commit is contained in:
Hamza-Ayed
2026-07-24 17:00:52 +03:00
parent 0b9dca5d75
commit 16d7ec0364
3 changed files with 77 additions and 9 deletions
@@ -88,6 +88,14 @@ export class MarketingController {
return this.marketingService.generateContent(user.tenantId, body.prompt);
}
@Post('publish-facebook')
@HttpCode(HttpStatus.OK)
async publishFacebook(
@Body() body: { topic: string; imageUrl?: string },
) {
return this.marketingService.generateAndPublishFacebook(body.topic, body.imageUrl);
}
@Delete('campaigns/:id')
@HttpCode(HttpStatus.NO_CONTENT)
async deleteCampaign(@Param('id') id: string) {
@@ -82,6 +82,57 @@ export class MarketingService {
}
}
async generateAndPublishFacebook(promptTopic: string, imageUrl?: string): Promise<{ ok: boolean; postId?: string; postContent: string }> {
const pageId = process.env.META_PAGE_ID || '1047354938453381';
const accessToken = (process.env.META_ACCESS_TOKEN || '').replace(/\s+/g, '');
if (!accessToken) {
throw new Error('META_ACCESS_TOKEN is not configured in backend environment.');
}
const systemPrompt = `أنت مدير تسويق ومحرر إعلاني محترف لمنصة Tripz (منصة SaaS عالمية لإطلاق تطبيقات نقل الركاب وإدارة أساطيل التكسي بعلامة المشغّل التجارية).
قواعد التسويق الحاكمة:
1. الملكية: علامتك أنت، وتطبيقك باسمك وشعارك، وبياناتك تبقى لك بعقد تصدير مكتوب.
2. الشفافية: أسعارنا معلنة بالكامل دون أسرار ولا مفاجآت في الفاتورة.
3. السرعة: إطلاق خلال 30 يوماً من التوقيع على آبل وجوجل.
4. الإثبات: محرك مجرّب منذ 2019 يخدم أكثر من 6,000 سائق نشط.
5. دعوة للإجراء (CTA): رابط حاسبة التوفير (https://tripz-egypt.com/apps/) ورابط واتساب المباشر (https://wa.me/962798583052).
اكتب منشوراً جذاباً جداً باللغة العربية، منسقاً بالإيموجيات والعناوين والنقاط الواضحة والهاشتاغات المناسبة عن الموضوع التالي:
${promptTopic}`;
const geminiRes = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models/${process.env.GEMINI_MODEL || 'gemini-2.0-flash'}:generateContent?key=${process.env.GEMINI_API_KEY}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ contents: [{ parts: [{ text: systemPrompt }] }] }),
},
);
const geminiData: any = await geminiRes.json();
const postContent = geminiData?.candidates?.[0]?.content?.parts?.[0]?.text?.trim() || promptTopic;
const endpoint = imageUrl ? `${pageId}/photos` : `${pageId}/feed`;
const metaUrl = `https://graph.facebook.com/v19.0/${endpoint}`;
const payload = imageUrl
? { access_token: accessToken, caption: postContent, url: imageUrl }
: { access_token: accessToken, message: postContent };
const fbRes = await fetch(metaUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
const fbData: any = await fbRes.json();
if (fbData.error) {
throw new Error(`Meta Graph API Error [${fbData.error.code}]: ${fbData.error.message}`);
}
return { ok: true, postId: fbData.id || fbData.post_id, postContent };
}
async runCampaign(campaignId: string, userIds: string[]): Promise<{ sent: number }> {
const campaign = await this.campaignRepo.findOne({ where: { id: campaignId } });
if (!campaign) throw new Error('Campaign not found');