diff --git a/ANALYTICS_SYSTEM.md b/ANALYTICS_SYSTEM.md deleted file mode 100644 index 50d40aa..0000000 --- a/ANALYTICS_SYSTEM.md +++ /dev/null @@ -1,205 +0,0 @@ -# Analytics System Implementation - -## Overview -A comprehensive analytics system has been implemented for FileFlow to track user activity, storage usage, and file operations. - -## Architecture - -### 1. Queue System (`analytics-queue.ts`) -- **Queue Name**: `fileflow-analytics-queue` -- **Event Types**: - - `FILE_UPLOADED` - Track file uploads - - `FILE_DELETED` - Track file deletions - - `FILE_DOWNLOADED` - Track file downloads - - `FILE_SHARED` - Track file shares - - `PUBLIC_LINK_CREATED` - Track public link creation - - `FOLDER_CREATED` - Track folder creation - -- **Configuration**: - - 5 retry attempts with exponential backoff - - Concurrent processing with 5 workers - - Auto-cleanup of completed jobs - - Keep last 10 failed jobs for debugging - -### 2. Repository (`analytics.repository.ts`) -**Main Functions**: -- `getTodaysAnalytics()` - Get or create today's analytics record -- `calculateUserStorage()` - Calculate total storage and categorize by file type -- `recordFileUpload()` - Update analytics when file is uploaded -- `recordFileDelete()` - Update analytics when file is deleted -- `recordFolderCreate()` - Update analytics when folder is created -- `recordFileDownload()` - Track downloads -- `recordFileShare()` - Track shares -- `recordPublicLinkCreate()` - Track public links -- `getAnalyticsByDateRange()` - Get analytics for date range -- `getLatestAnalytics()` - Get most recent analytics -- `getAnalyticsSummary()` - Get 30-day summary - -### 3. Service (`analytics.service.ts`) -**Main Functions**: -- `processAnalyticsEvent()` - Process analytics events from queue -- `getAnalyticsSummary()` - Get summary for user -- `getAnalyticsByDateRange()` - Get analytics by date range -- `getCurrentStorageOverview()` - Get current storage breakdown - -### 4. Controller (`analytics.controller.ts`) -**Endpoints Implemented**: -- `GET /api/v1/analytics/summary` - Get 30-day analytics summary -- `GET /api/v1/analytics/date-range?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD` - Get analytics for specific date range -- `GET /api/v1/analytics/storage` - Get current storage overview - -### 5. Routes (`analytics.routes.ts`) -All routes are protected with authentication middleware. - -## Data Model - -### StorageAnalytics Table -```typescript -{ - id: UUID - user_id: UUID - date: DATE (unique per user per day) - - // Storage metrics - total_files: INTEGER - total_folders: INTEGER - total_size: BIGINT - - // File type breakdown - images_count: INTEGER - images_size: BIGINT - videos_count: INTEGER - videos_size: BIGINT - audio_count: INTEGER - audio_size: BIGINT - documents_count: INTEGER - documents_size: BIGINT - other_count: INTEGER - other_size: BIGINT - - // Daily activity - uploads_today: INTEGER - downloads_today: INTEGER - shares_created_today: INTEGER - public_links_created_today: INTEGER - - created_at: TIMESTAMP -} -``` - -## Integration Points - -### File Controller -Analytics events are tracked when: -- Creating folders → `FOLDER_CREATED` -- Creating files → `FILE_UPLOADED` -- Deleting files → `FILE_DELETED` -- Sharing files → `FILE_SHARED` - -### Upload Controller -Analytics events are tracked when: -- Uploading files (direct) → `FILE_UPLOADED` -- Uploading files (multipart) → Handled in File controller after completion - -## API Usage Examples - -### 1. Get Storage Overview -```http -GET /api/v1/analytics/storage -Authorization: Bearer - -Response: -{ - "success": true, - "message": "Storage overview retrieved successfully", - "data": { - "storage": { - "totalFiles": 456, - "totalFolders": 23, - "totalSize": 48622632960, - "imageCount": 234, - "imageSize": 20131512320, - "videoCount": 67, - "videoSize": 9556590592, - "audioCount": 123, - "audioSize": 3442319360, - "documentCount": 32, - "documentSize": 13346765824 - }, - "todayActivity": { - "uploads": 12, - "downloads": 34, - "shares": 5, - "publicLinks": 2 - }, - "storageQuota": 107374182400, - "storageUsed": 48622632960, - "storageRemaining": 58751549440, - "storageUsedPercentage": "45.28" - } -} -``` - -### 2. Get 30-Day Summary -```http -GET /api/v1/analytics/summary -Authorization: Bearer - -Response: -{ - "success": true, - "message": "Analytics summary retrieved successfully", - "data": { - "current": { /* Today's analytics */ }, - "last30Days": [ /* Array of daily analytics */ ], - "totalUploads": 145, - "totalDownloads": 892, - "totalShares": 34 - } -} -``` - -### 3. Get Date Range Analytics -```http -GET /api/v1/analytics/date-range?startDate=2026-01-01&endDate=2026-01-31 -Authorization: Bearer - -Response: -{ - "success": true, - "message": "Analytics retrieved successfully", - "data": [ /* Array of analytics for each day */ ] -} -``` - -## File Type Categorization - -Files are automatically categorized based on MIME type: -- **Images**: `image/*` -- **Videos**: `video/*` -- **Audio**: `audio/*` -- **Documents**: PDFs, Word docs, spreadsheets, presentations, text files -- **Other**: Everything else - -## Performance Considerations - -1. **Async Processing**: All analytics updates happen asynchronously via queue -2. **Daily Aggregation**: One record per user per day reduces database size -3. **Efficient Queries**: Indexed on `user_id` and `date` -4. **Caching Ready**: Current storage can be cached and invalidated on changes - -## Monitoring - -- Queue events are logged: completed, failed, errors -- Failed jobs are retried up to 5 times -- Last 10 failed jobs are kept for debugging - -## Next Steps - -Consider adding: -1. **Cron Job**: Daily cleanup of old analytics (keep last 90 days) -2. **Real-time Dashboard**: WebSocket updates for live analytics -3. **Export Feature**: CSV/Excel export of analytics data -4. **Alerts**: Notify users when approaching storage quota -5. **Charts API**: Pre-aggregated data for frontend charts - diff --git a/src/controllers/upload.controller.ts b/src/controllers/upload.controller.ts index 6051dca..89ce652 100644 --- a/src/controllers/upload.controller.ts +++ b/src/controllers/upload.controller.ts @@ -294,7 +294,6 @@ const getAllFiles = async (c: Context) => { maxKeys?: number; continuationToken?: string }; - const { folder, maxKeys = 100, continuationToken } = validatedQuery || {}; const result = await s3Service.getAllFiles(folder, maxKeys, continuationToken); @@ -311,6 +310,7 @@ const getAllFiles = async (c: Context) => { }, }) } catch (error: any) { + console.log("error", error); return res.FailureResponse(c, 500, { message: "Failed to get all files", error: error.message, diff --git a/src/middleware/auth.middleware.ts b/src/middleware/auth.middleware.ts index e7352b8..ff91218 100644 --- a/src/middleware/auth.middleware.ts +++ b/src/middleware/auth.middleware.ts @@ -7,7 +7,7 @@ import redisConn from "@/config/redis.config"; import redisConstants from "@/global/redis-constants"; import { getValidPinSession } from "@/core/session"; import crypto from "crypto"; -import { UserRole } from "@/models/User.model"; +import { UserRole, type IUserAttributes } from "@/models/User.model"; @@ -151,8 +151,28 @@ const pinSessionMiddleware: MiddlewareHandler = async (c: Context, next: Next) = } }; +function checkPermissions(roles: UserRole[]) { + return async (c: Context, next: Next) => { + const user = c.get('user') as IUserAttributes; + + if (!user) { + return res.FailureResponse(c, 401, { message: "Unauthorized." }); + } + + if (!roles.includes(user.role)) { + return res.FailureResponse(c, 403, { + message: "Forbidden, you don't have permission to perform this action." + }); + } + + await next(); // ✅ IMPORTANT + }; +} + + export default { authMiddleware, + checkPermissions, pinSessionMiddleware, }; \ No newline at end of file diff --git a/src/routes/upload.routes.ts b/src/routes/upload.routes.ts index e75144c..7031efc 100644 --- a/src/routes/upload.routes.ts +++ b/src/routes/upload.routes.ts @@ -4,6 +4,7 @@ import { uploadImageMiddleware } from '@/middleware/multer.middleware' // rewrit import uploadController from "@/controllers/upload.controller"; import { validateBody, validateQuery, validateParams } from "@/utils/validation"; import uploadValidation from "@/validation/upload.validation"; +import { UserRole } from "@/models"; export class UploadRouter { /** Each router owns its own Hono instance */ @@ -17,55 +18,56 @@ export class UploadRouter { private MultipartUpload() { // Multipart upload - this.router.post('/initiate', - Middleware.authMiddleware, - validateBody(uploadValidation.initiateUploadValidation), + this.router.post('/initiate', + Middleware.authMiddleware, + validateBody(uploadValidation.initiateUploadValidation), uploadController.initiateUpload ) - this.router.post('/chunk/file/:uploadId', - Middleware.authMiddleware, + this.router.post('/chunk/file/:uploadId', + Middleware.authMiddleware, validateParams(uploadValidation.uploadIdValidation), uploadController.uploadChunk ) - this.router.post('/complete/file/:uploadId', - Middleware.authMiddleware, + this.router.post('/complete/file/:uploadId', + Middleware.authMiddleware, validateParams(uploadValidation.uploadIdValidation), - validateBody(uploadValidation.completeUploadValidation), + validateBody(uploadValidation.completeUploadValidation), uploadController.completeUpload ) - this.router.post('/abort/file/:uploadId', - Middleware.authMiddleware, + this.router.post('/abort/file/:uploadId', + Middleware.authMiddleware, validateParams(uploadValidation.uploadIdValidation), - validateBody(uploadValidation.abortUploadValidation), + validateBody(uploadValidation.abortUploadValidation), uploadController.abortUpload ) - this.router.get('/parts/file/:uploadId', - Middleware.authMiddleware, + this.router.get('/parts/file/:uploadId', + Middleware.authMiddleware, validateParams(uploadValidation.uploadIdValidation), - validateQuery(uploadValidation.getPartsValidation), + validateQuery(uploadValidation.getPartsValidation), uploadController.getPartsByUploadKey ) } private UploadImagesOrFiles() { // Image upload (max 5 files) - this.router.post('/file', - Middleware.authMiddleware, - uploadImageMiddleware, + this.router.post('/file', + Middleware.authMiddleware, + uploadImageMiddleware, uploadController.uploadFile ) - this.router.get('/file/get-file', - Middleware.authMiddleware, + this.router.get('/file/get-file', + Middleware.authMiddleware, validateQuery(uploadValidation.fileNameValidation), uploadController.getFiles ) - this.router.delete('/file/:fileName', - Middleware.authMiddleware, + this.router.delete('/file/:fileName', + Middleware.authMiddleware, validateParams(uploadValidation.fileNameValidation), uploadController.deleteFile ) - this.router.get('/file/get-all-files', + this.router.get('/file/get-all-files', Middleware.authMiddleware, + Middleware.checkPermissions([UserRole.ADMIN]), validateQuery(uploadValidation.getAllFilesValidation), uploadController.getAllFiles ) diff --git a/src/services/user.service.ts b/src/services/user.service.ts index 3822509..945919a 100644 --- a/src/services/user.service.ts +++ b/src/services/user.service.ts @@ -1,5 +1,4 @@ import constants from "@/global/constants"; -import jwt from "@/utils/jwt-token"; import { type IUserSessionAttributes } from "@/models/UserSession.model"; import userRepository from "@/repository/user.repository"; import { type IUserAttributes } from "@/models/User.model";