mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-11 17:26:45 +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;
|
||||
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]);
|
||||
|
||||
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(() => {
|
||||
if (openrouterConfig) {
|
||||
setOpenrouterModel((openrouterConfig as any).model || "anthropic/claude-3.5-sonnet");
|
||||
@@ -459,6 +509,60 @@ export default function Settings() {
|
||||
</CardContent>
|
||||
</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">
|
||||
<CardHeader className="p-6">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
|
||||
@@ -187,6 +187,34 @@ app.use((req, res, next) => {
|
||||
const jamendoClientId = process.env.JAMENDO_CLIENT_ID || '5b6bba49';
|
||||
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é)
|
||||
if (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 { openRouterService } from "./services/openrouter";
|
||||
import { cloudinaryService } from "./services/cloudinary";
|
||||
import { insertPostSchema, insertScheduledPostSchema, insertSocialPageSchema, insertAiGenerationSchema, insertCloudinaryConfigSchema, updateCloudinaryConfigSchema, insertOpenrouterConfigSchema, updateOpenrouterConfigSchema, insertUserSchema, postMedia, type SocialPage } from "@shared/schema";
|
||||
import type { User, InsertUser, ScheduledPost } 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, FreesoundConfig } from "@shared/schema";
|
||||
import { freeSoundService } from "./services/freesound";
|
||||
import { analyticsRouter } from "./routes/analytics";
|
||||
import { reelsRouter } from "./routes/reels";
|
||||
|
||||
@@ -346,6 +347,59 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
// Analytics Routes
|
||||
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
|
||||
app.use("/api", requireAuth, reelsRouter);
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ import {
|
||||
musicFavorites,
|
||||
type MusicFavorite,
|
||||
type InsertMusicFavorite,
|
||||
freesoundConfig,
|
||||
type FreesoundConfig,
|
||||
type InsertFreesoundConfig,
|
||||
} from "@shared/schema";
|
||||
import { db } from "./db";
|
||||
import { eq, and, gte, lte, desc, asc, isNull, inArray } from "drizzle-orm";
|
||||
@@ -107,6 +110,11 @@ export interface IStorage {
|
||||
addMusicFavorite(favorite: InsertMusicFavorite): Promise<MusicFavorite>;
|
||||
removeMusicFavorite(userId: string, trackId: string): Promise<void>;
|
||||
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 {
|
||||
@@ -489,6 +497,25 @@ export class DatabaseStorage implements IStorage {
|
||||
);
|
||||
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();
|
||||
@@ -131,6 +131,15 @@ export const openrouterConfig = pgTable("openrouter_config", {
|
||||
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", {
|
||||
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
||||
userId: varchar("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
||||
@@ -250,6 +259,7 @@ export const usersRelations = relations(users, ({ many, one }) => ({
|
||||
pagePermissions: many(userPagePermissions),
|
||||
cloudinaryConfig: one(cloudinaryConfig),
|
||||
openrouterConfig: one(openrouterConfig),
|
||||
freesoundConfig: one(freesoundConfig),
|
||||
}));
|
||||
|
||||
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 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