UNPKG

synt_backend

Version:

Synt light-weight node backend service

724 lines (673 loc) 19.9 kB
import { Router } from "express"; const router = Router(); const db = require("./../mysql/models/index"); import * as ValidationHelper from "./../helpers/validations"; const userHelper = require("./../helpers/user"); const Sequelize = require("sequelize"); const notifier = require("./../helpers/notifier"); import { formatMeetingItem, formatPowerOfAttorneys, findCommissioners, setMeetingItemResults, } from "../helpers/format"; import { jobReminderManager } from "../helpers/JobReminderManager"; router.get("/:MeetingId", getMeeting); router.get("/", getMeetings); router.post("/powerofattorney", getMeetingWithPowerOfAttorney); router.post("/postponed", postPostponedMeeting); router.post("/", postMeeting); router.post("/:MeetingId/item", postMeetingItem); router.post("/:MeetingId/stream", toggleStreamMeeting); router.post("/:MeetingId/user/:UserId", toggleUserPresenceConfirmation); router.post("/:MeetingId/left/:UserId", toggleUserLeft); router.post("/:MeetingId/powerofattorney", setPowerOfAttorney); router.post("/:MeetingId/presence", toggleUserPresence); async function getMeeting(req, res) { const { t } = req; // verify vme and get vme let user = await userHelper.getAuthUser(req); let VmeValidation = await ValidationHelper.validateVme(t, user.VMEId); //TODO: Validate user roles if (!VmeValidation.success) { return res.json(VmeValidation); } const { VME } = VmeValidation; const { MeetingId } = req.params; db.Meeting.findOne({ where: { id: MeetingId }, include: [ { model: db.MeetingItem, include: [ { model: db.FixedMeetingItem }, { model: db.User, as: "Users" }, ], }, { model: db.User, as: "Users", }, { model: db.FinancialYear }, ], }) .then(async (Meeting) => { if (Meeting.VMEId !== VME.id) { return res.json({ success: false, error: t( "api.meetings.errors.notYourVme", "This event is not part of your VME." ), }); } else if (Meeting) { const Users = await VME.getUsers(); // FIXME: include pivot through: how? (like above) Meeting = formatPowerOfAttorneys(Meeting, Users); Meeting.MeetingItems = Meeting.MeetingItems.map((item) => formatMeetingItem(item) ); // is rechtsgeldig Meeting = await formatLegalValidityMeeting(VME, Meeting); return res.json({ success: true, Meeting, }); } else { return res.json({ success: false, error: t( "api.meetings.errors.eventNotExists", "This event does not exist. Contact support" ), }); } }) .catch((err) => console.log(err)); } async function getMeetingWithPowerOfAttorney(req, res) { const { t } = req; const { first_name, last_name, token } = req.body; if (!first_name) { return res.json({ success: false, errors: { first_name: t( "api.meetings.errors.firstNameRequired", "First name is required." ), }, }); } if (!last_name) { return res.json({ success: false, errors: { last_name: t( "api.meetings.errors.lastNameRequired", "Last name is required." ), }, }); } if (!token) { return res.json({ success: false, errors: { token: t("api.meetings.errors.tokenRequired", "Token is required."), }, }); } db.MeetingUser.findOne({ where: { power_of_attorney_token: token }, }).then((MeetingUser) => { if (first_name !== MeetingUser.power_of_attorney_given_to_first_name) { return res.json({ success: false, errors: { first_name: t( "api.meetings.errors.firstNameIncorrect", "First name is incorrect. Check spelling." ), }, }); } if (last_name !== MeetingUser.power_of_attorney_given_to_last_name) { return res.json({ success: false, errors: { last_name: t( "api.meetings.errors.lastNameIncorrect", "Last name is incorrect. Check spelling." ), }, }); } db.Meeting.findOne({ where: { id: MeetingUser.MeetingId }, include: [ { model: db.MeetingItem, include: [ { model: db.FixedMeetingItem }, { model: db.User, as: "Users" }, ], }, { model: db.User, as: "Users", }, { model: db.FinancialYear, }, ], }) .then((Meeting) => { if (Meeting) { Meeting.MeetingItems = Meeting.MeetingItems.map((item) => formatMeetingItem(item) ); return res.json({ success: true, Meeting, User: Meeting.Users.reduce((acc, item) => { if (item.id === MeetingUser.UserId) { return item; } return acc; }, {}), }); } else { return res.json({ success: false, error: t( "api.meetings.errors.eventNotExists", "This event does not exist. Contact support" ), }); } }) .catch((err) => console.log(err)); }); } async function toggleUserPresence(req, res) { const { t } = req; let UserId; if (req.body.UserId) { // public flow UserId = req.body.UserId; } else { let User = await userHelper.getAuthUser(req); UserId = User.id || false; } if (UserId) { const { MeetingId } = req.params; db.MeetingUser.findOrCreate({ where: { MeetingId, UserId } }) .then((res) => console.log(res)) .catch((err) => console.log(err)); return res.json({ success: true }); } else { return res.json({ success: false, error: t("api.meetings.errors.noPermission", "No permission."), }); } } async function setPowerOfAttorney(req, res) { const { MeetingId } = req.params; let User = await userHelper.getAuthUser(req); if (User) { const { has_power_of_attorney, PowerOfAttorneyGivenToUserId, power_of_attorney_given_to_first_name, power_of_attorney_given_to_last_name, } = req.body; db.MeetingUser.findOrCreate({ where: { MeetingId, UserId: User.id } }).then( (MeetingUser) => { MeetingUser = MeetingUser[0]; MeetingUser.update({ has_power_of_attorney, power_of_attorney_given_to_first_name: has_power_of_attorney ? power_of_attorney_given_to_first_name : null, power_of_attorney_given_to_last_name: has_power_of_attorney ? power_of_attorney_given_to_last_name : null, PowerOfAttorneyGivenToUserId: has_power_of_attorney ? PowerOfAttorneyGivenToUserId : null, }); return res.json({ success: true, power_of_attorney_given_to_first_name: MeetingUser.power_of_attorney_given_to_first_name, power_of_attorney_given_to_last_name: MeetingUser.power_of_attorney_given_to_last_name, power_of_attorney_token: MeetingUser.power_of_attorney_token, }); } ); } } async function toggleUserPresenceConfirmation(req, res) { const { MeetingId, UserId } = req.params; db.MeetingUser.update( { is_confirmed_at: new Date() }, { where: { MeetingId, UserId } } ); return res.json({ success: true }); } async function toggleUserLeft(req, res) { const { MeetingId, UserId } = req.params; db.MeetingUser.update( { has_left_at: new Date() }, { where: { MeetingId, UserId } } ); return res.json({ success: true }); } async function toggleStreamMeeting(req, res) { //TODO: Safety const { MeetingId } = req.params; db.Meeting.update( { is_streaming: Sequelize.literal("NOT is_streaming") }, { where: { id: MeetingId } } ); return res.json({ success: true }); } async function postPostponedMeeting(req, res) { const { t } = req; try { let User = await userHelper.getAuthUser(req); if (User) { // verify vme and get vme let VmeValidation = await ValidationHelper.validateVme(t, User.VMEId); //TODO: Validate user roles if (!VmeValidation.success) { return res.json(VmeValidation); } const { VME } = VmeValidation; const { starts_at, ends_at, Meeting } = req.body; let errors = {}; let now = new Date(); if (Meeting.MeetingId) { // selected date is in the past errors["starts_at"] = t( "api.meetings.errors.meetingAlreadyStarted", "The meeting has already been restarted." ); } if (!starts_at) { // selected date is in the past errors["starts_at"] = t( "api.meetings.errors.startTimeRequired", "Start time is required." ); } if (new Date(starts_at) < now) { // selected date is in the past errors["starts_at"] = t( "api.meetings.errors.startTimeInPast", "Start date is in the past." ); } if (new Date(ends_at) < now) { // selected date is in the past errors["ends_at"] = t( "api.meetings.errors.endTimeInPast", "End date is in the past." ); } if (new Date(ends_at) < starts_at) { // selected date is in the past errors["ends_at"] = t( "api.meetings.errors.startTimeAfterEndTime", "End date must be after the start date." ); } // return errors if there are any if (Object.keys(errors).length) { return res.json({ success: false, errors, }); } // save new postponed meeting createMeeting( VME, { ...Meeting, id: null, // prevent unique constraint error starts_at, ends_at, MeetingId: Meeting.id, name: t("api.meetings.meetingNamePrefix", "(Renewed)") + " " + Meeting.name, description: t( "api.meetings.meetingDescriptionPrefix", "This is a renewed meeting and thus the last chance to participate." ) + " " + Meeting.description, is_streaming: false, createdAt: new Date(), updatedAt: new Date(), }, Meeting.MeetingItems.map((Item) => ({ ...Item, id: null })), User ); return res.json({ success: true }); } } catch (error) { return res.json({ success: false, error }); } } async function postMeetingItem(req, res) { const { t } = req; let user = await userHelper.getAuthUser(req); if (user) { const { name, description, decision, is_vote_required, majority, is_accepted, } = req.body; const { MeetingId } = req.params; // TODO: check user whether he/she has access to the meeting and permissions const meeting_item = { name, description, is_vote_required, is_accepted, majority, decision, UserId: user.id, // save which user is adding the item MeetingId, }; if (!MeetingId) { return res.json({ success: false, error: t( "api.meetings.errors.agendaNotLinkedToMeeting", "Agenda item must be linked to a meeting. Contact administrator." ), }); } if (!name || name === "") { return res.json({ success: false, errors: { name: t("api.meetings.errors.nameRequired", "Name is required."), }, }); } db.MeetingItem.create(meeting_item).then(() => { return res.json({ success: true }); }); } } function formatLegalValidityMeeting(VME, Meeting) { return findCommissioners(VME).then((Commissioners) => { const totals = Commissioners.reduce( (acc, C) => { C = C.toJSON(); let U = Meeting.Users.find((U) => C.id === U.id); if (U && U.MeetingUser && U.MeetingUser.is_confirmed_at) { acc.total_present_shares += C.representing_shares || 0; acc.total_present += 1; } acc.total_shares += C.representing_shares || 0; acc.total_commissioners += 1; return acc; }, { total_present_shares: 0, total_shares: 0, total_present: 0, total_commissioners: 0, } ); Meeting.setDataValue( "is_legally_valid", totals.total_present_shares / totals.total_shares > 0.5 && totals.total_present / totals.total_commissioners >= 0.5 ); Meeting.setDataValue("total_present_shares", totals.total_present_shares); Meeting.setDataValue("total_shares", totals.total_shares); Meeting.setDataValue("total_present", totals.total_present); Meeting.setDataValue("total_commissioners", totals.total_commissioners); return Meeting; }); } function formatMeetingVotes(Meeting) { Meeting.MeetingItems = Meeting.MeetingItems.map((MeetingItem) => { return setMeetingItemResults(MeetingItem); }); return Meeting; } async function getMeetings(req, res) { const { t } = req; try { // verify vme and get vme let user = await userHelper.getAuthUser(req); let VmeValidation = await ValidationHelper.validateVme(t, user.VMEId); //TODO: Validate user roles if (!VmeValidation.success) { return res.json(VmeValidation); } const { VME } = VmeValidation; VME.getMeetings({ include: [ { model: db.User, attributes: { exclude: [ "phone", "email", "email_verified_at", "phone_verified_at", ], }, }, { model: db.MeetingItem, include: [ { model: db.FixedMeetingItem }, { model: db.User, as: "Users", include: [ { model: db.LotPhase, include: [{ model: db.Lot, where: { VMEId: VME.id } }], }, ], }, ], }, { model: db.FinancialYear }, { model: db.User, as: "Users" }, ], }).then(async (Meetings) => { const Users = await VME.getUsers(); // update fixed meetings items Meetings = Meetings.map((Meeting) => { Meeting.MeetingItems = Meeting.MeetingItems.map((item) => formatMeetingItem(item) ); Meeting = formatMeetingVotes(Meeting); Meeting = formatPowerOfAttorneys(Meeting, Users); return Meeting; }); return res.json({ success: true, Meetings: Meetings.sort( (a, b) => new Date(b.starts_at) - new Date(a.starts_at) ), }); }); } catch (error) { return res.json({ success: false, error }); } } async function postMeeting(req, res) { const { t } = req; try { let user = await userHelper.getAuthUser(req); if (user) { // verify vme and get vme let VmeValidation = await ValidationHelper.validateVme(t, user.VMEId); //TODO: Validate user roles if (!VmeValidation.success) { return res.json(VmeValidation); } const { VME } = VmeValidation; //TODO: update existing const { id, name, description, is_cancelled, starts_at, hours, type, MeetingItems, address, FinancialYear, } = req.body; const data = { id, name, description, starts_at, is_cancelled, type, address, UserId: user.id, VMEId: VME.id, FinancialYearId: FinancialYear.id, }; data.ends_at = new Date(starts_at); data.ends_at.setTime(data.ends_at.getTime() + hours * 60 * 60 * 1000); const ends_at = data.ends_at; if (is_cancelled && id) { // validation not required } else { var now = new Date(); let errors = {}; if (!name || name === "") { errors["name"] = t( "api.meetings.errors.nameRequired", "Name is required." ); } if (!starts_at) { // selected date is in the past errors["starts_at"] = t( "api.meetings.errors.startTimeRequired", "Start time is required." ); } if (new Date(starts_at) < now) { // selected date is in the past errors["starts_at"] = t( "api.meetings.errors.startTimeInPast", "Start date is in the past." ); } if (new Date(ends_at) < now) { // selected date is in the past errors["ends_at"] = t( "api.meetings.errors.endTimeInPast", "End date is in the past." ); } if (new Date(ends_at) < starts_at) { // selected date is in the past errors["ends_at"] = t( "api.meetings.errors.startTimeAfterEndTime", "End date must be after the start date." ); } if (type === "general" && !address) { // selected date is in the past errors["address"] = t( "api.meetings.errors.addressRequired", "An address is required for a general meeting." ); } if (Object.keys(errors).length) { return res.json({ success: false, errors, }); } } if (id) { // update exisiting meeting const Meeting = await db.Meeting.findOne({ where: { id } }); await Meeting.update(data); jobReminderManager.relativeDatesAreChanged({ vmeId: VME.id, type: "general_meeting", }); await Promise.all( MeetingItems.map(async (MeetingItem) => { if (MeetingItem.id) { await db.MeetingItem.update(MeetingItem, { where: { id: MeetingItem.id }, }); } else { await db.MeetingItem.create({ ...MeetingItem, MeetingId: Meeting.id, }); } }) ); // send mail to all users // TODO: Use queue (cron-schedule / bull queue https://www.npmjs.com/package/bull) const Users = await VME.getUsers(); Users.forEach((U) => { notifier.notify(U, "update_meeting", { Meeting, VME, }); }); return res.json({ success: true, Meeting }); } else { await createMeeting(VME, data, MeetingItems, user); return res.json({ success: true }); } } } catch (error) { return res.json({ success: false, error }); } } async function createMeeting(VME, Meeting, MeetingItems, User) { const createdMeeting = await db.Meeting.create(Meeting); if (MeetingItems && MeetingItems.length) { await db.MeetingItem.bulkCreate( MeetingItems.map((item) => ({ ...item, MeetingId: createdMeeting.id, UserId: User.id, })) ); } jobReminderManager.relativeDatesAreChanged({ vmeId: VME.id, type: "general_meeting", }); // send mail to all users // TODO: Use queue (cron-schedule / bull queue https://www.npmjs.com/package/bull) const Users = await VME.getUsers(); // send notifications Users.forEach((User) => { notifier.notify(User, "new_meeting", { Meeting: createdMeeting, VME, }); }); } module.exports = router;