Project member management

This commit is contained in:
Takeshi Kimata
2024-05-29 23:12:38 +09:00
parent 4b46944440
commit abf2d48bee
20 changed files with 588 additions and 9 deletions

View File

@@ -112,6 +112,16 @@ app.use('/runcases', runCaseBuldNewRoute);
app.use('/runcases', runCaseDeleteRoute);
app.use('/runcases', runCaseBulkDeleteRoute);
// "/members"
const membersIndexRoute = require('./routes/members/index')(sequelize);
const membersNewRoute = require('./routes/members/new')(sequelize);
const membersEditRoute = require('./routes/members/edit')(sequelize);
const membersDeleteRoute = require('./routes/members/delete')(sequelize);
app.use('/members', membersIndexRoute);
app.use('/members', membersNewRoute);
app.use('/members', membersEditRoute);
app.use('/members', membersDeleteRoute);
// "/home"
const homeIndexRoute = require('./routes/home/index')(sequelize);
app.use('/home', homeIndexRoute);

View File

@@ -1,8 +1,9 @@
const jwt = require('jsonwebtoken');
const { roles, defaultDangerKey } = require('../routes/users/authSettings');
const { roles, memberRoles, defaultDangerKey } = require('../routes/users/authSettings');
const defineProject = require('../models/projects');
const defineUser = require('../models/users');
const { DataTypes } = require('sequelize');
const defineMember = require('../models/members');
function authMiddleware(sequelize) {
/**
@@ -97,7 +98,49 @@ function authMiddleware(sequelize) {
next();
}
return { verifySignedIn, verifyAdmin, verifyProjectVisible, verifyProjectOwner };
/**
* Verify user has permission of project management
* (User must be the owner or manager of the project )
* (have to be called after verifySignedIn() middleware)
*/
async function verifyProjectManager(req, res, next) {
const Project = defineProject(sequelize, DataTypes);
const projectId = req.params.projectId || req.query.projectId;
if (!projectId) {
return res.status(400).json({ error: 'projectId is required' });
}
const project = await Project.findByPk(projectId);
if (!project) {
return res.status(404).send('Project not found');
}
if (project.userId === req.userId) {
next();
return;
}
// check the user is manager of the project
const Member = defineMember(sequelize, DataTypes);
const member = await Member.findOne({
where: {
userId: req.userId,
projectId: projectId,
},
});
if (member) {
const managerRoleIndex = memberRoles.findIndex((entry) => entry.uid === 'manager');
if (member.role === managerRoleIndex) {
next();
return;
}
}
return res.status(403).json({ error: 'Forbidden' });
}
return { verifySignedIn, verifyAdmin, verifyProjectVisible, verifyProjectOwner, verifyProjectManager };
}
module.exports = authMiddleware;

View File

@@ -0,0 +1,52 @@
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('members', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
userId: {
type: Sequelize.INTEGER,
references: {
model: 'users',
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
},
projectId: {
type: Sequelize.INTEGER,
references: {
model: 'projects',
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
},
role: {
type: Sequelize.INTEGER,
allowNull: false,
},
createdAt: {
type: Sequelize.DATE,
allowNull: false,
},
updatedAt: {
type: Sequelize.DATE,
allowNull: false,
},
});
await queryInterface.addIndex('members', ['userId', 'projectId'], {
unique: true,
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('members');
},
};

31
backend/models/members.js Normal file
View File

@@ -0,0 +1,31 @@
function defineMember(sequelize, DataTypes) {
const Member = sequelize.define('Member', {
userId: {
type: DataTypes.INTEGER,
allowNull: false,
},
projectId: {
type: DataTypes.INTEGER,
allowNull: false,
},
role: {
type: DataTypes.INTEGER,
allowNull: false,
},
});
Member.associate = (models) => {
Member.belongsTo(models.User, {
foreignKey: 'userId',
onDelete: 'CASCADE',
});
Member.belongsTo(models.Project, {
foreignKey: 'projectId',
onDelete: 'CASCADE',
});
};
return Member;
}
module.exports = defineMember;

View File

@@ -0,0 +1,36 @@
const express = require('express');
const router = express.Router();
const defineMember = require('../../models/members');
const { DataTypes } = require('sequelize');
module.exports = function (sequelize) {
const { verifySignedIn, verifyProjectManager } = require('../../middleware/auth')(sequelize);
const Member = defineMember(sequelize, DataTypes);
router.delete('/', verifySignedIn, verifyProjectManager, async (req, res) => {
const userId = req.query.userId;
const projectId = req.query.projectId;
try {
// Get Member to be deleted.
const deletingMember = await Member.findOne({
where: {
userId: userId,
projectId: projectId,
},
});
if (!deletingMember) {
return res.status(404).send('Member not found');
}
await deletingMember.destroy();
res.status(204).send();
} catch (error) {
console.error(error);
res.status(500).send('Internal Server Error');
}
});
return router;
};

View File

@@ -0,0 +1,38 @@
const express = require('express');
const router = express.Router();
const defineMember = require('../../models/members');
const { DataTypes } = require('sequelize');
module.exports = function (sequelize) {
const { verifySignedIn, verifyProjectManager } = require('../../middleware/auth')(sequelize);
const Member = defineMember(sequelize, DataTypes);
router.put('/', verifySignedIn, verifyProjectManager, async (req, res) => {
const userId = req.query.userId;
const projectId = req.query.projectId;
try {
const member = await Member.findOne({
where: {
userId: userId,
projectId: projectId,
},
});
if (!member) {
return res.status(404).send('Member not found');
}
await member.update({
userId,
projectId,
});
res.json(member);
} catch (error) {
console.error(error);
res.status(500).send('Internal Server Error');
}
});
return router;
};

View File

@@ -0,0 +1,40 @@
const express = require('express');
const router = express.Router();
const defineMember = require('../../models/members');
const defineUser = require('../../models/users');
const { DataTypes } = require('sequelize');
module.exports = function (sequelize) {
const { verifySignedIn, verifyProjectManager } = require('../../middleware/auth')(sequelize);
const Member = defineMember(sequelize, DataTypes);
const User = defineUser(sequelize, DataTypes);
Member.belongsTo(User, { foreignKey: 'userId' });
router.get('/', verifySignedIn, verifyProjectManager, async (req, res) => {
const { projectId } = req.query;
if (!projectId) {
return res.status(400).json({ error: 'projectId is required' });
}
try {
const members = await Member.findAll({
where: {
projectId: projectId,
},
include: [
{
model: User,
},
],
});
res.json(members);
} catch (error) {
console.error(error);
res.status(500).send('Internal Server Error');
}
});
return router;
};

View File

@@ -0,0 +1,40 @@
const express = require('express');
const router = express.Router();
const defineMember = require('../../models/members');
const { DataTypes } = require('sequelize');
module.exports = function (sequelize) {
const { verifySignedIn, verifyProjectManager } = require('../../middleware/auth')(sequelize);
const Member = defineMember(sequelize, DataTypes);
router.post('/', verifySignedIn, verifyProjectManager, async (req, res) => {
const userId = req.query.userId;
const projectId = req.query.projectId;
try {
// Check if the record already exists
const existingMember = await Member.findOne({
where: {
userId: userId,
projectId: projectId,
},
});
if (existingMember) {
return res.status(400).send('Record already exists');
}
const newMember = await Member.create({
userId: userId,
projectId: projectId,
});
res.json(newMember);
} catch (error) {
console.error(error);
res.status(500).send('Internal Server Error');
}
});
return router;
};

View File

@@ -1,4 +1,5 @@
const roles = [{ uid: 'administrator' }, { uid: 'user' }];
const memberRoles = [{ uid: 'manager' }, { uid: 'developer' }, { uid: 'reporter' }];
const defaultDangerKey = 'test-plat-default-key';
module.exports = { roles, defaultDangerKey };
module.exports = { roles, memberRoles, defaultDangerKey };

View File

@@ -438,6 +438,23 @@ module.exports = {
updatedAt: new Date(),
},
]);
await queryInterface.bulkInsert('members', [
{
userId: 1,
projectId: 1,
role: 0,
createdAt: new Date(),
updatedAt: new Date(),
},
{
userId: 1,
projectId: 2,
role: 0,
createdAt: new Date(),
updatedAt: new Date(),
},
]);
},
down: async (queryInterface, Sequelize) => {