synt_backend
Version:
Synt light-weight node backend service
819 lines (717 loc) • 19.5 kB
JavaScript
import { Router } from "express";
const router = Router();
const db = require("./../mysql/models/index");
import * as ValidationHelper from "./../helpers/validations";
const userHelper = require("./../helpers/user");
import { formatUser, formatVME } from "./../helpers/format";
import { setupRemindersForVME } from "../database/reminders";
const notifier = require("./../helpers/notifier");
const { Op } = require("sequelize");
router.get("/", getVMEs);
router.get("/:VMEId", getVME);
router.post("/", postVME);
router.get("/user/:UserId", getUser);
router.post("/user", attachUser);
router.post("/:VMEId/freeze", freeze); // only for admin
router.delete("/:VMEId/delete", deleteVME); // only for admin
router.post("/:VMEId/select", selectVME);
async function getUser(req, res) {
const { t } = req;
try {
let user = await userHelper.getAuthUser(req);
let { UserId } = req.params;
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; // id is used
const Users = await VME.getUsers({ where: { id: UserId } });
if (Users.length) {
let User = Users[0];
if (User.CompanyId) {
let Company = await User.getCompany();
User.setDataValue("Company", Company);
}
let vmes = [];
if (User.UserVME?.type === "bookkeeper") {
vmes = await db.VME.findAll({
include: [{ model: db.User, where: { id: UserId }, as: "Users" }],
});
console.log(`vmes`, vmes);
}
return res.json({
success: true,
User: formatUser(User, req.t),
vmes,
});
}
return res.json({
success: false,
error: t(
"api.vmes.errors.notYourVme",
"This user is not a member of your VME."
),
});
}
} catch (error) {
return res.json({ success: false, error: error.message });
}
}
async function selectVME(req, res) {
const { t } = req;
let User = await userHelper.getAuthUser(req);
if (User) {
const { VMEId } = req.params;
if (!Number.isInteger(parseInt(VMEId))) {
return res.json({
success: false,
error: t("api.vmes.errors.firstSelectVme", "First select a VME."),
});
}
if (!User.is_admin) {
if (!User.hasVME(Number(VMEId))) {
return res.json({
success: false,
error: t(
"api.vmes.errors.noPermissions",
"You don't have the right role."
),
});
}
}
const VME = await db.VME.findOne({
where: { id: VMEId },
include: [{ model: db.Company }, { model: db.User, as: "Users" }],
});
const UserVME = await db.UserVME.findOne({
where: { UserId: User.id, VMEId: VME.id },
});
if (UserVME) {
User.setDataValue("UserVME", UserVME);
User = formatUser(User, req.t);
}
return res.json({
success: true,
User: userHelper.addJwtToken(User, VMEId),
VME: formatVME(VME, req.t),
});
}
}
async function deleteVME(req, res) {
const { t } = req;
let user = await userHelper.getAuthUser(req);
if (user) {
if (!user.is_admin) {
return res.json({
success: false,
error: t(
"api.vmes.errors.noPermissions",
"You don't have the right role."
),
});
}
const { VMEId } = req.params;
const VME = await db.VME.findOne({ where: { id: VMEId } });
await VME.destroy();
return res.json({ success: true });
}
}
async function freeze(req, res) {
const { t } = req;
let user = await userHelper.getAuthUser(req);
if (user) {
if (!user.is_admin) {
return res.json({
success: false,
error: t(
"api.vmes.errors.noPermissions",
"You don't have the right role."
),
});
}
const { VMEId } = req.params;
/*
db.VME.update(
{ is_frozen: Sequelize.literal("NOT is_frozen") },
{ where: { id: VMEId } }
);
*/
const VME = await db.VME.findOne({ where: { id: VMEId } });
VME.is_frozen = !VME.is_frozen;
await VME.save();
return res.json({
success: true,
VME,
});
}
}
async function createUser(id, email, userData) {
let newUser = null;
// if you have id, return by id
if (!newUser && id) {
newUser = await db.User.findOne({ where: { id } });
}
// email, CompanyId should be unique
if (!newUser && email) {
newUser = await db.User.findOne({
where: { email, CompanyId: userData["CompanyId"] },
});
}
if (!newUser) {
newUser = await db.User.create({ ...userData });
} else {
// FIXME: change email for known user...
await newUser.update({ ...userData });
}
return newUser;
}
const notifyCreateOrUpdateUser = (type, User, VME, Synt) => {
if (type === "synt_authoriser" || type === "synt_viewer") {
// send welcome mail synt user
notifier.notify(User, "new_synt", {
VME,
Synt,
});
} else if (type === "commissioner") {
// send welcome mail commissioner user
notifier.notify(User, "new_commissioner", {
VME,
Synt,
});
} else {
// send welcome mail normal user
notifier.notify(User, "new_user", {
VME,
Synt,
});
}
};
const depromoteUsers = async (type, VME) => {
// depromote existing synt
if (type === "synt_authoriser") {
await db.UserVME.update(
{ type: "synt_viewer" },
{
where: {
type: "synt_authoriser",
VMEId: VME.id,
},
}
);
}
if (type === "commissioner") {
await db.UserVME.update(
{ type: "regular_user" },
{
where: {
type: "commissioner",
VMEId: VME.id,
},
}
);
}
};
const getSynt = (type, User, VME) => {
let Synt = {};
if (type === "synt_authoriser") {
Synt = User;
} else {
VME.Users.forEach((User) => {
if (User.UserVME.type === "synt_authoriser") {
Synt = User;
}
});
}
return Synt;
};
const validateUserCharacter = async ({ t, id, email, UserAsCompany, VME }) => {
let existsAsNatural = false;
if (!UserAsCompany) {
existsAsNatural = await db.User.findOne({
where: {
email,
CompanyId: null,
...(id !== null && { id: { [Op.not]: id } }),
},
include: { model: db.VME, where: { id: VME.id }, as: "VMEs" },
});
}
if (!UserAsCompany && existsAsNatural) {
// cannot create new user because of exisiting email
return {
success: false,
errors: {
email: t(
"api.vmes.errors.emailAlreadyLinked",
"Email is already linked to a natural user in this VME."
),
},
};
}
let existsAsCompany = false;
if (UserAsCompany) {
existsAsCompany = await db.User.findOne({
where: {
email,
CompanyId: UserAsCompany.id,
...(id !== null && { id: { [Op.not]: id } }),
},
include: { model: db.VME, where: { id: VME.id }, as: "VMEs" },
});
}
if (UserAsCompany && existsAsCompany) {
// cannot create new user because of exisiting email
return {
success: false,
errors: {
email: t(
"api.vmes.errors.emailAlreadyLinkedCompany",
"Email and company already exist in this VME."
),
},
};
}
return { success: true };
};
async function attachUser(req, res) {
const { t } = req;
const {
id,
email,
phone,
first_name,
last_name,
vat_number, // vat_number of the VME (admin path)
Company, // user as company
character, // natural or legal
address,
UserVME,
type = "regular_user", // type user within vme
vmes,
} = req.body;
// verify user
let validation = ValidationHelper.validateNewUser(req.body, t);
if (!validation.success) {
return res.json(validation);
}
let errors = {};
if (!first_name) {
errors["first_name"] = t(
"api.vmes.errors.firstNameRequired",
"First name is required."
);
}
if (!last_name) {
errors["last_name"] = t(
"api.vmes.errors.lastNameRequired",
"Last name is rquired"
);
}
if (!phone) {
errors["phone"] = t("api.vmes.errors.phoneRequired", "Phone is required");
}
if (!address && character === "natural") {
errors["address"] = t(
"api.vmes.errors.addressRequired",
"Address is required"
);
}
if (character === "legal" && !Company) {
errors["vat_number"] = t(
"api.vmes.errors.companyRequired",
"Company must be known."
);
}
if (Object.keys(errors).length) {
return res.json({
success: false,
errors,
});
}
let UserAsCompany;
if (Company) {
// verify and format vat number
const vatValidation = ValidationHelper.formatVatNumber(
Company?.vat_number,
req.t
);
if (!vatValidation.success) {
return res.json(vatValidation);
}
// define const
const company_number = vatValidation.company_number;
const country_code = vatValidation.country_code;
UserAsCompany = await db.Company.findOne({
where: { company_number, country_code },
});
}
const userData = {
email,
phone,
first_name,
last_name,
character,
type,
CompanyId: UserAsCompany?.id || null,
address: address ? address : Company?.address,
};
try {
let User = await userHelper.getAuthUser(req);
if (User) {
//CHECK: IS ADMIN PATH USED???
if (vat_number) {
// admin path to add users to any VME
if (!User.is_admin) {
return res.json({
success: false,
error: t(
"api.vmes.errors.noPermissions",
"You don't have the right role."
),
});
}
// verify and format vat number
const vatValidation = ValidationHelper.formatVatNumber(
vat_number,
req.t
);
if (!vatValidation.success) {
return res.json(vatValidation);
}
// define const
const { company_number, country_code } = vatValidation;
const validation = await validateUserCharacter({
id,
email,
UserAsCompany,
VME,
t,
});
if (!validation.success) {
return res.json(validation);
}
let newUser = await createUser(id, email, userData);
const VMECompany = await db.Company.findOne({
where: { company_number, country_code },
});
if (!VMECompany) {
return res.json({
success: false,
error: t(
"api.vmes.errors.vmeNotExist",
"The VME with accompanying company number does not yet exist. First save the new VME and then link a user."
),
});
}
const VME = await db.VME.findOne({
where: { CompanyId: VMECompany.id },
});
if (!VME) {
return res.json({
success: false,
error: t(
"api.vmes.errors.vmeNotExist",
"The VME with accompanying company number does not yet exist. First save the new VME and then link a user."
),
});
}
// depromote
depromoteUsers(type, VME);
// get synt
const Synt = getSynt(type, newUser, VME);
if (type === "bookkeeper" && vmes) {
await syncUserVmes(type, newUser, vmes);
}
try {
await VME.addUser(newUser, {
through: {
type,
},
});
notifyCreateOrUpdateUser(type, newUser, VME, Synt);
} catch (e) {
console.log(e);
}
return res.json({ success: true });
} else {
// user logged in to specific VME
// verify vme and get vme
let VmeValidation = await ValidationHelper.validateVme(
t,
User.VMEId,
User,
true
); //TODO: Validate user roles
if (!VmeValidation.success) {
return res.json(VmeValidation);
}
const { VME } = VmeValidation;
const validation = await validateUserCharacter({
id,
email,
UserAsCompany,
VME,
t,
});
if (!validation.success) {
return res.json(validation);
}
/*
if (VME.is_frozen) {
return res.json({
success: false,
error: "VME is bevrozen. Je kan geen wijzigen aanbrengen.",
});
}
*/
let newUser = await createUser(id, email, userData);
// depromote
depromoteUsers(type, VME);
if (type === "bookkeeper" && vmes) {
await syncUserVmes(type, newUser, vmes);
}
if (UserVME) {
newUser.UserVME = UserVME;
newUser.UserVME.type = type;
}
const has = await VME.hasUser(newUser);
// get synt
const Synt = getSynt(type, newUser, VME);
if (!has) {
await VME.addUser(newUser, {
through: {
type,
},
});
try {
notifyCreateOrUpdateUser(type, newUser, VME, Synt);
// update disabled
if (id && typeof UserVME !== "undefined") {
await db.UserVME.update(
{ is_disabled: UserVME.is_disabled },
{
where: {
VMEId: VME.id,
UserId: id,
},
}
);
}
} catch (e) {
console.log(e);
}
return res.json({ success: true });
} else {
console.log("update");
// send update mail normal user
notifier.notify(newUser, "update_user", {
VME,
Synt,
});
// update disabled
if (id && typeof UserVME !== "undefined") {
await db.UserVME.update(
{ is_disabled: UserVME.is_disabled, type },
{
where: {
VMEId: VME.id,
UserId: id,
},
}
);
}
}
return res.json({ success: true });
}
}
} catch (error) {
console.log(error);
return res.json({ success: false, error });
}
}
async function syncUserVmes(type, user, vmes) {
const existingVmes = await db.UserVME.findAll({
include: [{ model: db.User, where: { id: user.id }, as: "User" }],
});
const toDelete = existingVmes.filter(
(ev) => !vmes.find((v) => v.value === ev.id)
);
if (toDelete.length > 0) {
await Promise.all(toDelete.map((td) => td.destroy()));
}
const toCreate = vmes.filter(
(v) => !existingVmes.find((ev) => v.value === ev.id)
);
if (toCreate.length > 0) {
await Promise.all(
toCreate.map(async (v) => {
const VME = await db.VME.findOne({ where: { id: v.value } });
await VME.addUser(user, {
through: {
type,
},
});
})
);
}
}
async function getVME(req, res) {
const { t } = req;
let user = await userHelper.getAuthUser(req);
const { VMEId } = req.params;
if (user) {
const VME = await db.VME.findOne({
where: { id: VMEId },
include: [{ model: db.Company }, { model: db.User, as: "Users" }],
});
if (VME) {
if (!user.is_admin) {
if (VME.hasUser(user)) {
return res.json({
success: true,
VME: formatVME(VME, req.t),
});
}
return res.json({
success: false,
error: t(
"api.vmes.errors.noAccountOrPermissions",
"You do not yet have an account or do not have the appropriate role."
),
});
}
// admin
return res.json({
success: true,
VME: formatVME(VME, req.t),
});
}
return res.json({
success: false,
error: t("api.vmes.errors.unknownVme", "VME is unknown."),
});
}
}
async function getVMEs(req, res) {
const { t } = req;
let User = await userHelper.getAuthUser(req);
if (User) {
if (!User.is_admin) {
// get specific VMEs linked to the user
const VMEs = await User.getVMEs({ include: [{ model: db.Company }] });
if (VMEs) {
return res.json({
success: true,
VMEs: VMEs.map((VME) => formatVME(VME, req.t)),
});
}
return res.json({
success: false,
error: t(
"api.vmes.errors.noAccountOrPermissions",
"You do not yet have an account or do not have the appropriate role."
),
});
}
// admin can get all
const VMEs = await db.VME.findAll({
include: [{ model: db.Company }, { model: db.User, as: "Users" }],
});
return res.json({
success: true,
VMEs: VMEs.map((VME) => formatVME(VME, req.t)),
});
}
return res.json({ success: false });
}
async function postVME(req, res) {
const { t } = req;
// verify and format vat number
const vatValidation = ValidationHelper.formatVatNumber(
req.body.vat_number,
req.t
);
if (!vatValidation.success) {
return res.json(vatValidation);
}
// define const
const { company_number, country_code } = vatValidation;
const {
alias,
meeting_period_starts_at,
meeting_period_ends_at,
insurance_company,
insurance_number,
division_id,
division_name,
total_shares,
phone,
email,
current_account,
savings_account,
financial_year_starts_at,
has_banking,
create_reminder = true,
} = req.body;
if (!alias) {
return res.json({
success: false,
errors: {
alias: t("api.vmes.post.errors.aliasRequired", "An alias is required."),
},
});
}
// find existing company
const company = await db.Company.findOne({
where: { company_number, country_code },
});
if (!company) {
return res.json({
success: false,
error: t(
"api.vmes.post.errors.companyNumberIncorrect",
"Company number incorrect."
),
});
}
company.phone = phone;
company.email = email;
// TODO: Change current account only after approval
company.current_account = current_account;
company.savings_account = savings_account;
await company.save();
// save vme
const VMEData = {
CompanyId: company.id,
alias,
meeting_period_ends_at,
meeting_period_starts_at,
insurance_company,
insurance_number,
division_id,
division_name,
total_shares,
financial_year_starts_at,
has_banking,
};
const VME = await db.VME.findOne({ where: { CompanyId: company.id } });
try {
if (VME) {
await VME.update(VMEData);
console.log("VME updated");
} else {
const vme = await db.VME.create(VMEData);
console.log("VME created");
if (create_reminder) {
await setupRemindersForVME(t, vme.id);
}
}
} catch (e) {
console.log(e);
return res.json({ success: false });
}
return res.json({ success: true });
}
module.exports = router;