Spaces:
Sleeping
Sleeping
| import { | |
| Controller, | |
| Get, | |
| Post, | |
| Put, | |
| Delete, | |
| Param, | |
| Body, | |
| Query, | |
| UseGuards, | |
| UseInterceptors, | |
| UploadedFile, | |
| HttpCode, | |
| HttpStatus, | |
| ParseIntPipe, | |
| Res, | |
| StreamableFile, | |
| } from '@nestjs/common'; | |
| import { | |
| ApiTags, | |
| ApiOperation, | |
| ApiResponse, | |
| ApiBearerAuth, | |
| ApiParam, | |
| ApiQuery, | |
| ApiBody, | |
| ApiConsumes, | |
| } from '@nestjs/swagger'; | |
| import { FileInterceptor } from '@nestjs/platform-express'; | |
| import type { Response } from 'express'; | |
| import { FilesService } from './files.service'; | |
| import { FilePermissionService } from './file-permission.service'; | |
| import { PermissionType } from './entities/file-permission.entity'; | |
| import { UploadFileDto } from './dto/upload-file.dto'; | |
| import { FileResponseDto } from './dto/file-response.dto'; | |
| import { FileVersionResponseDto } from './dto/file-version-response.dto'; | |
| import { FilePermissionDto } from './dto/file-permission.dto'; | |
| import { GrantPermissionDto } from './dto/grant-permission.dto'; | |
| import { FileSearchDto } from './dto/file-search.dto'; | |
| import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; | |
| import { CurrentUser } from '../../common/decorators/current-user.decorator'; | |
| import { User } from '../auth/entities/user.entity'; | |
| import { createReadStream } from 'fs'; | |
| ('📁 Files') | |
| ('api/files') | |
| (JwtAuthGuard) | |
| ('JWT-auth') | |
| export class FilesController { | |
| constructor( | |
| private readonly filesService: FilesService, | |
| private readonly filePermissionService: FilePermissionService, | |
| ) {} | |
| ('upload') | |
| (FileInterceptor('file')) | |
| (HttpStatus.CREATED) | |
| ({ | |
| summary: 'Upload a file', | |
| description: ` | |
| ## Upload File | |
| Uploads a file to the system. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: Any authenticated user | |
| ### Supported File Types | |
| Most common file types are supported including documents, images, videos, and archives. | |
| ### Notes | |
| - Maximum file size: 50MB (configurable) | |
| - Files are associated with the uploading user | |
| - Optional folder and course association | |
| `, | |
| }) | |
| ('multipart/form-data') | |
| ({ | |
| schema: { | |
| type: 'object', | |
| properties: { | |
| file: { type: 'string', format: 'binary', description: 'File to upload' }, | |
| folderId: { type: 'number', description: 'Optional folder ID' }, | |
| courseId: { type: 'number', description: 'Optional course ID' }, | |
| }, | |
| required: ['file'], | |
| }, | |
| }) | |
| ({ status: 201, description: 'File uploaded successfully' }) | |
| ({ status: 400, description: 'No file provided or invalid file type' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ status: 413, description: 'File too large' }) | |
| async uploadFile( | |
| () file: Express.Multer.File, | |
| () uploadDto: UploadFileDto, | |
| () user: User, | |
| ): Promise<FileResponseDto> { | |
| if (!file) { | |
| throw new Error('No file uploaded'); | |
| } | |
| return this.filesService.uploadFile( | |
| file, | |
| user.userId, | |
| uploadDto.folderId, | |
| ); | |
| } | |
| ('search') | |
| ({ | |
| summary: 'Search files', | |
| description: ` | |
| ## Search Files | |
| Searches for files by name, type, or other criteria. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: Any authenticated user | |
| ### Search Scope | |
| Only returns files the user has permission to access. | |
| `, | |
| }) | |
| ({ status: 200, description: 'Search results' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| async searchFiles( | |
| () searchDto: FileSearchDto, | |
| () user: User, | |
| ): Promise<FileResponseDto[]> { | |
| return this.filesService.searchFiles(searchDto, user.userId); | |
| } | |
| ('recent') | |
| ({ | |
| summary: 'Get recent files', | |
| description: ` | |
| ## Get Recently Accessed Files | |
| Returns files recently uploaded or accessed by the user. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: Any authenticated user | |
| `, | |
| }) | |
| ({ name: 'limit', required: false, type: Number, example: 10 }) | |
| ({ status: 200, description: 'Recent files' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| async getRecentFiles( | |
| () user: User, | |
| ('limit') limit?: number, | |
| ): Promise<FileResponseDto[]> { | |
| return this.filesService.getRecentFiles(user.userId, limit); | |
| } | |
| ('shared') | |
| ({ | |
| summary: 'Get shared files', | |
| description: ` | |
| ## Get Files Shared with User | |
| Returns files that others have shared with the current user. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: Any authenticated user | |
| `, | |
| }) | |
| ({ status: 200, description: 'Shared files' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| async getSharedFiles( | |
| () user: User, | |
| ): Promise<FileResponseDto[]> { | |
| return this.filesService.getSharedFiles(user.userId); | |
| } | |
| (':fileId') | |
| ({ | |
| summary: 'Get file metadata', | |
| description: ` | |
| ## Get File Information | |
| Retrieves metadata about a specific file. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: User with READ permission on the file | |
| ### Response Includes | |
| - File name, type, size | |
| - Upload date and uploader | |
| - Folder location | |
| - Version information | |
| `, | |
| }) | |
| ({ name: 'fileId', description: 'File ID', type: Number }) | |
| ({ status: 200, description: 'File metadata' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ status: 403, description: 'Forbidden - No read permission' }) | |
| ({ status: 404, description: 'File not found' }) | |
| async getFile( | |
| ('fileId', ParseIntPipe) fileId: number, | |
| () user: User, | |
| ): Promise<FileResponseDto> { | |
| return this.filesService.getFile(fileId, user.userId); | |
| } | |
| (':fileId/download') | |
| ({ | |
| summary: 'Download file', | |
| description: ` | |
| ## Download File | |
| Downloads the file content. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: User with READ permission on the file | |
| ### Response | |
| Returns the file as a binary stream with appropriate content headers. | |
| `, | |
| }) | |
| ({ name: 'fileId', description: 'File ID', type: Number }) | |
| ({ status: 200, description: 'File content' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ status: 403, description: 'Forbidden - No read permission' }) | |
| ({ status: 404, description: 'File not found' }) | |
| async downloadFile( | |
| ('fileId', ParseIntPipe) fileId: number, | |
| () user: User, | |
| ({ passthrough: true }) res: Response, | |
| ): Promise<StreamableFile> { | |
| const { file, filePath } = await this.filesService.downloadFile( | |
| fileId, | |
| user.userId, | |
| ); | |
| res.set({ | |
| 'Content-Type': file.mimeType, | |
| 'Content-Disposition': `attachment; filename="${file.originalFileName}"`, | |
| 'Content-Length': file.fileSize.toString(), | |
| }); | |
| const fileStream = createReadStream(filePath); | |
| return new StreamableFile(fileStream); | |
| } | |
| (':fileId') | |
| ({ | |
| summary: 'Update file', | |
| description: ` | |
| ## Update File Metadata | |
| Updates file name or folder location. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: User with WRITE permission on the file | |
| `, | |
| }) | |
| ({ name: 'fileId', description: 'File ID', type: Number }) | |
| ({ | |
| schema: { | |
| type: 'object', | |
| properties: { | |
| fileName: { type: 'string', description: 'New file name' }, | |
| folderId: { type: 'number', description: 'New folder ID' }, | |
| }, | |
| }, | |
| }) | |
| ({ status: 200, description: 'File updated' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ status: 403, description: 'Forbidden - No write permission' }) | |
| ({ status: 404, description: 'File not found' }) | |
| async updateFile( | |
| ('fileId', ParseIntPipe) fileId: number, | |
| () updates: { fileName?: string; folderId?: number }, | |
| () user: User, | |
| ): Promise<FileResponseDto> { | |
| return this.filesService.updateFile(fileId, user.userId, updates); | |
| } | |
| (':fileId') | |
| (HttpStatus.NO_CONTENT) | |
| ({ | |
| summary: 'Delete file', | |
| description: ` | |
| ## Delete File | |
| Permanently deletes a file. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: User with DELETE permission (usually owner) | |
| ### ⚠️ Warning | |
| This action is irreversible. All versions will be deleted. | |
| `, | |
| }) | |
| ({ name: 'fileId', description: 'File ID', type: Number }) | |
| ({ status: 204, description: 'File deleted' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ status: 403, description: 'Forbidden - No delete permission' }) | |
| ({ status: 404, description: 'File not found' }) | |
| async deleteFile( | |
| ('fileId', ParseIntPipe) fileId: number, | |
| () user: User, | |
| ): Promise<void> { | |
| return this.filesService.deleteFile(fileId, user.userId); | |
| } | |
| (':fileId/versions') | |
| (FileInterceptor('file')) | |
| (HttpStatus.CREATED) | |
| ({ | |
| summary: 'Upload new file version', | |
| description: ` | |
| ## Upload New Version | |
| Uploads a new version of an existing file. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: User with WRITE permission on the file | |
| ### Version Management | |
| - Previous versions are preserved | |
| - Version numbers auto-increment | |
| `, | |
| }) | |
| ({ name: 'fileId', description: 'File ID', type: Number }) | |
| ('multipart/form-data') | |
| ({ | |
| schema: { | |
| type: 'object', | |
| properties: { | |
| file: { type: 'string', format: 'binary' }, | |
| }, | |
| required: ['file'], | |
| }, | |
| }) | |
| ({ status: 201, description: 'Version created' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ status: 403, description: 'Forbidden - No write permission' }) | |
| ({ status: 404, description: 'File not found' }) | |
| async createVersion( | |
| ('fileId', ParseIntPipe) fileId: number, | |
| () file: Express.Multer.File, | |
| () user: User, | |
| ): Promise<FileVersionResponseDto> { | |
| if (!file) { | |
| throw new Error('No file uploaded'); | |
| } | |
| return this.filesService.createVersion(fileId, file, user.userId); | |
| } | |
| (':fileId/versions') | |
| ({ | |
| summary: 'List file versions', | |
| description: ` | |
| ## List File Versions | |
| Retrieves all versions of a file. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: User with READ permission on the file | |
| `, | |
| }) | |
| ({ name: 'fileId', description: 'File ID', type: Number }) | |
| ({ status: 200, description: 'List of versions' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ status: 403, description: 'Forbidden - No read permission' }) | |
| ({ status: 404, description: 'File not found' }) | |
| async getFileVersions( | |
| ('fileId', ParseIntPipe) fileId: number, | |
| () user: User, | |
| ): Promise<FileVersionResponseDto[]> { | |
| return this.filesService.getFileVersions(fileId, user.userId); | |
| } | |
| (':fileId/versions/:versionId/download') | |
| ({ | |
| summary: 'Download specific version', | |
| description: ` | |
| ## Download File Version | |
| Downloads a specific version of a file. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: User with READ permission on the file | |
| `, | |
| }) | |
| ({ name: 'fileId', description: 'File ID', type: Number }) | |
| ({ name: 'versionId', description: 'Version ID', type: Number }) | |
| ({ status: 200, description: 'Version content' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ status: 403, description: 'Forbidden - No read permission' }) | |
| ({ status: 404, description: 'File or version not found' }) | |
| async downloadVersion( | |
| ('fileId', ParseIntPipe) fileId: number, | |
| ('versionId', ParseIntPipe) versionId: number, | |
| () user: User, | |
| ({ passthrough: true }) res: Response, | |
| ): Promise<StreamableFile> { | |
| const { version, filePath } = await this.filesService.downloadVersion( | |
| fileId, | |
| versionId, | |
| user.userId, | |
| ); | |
| res.set({ | |
| 'Content-Type': 'application/octet-stream', | |
| 'Content-Disposition': `attachment; filename="version-${version.versionNumber}"`, | |
| 'Content-Length': version.fileSize.toString(), | |
| }); | |
| const fileStream = createReadStream(filePath); | |
| return new StreamableFile(fileStream); | |
| } | |
| (':fileId/permissions') | |
| ({ | |
| summary: 'Get file permissions', | |
| description: ` | |
| ## Get File Permissions | |
| Retrieves all permissions set on a file. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: User with READ permission on the file | |
| `, | |
| }) | |
| ({ name: 'fileId', description: 'File ID', type: Number }) | |
| ({ status: 200, description: 'List of permissions' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ status: 403, description: 'Forbidden' }) | |
| ({ status: 404, description: 'File not found' }) | |
| async getFilePermissions( | |
| ('fileId', ParseIntPipe) fileId: number, | |
| () user: User, | |
| ): Promise<FilePermissionDto[]> { | |
| // Check if user has permission to view permissions | |
| await this.filePermissionService.requirePermission( | |
| fileId, | |
| user.userId, | |
| PermissionType.READ, | |
| ); | |
| const permissions = await this.filePermissionService.getFilePermissions( | |
| fileId, | |
| ); | |
| return permissions.map((p) => ({ | |
| permissionId: p.permissionId, | |
| fileId: p.fileId, | |
| userId: p.userId, | |
| userName: p.user | |
| ? `${p.user.firstName} ${p.user.lastName}` | |
| : undefined, | |
| roleId: p.roleId, | |
| roleName: p.role?.roleName, | |
| permissionType: p.permissionType, | |
| grantedBy: p.grantedBy, | |
| granterName: p.granter | |
| ? `${p.granter.firstName} ${p.granter.lastName}` | |
| : undefined, | |
| createdAt: p.grantedAt, | |
| })); | |
| } | |
| (':fileId/permissions') | |
| (HttpStatus.CREATED) | |
| ({ | |
| summary: 'Grant file permission', | |
| description: ` | |
| ## Grant Permission on File | |
| Grants read, write, or delete permission to a user or role. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: User with WRITE permission (file owner or admin) | |
| ### Permission Types | |
| - READ: View and download file | |
| - WRITE: Edit and create versions | |
| - DELETE: Remove file | |
| `, | |
| }) | |
| ({ name: 'fileId', description: 'File ID', type: Number }) | |
| ({ type: GrantPermissionDto }) | |
| ({ status: 201, description: 'Permission granted' }) | |
| ({ status: 400, description: 'Invalid permission data' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ status: 403, description: 'Forbidden - Cannot grant permissions' }) | |
| ({ status: 404, description: 'File not found' }) | |
| async grantPermission( | |
| ('fileId', ParseIntPipe) fileId: number, | |
| () grantDto: GrantPermissionDto, | |
| () user: User, | |
| ): Promise<FilePermissionDto> { | |
| const permission = await this.filePermissionService.grantPermission( | |
| fileId, | |
| user.userId, | |
| grantDto.permissionType, | |
| user.userId, | |
| grantDto.userId, | |
| grantDto.roleId, | |
| ); | |
| return { | |
| permissionId: permission.permissionId, | |
| fileId: permission.fileId, | |
| userId: permission.userId, | |
| roleId: permission.roleId, | |
| permissionType: permission.permissionType, | |
| grantedBy: permission.grantedBy, | |
| createdAt: permission.grantedAt, | |
| }; | |
| } | |
| (':fileId/permissions/:permissionId') | |
| (HttpStatus.NO_CONTENT) | |
| ({ | |
| summary: 'Revoke file permission', | |
| description: ` | |
| ## Revoke Permission on File | |
| Removes a permission from a file. | |
| ### Access Control | |
| - **Authentication Required**: ✅ Yes (Bearer Token) | |
| - **Roles Required**: User with WRITE permission (file owner or admin) | |
| `, | |
| }) | |
| ({ name: 'fileId', description: 'File ID', type: Number }) | |
| ({ name: 'permissionId', description: 'Permission ID', type: Number }) | |
| ({ status: 204, description: 'Permission revoked' }) | |
| ({ status: 401, description: 'Unauthorized' }) | |
| ({ status: 403, description: 'Forbidden' }) | |
| ({ status: 404, description: 'Permission not found' }) | |
| async revokePermission( | |
| ('fileId', ParseIntPipe) fileId: number, | |
| ('permissionId', ParseIntPipe) permissionId: number, | |
| () user: User, | |
| ): Promise<void> { | |
| return this.filePermissionService.revokePermission(permissionId, user.userId); | |
| } | |
| } | |