import { BadRequestException, Body, Controller, Delete, ForbiddenException, Get, Param, Patch, Post, Query, UploadedFile, UseGuards, UseInterceptors, } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { FileInterceptor } from '@nestjs/platform-express'; import { ProductType } from '@prisma/client'; import { diskStorage } from 'multer'; import { extname } from 'path'; import { JwtAuthGuard } from '../auth/jwt-auth.guard'; import { PermissionsGuard } from '../auth/permissions.guard'; import { RequirePermissions } from '../auth/permissions.decorator'; import { CurrentUser } from '../auth/current-user.decorator'; import { productReadPermission, productWritePermission, userHasPermission, } from '../auth/permissions'; import { AVATARS_DIR, ensureUploadsDirs, publicApiBase, } from '../uploads/uploads.util'; import { CreateTenantDto } from './dto/create-tenant.dto'; import { UpdateTenantDto } from './dto/update-tenant.dto'; import { TenantsService } from './tenants.service'; const IMAGE_MIME = /^image\/(jpeg|png|gif|webp|svg\+xml)$/i; type AuthUser = { id: string; role?: string; permissions?: unknown }; @Controller('tenants') @UseGuards(JwtAuthGuard, PermissionsGuard) export class TenantsController { constructor( private readonly tenantsService: TenantsService, private readonly config: ConfigService, ) {} @Get() @RequirePermissions('hamkar:read', 'khanegi:read', 'others:read', { mode: 'any' }) async findAll( @Query('product') product?: ProductType, @CurrentUser() user?: AuthUser, ) { if (product) { this.assertProductAccess(user, product, 'read'); return this.tenantsService.findAll(product); } const rows = await this.tenantsService.findAll(); return rows.filter((row) => userHasPermission(user || {}, productReadPermission(row.product)), ); } @Post('upload-avatar') @RequirePermissions('hamkar:write', 'khanegi:write', 'others:write', { mode: 'any' }) @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: (_req, _file, cb) => { ensureUploadsDirs(); cb(null, AVATARS_DIR); }, filename: (_req, file, cb) => { const safeExt = extname(file.originalname).toLowerCase() || '.png'; const name = `avatar_${Date.now()}_${Math.random().toString(36).slice(2, 8)}${safeExt}`; cb(null, name); }, }), limits: { fileSize: 2 * 1024 * 1024 }, fileFilter: (_req, file, cb) => { if (!IMAGE_MIME.test(file.mimetype)) { cb( new BadRequestException( 'فقط تصویر (jpg/png/gif/webp/svg) مجاز است', ) as any, false, ); return; } cb(null, true); }, }), ) uploadAvatar(@UploadedFile() file: Express.Multer.File) { if (!file) { throw new BadRequestException('فایل ارسال نشده است'); } const base = publicApiBase(this.config); return { url: `${base}/uploads/avatars/${file.filename}` }; } @Get(':id') @RequirePermissions('hamkar:read', 'khanegi:read', 'others:read', { mode: 'any' }) async findOne(@Param('id') id: string, @CurrentUser() user: AuthUser) { const tenant = await this.tenantsService.findOne(id); this.assertProductAccess(user, tenant.product, 'read'); return tenant; } @Post() @RequirePermissions('hamkar:write', 'khanegi:write', 'others:write', { mode: 'any' }) create(@Body() dto: CreateTenantDto, @CurrentUser() user: AuthUser) { this.assertProductAccess(user, dto.product, 'write'); return this.tenantsService.create(dto, user.id); } @Patch(':id') @RequirePermissions('hamkar:write', 'khanegi:write', 'others:write', { mode: 'any' }) async update( @Param('id') id: string, @Body() dto: UpdateTenantDto, @CurrentUser() user: AuthUser, ) { const tenant = await this.tenantsService.findOne(id); this.assertProductAccess(user, tenant.product, 'write'); return this.tenantsService.update(id, dto, user.id); } @Delete(':id') @RequirePermissions('hamkar:write', 'khanegi:write', 'others:write', { mode: 'any' }) async remove(@Param('id') id: string, @CurrentUser() user: AuthUser) { const tenant = await this.tenantsService.findOne(id); this.assertProductAccess(user, tenant.product, 'write'); return this.tenantsService.remove(id, user.id); } private assertProductAccess( user: AuthUser | undefined, product: string, mode: 'read' | 'write', ) { const code = mode === 'read' ? productReadPermission(product) : productWritePermission(product); if (!userHasPermission(user || {}, code)) { throw new ForbiddenException('شما به این محصول دسترسی ندارید'); } } }