feat: add profile update endpoint with avatar upload
This commit is contained in:
@@ -3,6 +3,7 @@ import {
|
||||
loginUser,
|
||||
refreshSession,
|
||||
registerUser,
|
||||
updateProfile,
|
||||
} from '../services/authService.js';
|
||||
|
||||
const accessCookieName = 'noctune_access';
|
||||
@@ -92,4 +93,25 @@ export const authController = {
|
||||
clearAuthCookies(res);
|
||||
res.json({ success: true, data: { message: 'Logged out' } });
|
||||
},
|
||||
|
||||
async updateProfile(req, res, next) {
|
||||
try {
|
||||
const { name, username, email } = req.body;
|
||||
const avatarFile = req.file;
|
||||
const avatarUrl = avatarFile
|
||||
? `/uploads/avatars/${avatarFile.filename}`
|
||||
: undefined;
|
||||
|
||||
const user = await updateProfile(req.userId, {
|
||||
name,
|
||||
username,
|
||||
email,
|
||||
avatarUrl,
|
||||
});
|
||||
|
||||
return res.json({ success: true, data: { user } });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Router } from 'express';
|
||||
import { authController } from '../controllers/authController.js';
|
||||
import { auth } from '../middlewares/auth.js';
|
||||
import { uploadAvatar } from '../utils/multer.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -9,5 +10,6 @@ router.post('/login', authController.login);
|
||||
router.post('/refresh', authController.refresh);
|
||||
router.post('/logout', authController.logout);
|
||||
router.get('/me', auth, authController.me);
|
||||
router.put('/profile', auth, uploadAvatar.single('avatar'), authController.updateProfile);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -168,3 +168,30 @@ export const getSessionUser = async (userId) => {
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
export const updateProfile = async (userId, { name, username, email, avatarUrl }) => {
|
||||
const updateFields = {};
|
||||
if (name !== undefined) updateFields.name = name.trim();
|
||||
if (username !== undefined) updateFields.username = username.trim().toLowerCase();
|
||||
if (email !== undefined) updateFields.email = email.trim().toLowerCase();
|
||||
if (avatarUrl !== undefined) updateFields.avatarUrl = avatarUrl;
|
||||
|
||||
if (updateFields.username || updateFields.email) {
|
||||
const existing = await User.findOne({
|
||||
_id: { $ne: userId },
|
||||
$or: [
|
||||
...(updateFields.username ? [{ username: updateFields.username }] : []),
|
||||
...(updateFields.email ? [{ email: updateFields.email }] : []),
|
||||
],
|
||||
}).lean();
|
||||
|
||||
if (existing) {
|
||||
throw createError(409, 'Username or email already in use');
|
||||
}
|
||||
}
|
||||
|
||||
await User.updateOne({ _id: userId }, { $set: updateFields });
|
||||
const user = await getPublicUser(userId);
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import multer from 'multer';
|
||||
const coversDir = path.join(process.cwd(), 'uploads', 'covers');
|
||||
const audioDir = path.join(process.cwd(), 'uploads', 'audio');
|
||||
const playlistCoversDir = path.join(process.cwd(), 'uploads', 'playlists');
|
||||
const avatarsDir = path.join(process.cwd(), 'uploads', 'avatars');
|
||||
|
||||
if (!fs.existsSync(coversDir)) {
|
||||
fs.mkdirSync(coversDir, { recursive: true });
|
||||
@@ -18,6 +19,10 @@ if (!fs.existsSync(playlistCoversDir)) {
|
||||
fs.mkdirSync(playlistCoversDir, { recursive: true });
|
||||
}
|
||||
|
||||
if (!fs.existsSync(avatarsDir)) {
|
||||
fs.mkdirSync(avatarsDir, { recursive: true });
|
||||
}
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
if (file.fieldname === 'cover') {
|
||||
@@ -32,6 +37,10 @@ const storage = multer.diskStorage({
|
||||
return cb(null, playlistCoversDir);
|
||||
}
|
||||
|
||||
if (file.fieldname === 'avatar') {
|
||||
return cb(null, avatarsDir);
|
||||
}
|
||||
|
||||
return cb(new Error('Invalid upload field'));
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
@@ -76,8 +85,15 @@ const imageFilter = (req, file, cb) => {
|
||||
}
|
||||
|
||||
return cb(new Error('Invalid upload field'));
|
||||
};
|
||||
};
|
||||
|
||||
export const uploadAvatar = multer({
|
||||
storage,
|
||||
fileFilter: imageFilter,
|
||||
limits: { fileSize: 5 * 1024 * 1024 },
|
||||
});
|
||||
|
||||
export const uploadTrackAssets = multer({
|
||||
storage,
|
||||
fileFilter: imageFilter,
|
||||
|
||||
Reference in New Issue
Block a user