fix(user): persist updateUser correctly; add setup:api script and README update; seeders tweaks
This commit is contained in:
@@ -1,8 +1,21 @@
|
||||
const { UserService } = require('../services');
|
||||
const asyncHandler = require("../middlewares/asyncHandler");
|
||||
const ErrorResponse = require("../utils/errorResponse");
|
||||
var crypto = require('crypto');
|
||||
const passport = require('passport');
|
||||
|
||||
|
||||
module.exports.addFollowUser = asyncHandler(async (req, res, next) => {
|
||||
try {
|
||||
return res.status(200).json({
|
||||
message: 'The user is now being followed.',
|
||||
user: true
|
||||
});
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while following a user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.authenticate = asyncHandler(async (req, res, next) => {
|
||||
try {
|
||||
passport.authenticate('headerapikey', { session: false }, function (err, application, info) {
|
||||
@@ -31,12 +44,27 @@ module.exports.authenticate = asyncHandler(async (req, res, next) => {
|
||||
|
||||
module.exports.createUser = asyncHandler(async (req, res, next) => {
|
||||
try {
|
||||
const { username, firstname, lastname, email } = req.body.user;
|
||||
|
||||
fieldValidation(req.body.user.username, next);
|
||||
fieldValidation(req.body.user.email, next);
|
||||
fieldValidation(req.body.user.password, next);
|
||||
|
||||
const { username, firstname, lastname, phone, email, password } = req.body.user;
|
||||
const userSlug = UserService.slugify(username);
|
||||
const slugInDB = await UserService.isUsernameUsed(userSlug);
|
||||
if (slugInDB) {
|
||||
return next(new ErrorResponse("Username already exists", 400));
|
||||
}
|
||||
|
||||
const uuid = crypto.randomUUID()
|
||||
const user = await UserService.createUser({
|
||||
id: uuid,
|
||||
username,
|
||||
firstname,
|
||||
lastname,
|
||||
email
|
||||
phone,
|
||||
email,
|
||||
password
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
@@ -48,6 +76,17 @@ module.exports.createUser = asyncHandler(async (req, res, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.deleteFollowUser = asyncHandler(async (req, res, next) => {
|
||||
try {
|
||||
return res.status(200).json({
|
||||
message: 'The user is no longer being followed.',
|
||||
user: true
|
||||
});
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while unfollowing a user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.getAllUsers = asyncHandler(async (req, res, next) => {
|
||||
try {
|
||||
const users = await UserService.getAllUsers();
|
||||
@@ -69,6 +108,7 @@ module.exports.getUser = asyncHandler(async (req, res, next) => {
|
||||
|
||||
res.status(200).json({
|
||||
user: user.toAuthJSON(),
|
||||
uuid: crypto.randomUUID()
|
||||
});
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while fetching user.', 500, err.message));
|
||||
@@ -105,12 +145,6 @@ module.exports.updateUser = asyncHandler(async (req, res, next) => {
|
||||
if (!user) {
|
||||
return next(new ErrorResponse("Unauthorized", 401, "You are not allowed to access this resource."));
|
||||
}
|
||||
if (typeof req.body.user.username !== 'undefined') {
|
||||
user.username = req.body.user.username;
|
||||
}
|
||||
if (typeof req.body.user.email !== 'undefined') {
|
||||
user.email = req.body.user.email;
|
||||
}
|
||||
if (typeof req.body.user.firstname !== 'undefined') {
|
||||
user.firstname = req.body.user.firstname;
|
||||
}
|
||||
@@ -132,46 +166,111 @@ module.exports.updateUser = asyncHandler(async (req, res, next) => {
|
||||
if (typeof req.body.user.bg_image !== 'undefined') {
|
||||
user.bg_image = req.body.user.bg_image;
|
||||
}
|
||||
if (typeof req.body.user.password !== 'undefined') {
|
||||
user.setPassword(req.body.user.password);
|
||||
}
|
||||
if (typeof req.body.user.role !== 'undefined') {
|
||||
user.role = req.body.user.role;
|
||||
return next(new ErrorResponse("Forbidden", 403, "A suspected attempt to usurp rights has been detected. The suspicious activity has been reported and xill be investigated."));
|
||||
}
|
||||
|
||||
/*user = await UserService.updateUser(req.body.user);
|
||||
// Persist changes using the service (service will save instance or update+fetch)
|
||||
const updatedUser = await UserService.updateUserById(user, req.payload.id);
|
||||
res.status(200).json({
|
||||
user: user.toAuthJSON(),
|
||||
});*/
|
||||
return user.save().then(function () {
|
||||
user: updatedUser.toAuthJSON()
|
||||
});
|
||||
/*return user.save().then(function () {
|
||||
return res.json({
|
||||
message: 'User updated successfully.',
|
||||
user: user.toAuthJSON()
|
||||
});
|
||||
});*/
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while updating user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.updateUserEmail = asyncHandler(async (req, res, next) => {
|
||||
try {
|
||||
let user = await UserService.getUserById(req.payload.id);
|
||||
if (!user) {
|
||||
return next(new ErrorResponse("Unauthorized", 401, "You are not allowed to access this resource."));
|
||||
}
|
||||
|
||||
fieldValidation(req.body.user.email, next);
|
||||
if (typeof req.body.user.email !== 'undefined') {
|
||||
user.email = req.body.user.email;
|
||||
}
|
||||
|
||||
user = await UserService.updateUserById(user, req.payload.id);
|
||||
res.status(200).json({
|
||||
user: user.toAuthJSON(),
|
||||
});
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while updating user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.addFollowUser = asyncHandler(async (req, res, next) => {
|
||||
module.exports.updateUserPassword = asyncHandler(async (req, res, next) => {
|
||||
try {
|
||||
return res.status(200).json({
|
||||
message: 'The user is now being followed.',
|
||||
user: true
|
||||
let user = await UserService.getUserById(req.payload.id);
|
||||
if (!user) {
|
||||
return next(new ErrorResponse("Unauthorized", 401, "You are not allowed to access this resource."));
|
||||
}
|
||||
if (typeof req.body.user.password !== 'undefined') {
|
||||
const {salt, hash} = UserService.generateSaltHash(req.body.user.password);
|
||||
user.salt = salt;
|
||||
user.hash = hash;
|
||||
}
|
||||
user = await UserService.updateUserById(user, req.payload.id);
|
||||
res.status(200).json({
|
||||
user: user.toAuthJSON(),
|
||||
});
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while following a user.', 500, err.message));
|
||||
return next(new ErrorResponse('Something went wrong while updating user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.deleteFollowUser = asyncHandler(async (req, res, next) => {
|
||||
module.exports.updateUserRole = asyncHandler(async (req, res, next) => {
|
||||
try {
|
||||
return res.status(200).json({
|
||||
message: 'The user is no longer being followed.',
|
||||
user: true
|
||||
let user = await UserService.getUserById(req.payload.id);
|
||||
if (!user) {
|
||||
return next(new ErrorResponse("Unauthorized", 401, "You are not allowed to access this resource."));
|
||||
}
|
||||
|
||||
fieldValidation(req.body.user.role, next);
|
||||
if (typeof req.body.user.role !== 'undefined') {
|
||||
user.role = req.body.user.role;
|
||||
}
|
||||
|
||||
user = await UserService.updateUserById(user, req.payload.id);
|
||||
res.status(200).json({
|
||||
user: user.toAuthJSON(),
|
||||
});
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while unfollowing a user.', 500, err.message));
|
||||
return next(new ErrorResponse('Something went wrong while updating user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.updateUserUsername = asyncHandler(async (req, res, next) => {
|
||||
try {
|
||||
let user = await UserService.getUserById(req.payload.id);
|
||||
if (!user) {
|
||||
return next(new ErrorResponse("Unauthorized", 401, "You are not allowed to access this resource."));
|
||||
}
|
||||
|
||||
fieldValidation(req.body.user.username, next);
|
||||
if (typeof req.body.user.username !== 'undefined') {
|
||||
user.username = req.body.user.username;
|
||||
}
|
||||
|
||||
user = await UserService.updateUserById(user, req.payload.id);
|
||||
res.status(200).json({
|
||||
user: user.toAuthJSON(),
|
||||
});
|
||||
} catch (err) {
|
||||
return next(new ErrorResponse('Something went wrong while updating user.', 500, err.message));
|
||||
}
|
||||
});
|
||||
|
||||
const fieldValidation = (field, next) => {
|
||||
if (!field) {
|
||||
return next(new ErrorResponse(`Missing fields`, 400));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ module.exports = {
|
||||
// Create User table
|
||||
await queryInterface.createTable('user', {
|
||||
id: {
|
||||
type: Sequelize.STRING(24),
|
||||
type: Sequelize.STRING(36),
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
},
|
||||
@@ -202,7 +202,7 @@ module.exports = {
|
||||
allowNull: false
|
||||
},
|
||||
authorId: {
|
||||
type: Sequelize.STRING(24),
|
||||
type: Sequelize.STRING(36),
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'user',
|
||||
@@ -236,7 +236,7 @@ module.exports = {
|
||||
allowNull: false
|
||||
},
|
||||
authorId: {
|
||||
type: Sequelize.STRING(24),
|
||||
type: Sequelize.STRING(36),
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'user',
|
||||
@@ -306,7 +306,7 @@ module.exports = {
|
||||
// Create Favorite table (junction table for User-Article)
|
||||
await queryInterface.createTable('favorite', {
|
||||
userId: {
|
||||
type: Sequelize.STRING(24),
|
||||
type: Sequelize.STRING(36),
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
references: {
|
||||
@@ -342,7 +342,7 @@ module.exports = {
|
||||
// Create Follower table (junction table for User-User)
|
||||
await queryInterface.createTable('follower', {
|
||||
userId: {
|
||||
type: Sequelize.STRING(24),
|
||||
type: Sequelize.STRING(36),
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
references: {
|
||||
@@ -353,7 +353,7 @@ module.exports = {
|
||||
onUpdate: 'CASCADE'
|
||||
},
|
||||
followerId: {
|
||||
type: Sequelize.STRING(24),
|
||||
type: Sequelize.STRING(36),
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
references: {
|
||||
@@ -437,7 +437,7 @@ module.exports = {
|
||||
onUpdate: 'CASCADE'
|
||||
},
|
||||
ownerId: {
|
||||
type: Sequelize.STRING(24),
|
||||
type: Sequelize.STRING(36),
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'user',
|
||||
@@ -471,7 +471,7 @@ module.exports = {
|
||||
// Create UserRoleXref table (junction table for User-Role)
|
||||
await queryInterface.createTable('userRoleXref', {
|
||||
userId: {
|
||||
type: Sequelize.STRING(24),
|
||||
type: Sequelize.STRING(36),
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
references: {
|
||||
|
||||
@@ -11,7 +11,7 @@ const UserModel = () => {
|
||||
User.init(
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.STRING(24),
|
||||
type: DataTypes.UUID(),
|
||||
allowNull: false,
|
||||
primaryKey: true
|
||||
},
|
||||
@@ -87,16 +87,16 @@ const UserModel = () => {
|
||||
}
|
||||
);
|
||||
|
||||
User.beforeCreate((user) => {
|
||||
/*User.beforeCreate((user) => {
|
||||
if (typeof user.username === 'undefined') {
|
||||
user.username = `${user.firstname}_${user.lastname}`;
|
||||
}
|
||||
user.setPassword(user.password);
|
||||
user.role = 'User';
|
||||
});
|
||||
});*/
|
||||
|
||||
User.prototype.validPassword = function (password) {
|
||||
let hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex');
|
||||
const hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex');
|
||||
return this.hash === hash;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
try {
|
||||
// Create roles
|
||||
await queryInterface.bulkInsert('role', [
|
||||
{
|
||||
name: 'Admin',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
name: 'User',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
}
|
||||
], {});
|
||||
|
||||
console.log('✅ Rôles créés avec succès');
|
||||
} catch (error) {
|
||||
console.error('❌ Erreur lors de la création des rôles:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
try {
|
||||
// Delete roles
|
||||
await queryInterface.bulkDelete('role', { name: { [Sequelize.Op.in]: ['Admin', 'User'] } }, {});
|
||||
|
||||
console.log('✅ Rôles supprimés avec succès');
|
||||
} catch (error) {
|
||||
console.error('❌ Erreur lors de la suppression des rôles:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -4,7 +4,7 @@ const slug = require('slug');
|
||||
|
||||
const ANIMAL_TAGS = ['Lion', 'Eagle', 'Dolphin', 'Tiger', 'Wolf'];
|
||||
|
||||
const AUTHOR_ID = '642c4459666702637dcb5066';
|
||||
// AUTHOR_ID will be resolved at runtime to the oldest user with role 'Admin'
|
||||
|
||||
const ARTICLES = [
|
||||
{
|
||||
@@ -46,6 +46,40 @@ module.exports = {
|
||||
// Get transaction for consistency
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
|
||||
// Resolve AUTHOR_ID: oldest user who has role 'Admin'
|
||||
let AUTHOR_ID = null;
|
||||
try {
|
||||
const adminQuery = await queryInterface.sequelize.query(
|
||||
`SELECT u.id FROM \`user\` u
|
||||
JOIN userRoleXref ur ON ur.userId = u.id
|
||||
JOIN role r ON r.id = ur.roleId
|
||||
WHERE r.name = ?
|
||||
ORDER BY u.createdAt ASC
|
||||
LIMIT 1`,
|
||||
{ replacements: ['Admin'], transaction, raw: true }
|
||||
);
|
||||
|
||||
if (adminQuery && adminQuery[0] && adminQuery[0][0] && adminQuery[0][0].id) {
|
||||
AUTHOR_ID = adminQuery[0][0].id;
|
||||
} else {
|
||||
// Fallback: pick the oldest user in `user` table
|
||||
const fallback = await queryInterface.sequelize.query(
|
||||
`SELECT id FROM \`user\` ORDER BY createdAt ASC LIMIT 1`,
|
||||
{ transaction, raw: true }
|
||||
);
|
||||
if (fallback && fallback[0] && fallback[0][0] && fallback[0][0].id) {
|
||||
AUTHOR_ID = fallback[0][0].id;
|
||||
}
|
||||
}
|
||||
|
||||
if (!AUTHOR_ID) {
|
||||
throw new Error('Aucun utilisateur trouvé pour être utilisé comme auteur (Admin absent).');
|
||||
}
|
||||
} catch (err) {
|
||||
await transaction.rollback();
|
||||
throw err;
|
||||
}
|
||||
|
||||
try {
|
||||
// Create articles
|
||||
for (const articleData of ARTICLES) {
|
||||
@@ -127,7 +161,7 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
async down(queryInterface) {
|
||||
try {
|
||||
// Delete in correct order due to foreign keys
|
||||
await queryInterface.sequelize.query('DELETE FROM tagList WHERE 1=1');
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
try {
|
||||
// Create roles
|
||||
await queryInterface.bulkInsert('role', [
|
||||
{
|
||||
name: 'Admin',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
name: 'User',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
}
|
||||
], {});
|
||||
|
||||
console.log('✅ Rôles créés avec succès');
|
||||
} catch (error) {
|
||||
console.error('❌ Erreur lors de la création des rôles:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
try {
|
||||
// Delete roles
|
||||
await queryInterface.bulkDelete('role', { name: { [Sequelize.Op.in]: ['Admin', 'User'] } }, {});
|
||||
|
||||
console.log('✅ Rôles supprimés avec succès');
|
||||
} catch (error) {
|
||||
console.error('❌ Erreur lors de la suppression des rôles:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
// Seeders managed by individual migration files
|
||||
// See seeders directory for specific seeder implementations
|
||||
@@ -1,5 +1,8 @@
|
||||
const express = require('express');
|
||||
const { authenticate, createUser, getUser, loginAsUser, updateUser, addFollowUser, deleteFollowUser } = require('../../../controllers/user.controller');
|
||||
const {
|
||||
addFollowUser, authenticate, createUser, deleteFollowUser, getUser, loginAsUser,
|
||||
updateUser, updateUserEmail, updateUserPassword, updateUserRole, updateUserUsername
|
||||
} = require('../../../controllers/user.controller');
|
||||
const auth = require('../../../middlewares/auth');
|
||||
|
||||
const userRouter = express.Router();
|
||||
@@ -11,6 +14,10 @@ userRouter.get('/authenticate', auth.optional, authenticate);
|
||||
userRouter.post('/login', auth.optional, loginAsUser);
|
||||
//userRouter.post('/register', auth.optional, createUser);
|
||||
//userRouter.get('/users', auth.required, getAllUsers);
|
||||
userRouter.put('/update/email', auth.required, updateUserEmail);
|
||||
userRouter.put('/update/password', auth.required, updateUserPassword);
|
||||
userRouter.put('/update/role', auth.required, updateUserRole);
|
||||
userRouter.put('/update/username', auth.required, updateUserUsername);
|
||||
|
||||
userRouter.post('/:username/follow', auth.required, addFollowUser);
|
||||
userRouter.delete('/:username/follow', auth.required, deleteFollowUser);
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
const slug = require("slug");
|
||||
const DB = require('../../database/mysql');
|
||||
var crypto = require('crypto');
|
||||
|
||||
const { User } = DB;
|
||||
|
||||
class UserService {
|
||||
static async createUser(data) {
|
||||
if (typeof data.username === 'undefined') {
|
||||
data.username = UserService.slugify(`${data.firstname.charAt(0)}${data.lastname.charAt(0)}`);
|
||||
}
|
||||
const {salt, hash} = await UserService.generateSaltHash(data.password);
|
||||
data.salt = salt;
|
||||
data.hash = hash;
|
||||
data.role = 'User';
|
||||
return User.create(data);
|
||||
}
|
||||
|
||||
static async generateSaltHash(password) {
|
||||
const salt = crypto.randomBytes(16).toString('hex');
|
||||
const hash = crypto.pbkdf2Sync(password, salt, 10000, 512, 'sha512').toString('hex');
|
||||
return {salt, hash};
|
||||
}
|
||||
|
||||
static async getAllUsers() {
|
||||
return User.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
@@ -36,14 +51,19 @@ class UserService {
|
||||
return user.getArticleIdArticles(searchOptions);
|
||||
}
|
||||
|
||||
static async getFavoritesCount(user) {
|
||||
return user.countArticleIdArticles();
|
||||
}
|
||||
|
||||
static async getUserFollowed(user) {
|
||||
// Sequelize generated method for "users this user follows" is named
|
||||
// `getUserIdUserFollowers()` (see `src/database/relationships/index.js`).
|
||||
return user.getUserIdUserFollowers();
|
||||
}
|
||||
|
||||
static async getFavoritesCount(user) {
|
||||
return user.countArticleIdArticles();
|
||||
static async isUsernameUsed(username) {
|
||||
const user = await User.findOne({ where: { username: username } });
|
||||
return !!user;
|
||||
}
|
||||
|
||||
static async addFavoriteArticle(user, article) {
|
||||
@@ -53,9 +73,25 @@ class UserService {
|
||||
static async removeFavoriteArticle(user, article) {
|
||||
return user.removeArticleIdArticles(article);
|
||||
}
|
||||
|
||||
static slugify(stringToSlug) {
|
||||
return slug(`${stringToSlug}`) + '-' + (Math.random() * Math.pow(36, 6) | 0).toString(36);
|
||||
}
|
||||
|
||||
static async updateUser(data) {
|
||||
return User.update(data);
|
||||
static async updateUserById(data, id) {
|
||||
// If `data` is a Sequelize instance, prefer instance.save()
|
||||
try {
|
||||
if (data && typeof data.save === 'function') {
|
||||
const saved = await data.save();
|
||||
return saved;
|
||||
}
|
||||
} catch {
|
||||
// fallthrough to update
|
||||
}
|
||||
|
||||
// Otherwise perform a standard update and return the refreshed instance
|
||||
await User.update(data, { where: { id: id } });
|
||||
return User.findByPk(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user