|
1 | 1 | import { Request, Response } from 'express'; |
| 2 | +import multer from 'multer'; |
2 | 3 | import bcrypt from 'bcrypt'; |
3 | | -import { sendBadRequest, sendSuccess } from '../utils/response.js'; |
| 4 | +import { sendBadRequest, sendSuccess, sendUnauthorized } from '../utils/response.js'; |
4 | 5 | import { query, queryOne, getSqliteClient } from '../utils/db.js'; |
5 | 6 | import { User } from '../types/user.js'; |
6 | 7 | import { getAuthConfig, logAuditEvent } from '../utils/auth.js'; |
| 8 | +import { UploadController } from './upload.controller.js'; |
7 | 9 |
|
8 | 10 | export class UsersController { |
9 | 11 | /** |
@@ -262,4 +264,65 @@ export class UsersController { |
262 | 264 | sendBadRequest(res, error.message || 'Failed to delete user.'); |
263 | 265 | } |
264 | 266 | } |
| 267 | + |
| 268 | + /** |
| 269 | + * Upload user avatar |
| 270 | + */ |
| 271 | + static async uploadAvatar(req: Request, res: Response): Promise<void> { |
| 272 | + try { |
| 273 | + const user = (req as any).user; |
| 274 | + if (!user) { |
| 275 | + sendUnauthorized(res, 'Authentication required.'); |
| 276 | + return; |
| 277 | + } |
| 278 | + |
| 279 | + const storage = 'avatars'; |
| 280 | + const upload = multer({ |
| 281 | + storage: UploadController.storage(storage), |
| 282 | + limits: { |
| 283 | + fileSize: 5 * 1024 * 1024 // 5MB limit |
| 284 | + }, |
| 285 | + fileFilter: (req, file, cb) => { |
| 286 | + const allowedTypes = ['image/jpeg', 'image/png', 'image/webp']; |
| 287 | + if (allowedTypes.includes(file.mimetype)) { |
| 288 | + cb(null, true); |
| 289 | + } else { |
| 290 | + cb(new Error('Invalid file type. Only JPG, PNG and WebP are allowed.')); |
| 291 | + } |
| 292 | + } |
| 293 | + }).single('avatar'); |
| 294 | + |
| 295 | + upload(req, res, async (err: any) => { |
| 296 | + if (err instanceof multer.MulterError) { |
| 297 | + sendBadRequest(res, `Upload error: ${err.message}`); |
| 298 | + return; |
| 299 | + } else if (err) { |
| 300 | + sendBadRequest(res, err.message); |
| 301 | + return; |
| 302 | + } |
| 303 | + |
| 304 | + if (!req.file) { |
| 305 | + sendBadRequest(res, 'No file uploaded.'); |
| 306 | + return; |
| 307 | + } |
| 308 | + |
| 309 | + const avatarUrl = `/api/image/${req.file.filename}`; |
| 310 | + |
| 311 | + // Update user avatar in database |
| 312 | + const sqlite = getSqliteClient(); |
| 313 | + const stmt = sqlite.prepare('UPDATE users SET avatar = ?, updated_at = datetime(\'now\') WHERE id = ?'); |
| 314 | + stmt.run(avatarUrl, user.id); |
| 315 | + |
| 316 | + await logAuditEvent(user.id, 'USER_AVATAR_UPLOADED', req, { |
| 317 | + avatarUrl |
| 318 | + }); |
| 319 | + |
| 320 | + sendSuccess(res, { avatarUrl }, 'Avatar uploaded successfully'); |
| 321 | + }); |
| 322 | + |
| 323 | + } catch (error: any) { |
| 324 | + console.error('Upload avatar error:', error); |
| 325 | + sendBadRequest(res, error.message || 'Failed to upload avatar.'); |
| 326 | + } |
| 327 | + } |
265 | 328 | } |
0 commit comments