mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-22 14:38:11 +02:00
feat: Implement dynamic Freesound configuration (DB, API, UI)
This commit is contained in:
1 parent
1f7d0d1675
commit
62562d37f1
5 files changed
+240
-2
No files matched your search
@@ -56,9 +56,59 @@ export default function Settings() {
|
|||||||
const logoPublicId = (cloudinaryConfig as any).logoPublicId;
|
const logoPublicId = (cloudinaryConfig as any).logoPublicId;
|
||||||
setLogoPreview(`https://res.cloudinary.com/${cloudName}/image/upload/${logoPublicId}`);
|
setLogoPreview(`https://res.cloudinary.com/${cloudName}/image/upload/${logoPublicId}`);
|
||||||
}
|
}
|
||||||
|
if ((cloudinaryConfig as any).logoPublicId) {
|
||||||
|
const cloudName = (cloudinaryConfig as any).cloudName;
|
||||||
|
const logoPublicId = (cloudinaryConfig as any).logoPublicId;
|
||||||
|
setLogoPreview(`https://res.cloudinary.com/${cloudName}/image/upload/${logoPublicId}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [cloudinaryConfig]);
|
}, [cloudinaryConfig]);
|
||||||
|
|
||||||
|
const { data: freesoundConfig } = useQuery({
|
||||||
|
queryKey: ['/api/freesound/config'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const [freesoundClientId, setFreesoundClientId] = useState("");
|
||||||
|
const [freesoundClientSecret, setFreesoundClientSecret] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (freesoundConfig) {
|
||||||
|
setFreesoundClientId((freesoundConfig as any).clientId || "");
|
||||||
|
setFreesoundClientSecret((freesoundConfig as any).clientSecret || ""); // Usually masked
|
||||||
|
}
|
||||||
|
}, [freesoundConfig]);
|
||||||
|
|
||||||
|
const hasExistingFreesoundConfig = !!freesoundConfig;
|
||||||
|
|
||||||
|
const saveFreesoundMutation = useMutation({
|
||||||
|
mutationFn: () => {
|
||||||
|
const payload: any = {
|
||||||
|
clientId: freesoundClientId,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (freesoundClientSecret && freesoundClientSecret.trim() !== "" && freesoundClientSecret !== "********") {
|
||||||
|
payload.clientSecret = freesoundClientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiRequest('POST', '/api/freesound/config', payload);
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['/api/freesound/config'] });
|
||||||
|
toast({
|
||||||
|
title: "Configuration sauvegardée",
|
||||||
|
description: "Vos paramètres Freesound ont été enregistrés",
|
||||||
|
});
|
||||||
|
// On garde l'affichage tel quel ou on remet masked si retourné
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
toast({
|
||||||
|
title: "Erreur",
|
||||||
|
description: "Impossible de sauvegarder la configuration Freesound",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (openrouterConfig) {
|
if (openrouterConfig) {
|
||||||
setOpenrouterModel((openrouterConfig as any).model || "anthropic/claude-3.5-sonnet");
|
setOpenrouterModel((openrouterConfig as any).model || "anthropic/claude-3.5-sonnet");
|
||||||
@@ -459,6 +509,60 @@ export default function Settings() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<Card className="rounded-2xl border-border/50 shadow-lg">
|
||||||
|
<CardHeader className="p-6">
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<SettingsIcon className="w-5 h-5" />
|
||||||
|
Configuration Freesound (Musique)
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Configurez l'API Freesound pour la recherche de musique dans les Reels
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="freesoundClientId">Client ID</Label>
|
||||||
|
<Input
|
||||||
|
id="freesoundClientId"
|
||||||
|
type="text"
|
||||||
|
value={freesoundClientId}
|
||||||
|
onChange={(e) => setFreesoundClientId(e.target.value)}
|
||||||
|
placeholder="Votre Client ID Freesound"
|
||||||
|
data-testid="input-freesound-client-id"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Disponible dans vos paramètres d'API Freesound
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="freesoundClientSecret">Client Secret</Label>
|
||||||
|
<Input
|
||||||
|
id="freesoundClientSecret"
|
||||||
|
type="password"
|
||||||
|
value={freesoundClientSecret}
|
||||||
|
onChange={(e) => setFreesoundClientSecret(e.target.value)}
|
||||||
|
placeholder={hasExistingFreesoundConfig ? "••••••••" : "Votre Client Secret"}
|
||||||
|
data-testid="input-freesound-client-secret"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{hasExistingFreesoundConfig ? (
|
||||||
|
<span className="text-green-600 dark:text-green-400">✓ Configuré (Laissez vide pour conserver)</span>
|
||||||
|
) : (
|
||||||
|
"Votre clé secrète ne sera jamais exposée"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
onClick={() => saveFreesoundMutation.mutate()}
|
||||||
|
disabled={saveFreesoundMutation.isPending || !freesoundClientId}
|
||||||
|
data-testid="button-save-freesound"
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{saveFreesoundMutation.isPending ? "Enregistrement..." : "Enregistrer Freesound"}
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Card className="rounded-2xl border-border/50 shadow-lg">
|
<Card className="rounded-2xl border-border/50 shadow-lg">
|
||||||
<CardHeader className="p-6">
|
<CardHeader className="p-6">
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -187,6 +187,34 @@ app.use((req, res, next) => {
|
|||||||
const jamendoClientId = process.env.JAMENDO_CLIENT_ID || '5b6bba49';
|
const jamendoClientId = process.env.JAMENDO_CLIENT_ID || '5b6bba49';
|
||||||
jamendoService.configure(jamendoClientId);
|
jamendoService.configure(jamendoClientId);
|
||||||
|
|
||||||
|
// Configurer le service FreeSound pour les Reels
|
||||||
|
// 1. Essayer de charger depuis la DB (suppose un utilisateur admin ou premier utilisateur)
|
||||||
|
// Note: Dans un contexte multi-utilisateur, cela devrait être fait par requête.
|
||||||
|
// Pour l'instant, on charge la config du premier user trouvé ou via variable d'env.
|
||||||
|
|
||||||
|
let freesoundConfigured = false;
|
||||||
|
try {
|
||||||
|
const { storage } = await import("./storage");
|
||||||
|
const allUsers = await storage.getAllUsers();
|
||||||
|
if (allUsers.length > 0) {
|
||||||
|
const dbConfig = await storage.getFreesoundConfig(allUsers[0].id);
|
||||||
|
if (dbConfig && dbConfig.clientId && dbConfig.clientSecret) {
|
||||||
|
freeSoundService.configure(dbConfig.clientId, dbConfig.clientSecret);
|
||||||
|
freesoundConfigured = true;
|
||||||
|
log('🎵 FreeSound configured from Database');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Failed to load Freesound config from DB:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!freesoundConfigured && process.env.FREESOUND_CLIENT_ID && process.env.FREESOUND_CLIENT_SECRET) {
|
||||||
|
freeSoundService.configure(process.env.FREESOUND_CLIENT_ID, process.env.FREESOUND_CLIENT_SECRET);
|
||||||
|
log('🎵 FreeSound configured from Environment Variables');
|
||||||
|
} else if (!freesoundConfigured) {
|
||||||
|
log('⚠️ FreeSound credentials not found - music search will be disabled');
|
||||||
|
}
|
||||||
|
|
||||||
// Configurer le service FFmpeg pour le traitement vidéo (si configuré)
|
// Configurer le service FFmpeg pour le traitement vidéo (si configuré)
|
||||||
if (process.env.FFMPEG_API_URL && process.env.FFMPEG_API_KEY) {
|
if (process.env.FFMPEG_API_URL && process.env.FFMPEG_API_KEY) {
|
||||||
ffmpegService.configure(process.env.FFMPEG_API_URL, process.env.FFMPEG_API_KEY);
|
ffmpegService.configure(process.env.FFMPEG_API_URL, process.env.FFMPEG_API_KEY);
|
||||||
|
|||||||
+56
-2
@@ -10,8 +10,9 @@ import passport from "./auth";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { openRouterService } from "./services/openrouter";
|
import { openRouterService } from "./services/openrouter";
|
||||||
import { cloudinaryService } from "./services/cloudinary";
|
import { cloudinaryService } from "./services/cloudinary";
|
||||||
import { insertPostSchema, insertScheduledPostSchema, insertSocialPageSchema, insertAiGenerationSchema, insertCloudinaryConfigSchema, updateCloudinaryConfigSchema, insertOpenrouterConfigSchema, updateOpenrouterConfigSchema, insertUserSchema, postMedia, type SocialPage } from "@shared/schema";
|
import { insertPostSchema, insertScheduledPostSchema, insertSocialPageSchema, insertAiGenerationSchema, insertCloudinaryConfigSchema, updateCloudinaryConfigSchema, insertOpenrouterConfigSchema, updateOpenrouterConfigSchema, insertFreesoundConfigSchema, updateFreesoundConfigSchema, insertUserSchema, postMedia, type SocialPage } from "@shared/schema";
|
||||||
import type { User, InsertUser, ScheduledPost } from "@shared/schema";
|
import type { User, InsertUser, ScheduledPost, FreesoundConfig } from "@shared/schema";
|
||||||
|
import { freeSoundService } from "./services/freesound";
|
||||||
import { analyticsRouter } from "./routes/analytics";
|
import { analyticsRouter } from "./routes/analytics";
|
||||||
import { reelsRouter } from "./routes/reels";
|
import { reelsRouter } from "./routes/reels";
|
||||||
|
|
||||||
@@ -346,6 +347,59 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
// Analytics Routes
|
// Analytics Routes
|
||||||
app.use("/api/analytics", requireAuth, analyticsRouter);
|
app.use("/api/analytics", requireAuth, analyticsRouter);
|
||||||
|
|
||||||
|
// Freesound Configuration Routes
|
||||||
|
app.get("/api/freesound/config", requireAuth, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const user = req.user as User;
|
||||||
|
const config = await storage.getFreesoundConfig(user.id);
|
||||||
|
if (!config) {
|
||||||
|
return res.json(null);
|
||||||
|
}
|
||||||
|
return res.json({
|
||||||
|
...config,
|
||||||
|
clientSecret: config.clientSecret ? "********" : "",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching Freesound config:", error);
|
||||||
|
res.status(500).json({ error: "Failed to fetch configuration" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/api/freesound/config", requireAuth, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const user = req.user as User;
|
||||||
|
const data = updateFreesoundConfigSchema.parse(req.body);
|
||||||
|
|
||||||
|
let config = await storage.getFreesoundConfig(user.id);
|
||||||
|
const updateData: any = { ...data };
|
||||||
|
|
||||||
|
if (!updateData.clientSecret || updateData.clientSecret === "********") {
|
||||||
|
delete updateData.clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config) {
|
||||||
|
config = await storage.updateFreesoundConfig(user.id, updateData);
|
||||||
|
} else {
|
||||||
|
if (!data.clientId || !data.clientSecret) {
|
||||||
|
return res.status(400).json({ error: "Client ID and Secret are required" });
|
||||||
|
}
|
||||||
|
config = await storage.createFreesoundConfig({
|
||||||
|
...data as any,
|
||||||
|
userId: user.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config && config.clientId && config.clientSecret) {
|
||||||
|
freeSoundService.configure(config.clientId, config.clientSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error saving Freesound config:", error);
|
||||||
|
res.status(500).json({ error: "Failed to save configuration" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Reels & Music Routes
|
// Reels & Music Routes
|
||||||
app.use("/api", requireAuth, reelsRouter);
|
app.use("/api", requireAuth, reelsRouter);
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ import {
|
|||||||
musicFavorites,
|
musicFavorites,
|
||||||
type MusicFavorite,
|
type MusicFavorite,
|
||||||
type InsertMusicFavorite,
|
type InsertMusicFavorite,
|
||||||
|
freesoundConfig,
|
||||||
|
type FreesoundConfig,
|
||||||
|
type InsertFreesoundConfig,
|
||||||
} from "@shared/schema";
|
} from "@shared/schema";
|
||||||
import { db } from "./db";
|
import { db } from "./db";
|
||||||
import { eq, and, gte, lte, desc, asc, isNull, inArray } from "drizzle-orm";
|
import { eq, and, gte, lte, desc, asc, isNull, inArray } from "drizzle-orm";
|
||||||
@@ -107,6 +110,11 @@ export interface IStorage {
|
|||||||
addMusicFavorite(favorite: InsertMusicFavorite): Promise<MusicFavorite>;
|
addMusicFavorite(favorite: InsertMusicFavorite): Promise<MusicFavorite>;
|
||||||
removeMusicFavorite(userId: string, trackId: string): Promise<void>;
|
removeMusicFavorite(userId: string, trackId: string): Promise<void>;
|
||||||
isMusicFavorite(userId: string, trackId: string): Promise<boolean>;
|
isMusicFavorite(userId: string, trackId: string): Promise<boolean>;
|
||||||
|
|
||||||
|
// Freesound Config
|
||||||
|
getFreesoundConfig(userId: string): Promise<FreesoundConfig | undefined>;
|
||||||
|
createFreesoundConfig(config: InsertFreesoundConfig): Promise<FreesoundConfig>;
|
||||||
|
updateFreesoundConfig(userId: string, config: Partial<InsertFreesoundConfig>): Promise<FreesoundConfig>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DatabaseStorage implements IStorage {
|
export class DatabaseStorage implements IStorage {
|
||||||
@@ -489,6 +497,25 @@ export class DatabaseStorage implements IStorage {
|
|||||||
);
|
);
|
||||||
return !!fav;
|
return !!fav;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Freesound Config
|
||||||
|
async getFreesoundConfig(userId: string): Promise<FreesoundConfig | undefined> {
|
||||||
|
const [config] = await db.select().from(freesoundConfig).where(eq(freesoundConfig.userId, userId));
|
||||||
|
return config || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createFreesoundConfig(config: InsertFreesoundConfig): Promise<FreesoundConfig> {
|
||||||
|
const [newConfig] = await db.insert(freesoundConfig).values(config).returning();
|
||||||
|
return newConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateFreesoundConfig(userId: string, config: Partial<InsertFreesoundConfig>): Promise<FreesoundConfig> {
|
||||||
|
const [updated] = await db.update(freesoundConfig)
|
||||||
|
.set({ ...config, updatedAt: new Date() })
|
||||||
|
.where(eq(freesoundConfig.userId, userId))
|
||||||
|
.returning();
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const storage = new DatabaseStorage();
|
export const storage = new DatabaseStorage();
|
||||||
@@ -131,6 +131,15 @@ export const openrouterConfig = pgTable("openrouter_config", {
|
|||||||
updatedAt: timestamp("updated_at").defaultNow(),
|
updatedAt: timestamp("updated_at").defaultNow(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const freesoundConfig = pgTable("freesound_config", {
|
||||||
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
||||||
|
userId: varchar("user_id").notNull().unique().references(() => users.id, { onDelete: "cascade" }),
|
||||||
|
clientId: text("client_id").notNull(),
|
||||||
|
clientSecret: text("client_secret").notNull(),
|
||||||
|
createdAt: timestamp("created_at").defaultNow(),
|
||||||
|
updatedAt: timestamp("updated_at").defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
export const media = pgTable("media", {
|
export const media = pgTable("media", {
|
||||||
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
||||||
userId: varchar("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
userId: varchar("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
||||||
@@ -250,6 +259,7 @@ export const usersRelations = relations(users, ({ many, one }) => ({
|
|||||||
pagePermissions: many(userPagePermissions),
|
pagePermissions: many(userPagePermissions),
|
||||||
cloudinaryConfig: one(cloudinaryConfig),
|
cloudinaryConfig: one(cloudinaryConfig),
|
||||||
openrouterConfig: one(openrouterConfig),
|
openrouterConfig: one(openrouterConfig),
|
||||||
|
freesoundConfig: one(freesoundConfig),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const cloudinaryConfigRelations = relations(cloudinaryConfig, ({ one }) => ({
|
export const cloudinaryConfigRelations = relations(cloudinaryConfig, ({ one }) => ({
|
||||||
@@ -425,4 +435,19 @@ export type InsertPostMedia = z.infer<typeof insertPostMediaSchema>;
|
|||||||
export type MusicFavorite = typeof musicFavorites.$inferSelect;
|
export type MusicFavorite = typeof musicFavorites.$inferSelect;
|
||||||
export type InsertMusicFavorite = z.infer<typeof insertMusicFavoriteSchema>;
|
export type InsertMusicFavorite = z.infer<typeof insertMusicFavoriteSchema>;
|
||||||
|
|
||||||
|
export const insertFreesoundConfigSchema = createInsertSchema(freesoundConfig).omit({
|
||||||
|
id: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const updateFreesoundConfigSchema = insertFreesoundConfigSchema.partial({
|
||||||
|
clientId: true,
|
||||||
|
clientSecret: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type FreesoundConfig = typeof freesoundConfig.$inferSelect;
|
||||||
|
export type InsertFreesoundConfig = z.infer<typeof insertFreesoundConfigSchema>;
|
||||||
|
export type UpdateFreesoundConfig = z.infer<typeof updateFreesoundConfigSchema>;
|
||||||
|
|
||||||
|
|
||||||
Reference in new issue
Block a user