import { BadRequestException, Body, Controller, Get, Patch, Query, Req, UploadedFile, UseGuards, UseInterceptors } from "@nestjs/common"; import type { Request } from "express"; import { FileInterceptor } from "@nestjs/platform-express"; import { diskStorage } from "multer"; import path from "node:path"; import { randomBytes } from "node:crypto"; import { JwtAuthGuard, PermissionsGuard, RequirePermissions, TenantGuard } from "../auth/guards"; import { UpdateTenantSettingsDto } from "./dto"; import { SettingsService } from "./settings.service"; import { PrismaService } from "../../prisma/prisma.service"; @Controller("settings") export class SettingsController { constructor( private readonly service: SettingsService, private readonly prisma: PrismaService ) {} @UseGuards(JwtAuthGuard, TenantGuard, PermissionsGuard) @RequirePermissions("settings.read") @Get() get(@Req() req: Request) { const tenantId = (req.user as any).tenantId as string; return this.service.get(tenantId); } @UseGuards(JwtAuthGuard, TenantGuard, PermissionsGuard) @RequirePermissions("settings.write") @Patch() update(@Req() req: Request, @Body() dto: UpdateTenantSettingsDto) { const tenantId = (req.user as any).tenantId as string; return this.service.update(tenantId, dto); } @UseGuards(JwtAuthGuard, TenantGuard, PermissionsGuard) @RequirePermissions("settings.write") @Patch("logo") @UseInterceptors( FileInterceptor("file", { storage: diskStorage({ destination: (_req, _file, cb) => { const dir = process.env.UPLOADS_DIR ?? path.join(process.cwd(), "uploads"); cb(null, dir); }, filename: (_req, file, cb) => { const ext = path.extname(file.originalname || "").slice(0, 10); cb(null, `logo-${Date.now()}-${randomBytes(6).toString("hex")}${ext}`); } }), limits: { fileSize: 5 * 1024 * 1024 } }) ) async uploadLogo(@Req() req: Request, @UploadedFile() file?: any) { const tenantId = (req.user as any).tenantId as string; if (!file) throw new BadRequestException("Missing file"); const base = process.env.PUBLIC_API_URL ?? ""; const url = `${base.replace(/\/$/, "")}/uploads/${encodeURIComponent(file.filename)}`; return this.service.setLogo(tenantId, url); } } @Controller("public/branding") export class PublicBrandingController { constructor(private readonly prisma: PrismaService) {} @Get() async get(@Query("tenantSlug") tenantSlug?: string) { const slug = tenantSlug ?? process.env.DEFAULT_TENANT_SLUG ?? ""; if (!slug) throw new BadRequestException("Missing tenantSlug"); const tenant = await this.prisma.tenant.findUnique({ where: { slug } }); if (!tenant) throw new BadRequestException("Tenant not found"); const settings = await this.prisma.tenantSetting.findUnique({ where: { tenantId: tenant.id } }); return { tenant: { id: tenant.id, slug: tenant.slug, name: tenant.name }, branding: { appName: settings?.appName ?? tenant.name, logoUrl: settings?.logoUrl ?? null }, modules: (settings?.modules as any) ?? null }; } }