mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-11 17:26:45 +02:00
feat: Implement analytics, media management, and third-party service configurations with new settings pages.
This commit is contained in:
1 parent
cb0ff8a0ea
commit
82befb2264
5 files changed
+46
-49
No files matched your search
@@ -27,6 +27,13 @@ export default function SettingsMobile() {
|
||||
const [logoPreview, setLogoPreview] = useState<string | null>(null);
|
||||
const { toast } = useToast();
|
||||
|
||||
const { data: session } = useQuery<{ id: string; username: string; role: string }>({
|
||||
queryKey: ["/api/auth/session"],
|
||||
retry: false,
|
||||
});
|
||||
|
||||
const isAdmin = session?.role === "admin";
|
||||
|
||||
const { data: cloudinaryConfig } = useQuery({
|
||||
queryKey: ['/api/cloudinary/config'],
|
||||
});
|
||||
@@ -355,6 +362,7 @@ export default function SettingsMobile() {
|
||||
</Card>
|
||||
|
||||
{/* Cloudinary */}
|
||||
{isAdmin && (
|
||||
<Card className="rounded-2xl border-border/50 shadow-lg">
|
||||
<CardHeader className="p-5">
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
@@ -418,8 +426,10 @@ export default function SettingsMobile() {
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Logo */}
|
||||
{isAdmin && (
|
||||
<Card className="rounded-2xl border-border/50 shadow-lg">
|
||||
<CardHeader className="p-5">
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
@@ -518,6 +528,7 @@ export default function SettingsMobile() {
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -29,6 +29,13 @@ export default function Settings() {
|
||||
const [ffmpegApiKey, setFfmpegApiKey] = useState("");
|
||||
const { toast } = useToast();
|
||||
|
||||
const { data: session } = useQuery<{ id: string; username: string; role: string }>({
|
||||
queryKey: ["/api/auth/session"],
|
||||
retry: false,
|
||||
});
|
||||
|
||||
const isAdmin = session?.role === "admin";
|
||||
|
||||
const { data: cloudinaryConfig } = useQuery({
|
||||
queryKey: ['/api/cloudinary/config'],
|
||||
});
|
||||
@@ -448,6 +455,7 @@ export default function Settings() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{isAdmin && (
|
||||
<Card className="rounded-2xl border-border/50 shadow-lg">
|
||||
<CardHeader className="p-6">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
@@ -508,6 +516,7 @@ export default function Settings() {
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<Card className="rounded-2xl border-border/50 shadow-lg">
|
||||
<CardHeader className="p-6">
|
||||
@@ -623,6 +632,7 @@ export default function Settings() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{isAdmin && (
|
||||
<Card className="rounded-2xl border-border/50 shadow-lg">
|
||||
<CardHeader className="p-6">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
@@ -720,6 +730,7 @@ export default function Settings() {
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<Card className="rounded-2xl border-border/50 shadow-lg">
|
||||
<CardHeader className="p-6">
|
||||
|
||||
+15
-31
@@ -1447,11 +1447,10 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
});
|
||||
|
||||
// Cloudinary Config
|
||||
app.get("/api/cloudinary/config", requireAdmin, async (req, res) => {
|
||||
app.get("/api/cloudinary/config", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const user = req.user as User;
|
||||
const userId = user.id;
|
||||
const config = await storage.getCloudinaryConfig(userId);
|
||||
// Configuration est maintenant globale, plus de "userId"
|
||||
const config = await storage.getCloudinaryConfig();
|
||||
|
||||
if (!config) {
|
||||
return res.json(null);
|
||||
@@ -1468,19 +1467,13 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
|
||||
app.post("/api/cloudinary/config", requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const user = req.user as User;
|
||||
const userId = user.id;
|
||||
|
||||
// Check if config already exists
|
||||
const existingConfig = await storage.getCloudinaryConfig(userId);
|
||||
// Check if config already exists globally
|
||||
const existingConfig = await storage.getCloudinaryConfig();
|
||||
|
||||
let config;
|
||||
if (existingConfig) {
|
||||
// Pour les mises à jour, utiliser le schéma qui rend les secrets optionnels
|
||||
const updateData = updateCloudinaryConfigSchema.parse({
|
||||
...req.body,
|
||||
userId,
|
||||
});
|
||||
const updateData = updateCloudinaryConfigSchema.parse(req.body);
|
||||
|
||||
// Si apiKey/apiSecret ne sont pas fournis, garder les anciens
|
||||
const finalData = {
|
||||
@@ -1489,13 +1482,10 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
apiSecret: updateData.apiSecret || existingConfig.apiSecret,
|
||||
};
|
||||
|
||||
config = await storage.updateCloudinaryConfig(userId, finalData);
|
||||
config = await storage.updateCloudinaryConfig(finalData);
|
||||
} else {
|
||||
// Pour les créations, exiger tous les champs
|
||||
const configData = insertCloudinaryConfigSchema.parse({
|
||||
...req.body,
|
||||
userId,
|
||||
});
|
||||
const configData = insertCloudinaryConfigSchema.parse(req.body);
|
||||
config = await storage.createCloudinaryConfig(configData);
|
||||
}
|
||||
|
||||
@@ -1515,11 +1505,8 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
return res.status(400).json({ error: "No logo file uploaded" });
|
||||
}
|
||||
|
||||
const user = req.user as User;
|
||||
const userId = user.id;
|
||||
|
||||
// Check if Cloudinary is configured
|
||||
const cloudinaryConfig = await storage.getCloudinaryConfig(userId);
|
||||
// Check if Cloudinary is configured globally
|
||||
const cloudinaryConfig = await storage.getCloudinaryConfig();
|
||||
if (!cloudinaryConfig) {
|
||||
return res.status(400).json({
|
||||
error: "Cloudinary not configured. Please configure Cloudinary first."
|
||||
@@ -1542,8 +1529,8 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
req.file.originalname
|
||||
);
|
||||
|
||||
// Update config with new logo public ID
|
||||
const updatedConfig = await storage.updateCloudinaryConfig(userId, {
|
||||
// Update global config with new logo public ID
|
||||
const updatedConfig = await storage.updateCloudinaryConfig({
|
||||
logoPublicId: uploadResult.publicId,
|
||||
});
|
||||
|
||||
@@ -1561,10 +1548,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
// Delete company logo
|
||||
app.delete("/api/cloudinary/logo", requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const user = req.user as User;
|
||||
const userId = user.id;
|
||||
|
||||
const cloudinaryConfig = await storage.getCloudinaryConfig(userId);
|
||||
const cloudinaryConfig = await storage.getCloudinaryConfig();
|
||||
if (!cloudinaryConfig || !cloudinaryConfig.logoPublicId) {
|
||||
return res.status(404).json({ error: "No logo found" });
|
||||
}
|
||||
@@ -1572,8 +1556,8 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
// Delete from Cloudinary
|
||||
await cloudinaryService.deleteLogo(cloudinaryConfig.logoPublicId);
|
||||
|
||||
// Remove from config
|
||||
await storage.updateCloudinaryConfig(userId, {
|
||||
// Remove from global config
|
||||
await storage.updateCloudinaryConfig({
|
||||
logoPublicId: null,
|
||||
});
|
||||
|
||||
|
||||
+8
-11
@@ -87,10 +87,9 @@ export interface IStorage {
|
||||
createAiGeneration(generation: InsertAiGeneration): Promise<AiGeneration>;
|
||||
|
||||
// Cloudinary Config
|
||||
getCloudinaryConfig(userId: string): Promise<CloudinaryConfig | undefined>;
|
||||
getAnyCloudinaryConfig(): Promise<CloudinaryConfig | undefined>;
|
||||
getCloudinaryConfig(): Promise<CloudinaryConfig | undefined>;
|
||||
createCloudinaryConfig(config: InsertCloudinaryConfig): Promise<CloudinaryConfig>;
|
||||
updateCloudinaryConfig(userId: string, config: Partial<InsertCloudinaryConfig>): Promise<CloudinaryConfig>;
|
||||
updateCloudinaryConfig(config: Partial<InsertCloudinaryConfig>): Promise<CloudinaryConfig>;
|
||||
|
||||
// OpenRouter Config
|
||||
getOpenrouterConfig(userId: string): Promise<OpenrouterConfig | undefined>;
|
||||
@@ -406,12 +405,7 @@ export class DatabaseStorage implements IStorage {
|
||||
}
|
||||
|
||||
// Cloudinary Config
|
||||
async getCloudinaryConfig(userId: string): Promise<CloudinaryConfig | undefined> {
|
||||
const [config] = await db.select().from(cloudinaryConfig).where(eq(cloudinaryConfig.userId, userId));
|
||||
return config || undefined;
|
||||
}
|
||||
|
||||
async getAnyCloudinaryConfig(): Promise<CloudinaryConfig | undefined> {
|
||||
async getCloudinaryConfig(): Promise<CloudinaryConfig | undefined> {
|
||||
const [config] = await db.select().from(cloudinaryConfig).limit(1);
|
||||
return config || undefined;
|
||||
}
|
||||
@@ -421,10 +415,13 @@ export class DatabaseStorage implements IStorage {
|
||||
return newConfig;
|
||||
}
|
||||
|
||||
async updateCloudinaryConfig(userId: string, config: Partial<InsertCloudinaryConfig>): Promise<CloudinaryConfig> {
|
||||
async updateCloudinaryConfig(config: Partial<InsertCloudinaryConfig>): Promise<CloudinaryConfig> {
|
||||
const existingConfig = await this.getCloudinaryConfig();
|
||||
if (!existingConfig) throw new Error("Cloudinary config not initialized");
|
||||
|
||||
const [updated] = await db.update(cloudinaryConfig)
|
||||
.set({ ...config, updatedAt: new Date() })
|
||||
.where(eq(cloudinaryConfig.userId, userId))
|
||||
.where(eq(cloudinaryConfig.id, existingConfig.id))
|
||||
.returning();
|
||||
return updated;
|
||||
}
|
||||
|
||||
+1
-7
@@ -113,7 +113,6 @@ export type InsertUserPagePermission = z.infer<typeof insertUserPagePermissionSc
|
||||
|
||||
export const cloudinaryConfig = pgTable("cloudinary_config", {
|
||||
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
||||
userId: varchar("user_id").notNull().unique().references(() => users.id, { onDelete: "cascade" }),
|
||||
cloudName: text("cloud_name").notNull(),
|
||||
apiKey: text("api_key").notNull(),
|
||||
apiSecret: text("api_secret").notNull(),
|
||||
@@ -267,12 +266,7 @@ export const usersRelations = relations(users, ({ many, one }) => ({
|
||||
freesoundConfig: one(freesoundConfig),
|
||||
}));
|
||||
|
||||
export const cloudinaryConfigRelations = relations(cloudinaryConfig, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [cloudinaryConfig.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
// Pas de relation User pour CloudinaryConfig (maintenant global)
|
||||
|
||||
export const openrouterConfigRelations = relations(openrouterConfig, ({ one }) => ({
|
||||
user: one(users, {
|
||||
|
||||
Reference in new issue
Block a user