mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-11 17:26:45 +02:00
5 files changed
+491
-27
No files matched your search
@@ -15,13 +15,9 @@ localPort = 5000
|
||||
externalPort = 80
|
||||
|
||||
[[ports]]
|
||||
localPort = 5555
|
||||
localPort = 35981
|
||||
externalPort = 3002
|
||||
|
||||
[[ports]]
|
||||
localPort = 37219
|
||||
externalPort = 3003
|
||||
|
||||
[[ports]]
|
||||
localPort = 40537
|
||||
externalPort = 3000
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
{
|
||||
"name": "My workflow 8",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"formTitle": "SocialFlow",
|
||||
"formFields": {
|
||||
"values": [
|
||||
{
|
||||
"fieldLabel": "Non du produit",
|
||||
"placeholder": "nom_produit",
|
||||
"requiredField": true
|
||||
},
|
||||
{
|
||||
"fieldLabel": "Prix",
|
||||
"placeholder": "prix",
|
||||
"requiredField": true
|
||||
},
|
||||
{
|
||||
"fieldLabel": "Caractéristiques",
|
||||
"placeholder": "caractéristiques"
|
||||
},
|
||||
{
|
||||
"fieldLabel": "Promo",
|
||||
"placeholder": "promo"
|
||||
},
|
||||
{
|
||||
"fieldLabel": "Date publication",
|
||||
"fieldType": "date",
|
||||
"requiredField": true
|
||||
},
|
||||
{
|
||||
"fieldLabel": "Heure de publication (HH:mm)",
|
||||
"placeholder": "heure_publication",
|
||||
"requiredField": true
|
||||
},
|
||||
{
|
||||
"fieldLabel": "Photo",
|
||||
"fieldType": "file",
|
||||
"acceptFileTypes": "photo"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.formTrigger",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
-720,
|
||||
160
|
||||
],
|
||||
"id": "3441e9d7-35ff-4f14-bee8-78397d0e2e28",
|
||||
"name": "On form submission",
|
||||
"webhookId": "17357858-667e-4a3d-afd1-82039f41c927"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.cloudinary.com/v1_1/dspr8yjfu/image/upload",
|
||||
"sendBody": true,
|
||||
"contentType": "multipart-form-data",
|
||||
"bodyParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "upload_preset",
|
||||
"value": "=upload_n8n"
|
||||
},
|
||||
{
|
||||
"parameterType": "formBinaryData",
|
||||
"name": "file",
|
||||
"inputDataFieldName": "Photo"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-208,
|
||||
16
|
||||
],
|
||||
"id": "49ff16a6-a004-4ff4-a1dc-cb6902f7f46a",
|
||||
"name": "HTTP Request"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "14257b6b-82c8-4151-85a5-f56ab814f6f3",
|
||||
"name": "image_ready_url",
|
||||
"value": "={{$json.secure_url\n ? $json.secure_url.replace('/upload/','/upload/c_fill,g_auto,h_1350,w_1080,q_auto,f_auto,e_auto_contrast,e_auto_color,e_sharpen:50/')\n : ''}}\n",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "74f9dbae-0e18-4cdb-b316-5976f65ad6c8",
|
||||
"name": "image_public_id",
|
||||
"value": "={{$json.public_id}}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"includeOtherFields": true,
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [
|
||||
16,
|
||||
16
|
||||
],
|
||||
"id": "dc95401f-403a-4a72-87e6-622709b10046",
|
||||
"name": "Edit Fields"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "=Langue: fr\nPAS DE TEXTE AVANT OU APRÈS.\n\nContraintes:\n- Accroche d'ouverture engageante\n- 1-2 phrases sur bénéfice client, pas style catalogue\n- Pas de lien, pas d'URL\n- 3-5 hashtags ciblés (locaux/thématiques) Ajouter #lafoirfouille\n- Ton chaleureux, concret, non agressif\n- Maximum 250 caractères \n\nContexte:\n- Produit: {{ $('On form submission').item.json['Non du produit'] }}\n- Prix: {{ $('On form submission').item.json.Prix }}\n- Caractéristiques: {{ $('On form submission').item.json['Caractéristiques'] }}\n- Promo: {{$json.promo || \"aucune\"}}\n",
|
||||
"messages": {
|
||||
"messageValues": [
|
||||
{
|
||||
"message": "Tu es un community manager de boutique physique. Tu écris pour Facebook. Objectif: engagement + visites en magasin. Pas de lien web. pas de json que du texte pure avec quelques emoji. structure le texte pour un post facebook et instagram - ne parle pas de boutique locale."
|
||||
}
|
||||
]
|
||||
},
|
||||
"batching": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.chainLlm",
|
||||
"typeVersion": 1.7,
|
||||
"position": [
|
||||
240,
|
||||
16
|
||||
],
|
||||
"id": "b7de85b4-8a06-4cd6-b3b1-dd28f7e30720",
|
||||
"name": "Basic LLM Chain",
|
||||
"alwaysOutputData": true
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"model": "openai/gpt-4o",
|
||||
"options": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
224,
|
||||
320
|
||||
],
|
||||
"id": "03c7dc91-a5e0-4b58-946f-7217cfbb7f22",
|
||||
"name": "OpenRouter Chat Model",
|
||||
"credentials": {
|
||||
"openRouterApi": {
|
||||
"id": "QrDzqP7MluNzhC1L",
|
||||
"name": "OpenRouter account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://graph.facebook.com/v23.0/119315771483782/photos",
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "url",
|
||||
"value": "={{ $('Edit Fields').first().json.image_ready_url.trim().replace(/\\)+$/,'') }}\n"
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"value": "={{ $json.text }}"
|
||||
},
|
||||
{
|
||||
"name": "access_token",
|
||||
"value": "EAASQALmsF1UBPlhtfZBbZCkuu9fZAbr8OpEAXuXcLuZCAx6F2BmhZBprak0AQfdqKZA0aGsR8pDtpqmkBjb7lIqZCadMJ919IrvwwE75nEo1uZCIIuAJDqkq3Cl9feQMX126sPIabGerrLtd8Dw5NGlQTNzYB7KYqUgcjZA6N5fDt7Bn4pMKMzsh0p5MCxMxq5xV1qyNLVykNMbgE7HZBkas1oLdeFj3M2hAZAYlS4NBuoZD"
|
||||
},
|
||||
{
|
||||
"name": "published",
|
||||
"value": "=0"
|
||||
},
|
||||
{
|
||||
"name": "scheduled_publish_time",
|
||||
"value": "={{ $json.scheduled_publish_time }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
1040,
|
||||
-16
|
||||
],
|
||||
"id": "6fbaf6c3-1744-4116-b599-047a978ba504",
|
||||
"name": "Facebook Post Photo"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "runOnceForEachItem",
|
||||
"jsCode": "// Récupère les valeurs depuis le Form Trigger, pas depuis l'item courant\nconst dateStr = $item(0).$node[\"On form submission\"].json[\"Date publication\"];\nconst timeStr = $item(0).$node[\"On form submission\"].json[\"Heure de publication (HH:mm)\"];\n\nif (!dateStr || !timeStr) {\n throw new Error(\"Champs manquants: \" + JSON.stringify({ dateStr, timeStr }));\n}\n\nconst [y,m,d] = dateStr.split('-').map(Number);\nconst [H,Mi] = timeStr.split(':').map(Number);\n\n// Epoch UTC\nlet ts = Math.floor(Date.UTC(y, m - 1, d, H, Mi) / 1000);\n\n// Sécurité: forcer ≥ maintenant+600s\nconst now = Math.floor(Date.now()/1000);\nif (ts < now + 600) ts = now + 1200;\n\nreturn { json: { ...$json, scheduled_publish_time: ts } };\n"
|
||||
},
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
816,
|
||||
144
|
||||
],
|
||||
"id": "93f14550-2ccd-4da7-bfb7-7ecacee3e830",
|
||||
"name": "Code",
|
||||
"alwaysOutputData": true
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
592,
|
||||
144
|
||||
],
|
||||
"id": "557d9b2a-ceb1-42d8-ad2d-29e8c2533d51",
|
||||
"name": "Merge"
|
||||
}
|
||||
],
|
||||
"pinData": {},
|
||||
"connections": {
|
||||
"On form submission": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "HTTP Request",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Merge",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"HTTP Request": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Edit Fields",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"OpenRouter Chat Model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Basic LLM Chain",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Edit Fields": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Basic LLM Chain",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Basic LLM Chain": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Facebook Post Photo",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "72a43b73-4460-49de-88aa-90afde0ef73b",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true,
|
||||
"instanceId": "95265e6405e55fa03a3687565ed8e8c464bd659d0d56a9696d7d25f954abf511"
|
||||
},
|
||||
"id": "cTnh6OMO9wxPEPBS",
|
||||
"tags": []
|
||||
}
|
||||
+121
-10
@@ -1,6 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import { Plus, Facebook, Instagram, Trash2, RefreshCw } from "lucide-react";
|
||||
import { Plus, Facebook, Instagram, Trash2, RefreshCw, Edit } from "lucide-react";
|
||||
import Sidebar from "@/components/sidebar";
|
||||
import TopBar from "@/components/topbar";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -16,6 +16,7 @@ import type { SocialPage } from "@shared/schema";
|
||||
export default function PagesManagement() {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [editingPage, setEditingPage] = useState<SocialPage | null>(null);
|
||||
const { toast } = useToast();
|
||||
|
||||
const { data: pages = [], isLoading } = useQuery<SocialPage[]>({
|
||||
@@ -69,6 +70,7 @@ export default function PagesManagement() {
|
||||
</p>
|
||||
</div>
|
||||
<AddPageDialog open={dialogOpen} onOpenChange={setDialogOpen} />
|
||||
<EditPageDialog page={editingPage} onOpenChange={(open) => !open && setEditingPage(null)} />
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
@@ -125,15 +127,25 @@ export default function PagesManagement() {
|
||||
<div className="text-sm text-muted-foreground">
|
||||
ID: {page.pageId}
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => deleteMutation.mutate(page.id)}
|
||||
disabled={deleteMutation.isPending}
|
||||
data-testid={`button-delete-page-${page.id}`}
|
||||
>
|
||||
<Trash2 className="w-4 h-4 text-destructive" />
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setEditingPage(page)}
|
||||
data-testid={`button-edit-page-${page.id}`}
|
||||
>
|
||||
<Edit className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => deleteMutation.mutate(page.id)}
|
||||
disabled={deleteMutation.isPending}
|
||||
data-testid={`button-delete-page-${page.id}`}
|
||||
>
|
||||
<Trash2 className="w-4 h-4 text-destructive" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -293,3 +305,102 @@ function AddPageDialog({ open, onOpenChange }: { open: boolean; onOpenChange: (o
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function EditPageDialog({ page, onOpenChange }: { page: SocialPage | null; onOpenChange: (open: boolean) => void }) {
|
||||
const [accessToken, setAccessToken] = useState('');
|
||||
const { toast } = useToast();
|
||||
|
||||
const editMutation = useMutation({
|
||||
mutationFn: (data: any) =>
|
||||
apiRequest('PUT', `/api/pages/${page?.id}`, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['/api/pages'] });
|
||||
toast({
|
||||
title: "Page modifiée",
|
||||
description: "Le jeton d'accès a été mis à jour avec succès",
|
||||
});
|
||||
onOpenChange(false);
|
||||
setAccessToken('');
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: "Erreur",
|
||||
description: "Impossible de modifier la page",
|
||||
variant: "destructive",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!page) return;
|
||||
editMutation.mutate({
|
||||
accessToken,
|
||||
});
|
||||
};
|
||||
|
||||
if (!page) return null;
|
||||
|
||||
return (
|
||||
<Dialog open={!!page} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Modifier le jeton d'accès</DialogTitle>
|
||||
<DialogDescription>
|
||||
Mettez à jour le jeton d'accès pour {page.pageName}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="editAccessToken">Nouveau jeton d'accès</Label>
|
||||
<Input
|
||||
id="editAccessToken"
|
||||
type="password"
|
||||
value={accessToken}
|
||||
onChange={(e) => setAccessToken(e.target.value)}
|
||||
placeholder="EAAxxxxxxxxxxxxx"
|
||||
required
|
||||
data-testid="input-edit-access-token"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Obtenez un nouveau jeton depuis{' '}
|
||||
<a
|
||||
href="https://developers.facebook.com/tools/explorer"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
Facebook Graph API Explorer
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => onOpenChange(false)}
|
||||
data-testid="button-cancel-edit"
|
||||
>
|
||||
Annuler
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={editMutation.isPending}
|
||||
data-testid="button-submit-edit"
|
||||
>
|
||||
{editMutation.isPending ? (
|
||||
<>
|
||||
<RefreshCw className="w-4 h-4 mr-2 animate-spin" />
|
||||
Modification...
|
||||
</>
|
||||
) : (
|
||||
'Modifier'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -541,6 +541,45 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
}
|
||||
});
|
||||
|
||||
app.put("/api/pages/:id", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const user = req.user as User;
|
||||
const userId = user.id;
|
||||
const pageId = req.params.id;
|
||||
|
||||
const existingPage = await storage.getSocialPage(pageId);
|
||||
if (!existingPage || existingPage.userId !== userId) {
|
||||
return res.status(404).json({ error: "Page non trouvée" });
|
||||
}
|
||||
|
||||
const pageData = insertSocialPageSchema.partial().parse(req.body);
|
||||
const updatedPage = await storage.updateSocialPage(pageId, pageData);
|
||||
res.json(updatedPage);
|
||||
} catch (error) {
|
||||
console.error("Error updating page:", error);
|
||||
res.status(500).json({ error: "Failed to update page" });
|
||||
}
|
||||
});
|
||||
|
||||
app.delete("/api/pages/:id", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const user = req.user as User;
|
||||
const userId = user.id;
|
||||
const pageId = req.params.id;
|
||||
|
||||
const existingPage = await storage.getSocialPage(pageId);
|
||||
if (!existingPage || existingPage.userId !== userId) {
|
||||
return res.status(404).json({ error: "Page non trouvée" });
|
||||
}
|
||||
|
||||
await storage.deleteSocialPage(pageId);
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error("Error deleting page:", error);
|
||||
res.status(500).json({ error: "Failed to delete page" });
|
||||
}
|
||||
});
|
||||
|
||||
// AI Generations
|
||||
app.get("/api/ai/generations", requireAuth, async (req, res) => {
|
||||
try {
|
||||
|
||||
@@ -39,16 +39,15 @@ export class FacebookService {
|
||||
}
|
||||
|
||||
private async publishTextPost(post: Post, page: SocialPage): Promise<string> {
|
||||
const url = `${this.baseUrl}/${page.pageId}/feed`;
|
||||
|
||||
const params = new URLSearchParams({
|
||||
access_token: page.accessToken!,
|
||||
message: post.content,
|
||||
});
|
||||
|
||||
const url = `${this.baseUrl}/${page.pageId}/feed?${params.toString()}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -61,8 +60,6 @@ export class FacebookService {
|
||||
}
|
||||
|
||||
private async publishPhotoPost(post: Post, page: SocialPage, media: Media): Promise<string> {
|
||||
const url = `${this.baseUrl}/${page.pageId}/photos`;
|
||||
|
||||
// Use the Facebook Feed optimized URL (1200x630)
|
||||
const photoUrl = media.facebookFeedUrl || media.originalUrl;
|
||||
|
||||
@@ -72,9 +69,10 @@ export class FacebookService {
|
||||
url: photoUrl,
|
||||
});
|
||||
|
||||
const url = `${this.baseUrl}/${page.pageId}/photos?${params.toString()}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -120,8 +118,6 @@ export class FacebookService {
|
||||
}
|
||||
|
||||
private async scheduleTextPost(post: Post, page: SocialPage, scheduledTimestamp: number): Promise<string> {
|
||||
const url = `${this.baseUrl}/${page.pageId}/feed`;
|
||||
|
||||
const params = new URLSearchParams({
|
||||
access_token: page.accessToken!,
|
||||
message: post.content,
|
||||
@@ -129,9 +125,10 @@ export class FacebookService {
|
||||
scheduled_publish_time: scheduledTimestamp.toString(),
|
||||
});
|
||||
|
||||
const url = `${this.baseUrl}/${page.pageId}/feed?${params.toString()}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -144,8 +141,6 @@ export class FacebookService {
|
||||
}
|
||||
|
||||
private async schedulePhotoPost(post: Post, page: SocialPage, scheduledTimestamp: number, media: Media): Promise<string> {
|
||||
const url = `${this.baseUrl}/${page.pageId}/photos`;
|
||||
|
||||
const photoUrl = media.facebookFeedUrl || media.originalUrl;
|
||||
|
||||
const params = new URLSearchParams({
|
||||
@@ -156,9 +151,10 @@ export class FacebookService {
|
||||
scheduled_publish_time: scheduledTimestamp.toString(),
|
||||
});
|
||||
|
||||
const url = `${this.baseUrl}/${page.pageId}/photos?${params.toString()}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
||||
Reference in new issue
Block a user