mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-11 17:26:45 +02:00
feat(facebook): renouvellement automatique des jetons de page
Un jeton de page collé à la main meurt au bout de 60 jours — ou du jour au lendemain si le mot de passe change, si le compte perd son rôle d'admin ou si Meta révoque l'autorisation — et on ne l'apprenait qu'à la première publication en échec : le contrôle quotidien existant se contentait de marquer la page « expired ». On reprend la chaîne recommandée par Meta, comme dans PleinR : autorisation OAuth, échange du code contre un jeton utilisateur longue durée (~60 j), puis dérivation des jetons de page — qui n'expirent pas. Le jeton utilisateur est conservé chiffré sur chaque page : c'est lui qui permet au TokenManager de régénérer un jeton révoqué sans intervention humaine, et de se prolonger lui-même avant échéance. - facebook_config : App ID / App Secret de l'application, saisis dans les paramètres (chiffrés en base) ou par variables d'environnement. - /api/facebook : configuration, connexion OAuth, callback qui crée ou met à jour les pages, contrôle d'une page à la demande, contrôle global. - TokenManager : /debug_token pour connaître l'état réel du jeton, puis renouvellement automatique 15 jours avant l'échéance ou dès révocation ; repli sur le contrôle historique tant que l'application n'est pas configurée ou si Facebook est injoignable, pour ne pas passer l'UI au rouge sur une panne réseau. Le cron passe à deux fois par jour. - Les comptes Instagram professionnels rattachés à une page reçoivent le même traitement, puisqu'ils publient avec le jeton de cette page. - L'UI montre l'état constaté (jeton valide, bientôt expiré, révoqué), l'erreur à corriger et un bouton de contrôle immédiat ; un bandeau signale les pages à reconnecter. - /api/pages ne renvoie plus aucun jeton au navigateur, seulement un indicateur de renouvellement automatique. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qns6Gcpd95bCHWUxySh4Ew
This commit is contained in:
13 files changed
+1385
-249
No files matched your search
@@ -39,6 +39,9 @@ import {
|
||||
tiktokConfig,
|
||||
type TiktokConfig,
|
||||
type InsertTiktokConfig,
|
||||
facebookConfig,
|
||||
type FacebookConfig,
|
||||
type InsertFacebookConfig,
|
||||
} from "@shared/schema";
|
||||
import { db } from "./db";
|
||||
import { eq, and, gte, lte, desc, asc, isNull, isNotNull, inArray, notInArray } from "drizzle-orm";
|
||||
@@ -121,6 +124,8 @@ export interface IStorage {
|
||||
// TikTok Config
|
||||
getTiktokConfig(): Promise<TiktokConfig | undefined>;
|
||||
upsertTiktokConfig(config: Partial<InsertTiktokConfig>): Promise<TiktokConfig>;
|
||||
getFacebookConfig(): Promise<FacebookConfig | undefined>;
|
||||
upsertFacebookConfig(config: Partial<InsertFacebookConfig>): Promise<FacebookConfig>;
|
||||
|
||||
// Cloudinary Config
|
||||
getCloudinaryConfig(): Promise<CloudinaryConfig | undefined>;
|
||||
@@ -612,6 +617,38 @@ export class DatabaseStorage implements IStorage {
|
||||
return config || undefined;
|
||||
}
|
||||
|
||||
// Facebook Config (application développeur, globale à l'instance)
|
||||
async getFacebookConfig(): Promise<FacebookConfig | undefined> {
|
||||
const [config] = await db.select().from(facebookConfig).limit(1);
|
||||
if (!config) return undefined;
|
||||
return { ...config, appSecret: decrypt(config.appSecret) };
|
||||
}
|
||||
|
||||
async upsertFacebookConfig(data: Partial<InsertFacebookConfig>): Promise<FacebookConfig> {
|
||||
const values: Partial<FacebookConfig> = { ...data };
|
||||
if (data.appSecret) {
|
||||
values.appSecret = encrypt(data.appSecret);
|
||||
}
|
||||
|
||||
const [existing] = await db.select().from(facebookConfig).limit(1);
|
||||
if (existing) {
|
||||
const [updated] = await db.update(facebookConfig)
|
||||
.set({ ...values, updatedAt: new Date() })
|
||||
.where(eq(facebookConfig.id, existing.id))
|
||||
.returning();
|
||||
return { ...updated, appSecret: decrypt(updated.appSecret) };
|
||||
}
|
||||
|
||||
if (!values.appId || !values.appSecret) {
|
||||
throw new Error("L'App ID et l'App Secret Facebook sont requis");
|
||||
}
|
||||
|
||||
const [created] = await db.insert(facebookConfig)
|
||||
.values({ appId: values.appId, appSecret: values.appSecret })
|
||||
.returning();
|
||||
return { ...created, appSecret: decrypt(created.appSecret) };
|
||||
}
|
||||
|
||||
// TikTok Config
|
||||
async getTiktokConfig(): Promise<TiktokConfig | undefined> {
|
||||
const [config] = await db.select().from(tiktokConfig).limit(1);
|
||||
|
||||
Reference in new issue
Block a user