feat: Implement analytics, media management, and third-party service configurations with new settings pages.

This commit is contained in:
Michael committed 2026-03-04 14:25:02 +01:00
1 parent cb0ff8a0ea
commit 82befb2264
5 files changed
+46 -49

No files matched your search

+11
View File
@@ -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>
+11
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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, {