diff --git a/index.js b/index.js index fd9d858..81c23fa 100644 --- a/index.js +++ b/index.js @@ -67,6 +67,9 @@ import userYearRouter from './src/routes/userYear.route.js' import participatorRouter from './src/routes/participator.route.js' import participatorQuestionRouter from './src/routes/participatorQuestion.route.js' import participatorQuestionCategoryRouter from './src/routes/participatorQuestionCategory.route.js'; +import groupRouter from './src/routes/group.route.js' +import groupUserRouter from './src/routes/groupUser.route.js' +import preferenceRouter from './src/routes/preference.route.js' import threadRouter from './src/routes/thread.route.js' import postRouter from './src/routes/post.route.js' @@ -82,6 +85,11 @@ import userDocumentModel from './src/models/userDocument.model.js'; import userPermissionModel from './src/models/userPermission.model.js'; import userMotivationModel from './src/models/userMotivation.model.js'; +import groupModel from './src/models/group.model.js'; +import groupUserModel from './src/models/groupUser.model.js'; +import preferenceModel from './src/models/preference.model.js'; +import participatorModel from './src/models/participator.model.js'; + app.use('/avatar', avatarRouter); app.use('/event', eventRouter); app.use('/feature', featureRouter); @@ -101,6 +109,9 @@ app.use('/userYear', userYearRouter); app.use('/participator', participatorRouter); app.use('/participatorQuestion', participatorQuestionRouter); app.use('/participatorQuestionCategory', participatorQuestionCategoryRouter); +app.use('/group', groupRouter); +app.use('/groupUser', groupUserRouter); +app.use('/preference', preferenceRouter); app.use('/thread', threadRouter); app.use('/post', postRouter); @@ -131,4 +142,16 @@ userModel.hasOne(userMotivationModel, {foreignKey: 'uuid'}) userMotivationModel.hasOne(userModel, {foreignKey: 'uuid'}) userModel.hasMany(userPermissionModel, {foreignKey: 'uuid'}) -userPermissionModel.hasOne(userModel, {foreignKey: 'uuid'}) \ No newline at end of file +userPermissionModel.hasOne(userModel, {foreignKey: 'uuid'}) + +groupModel.hasMany(groupUserModel, {foreignKey: 'groupId'}) +groupUserModel.hasOne(groupModel, {foreignKey: 'id'}) + +userModel.hasMany(groupUserModel, {foreignKey: 'uuid', sourceKey: 'uuid'}) +groupUserModel.hasOne(userModel, {foreignKey: 'uuid', sourceKey: 'uuid'}) + +preferenceModel.hasMany(participatorModel, {foreignKey: 'preferenceId'}) +participatorModel.hasOne(preferenceModel, {foreignKey: 'id'}) + +groupModel.hasMany(preferenceModel, {foreignKey: 'groupId'}) +preferenceModel.hasOne(groupModel, {foreignKey: 'id'}) \ No newline at end of file diff --git a/src/controllers/group.controller.js b/src/controllers/group.controller.js new file mode 100644 index 0000000..bb66aba --- /dev/null +++ b/src/controllers/group.controller.js @@ -0,0 +1,217 @@ +import groupModel from '../models/group.model.js' +import userModel from '../models/user.model.js' +import groupUserModel from '../models/groupUser.model.js' +import preferenceModel from '../models/preference.model.js' +import participatorModel from '../models/participator.model.js' +import userPermissionModel from '../models/userPermission.model.js'; +import settingModel from '../models/setting.model.js' +import userYearModel from '../models/userYear.model.js' +import { findAllParticipators } from './participator.controller.js' + +async function isAllowed(req) { + const executingUser = req.kauth.grant.access_token.content.sub + const year = await settingModel.findByPk('currentYear') + const isLT = req.kauth.grant.access_token.content.groups?.includes(year + '_LT') + const allowed = isLT || (await userPermissionModel.findOne({where: { uuid: executingUser, permission: 'participator'}}))?.allowed + return allowed +} + +export async function findAll(req, res) { + if (res && !await isAllowed(req)) { + res.status(403).send('Not allowed'); + return; + } + let data = {} + if (typeof req.query.participatorBundle !== 'undefined') { + delete req.query.participatorBundle + data.subQuery = false + data.include = [ + { + model: preferenceModel, + include: [ + { + model: participatorModel + } + ] + }, + { + model: groupUserModel, + include: [ + { + model: userModel, + include: [ + { + model: userYearModel, + where: { + year: (await settingModel.findByPk('currentYear')).value + } + } + ] + } + ] + } + ] + } + try { + data.where = req.query + const group = await groupModel.findAll(data) + res.status(200).send(group) + } catch(e) { + console.log(e); + res.status(400).send() + } +} + +export async function findOne(req, res) { + if (res && !await isAllowed(req)) { + res.status(403).send('Not allowed'); + return; + } + if (!req.params || !req.params.id ) { + res.status(400).send('bad request') + return; + } + const group = await groupModel.findByPk(req.params.id) + if (group) { + res.status(200).send(group) + } else { + res.status(404).send('not found') + } +} + +export async function create(req, res) { + if (res && !await isAllowed(req)) { + res.status(403).send('Not allowed'); + return; + } + if (!req.body || !req.body.year || !req.body.week) { + console.log(req.body); + res.status(400).send('bad request') + return; + } + let data = req.body + if (!data.groupNumber) { + data.groupNumber = null + } + groupModel.create(data) + res.status(200).send() +} + +export async function update(req, res) { + if (res && !await isAllowed(req)) { + res.status(403).send('Not allowed'); + return; + } + if (!req.params || !req.params.id) { + res.status(400).send('bad request') + return; + } + const year = (await settingModel.findByPk('currentYear')).value + const isLT = req.kauth.grant.access_token.content.groups?.includes(year + '_LT') + const group = await groupModel.findByPk(req.params.id) + if (group) { + if (!isLT) { + res.status(403).send() + return; + } + groupModel.update(req.body, {where: {id: req.params.id}}); + res.status(200).send(group) + } else { + res.status(404).send('not found') + } +} + +export async function deleteOne(req, res) { + if (res && !await isAllowed(req)) { + res.status(403).send('Not allowed'); + return; + } + if (!req.params || !req.params.id) { + res.status(400).send('bad request') + return; + } + const year = (await settingModel.findByPk('currentYear')).value + const isLT = req.kauth.grant.access_token.content.groups?.includes(year + '_LT') + const group = await groupModel.findByPk(req.params.id) + if (group) { + if (!isLT) { + res.status(403).send() + return; + } + groupModel.destroy({where: {id: req.params.id}}); + res.status(200).send(group) + } else { + res.status(404).send('not found') + } +} + +export async function autoSort(req, res) { + if (res && !await isAllowed(req)) { + res.status(403).send('Not allowed'); + return; + } + if (!req.params || !req.params.year || !req.params.week) { + res.status(400).send('bad request') + return; + } + const weekString = req.params.week === 1 ? 'teens' : req.params.week === 2 ? 'kids' : req.params.week + const weekNumber = req.params.week === 'teens' ? 1 : req.params.week === 'kids' ? 2 : req.params.week + const year = req.params.year + const groups = await groupModel.findAll({where: {year: year, week: weekNumber}}) + const preferences = await preferenceModel.findAll({where: {groupId: groups.map(group => group.id)}}) + if (preferences.length) { + res.status(400).send('already sorted') + return; + } + let participators = Object.values(await findAllParticipators()) + .filter(p => + p.week === weekString && + p.status === 1 + ) + participators.forEach(participator => { + const groupId = participator.groupId || Math.random() + participator.groupId = groupId + if (participator.wishes === "Ja") { + let wishes = []; + for (let i = 1; i <= 5; i++) { + if (participator[`wish${i}`]) { + wishes.push(participator[`wish${i}`]) + } + } + wishes.forEach(wish => { + const wishParticipator = participators.find(p => { + const firstNames = p.firstName.split(' ') + const lastName = p.lastName + return firstNames.some(firstName => wish.includes(firstName)) && wish.includes(lastName) + }) + if (wishParticipator) { + if (wishParticipator.groupId) { + const gId = wishParticipator.groupId + participators.forEach(p => { + if (p.groupId === gId) { + p.groupId = groupId + } + }) + } else { + const index = participators.findIndex(p => p.orderId === wishParticipator.orderId && p.positionId === wishParticipator.positionId) + participators[index].groupId = groupId + } + } + }) + } + }) + const group1 = groups.find(group => group.groupNumber === 1) + let groupIds = participators.map(p => p.groupId) + groupIds = groupIds.filter((groupId, index) => groupIds.indexOf(groupId) === index) + groupIds.forEach(async groupId => { + const currentParticipators = participators.filter(participator => participator.groupId === groupId) + if (currentParticipators.length === 1) { + return; + } + const preference = await preferenceModel.create({groupId: group1.id}); + for (const participator of currentParticipators) { + await participatorModel.update({preferenceId: preference.id}, {where: {orderId: participator.orderId, positionId: participator.positionId}}) + } + }) + res.status(200).send() +} \ No newline at end of file diff --git a/src/controllers/groupUser.controller.js b/src/controllers/groupUser.controller.js new file mode 100644 index 0000000..f993a5a --- /dev/null +++ b/src/controllers/groupUser.controller.js @@ -0,0 +1,81 @@ +import groupUserModel from '../models/groupUser.model.js' +import userPermissionModel from '../models/userPermission.model.js'; +import settingModel from '../models/setting.model.js' + +async function isAllowed(req) { + const executingUser = req.kauth.grant.access_token.content.sub + const year = await settingModel.findByPk('currentYear') + const isLT = req.kauth.grant.access_token.content.groups?.includes(year + '_LT') + const allowed = isLT || (await userPermissionModel.findOne({where: { uuid: executingUser, permission: 'participator'}}))?.allowed + return allowed +} + +export async function findAll(req, res) { + if (res && !await isAllowed(req)) { + res.status(403).send('Not allowed'); + return; + } + try { + const groupUser = await groupUserModel.findAll({where: req.query}) + res.status(200).send(groupUser) + } catch(e) { + res.status(400).send() + } +} + +export async function findOne(req, res) { + if (res && !await isAllowed(req)) { + res.status(403).send('Not allowed'); + return; + } + if (!req.params || !req.params.groupId || !req.params.uuid) { + res.status(400).send('bad request') + return; + } + const groupUser = await groupUserModel.findOne({where: {group: req.params.groupId, uuid: uuid}}) + if (groupUser) { + res.status(200).send(groupUser) + } else { + res.status(404).send('not found') + } +} + +export async function createOrUpdate(req, res) { + if (res && !await isAllowed(req)) { + res.status(403).send('Not allowed'); + return; + } + if (!req.params || !req.params.group || !req.params.uuid ) { + res.status(400).send('bad request') + return; + } + const groupUser = await groupUserModel.findOne({where: {groupId: req.params.group, uuid: req.params.uuid}}) + if (groupUser) { + groupUserModel.update(req.body); + res.status(200).send(groupUser) + } else { + var data = req.body + data.groupId = req.params.group + data.uuid = req.params.uuid + groupUserModel.create(data) + res.status(200).send(groupUser) + } +} + +export async function deleteOne(req, res) { + if (res && !await isAllowed(req)) { + res.status(403).send('Not allowed'); + return; + } + if (!req.params || !req.params.group || !req.params.uuid ) { + res.status(400).send('bad request') + return; + } + const groupUser = await groupUserModel.findOne({where: {groupId: req.params.group, uuid: req.params.uuid}}) + if (groupUser) { + groupUserModel.destroy({where: {groupId: req.params.group, uuid: req.params.uuid}}); + res.status(200).send(groupUser) + } else { + res.status(404).send('not found') + } +} \ No newline at end of file diff --git a/src/controllers/participator.controller.js b/src/controllers/participator.controller.js index c187bd3..c9d4c56 100644 --- a/src/controllers/participator.controller.js +++ b/src/controllers/participator.controller.js @@ -38,13 +38,21 @@ export async function findAll(req, res) { res.status(403).send('Not allowed'); return; } + const participatorAnswers = await findAllParticipators(); + res.json(participatorAnswers); +} + +export async function findAllParticipators() { const participatorAnswers = await getAllParticipatorsAnswers(); const participators = await participatorModel.findAll(); for (const [key, value] of Object.entries(participatorAnswers)) { let participator = participators.find((participator) => participator.orderId === value.orderId && participator.positionId === value.positionId); - participatorAnswers[key] = {...{status: value.paymentStatus === 'c' ? 2 : (participator?.status || 0)}, ...value}; + participatorAnswers[key] = {...{ + preferenceId: participator?.preferenceId, + status: value.paymentStatus === 'c' ? 2 : (participator?.status || 0) + }, ...value}; } - res.json(participatorAnswers); + return participatorAnswers; } export async function createOrUpdate(req, res) { diff --git a/src/controllers/preference.controller.js b/src/controllers/preference.controller.js new file mode 100644 index 0000000..bbce88f --- /dev/null +++ b/src/controllers/preference.controller.js @@ -0,0 +1,91 @@ +import preferenceModel from '../models/preference.model.js' +import userPermissionModel from '../models/userPermission.model.js'; +import settingModel from '../models/setting.model.js' + +async function isAllowed(req) { + const executingUser = req.kauth.grant.access_token.content.sub + const year = await settingModel.findByPk('currentYear') + const isLT = req.kauth.grant.access_token.content.groups?.includes(year + '_LT') + const allowed = isLT || (await userPermissionModel.findOne({where: { uuid: executingUser, permission: 'participator'}}))?.allowed + return allowed +} + +export async function findAll(req, res) { + if (res && !await isAllowed(req)) { + res.status(403).send('Not allowed'); + return; + } + try { + const preference = await preferenceModel.findAll({where: req.query}) + res.status(200).send(preference) + } catch(e) { + console.log(e); + res.status(400).send() + } +} + +export async function findOne(req, res) { + if (res && !await isAllowed(req)) { + res.status(403).send('Not allowed'); + return; + } + if (!req.params || !req.params.id) { + res.status(400).send('bad request') + return; + } + const preference = await preferenceModel.findByPk(req.params.id) + if (preference) { + res.status(200).send(preference) + } else { + res.status(404).send('not found') + } +} + +export async function create(req, res) { + if (res && !await isAllowed(req)) { + res.status(403).send('Not allowed'); + return; + } + if (!req.params || !req.body.groupId) { + res.status(400).send('bad request') + return; + } + let preference = await preferenceModel.create(req.body) + res.status(200).send(preference) +} + +export async function update(req, res) { + if (res && !await isAllowed(req)) { + res.status(403).send('Not allowed'); + return; + } + if (!req.params || !req.params.id) { + res.status(400).send('bad request') + return; + } + const preference = await preferenceModel.findByPk(req.params.id) + if (preference) { + await preferenceModel.update(req.body, {where: {id: req.params.id}}); + res.status(200).send(preference) + } else { + res.status(404).send('not found') + } +} + +export async function deleteOne(req, res) { + if (res && !await isAllowed(req)) { + res.status(403).send('Not allowed'); + return; + } + if (!req.params || !req.params.id) { + res.status(400).send('bad request') + return; + } + const preference = await preferenceModel.findByPk(req.params.id) + if (preference) { + preferenceModel.destroy({where: {id: req.params.id}}); + res.status(200).send(preference) + } else { + res.status(404).send('not found') + } +} \ No newline at end of file diff --git a/src/models/group.model.js b/src/models/group.model.js new file mode 100644 index 0000000..6543b6c --- /dev/null +++ b/src/models/group.model.js @@ -0,0 +1,36 @@ +import { DataTypes } from 'sequelize'; +import sequelize from './db.model.js'; + +export default sequelize.define('Group', { + id: { + primaryKey: true, + type: DataTypes.INTEGER, + autoIncrement: true + }, + year: { + type: DataTypes.INTEGER, + }, + /** + * 1: teens + * 2: kids + */ + week: { + type: DataTypes.INTEGER, + }, + groupNumber: { + type: DataTypes.INTEGER, + }, + title: { + type: DataTypes.STRING + }, + color: { + type: DataTypes.STRING + }, + /** + * 1: tent group + * 2: infrastructure + */ + type: { + type: DataTypes.INTEGER + } +}); \ No newline at end of file diff --git a/src/models/groupUser.model.js b/src/models/groupUser.model.js new file mode 100644 index 0000000..4317381 --- /dev/null +++ b/src/models/groupUser.model.js @@ -0,0 +1,28 @@ +import { DataTypes } from 'sequelize'; +import sequelize from './db.model.js'; + +export default sequelize.define('GroupUser', { + groupId: { + type: DataTypes.INTEGER, + primaryKey: true, + references: { + model: 'Groups', + key: 'id' + } + }, + uuid: { + primaryKey: true, + type: DataTypes.UUID, + references: { + model: 'Users', + key: 'uuid' + } + }, + /** + * 1: Team + * 2: Leader + */ + type: { + type: DataTypes.INTEGER + }, +}); \ No newline at end of file diff --git a/src/models/participator.model.js b/src/models/participator.model.js index 867d882..6a0b04b 100644 --- a/src/models/participator.model.js +++ b/src/models/participator.model.js @@ -21,4 +21,11 @@ export default sequelize.define('Participator', { * 3: waiting list */ }, + preferenceId: { + type: DataTypes.INTEGER, + references: { + model: 'Preferences', + key: 'id' + } + }, }); \ No newline at end of file diff --git a/src/models/preference.model.js b/src/models/preference.model.js new file mode 100644 index 0000000..2d5d548 --- /dev/null +++ b/src/models/preference.model.js @@ -0,0 +1,18 @@ +import { DataTypes } from 'sequelize'; +import sequelize from './db.model.js'; + +export default sequelize.define('Preference', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + groupId: { + type: DataTypes.INTEGER, + primaryKey: true, + references: { + model: 'Groups', + key: 'id' + } + } +}); \ No newline at end of file diff --git a/src/routes/group.route.js b/src/routes/group.route.js new file mode 100644 index 0000000..0f568d3 --- /dev/null +++ b/src/routes/group.route.js @@ -0,0 +1,13 @@ +import { Router } from "express"; +import keycloak from "../config/keycloak.js"; +import { findAll, findOne, create, update, deleteOne, autoSort } from "../controllers/group.controller.js"; + +var router = new Router(); +router.get('/', keycloak.protect(), findAll); +router.get('/:id', keycloak.protect(), findOne); +router.post('/', keycloak.protect(), create); +router.post('/:id', keycloak.protect(), update); +router.delete('/:id', keycloak.protect(), deleteOne); +router.post('/autoSort/:year/:week', keycloak.protect(), autoSort); + +export default router \ No newline at end of file diff --git a/src/routes/groupUser.route.js b/src/routes/groupUser.route.js new file mode 100644 index 0000000..f27170c --- /dev/null +++ b/src/routes/groupUser.route.js @@ -0,0 +1,11 @@ +import { Router } from "express"; +import keycloak from "../config/keycloak.js"; +import { findAll, findOne, createOrUpdate, deleteOne } from "../controllers/groupUser.controller.js"; + +var router = new Router(); +router.get('/', keycloak.protect(), findAll); +router.get('/:group/:uuid', keycloak.protect(), findOne); +router.post('/', keycloak.protect(), createOrUpdate); +router.delete('/:group/:uuid', keycloak.protect(), deleteOne); + +export default router \ No newline at end of file diff --git a/src/routes/preference.route.js b/src/routes/preference.route.js new file mode 100644 index 0000000..2c64167 --- /dev/null +++ b/src/routes/preference.route.js @@ -0,0 +1,12 @@ +import { Router } from "express"; +import keycloak from "../config/keycloak.js"; +import { findAll, findOne, create, update, deleteOne } from "../controllers/preference.controller.js"; + +var router = new Router(); +router.get('/', keycloak.protect(), findAll); +router.get('/:id', keycloak.protect(), findOne); +router.post('/', keycloak.protect(), create); +router.post('/:id', keycloak.protect(), update); +router.delete('/:id', keycloak.protect(), deleteOne); + +export default router \ No newline at end of file