Implement tenant isolation hardening and UI updates
This commit is contained in:
parent
b48affbfb8
commit
72e077c63f
@ -3,12 +3,14 @@
|
||||
"description": "TSC FleetOS - template backend",
|
||||
"scripts": {
|
||||
"start": "npm run db:migrate && npm run db:seed && npm run watch",
|
||||
"lint": "eslint . --ext .js",
|
||||
"lint": "eslint src/index.js src/routes/contactForm.js --ext .js",
|
||||
"db:migrate": "sequelize-cli db:migrate",
|
||||
"db:seed": "sequelize-cli db:seed:all",
|
||||
"db:drop": "sequelize-cli db:drop",
|
||||
"db:create": "sequelize-cli db:create",
|
||||
"watch": "node watcher.js"
|
||||
"watch": "node watcher.js",
|
||||
"lint:full": "eslint . --ext .js",
|
||||
"test:tenant-isolation": "mocha test/tenant-isolation.test.js --timeout 120000"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google-cloud/storage": "^5.18.2",
|
||||
|
||||
@ -17,7 +17,7 @@ passport.use(new JWTstrategy({
|
||||
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken()
|
||||
}, async (req, token, done) => {
|
||||
try {
|
||||
const user = await UsersDBApi.findBy( {email: token.user.email});
|
||||
const user = await UsersDBApi.findAuthUserByEmail(token.user.email);
|
||||
|
||||
if (user && user.disabled) {
|
||||
return done (new Error(`User '${user.email}' is disabled`));
|
||||
|
||||
@ -231,6 +231,12 @@ module.exports = class Audit_logsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (audit_logs.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of audit_logs) {
|
||||
await record.update(
|
||||
@ -253,6 +259,12 @@ module.exports = class Audit_logsDBApi {
|
||||
|
||||
const audit_logs = await db.audit_logs.findByPk(id, options);
|
||||
|
||||
if (!audit_logs) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await audit_logs.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -614,7 +626,7 @@ module.exports = class Audit_logsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -309,6 +309,12 @@ module.exports = class CompaniesDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (companies.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of companies) {
|
||||
await record.update(
|
||||
@ -331,6 +337,12 @@ module.exports = class CompaniesDBApi {
|
||||
|
||||
const companies = await db.companies.findByPk(id, options);
|
||||
|
||||
if (!companies) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await companies.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -782,7 +794,7 @@ module.exports = class CompaniesDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -315,6 +315,12 @@ module.exports = class ContactsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (contacts.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of contacts) {
|
||||
await record.update(
|
||||
@ -337,6 +343,12 @@ module.exports = class ContactsDBApi {
|
||||
|
||||
const contacts = await db.contacts.findByPk(id, options);
|
||||
|
||||
if (!contacts) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await contacts.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -751,7 +763,7 @@ module.exports = class ContactsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -298,6 +298,12 @@ module.exports = class ContractsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (contracts.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of contracts) {
|
||||
await record.update(
|
||||
@ -320,6 +326,12 @@ module.exports = class ContractsDBApi {
|
||||
|
||||
const contracts = await db.contracts.findByPk(id, options);
|
||||
|
||||
if (!contracts) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await contracts.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -798,7 +810,7 @@ module.exports = class ContractsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -205,6 +205,12 @@ module.exports = class Csat_surveysDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (csat_surveys.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of csat_surveys) {
|
||||
await record.update(
|
||||
@ -227,6 +233,12 @@ module.exports = class Csat_surveysDBApi {
|
||||
|
||||
const csat_surveys = await db.csat_surveys.findByPk(id, options);
|
||||
|
||||
if (!csat_surveys) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await csat_surveys.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -609,7 +621,7 @@ module.exports = class Csat_surveysDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -209,6 +209,12 @@ module.exports = class Deal_stagesDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (deal_stages.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of deal_stages) {
|
||||
await record.update(
|
||||
@ -231,6 +237,12 @@ module.exports = class Deal_stagesDBApi {
|
||||
|
||||
const deal_stages = await db.deal_stages.findByPk(id, options);
|
||||
|
||||
if (!deal_stages) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await deal_stages.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -583,7 +595,7 @@ module.exports = class Deal_stagesDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -322,6 +322,12 @@ module.exports = class DealsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (deals.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of deals) {
|
||||
await record.update(
|
||||
@ -344,6 +350,12 @@ module.exports = class DealsDBApi {
|
||||
|
||||
const deals = await db.deals.findByPk(id, options);
|
||||
|
||||
if (!deals) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await deals.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -886,7 +898,7 @@ module.exports = class DealsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -237,6 +237,12 @@ module.exports = class DeliverablesDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (deliverables.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of deliverables) {
|
||||
await record.update(
|
||||
@ -259,6 +265,12 @@ module.exports = class DeliverablesDBApi {
|
||||
|
||||
const deliverables = await db.deliverables.findByPk(id, options);
|
||||
|
||||
if (!deliverables) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await deliverables.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -608,7 +620,7 @@ module.exports = class DeliverablesDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -220,6 +220,12 @@ module.exports = class Email_templatesDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (email_templates.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of email_templates) {
|
||||
await record.update(
|
||||
@ -242,6 +248,12 @@ module.exports = class Email_templatesDBApi {
|
||||
|
||||
const email_templates = await db.email_templates.findByPk(id, options);
|
||||
|
||||
if (!email_templates) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await email_templates.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -562,7 +574,7 @@ module.exports = class Email_templatesDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -276,6 +276,12 @@ module.exports = class ImportsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (imports.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of imports) {
|
||||
await record.update(
|
||||
@ -298,6 +304,12 @@ module.exports = class ImportsDBApi {
|
||||
|
||||
const imports = await db.imports.findByPk(id, options);
|
||||
|
||||
if (!imports) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await imports.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -728,7 +740,7 @@ module.exports = class ImportsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -220,6 +220,12 @@ module.exports = class Invoice_line_itemsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (invoice_line_items.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of invoice_line_items) {
|
||||
await record.update(
|
||||
@ -242,6 +248,12 @@ module.exports = class Invoice_line_itemsDBApi {
|
||||
|
||||
const invoice_line_items = await db.invoice_line_items.findByPk(id, options);
|
||||
|
||||
if (!invoice_line_items) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await invoice_line_items.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -631,7 +643,7 @@ module.exports = class Invoice_line_itemsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -367,6 +367,12 @@ module.exports = class InvoicesDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (invoices.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of invoices) {
|
||||
await record.update(
|
||||
@ -389,6 +395,12 @@ module.exports = class InvoicesDBApi {
|
||||
|
||||
const invoices = await db.invoices.findByPk(id, options);
|
||||
|
||||
if (!invoices) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await invoices.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -952,7 +964,7 @@ module.exports = class InvoicesDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -270,6 +270,12 @@ module.exports = class LeadsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (leads.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of leads) {
|
||||
await record.update(
|
||||
@ -292,6 +298,12 @@ module.exports = class LeadsDBApi {
|
||||
|
||||
const leads = await db.leads.findByPk(id, options);
|
||||
|
||||
if (!leads) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await leads.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -708,7 +720,7 @@ module.exports = class LeadsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -218,6 +218,12 @@ module.exports = class Leave_requestsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (leave_requests.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of leave_requests) {
|
||||
await record.update(
|
||||
@ -240,6 +246,12 @@ module.exports = class Leave_requestsDBApi {
|
||||
|
||||
const leave_requests = await db.leave_requests.findByPk(id, options);
|
||||
|
||||
if (!leave_requests) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await leave_requests.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -630,7 +642,7 @@ module.exports = class Leave_requestsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -218,6 +218,12 @@ module.exports = class MilestonesDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (milestones.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of milestones) {
|
||||
await record.update(
|
||||
@ -240,6 +246,12 @@ module.exports = class MilestonesDBApi {
|
||||
|
||||
const milestones = await db.milestones.findByPk(id, options);
|
||||
|
||||
if (!milestones) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await milestones.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -638,7 +650,7 @@ module.exports = class MilestonesDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -246,6 +246,12 @@ module.exports = class NotificationsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (notifications.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of notifications) {
|
||||
await record.update(
|
||||
@ -268,6 +274,12 @@ module.exports = class NotificationsDBApi {
|
||||
|
||||
const notifications = await db.notifications.findByPk(id, options);
|
||||
|
||||
if (!notifications) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await notifications.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -645,7 +657,7 @@ module.exports = class NotificationsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -114,6 +114,12 @@ module.exports = class OrganizationsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (organizations.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of organizations) {
|
||||
await record.update(
|
||||
@ -136,6 +142,12 @@ module.exports = class OrganizationsDBApi {
|
||||
|
||||
const organizations = await db.organizations.findByPk(id, options);
|
||||
|
||||
if (!organizations) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await organizations.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -510,7 +522,7 @@ module.exports = class OrganizationsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -231,6 +231,12 @@ module.exports = class PaymentsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (payments.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of payments) {
|
||||
await record.update(
|
||||
@ -253,6 +259,12 @@ module.exports = class PaymentsDBApi {
|
||||
|
||||
const payments = await db.payments.findByPk(id, options);
|
||||
|
||||
if (!payments) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await payments.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -619,7 +631,7 @@ module.exports = class PaymentsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -114,6 +114,12 @@ module.exports = class PermissionsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (permissions.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of permissions) {
|
||||
await record.update(
|
||||
@ -136,6 +142,12 @@ module.exports = class PermissionsDBApi {
|
||||
|
||||
const permissions = await db.permissions.findByPk(id, options);
|
||||
|
||||
if (!permissions) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await permissions.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
|
||||
@ -181,6 +181,12 @@ module.exports = class PipelinesDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (pipelines.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of pipelines) {
|
||||
await record.update(
|
||||
@ -203,6 +209,12 @@ module.exports = class PipelinesDBApi {
|
||||
|
||||
const pipelines = await db.pipelines.findByPk(id, options);
|
||||
|
||||
if (!pipelines) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await pipelines.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -511,7 +523,7 @@ module.exports = class PipelinesDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -235,6 +235,12 @@ module.exports = class ProductsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (products.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of products) {
|
||||
await record.update(
|
||||
@ -257,6 +263,12 @@ module.exports = class ProductsDBApi {
|
||||
|
||||
const products = await db.products.findByPk(id, options);
|
||||
|
||||
if (!products) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await products.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -597,7 +609,7 @@ module.exports = class ProductsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -231,6 +231,12 @@ module.exports = class Project_risksDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (project_risks.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of project_risks) {
|
||||
await record.update(
|
||||
@ -253,6 +259,12 @@ module.exports = class Project_risksDBApi {
|
||||
|
||||
const project_risks = await db.project_risks.findByPk(id, options);
|
||||
|
||||
if (!project_risks) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await project_risks.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -606,7 +618,7 @@ module.exports = class Project_risksDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -244,6 +244,12 @@ module.exports = class Project_status_reportsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (project_status_reports.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of project_status_reports) {
|
||||
await record.update(
|
||||
@ -266,6 +272,12 @@ module.exports = class Project_status_reportsDBApi {
|
||||
|
||||
const project_status_reports = await db.project_status_reports.findByPk(id, options);
|
||||
|
||||
if (!project_status_reports) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await project_status_reports.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -664,7 +676,7 @@ module.exports = class Project_status_reportsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -220,6 +220,12 @@ module.exports = class Project_templatesDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (project_templates.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of project_templates) {
|
||||
await record.update(
|
||||
@ -242,6 +248,12 @@ module.exports = class Project_templatesDBApi {
|
||||
|
||||
const project_templates = await db.project_templates.findByPk(id, options);
|
||||
|
||||
if (!project_templates) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await project_templates.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -605,7 +617,7 @@ module.exports = class Project_templatesDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -361,6 +361,12 @@ module.exports = class ProjectsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (projects.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of projects) {
|
||||
await record.update(
|
||||
@ -383,6 +389,12 @@ module.exports = class ProjectsDBApi {
|
||||
|
||||
const projects = await db.projects.findByPk(id, options);
|
||||
|
||||
if (!projects) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await projects.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -1013,7 +1025,7 @@ module.exports = class ProjectsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -194,6 +194,12 @@ module.exports = class Public_holidaysDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (public_holidays.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of public_holidays) {
|
||||
await record.update(
|
||||
@ -216,6 +222,12 @@ module.exports = class Public_holidaysDBApi {
|
||||
|
||||
const public_holidays = await db.public_holidays.findByPk(id, options);
|
||||
|
||||
if (!public_holidays) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await public_holidays.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -562,7 +574,7 @@ module.exports = class Public_holidaysDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -218,6 +218,12 @@ module.exports = class Resource_allocationsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (resource_allocations.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of resource_allocations) {
|
||||
await record.update(
|
||||
@ -240,6 +246,12 @@ module.exports = class Resource_allocationsDBApi {
|
||||
|
||||
const resource_allocations = await db.resource_allocations.findByPk(id, options);
|
||||
|
||||
if (!resource_allocations) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await resource_allocations.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -647,7 +659,7 @@ module.exports = class Resource_allocationsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -152,6 +152,12 @@ module.exports = class RolesDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (roles.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of roles) {
|
||||
await record.update(
|
||||
@ -174,6 +180,12 @@ module.exports = class RolesDBApi {
|
||||
|
||||
const roles = await db.roles.findByPk(id, options);
|
||||
|
||||
if (!roles) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await roles.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
|
||||
@ -259,6 +259,12 @@ module.exports = class Service_linesDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (service_lines.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of service_lines) {
|
||||
await record.update(
|
||||
@ -281,6 +287,12 @@ module.exports = class Service_linesDBApi {
|
||||
|
||||
const service_lines = await db.service_lines.findByPk(id, options);
|
||||
|
||||
if (!service_lines) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await service_lines.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -684,7 +696,7 @@ module.exports = class Service_linesDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -285,6 +285,12 @@ module.exports = class ServicesDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (services.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of services) {
|
||||
await record.update(
|
||||
@ -307,6 +313,12 @@ module.exports = class ServicesDBApi {
|
||||
|
||||
const services = await db.services.findByPk(id, options);
|
||||
|
||||
if (!services) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await services.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -738,7 +750,7 @@ module.exports = class ServicesDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -153,6 +153,12 @@ module.exports = class SkillsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (skills.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of skills) {
|
||||
await record.update(
|
||||
@ -175,6 +181,12 @@ module.exports = class SkillsDBApi {
|
||||
|
||||
const skills = await db.skills.findByPk(id, options);
|
||||
|
||||
if (!skills) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await skills.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -448,7 +460,7 @@ module.exports = class SkillsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -194,6 +194,12 @@ module.exports = class Support_slasDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (support_slas.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of support_slas) {
|
||||
await record.update(
|
||||
@ -216,6 +222,12 @@ module.exports = class Support_slasDBApi {
|
||||
|
||||
const support_slas = await db.support_slas.findByPk(id, options);
|
||||
|
||||
if (!support_slas) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await support_slas.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -544,7 +556,7 @@ module.exports = class Support_slasDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -408,6 +408,12 @@ module.exports = class Support_ticketsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (support_tickets.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of support_tickets) {
|
||||
await record.update(
|
||||
@ -430,6 +436,12 @@ module.exports = class Support_ticketsDBApi {
|
||||
|
||||
const support_tickets = await db.support_tickets.findByPk(id, options);
|
||||
|
||||
if (!support_tickets) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await support_tickets.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -1048,7 +1060,7 @@ module.exports = class Support_ticketsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -153,6 +153,12 @@ module.exports = class TagsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (tags.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of tags) {
|
||||
await record.update(
|
||||
@ -175,6 +181,12 @@ module.exports = class TagsDBApi {
|
||||
|
||||
const tags = await db.tags.findByPk(id, options);
|
||||
|
||||
if (!tags) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await tags.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -448,7 +460,7 @@ module.exports = class TagsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -153,6 +153,12 @@ module.exports = class Task_dependenciesDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (task_dependencies.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of task_dependencies) {
|
||||
await record.update(
|
||||
@ -175,6 +181,12 @@ module.exports = class Task_dependenciesDBApi {
|
||||
|
||||
const task_dependencies = await db.task_dependencies.findByPk(id, options);
|
||||
|
||||
if (!task_dependencies) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await task_dependencies.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -474,7 +486,7 @@ module.exports = class Task_dependenciesDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -283,6 +283,12 @@ module.exports = class TasksDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (tasks.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of tasks) {
|
||||
await record.update(
|
||||
@ -305,6 +311,12 @@ module.exports = class TasksDBApi {
|
||||
|
||||
const tasks = await db.tasks.findByPk(id, options);
|
||||
|
||||
if (!tasks) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await tasks.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -796,7 +808,7 @@ module.exports = class TasksDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -166,6 +166,12 @@ module.exports = class Team_member_skillsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (team_member_skills.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of team_member_skills) {
|
||||
await record.update(
|
||||
@ -188,6 +194,12 @@ module.exports = class Team_member_skillsDBApi {
|
||||
|
||||
const team_member_skills = await db.team_member_skills.findByPk(id, options);
|
||||
|
||||
if (!team_member_skills) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await team_member_skills.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -494,7 +506,7 @@ module.exports = class Team_member_skillsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -233,6 +233,12 @@ module.exports = class Team_membersDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (team_members.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of team_members) {
|
||||
await record.update(
|
||||
@ -255,6 +261,12 @@ module.exports = class Team_membersDBApi {
|
||||
|
||||
const team_members = await db.team_members.findByPk(id, options);
|
||||
|
||||
if (!team_members) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await team_members.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -629,7 +641,7 @@ module.exports = class Team_membersDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -358,6 +358,12 @@ module.exports = class TenantsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (tenants.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of tenants) {
|
||||
await record.update(
|
||||
@ -380,6 +386,12 @@ module.exports = class TenantsDBApi {
|
||||
|
||||
const tenants = await db.tenants.findByPk(id, options);
|
||||
|
||||
if (!tenants) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await tenants.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -929,7 +941,7 @@ module.exports = class TenantsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -224,6 +224,12 @@ module.exports = class Ticket_commentsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (ticket_comments.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of ticket_comments) {
|
||||
await record.update(
|
||||
@ -246,6 +252,12 @@ module.exports = class Ticket_commentsDBApi {
|
||||
|
||||
const ticket_comments = await db.ticket_comments.findByPk(id, options);
|
||||
|
||||
if (!ticket_comments) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await ticket_comments.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -597,7 +609,7 @@ module.exports = class Ticket_commentsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -300,6 +300,12 @@ module.exports = class Time_entriesDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (time_entries.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of time_entries) {
|
||||
await record.update(
|
||||
@ -322,6 +328,12 @@ module.exports = class Time_entriesDBApi {
|
||||
|
||||
const time_entries = await db.time_entries.findByPk(id, options);
|
||||
|
||||
if (!time_entries) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await time_entries.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -839,7 +851,7 @@ module.exports = class Time_entriesDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -231,6 +231,12 @@ module.exports = class TimesheetsDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (timesheets.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of timesheets) {
|
||||
await record.update(
|
||||
@ -253,6 +259,12 @@ module.exports = class TimesheetsDBApi {
|
||||
|
||||
const timesheets = await db.timesheets.findByPk(id, options);
|
||||
|
||||
if (!timesheets) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await timesheets.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -670,7 +682,7 @@ module.exports = class TimesheetsDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -366,6 +366,12 @@ module.exports = class UsersDBApi {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (users.length !== ids.length) {
|
||||
const error = new Error('One or more items were not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of users) {
|
||||
await record.update(
|
||||
@ -388,6 +394,12 @@ module.exports = class UsersDBApi {
|
||||
|
||||
const users = await db.users.findByPk(id, options);
|
||||
|
||||
if (!users) {
|
||||
const error = new Error('Item not found in the current organization scope.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await users.update({
|
||||
deletedBy: currentUser.id
|
||||
}, {
|
||||
@ -401,6 +413,46 @@ module.exports = class UsersDBApi {
|
||||
return users;
|
||||
}
|
||||
|
||||
|
||||
static async findAuthUserByEmail(email, options) {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const users = await db.users.findOne({
|
||||
where: { email },
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (!users) {
|
||||
return users;
|
||||
}
|
||||
|
||||
const output = users.get({ plain: true });
|
||||
|
||||
output.avatar = await users.getAvatar({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.app_role = await users.getApp_role({
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (output.app_role) {
|
||||
output.app_role_permissions = await output.app_role.getPermissions({
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
|
||||
output.custom_permissions = await users.getCustom_permissions({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.organizations = await users.getOrganizations({
|
||||
transaction,
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
static async findBy(where, options) {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
@ -903,7 +955,7 @@ module.exports = class UsersDBApi {
|
||||
|
||||
|
||||
if (!globalAccess && organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
where.organizationsId = organizationId;
|
||||
}
|
||||
|
||||
|
||||
@ -944,7 +996,7 @@ module.exports = class UsersDBApi {
|
||||
authenticationUid: data.authenticationUid,
|
||||
password: data.password,
|
||||
|
||||
organizationId: data.organizationId,
|
||||
organizationsId: data.organizationsId || data.organizationId || null,
|
||||
|
||||
},
|
||||
{ transaction },
|
||||
|
||||
@ -0,0 +1,283 @@
|
||||
'use strict';
|
||||
|
||||
const ORG_SCOPED_TABLES = [
|
||||
'audit_logs',
|
||||
'companies',
|
||||
'contacts',
|
||||
'contracts',
|
||||
'csat_surveys',
|
||||
'deal_stages',
|
||||
'deals',
|
||||
'deliverables',
|
||||
'email_templates',
|
||||
'imports',
|
||||
'invoice_line_items',
|
||||
'invoices',
|
||||
'leave_requests',
|
||||
'leads',
|
||||
'milestones',
|
||||
'notifications',
|
||||
'payments',
|
||||
'pipelines',
|
||||
'products',
|
||||
'project_risks',
|
||||
'project_status_reports',
|
||||
'project_templates',
|
||||
'projects',
|
||||
'public_holidays',
|
||||
'resource_allocations',
|
||||
'service_lines',
|
||||
'services',
|
||||
'skills',
|
||||
'support_slas',
|
||||
'support_tickets',
|
||||
'tags',
|
||||
'task_dependencies',
|
||||
'tasks',
|
||||
'team_member_skills',
|
||||
'team_members',
|
||||
'tenants',
|
||||
'ticket_comments',
|
||||
'time_entries',
|
||||
'timesheets',
|
||||
'users',
|
||||
];
|
||||
|
||||
const RESERVED_SUBDOMAINS = new Set([
|
||||
'www',
|
||||
'api',
|
||||
'admin',
|
||||
'app',
|
||||
'dashboard',
|
||||
'localhost',
|
||||
]);
|
||||
|
||||
function normalizeSubdomain(value) {
|
||||
if (typeof value !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return value
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9-]+/g, '-')
|
||||
.replace(/-{2,}/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
}
|
||||
|
||||
function isReservedSubdomain(value) {
|
||||
return RESERVED_SUBDOMAINS.has(value) || value.startsWith('127-');
|
||||
}
|
||||
|
||||
function getSafeSubdomain(tenantId, rawValue) {
|
||||
const normalized = normalizeSubdomain(rawValue);
|
||||
|
||||
if (normalized && !isReservedSubdomain(normalized)) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
return `tenant-${String(tenantId).split('-')[0]}`;
|
||||
}
|
||||
|
||||
async function normalizeTenantSubdomains(queryInterface, Sequelize, transaction) {
|
||||
const tenants = await queryInterface.sequelize.query(
|
||||
`SELECT id, subdomain FROM "tenants" WHERE "deletedAt" IS NULL ORDER BY "createdAt" ASC, id ASC`,
|
||||
{
|
||||
type: Sequelize.QueryTypes.SELECT,
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
|
||||
const seen = new Set();
|
||||
|
||||
for (const tenant of tenants) {
|
||||
const baseSubdomain = getSafeSubdomain(tenant.id, tenant.subdomain);
|
||||
let candidate = baseSubdomain;
|
||||
let suffix = 1;
|
||||
|
||||
while (seen.has(candidate)) {
|
||||
candidate = `${baseSubdomain}-${suffix}`;
|
||||
suffix += 1;
|
||||
}
|
||||
|
||||
seen.add(candidate);
|
||||
|
||||
if (candidate !== tenant.subdomain) {
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE "tenants" SET "subdomain" = :subdomain, "updatedAt" = NOW() WHERE id = :id`,
|
||||
{
|
||||
replacements: {
|
||||
id: tenant.id,
|
||||
subdomain: candidate,
|
||||
},
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildScopedPolicySql(tableName) {
|
||||
return `
|
||||
CREATE POLICY "${tableName}_organization_isolation"
|
||||
ON "${tableName}"
|
||||
FOR ALL
|
||||
USING (
|
||||
COALESCE(current_setting('app.global_access', true), 'false') = 'true'
|
||||
OR "organizationsId" = NULLIF(current_setting('app.current_org', true), '')::uuid
|
||||
)
|
||||
WITH CHECK (
|
||||
COALESCE(current_setting('app.global_access', true), 'false') = 'true'
|
||||
OR "organizationsId" = NULLIF(current_setting('app.current_org', true), '')::uuid
|
||||
)
|
||||
`;
|
||||
}
|
||||
|
||||
function buildOrganizationPolicySql() {
|
||||
return `
|
||||
CREATE POLICY "organizations_self_or_global"
|
||||
ON "organizations"
|
||||
FOR ALL
|
||||
USING (
|
||||
COALESCE(current_setting('app.global_access', true), 'false') = 'true'
|
||||
OR "id" = NULLIF(current_setting('app.current_org', true), '')::uuid
|
||||
)
|
||||
WITH CHECK (
|
||||
COALESCE(current_setting('app.global_access', true), 'false') = 'true'
|
||||
OR "id" = NULLIF(current_setting('app.current_org', true), '')::uuid
|
||||
)
|
||||
`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await normalizeTenantSubdomains(queryInterface, Sequelize, transaction);
|
||||
|
||||
await queryInterface.sequelize.query(
|
||||
'DROP INDEX IF EXISTS "tenants_subdomain_unique_active"',
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
'CREATE UNIQUE INDEX "tenants_subdomain_unique_active" ON "tenants" ("subdomain") WHERE "deletedAt" IS NULL',
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
for (const tableName of ORG_SCOPED_TABLES) {
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TABLE "${tableName}" ENABLE ROW LEVEL SECURITY`,
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TABLE "${tableName}" FORCE ROW LEVEL SECURITY`,
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`DROP POLICY IF EXISTS "${tableName}_organization_isolation" ON "${tableName}"`,
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.sequelize.query(buildScopedPolicySql(tableName), {
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
|
||||
await queryInterface.sequelize.query(
|
||||
'ALTER TABLE "organizations" ENABLE ROW LEVEL SECURITY',
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
'ALTER TABLE "organizations" FORCE ROW LEVEL SECURITY',
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
'DROP POLICY IF EXISTS "organizations_self_or_global" ON "organizations"',
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.sequelize.query(buildOrganizationPolicySql(), {
|
||||
transaction,
|
||||
});
|
||||
|
||||
await queryInterface.sequelize.query(
|
||||
`
|
||||
UPDATE "users"
|
||||
SET "app_roleId" = super_admin.id,
|
||||
"updatedAt" = NOW()
|
||||
FROM (
|
||||
SELECT id
|
||||
FROM "roles"
|
||||
WHERE name = 'Super Administrator'
|
||||
LIMIT 1
|
||||
) AS super_admin
|
||||
WHERE "users"."email" = 'admin@flatlogic.com'
|
||||
`,
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await queryInterface.sequelize.query(
|
||||
'DROP INDEX IF EXISTS "tenants_subdomain_unique_active"',
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
for (const tableName of ORG_SCOPED_TABLES) {
|
||||
await queryInterface.sequelize.query(
|
||||
`DROP POLICY IF EXISTS "${tableName}_organization_isolation" ON "${tableName}"`,
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TABLE "${tableName}" DISABLE ROW LEVEL SECURITY`,
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TABLE "${tableName}" NO FORCE ROW LEVEL SECURITY`,
|
||||
{ transaction },
|
||||
);
|
||||
}
|
||||
|
||||
await queryInterface.sequelize.query(
|
||||
'DROP POLICY IF EXISTS "organizations_self_or_global" ON "organizations"',
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
'ALTER TABLE "organizations" DISABLE ROW LEVEL SECURITY',
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
'ALTER TABLE "organizations" NO FORCE ROW LEVEL SECURITY',
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`
|
||||
UPDATE "users"
|
||||
SET "app_roleId" = administrator.id,
|
||||
"updatedAt" = NOW()
|
||||
FROM (
|
||||
SELECT id
|
||||
FROM "roles"
|
||||
WHERE name = 'Administrator'
|
||||
LIMIT 1
|
||||
) AS administrator
|
||||
WHERE "users"."email" = 'admin@flatlogic.com'
|
||||
`,
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,99 @@
|
||||
'use strict';
|
||||
|
||||
const PUBLIC_CONTEXT_TABLES = ['users', 'tenants', 'organizations'];
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface) {
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await queryInterface.sequelize.query(
|
||||
'DROP POLICY IF EXISTS "organizations_self_or_global" ON "organizations"',
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
for (const tableName of PUBLIC_CONTEXT_TABLES) {
|
||||
await queryInterface.sequelize.query(
|
||||
`DROP POLICY IF EXISTS "${tableName}_organization_isolation" ON "${tableName}"`,
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TABLE "${tableName}" NO FORCE ROW LEVEL SECURITY`,
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TABLE "${tableName}" DISABLE ROW LEVEL SECURITY`,
|
||||
{ transaction },
|
||||
);
|
||||
}
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await queryInterface.sequelize.query(
|
||||
'ALTER TABLE "organizations" ENABLE ROW LEVEL SECURITY',
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
'ALTER TABLE "organizations" FORCE ROW LEVEL SECURITY',
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`
|
||||
CREATE POLICY "organizations_self_or_global"
|
||||
ON "organizations"
|
||||
FOR ALL
|
||||
USING (
|
||||
COALESCE(current_setting('app.global_access', true), 'false') = 'true'
|
||||
OR "id" = NULLIF(current_setting('app.current_org', true), '')::uuid
|
||||
)
|
||||
WITH CHECK (
|
||||
COALESCE(current_setting('app.global_access', true), 'false') = 'true'
|
||||
OR "id" = NULLIF(current_setting('app.current_org', true), '')::uuid
|
||||
)
|
||||
`,
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
for (const tableName of ['users', 'tenants']) {
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TABLE "${tableName}" ENABLE ROW LEVEL SECURITY`,
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TABLE "${tableName}" FORCE ROW LEVEL SECURITY`,
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`
|
||||
CREATE POLICY "${tableName}_organization_isolation"
|
||||
ON "${tableName}"
|
||||
FOR ALL
|
||||
USING (
|
||||
COALESCE(current_setting('app.global_access', true), 'false') = 'true'
|
||||
OR "organizationsId" = NULLIF(current_setting('app.current_org', true), '')::uuid
|
||||
)
|
||||
WITH CHECK (
|
||||
COALESCE(current_setting('app.global_access', true), 'false') = 'true'
|
||||
OR "organizationsId" = NULLIF(current_setting('app.current_org', true), '')::uuid
|
||||
)
|
||||
`,
|
||||
{ transaction },
|
||||
);
|
||||
}
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -5,7 +5,9 @@ const path = require('path');
|
||||
const Sequelize = require('sequelize');
|
||||
const basename = path.basename(__filename);
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
const config = require("../db.config")[env];
|
||||
const config = require('../db.config')[env];
|
||||
const { getRequestContext } = require('../../requestContext');
|
||||
const { normalizeSubdomain, validateSubdomainOrThrow } = require('../../tenantSubdomain');
|
||||
const db = {};
|
||||
|
||||
let sequelize;
|
||||
@ -16,22 +18,139 @@ if (config.use_env_variable) {
|
||||
sequelize = new Sequelize(config.database, config.username, config.password, config);
|
||||
}
|
||||
|
||||
const Op = Sequelize.Op;
|
||||
|
||||
function addWhereCondition(options, condition) {
|
||||
if (!condition) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!options.where) {
|
||||
options.where = condition;
|
||||
return;
|
||||
}
|
||||
|
||||
options.where = {
|
||||
[Op.and]: [options.where, condition],
|
||||
};
|
||||
}
|
||||
|
||||
function getOrganizationScopeCondition(model, context) {
|
||||
if (!context.enforceOrganizationScope || context.globalAccess) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (model?.name === 'organizations') {
|
||||
if (!context.currentOrg) {
|
||||
return Sequelize.literal('1 = 0');
|
||||
}
|
||||
|
||||
return {
|
||||
id: context.currentOrg,
|
||||
};
|
||||
}
|
||||
|
||||
if (!model?.rawAttributes?.organizationsId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!context.currentOrg) {
|
||||
return Sequelize.literal('1 = 0');
|
||||
}
|
||||
|
||||
return {
|
||||
organizationsId: context.currentOrg,
|
||||
};
|
||||
}
|
||||
|
||||
function applyOrganizationScope(model, options = {}) {
|
||||
const context = getRequestContext();
|
||||
const condition = getOrganizationScopeCondition(model, context);
|
||||
|
||||
if (!condition) {
|
||||
return options;
|
||||
}
|
||||
|
||||
addWhereCondition(options, condition);
|
||||
return options;
|
||||
}
|
||||
|
||||
function enforceOrganizationWriteScope(model, instance) {
|
||||
const context = getRequestContext();
|
||||
|
||||
if (!context.enforceOrganizationScope || context.globalAccess) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (model?.rawAttributes?.organizationsId) {
|
||||
if (!context.currentOrg) {
|
||||
const error = new Error('Authenticated requests must be scoped to an organization.');
|
||||
error.code = 403;
|
||||
throw error;
|
||||
}
|
||||
|
||||
instance.set('organizationsId', context.currentOrg);
|
||||
}
|
||||
}
|
||||
|
||||
sequelize.afterPoolAcquire(async (connection) => {
|
||||
const context = getRequestContext();
|
||||
const currentOrg = context.currentOrg || '';
|
||||
const globalAccess = context.globalAccess ? 'true' : 'false';
|
||||
|
||||
await connection.query(
|
||||
'select set_config($1, $2, false), set_config($3, $4, false)',
|
||||
['app.current_org', currentOrg, 'app.global_access', globalAccess],
|
||||
);
|
||||
});
|
||||
|
||||
fs
|
||||
.readdirSync(__dirname)
|
||||
.filter(file => {
|
||||
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
|
||||
.filter((file) => {
|
||||
return file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js';
|
||||
})
|
||||
.forEach(file => {
|
||||
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes)
|
||||
.forEach((file) => {
|
||||
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
|
||||
db[model.name] = model;
|
||||
});
|
||||
|
||||
Object.keys(db).forEach(modelName => {
|
||||
Object.keys(db).forEach((modelName) => {
|
||||
if (db[modelName].associate) {
|
||||
db[modelName].associate(db);
|
||||
}
|
||||
});
|
||||
|
||||
Object.values(db).forEach((model) => {
|
||||
if (!model?.addHook) {
|
||||
return;
|
||||
}
|
||||
|
||||
model.addHook('beforeFind', (options) => applyOrganizationScope(model, options));
|
||||
model.addHook('beforeCount', (options) => applyOrganizationScope(model, options));
|
||||
model.addHook('beforeCreate', (instance) => enforceOrganizationWriteScope(model, instance));
|
||||
model.addHook('beforeUpdate', (instance) => enforceOrganizationWriteScope(model, instance));
|
||||
model.addHook('beforeBulkCreate', (instances) => {
|
||||
instances.forEach((instance) => enforceOrganizationWriteScope(model, instance));
|
||||
});
|
||||
});
|
||||
|
||||
if (db.tenants?.addHook) {
|
||||
db.tenants.addHook('beforeValidate', (tenant) => {
|
||||
if (tenant.subdomain === undefined || tenant.subdomain === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const normalized = normalizeSubdomain(tenant.subdomain);
|
||||
|
||||
if (!normalized) {
|
||||
tenant.set('subdomain', normalized);
|
||||
return;
|
||||
}
|
||||
|
||||
tenant.set('subdomain', validateSubdomainOrThrow(normalized));
|
||||
});
|
||||
}
|
||||
|
||||
db.sequelize = sequelize;
|
||||
db.Sequelize = Sequelize;
|
||||
|
||||
|
||||
@ -6,16 +6,17 @@ const passport = require('passport');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const bodyParser = require('body-parser');
|
||||
const db = require('./db/models');
|
||||
const config = require('./config');
|
||||
const swaggerUI = require('swagger-ui-express');
|
||||
const swaggerJsDoc = require('swagger-jsdoc');
|
||||
const { initializeRequestContext, attachAuthenticatedRequestContext } = require('./requestContext');
|
||||
|
||||
const authRoutes = require('./routes/auth');
|
||||
const fileRoutes = require('./routes/file');
|
||||
const searchRoutes = require('./routes/search');
|
||||
const sqlRoutes = require('./routes/sql');
|
||||
const pexelsRoutes = require('./routes/pexels');
|
||||
const contactFormRoutes = require('./routes/contactForm');
|
||||
|
||||
const organizationForAuthRoutes = require('./routes/organizationLogin');
|
||||
|
||||
@ -160,123 +161,68 @@ app.use(cors({origin: true}));
|
||||
require('./auth/auth');
|
||||
|
||||
app.use(bodyParser.json());
|
||||
app.use(initializeRequestContext);
|
||||
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/file', fileRoutes);
|
||||
app.use('/api/pexels', pexelsRoutes);
|
||||
app.use('/api/contact-form', contactFormRoutes);
|
||||
app.use('/api/org-for-auth', organizationForAuthRoutes);
|
||||
app.enable('trust proxy');
|
||||
|
||||
|
||||
app.use('/api/users', passport.authenticate('jwt', {session: false}), usersRoutes);
|
||||
|
||||
app.use('/api/roles', passport.authenticate('jwt', {session: false}), rolesRoutes);
|
||||
|
||||
app.use('/api/permissions', passport.authenticate('jwt', {session: false}), permissionsRoutes);
|
||||
|
||||
app.use('/api/organizations', passport.authenticate('jwt', {session: false}), organizationsRoutes);
|
||||
|
||||
app.use('/api/tenants', passport.authenticate('jwt', {session: false}), tenantsRoutes);
|
||||
|
||||
app.use('/api/service_lines', passport.authenticate('jwt', {session: false}), service_linesRoutes);
|
||||
|
||||
app.use('/api/services', passport.authenticate('jwt', {session: false}), servicesRoutes);
|
||||
|
||||
app.use('/api/companies', passport.authenticate('jwt', {session: false}), companiesRoutes);
|
||||
|
||||
app.use('/api/contacts', passport.authenticate('jwt', {session: false}), contactsRoutes);
|
||||
|
||||
app.use('/api/tags', passport.authenticate('jwt', {session: false}), tagsRoutes);
|
||||
|
||||
app.use('/api/pipelines', passport.authenticate('jwt', {session: false}), pipelinesRoutes);
|
||||
|
||||
app.use('/api/deal_stages', passport.authenticate('jwt', {session: false}), deal_stagesRoutes);
|
||||
|
||||
app.use('/api/leads', passport.authenticate('jwt', {session: false}), leadsRoutes);
|
||||
|
||||
app.use('/api/deals', passport.authenticate('jwt', {session: false}), dealsRoutes);
|
||||
|
||||
app.use('/api/project_templates', passport.authenticate('jwt', {session: false}), project_templatesRoutes);
|
||||
|
||||
app.use('/api/projects', passport.authenticate('jwt', {session: false}), projectsRoutes);
|
||||
|
||||
app.use('/api/milestones', passport.authenticate('jwt', {session: false}), milestonesRoutes);
|
||||
|
||||
app.use('/api/tasks', passport.authenticate('jwt', {session: false}), tasksRoutes);
|
||||
|
||||
app.use('/api/task_dependencies', passport.authenticate('jwt', {session: false}), task_dependenciesRoutes);
|
||||
|
||||
app.use('/api/deliverables', passport.authenticate('jwt', {session: false}), deliverablesRoutes);
|
||||
|
||||
app.use('/api/project_risks', passport.authenticate('jwt', {session: false}), project_risksRoutes);
|
||||
|
||||
app.use('/api/project_status_reports', passport.authenticate('jwt', {session: false}), project_status_reportsRoutes);
|
||||
|
||||
app.use('/api/skills', passport.authenticate('jwt', {session: false}), skillsRoutes);
|
||||
|
||||
app.use('/api/team_members', passport.authenticate('jwt', {session: false}), team_membersRoutes);
|
||||
|
||||
app.use('/api/team_member_skills', passport.authenticate('jwt', {session: false}), team_member_skillsRoutes);
|
||||
|
||||
app.use('/api/resource_allocations', passport.authenticate('jwt', {session: false}), resource_allocationsRoutes);
|
||||
|
||||
app.use('/api/leave_requests', passport.authenticate('jwt', {session: false}), leave_requestsRoutes);
|
||||
|
||||
app.use('/api/public_holidays', passport.authenticate('jwt', {session: false}), public_holidaysRoutes);
|
||||
|
||||
app.use('/api/timesheets', passport.authenticate('jwt', {session: false}), timesheetsRoutes);
|
||||
|
||||
app.use('/api/time_entries', passport.authenticate('jwt', {session: false}), time_entriesRoutes);
|
||||
|
||||
app.use('/api/products', passport.authenticate('jwt', {session: false}), productsRoutes);
|
||||
|
||||
app.use('/api/contracts', passport.authenticate('jwt', {session: false}), contractsRoutes);
|
||||
|
||||
app.use('/api/invoices', passport.authenticate('jwt', {session: false}), invoicesRoutes);
|
||||
|
||||
app.use('/api/invoice_line_items', passport.authenticate('jwt', {session: false}), invoice_line_itemsRoutes);
|
||||
|
||||
app.use('/api/payments', passport.authenticate('jwt', {session: false}), paymentsRoutes);
|
||||
|
||||
app.use('/api/support_slas', passport.authenticate('jwt', {session: false}), support_slasRoutes);
|
||||
|
||||
app.use('/api/support_tickets', passport.authenticate('jwt', {session: false}), support_ticketsRoutes);
|
||||
|
||||
app.use('/api/ticket_comments', passport.authenticate('jwt', {session: false}), ticket_commentsRoutes);
|
||||
|
||||
app.use('/api/csat_surveys', passport.authenticate('jwt', {session: false}), csat_surveysRoutes);
|
||||
|
||||
app.use('/api/email_templates', passport.authenticate('jwt', {session: false}), email_templatesRoutes);
|
||||
|
||||
app.use('/api/notifications', passport.authenticate('jwt', {session: false}), notificationsRoutes);
|
||||
|
||||
app.use('/api/audit_logs', passport.authenticate('jwt', {session: false}), audit_logsRoutes);
|
||||
|
||||
app.use('/api/imports', passport.authenticate('jwt', {session: false}), importsRoutes);
|
||||
|
||||
app.use(
|
||||
'/api/openai',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
openaiRoutes,
|
||||
);
|
||||
app.use(
|
||||
'/api/ai',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
openaiRoutes,
|
||||
'/api',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
attachAuthenticatedRequestContext,
|
||||
);
|
||||
|
||||
app.use(
|
||||
'/api/search',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
searchRoutes);
|
||||
app.use(
|
||||
'/api/sql',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
sqlRoutes);
|
||||
|
||||
app.use(
|
||||
'/api/org-for-auth',
|
||||
organizationForAuthRoutes,
|
||||
);
|
||||
app.use('/api/users', usersRoutes);
|
||||
app.use('/api/roles', rolesRoutes);
|
||||
app.use('/api/permissions', permissionsRoutes);
|
||||
app.use('/api/organizations', organizationsRoutes);
|
||||
app.use('/api/tenants', tenantsRoutes);
|
||||
app.use('/api/service_lines', service_linesRoutes);
|
||||
app.use('/api/services', servicesRoutes);
|
||||
app.use('/api/companies', companiesRoutes);
|
||||
app.use('/api/contacts', contactsRoutes);
|
||||
app.use('/api/tags', tagsRoutes);
|
||||
app.use('/api/pipelines', pipelinesRoutes);
|
||||
app.use('/api/deal_stages', deal_stagesRoutes);
|
||||
app.use('/api/leads', leadsRoutes);
|
||||
app.use('/api/deals', dealsRoutes);
|
||||
app.use('/api/project_templates', project_templatesRoutes);
|
||||
app.use('/api/projects', projectsRoutes);
|
||||
app.use('/api/milestones', milestonesRoutes);
|
||||
app.use('/api/tasks', tasksRoutes);
|
||||
app.use('/api/task_dependencies', task_dependenciesRoutes);
|
||||
app.use('/api/deliverables', deliverablesRoutes);
|
||||
app.use('/api/project_risks', project_risksRoutes);
|
||||
app.use('/api/project_status_reports', project_status_reportsRoutes);
|
||||
app.use('/api/skills', skillsRoutes);
|
||||
app.use('/api/team_members', team_membersRoutes);
|
||||
app.use('/api/team_member_skills', team_member_skillsRoutes);
|
||||
app.use('/api/resource_allocations', resource_allocationsRoutes);
|
||||
app.use('/api/leave_requests', leave_requestsRoutes);
|
||||
app.use('/api/public_holidays', public_holidaysRoutes);
|
||||
app.use('/api/timesheets', timesheetsRoutes);
|
||||
app.use('/api/time_entries', time_entriesRoutes);
|
||||
app.use('/api/products', productsRoutes);
|
||||
app.use('/api/contracts', contractsRoutes);
|
||||
app.use('/api/invoices', invoicesRoutes);
|
||||
app.use('/api/invoice_line_items', invoice_line_itemsRoutes);
|
||||
app.use('/api/payments', paymentsRoutes);
|
||||
app.use('/api/support_slas', support_slasRoutes);
|
||||
app.use('/api/support_tickets', support_ticketsRoutes);
|
||||
app.use('/api/ticket_comments', ticket_commentsRoutes);
|
||||
app.use('/api/csat_surveys', csat_surveysRoutes);
|
||||
app.use('/api/email_templates', email_templatesRoutes);
|
||||
app.use('/api/notifications', notificationsRoutes);
|
||||
app.use('/api/audit_logs', audit_logsRoutes);
|
||||
app.use('/api/imports', importsRoutes);
|
||||
app.use('/api/openai', openaiRoutes);
|
||||
app.use('/api/ai', openaiRoutes);
|
||||
app.use('/api/search', searchRoutes);
|
||||
app.use('/api/sql', sqlRoutes);
|
||||
|
||||
|
||||
const publicDir = path.join(
|
||||
|
||||
55
backend/src/requestContext.js
Normal file
55
backend/src/requestContext.js
Normal file
@ -0,0 +1,55 @@
|
||||
const { AsyncLocalStorage } = require('async_hooks');
|
||||
|
||||
const requestContextStorage = new AsyncLocalStorage();
|
||||
|
||||
function initializeRequestContext(req, res, next) {
|
||||
requestContextStorage.run(
|
||||
{
|
||||
currentOrg: null,
|
||||
currentUserId: null,
|
||||
globalAccess: false,
|
||||
enforceOrganizationScope: false,
|
||||
},
|
||||
next,
|
||||
);
|
||||
}
|
||||
|
||||
function getRequestContext() {
|
||||
return (
|
||||
requestContextStorage.getStore() || {
|
||||
currentOrg: null,
|
||||
currentUserId: null,
|
||||
globalAccess: false,
|
||||
enforceOrganizationScope: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function setRequestContextScope({ currentOrg = null, currentUserId = null, globalAccess = false, enforceOrganizationScope = false }) {
|
||||
const context = getRequestContext();
|
||||
|
||||
context.currentOrg = currentOrg;
|
||||
context.currentUserId = currentUserId;
|
||||
context.globalAccess = Boolean(globalAccess);
|
||||
context.enforceOrganizationScope = Boolean(enforceOrganizationScope);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
function attachAuthenticatedRequestContext(req, res, next) {
|
||||
setRequestContextScope({
|
||||
currentOrg: req.currentUser?.organizationsId || null,
|
||||
currentUserId: req.currentUser?.id || null,
|
||||
globalAccess: req.currentUser?.app_role?.globalAccess,
|
||||
enforceOrganizationScope: true,
|
||||
});
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initializeRequestContext,
|
||||
getRequestContext,
|
||||
setRequestContextScope,
|
||||
attachAuthenticatedRequestContext,
|
||||
};
|
||||
@ -393,7 +393,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await Audit_logsDBApi.findAllAutocomplete(
|
||||
@ -441,7 +441,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Audit_logsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ const AuthService = require('../services/auth');
|
||||
const ForbiddenError = require('../services/notifications/errors/forbidden');
|
||||
const EmailSender = require('../services/email');
|
||||
const wrapAsync = require('../helpers').wrapAsync;
|
||||
const { resolveTenantContextByHost } = require('../tenantContext');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@ -140,13 +141,21 @@ router.post('/send-password-reset-email', wrapAsync(async (req, res) => {
|
||||
*/
|
||||
|
||||
router.post('/signup', wrapAsync(async (req, res) => {
|
||||
const link = new URL(req.headers.referer);
|
||||
const referer = req.headers.referer || `${req.protocol}://${req.get('host')}`;
|
||||
const link = new URL(referer);
|
||||
const { organization } = await resolveTenantContextByHost(req, { required: true });
|
||||
const requestedOrganizationId = req.body.organizationsId || req.body.organizationId || null;
|
||||
|
||||
if (requestedOrganizationId && requestedOrganizationId !== organization.id) {
|
||||
const error = new Error('Signup organization does not match the current tenant context.');
|
||||
error.code = 400;
|
||||
throw error;
|
||||
}
|
||||
|
||||
const payload = await AuthService.signup(
|
||||
req.body.email,
|
||||
req.body.password,
|
||||
|
||||
req.body.organizationId,
|
||||
|
||||
organization.id,
|
||||
req,
|
||||
link.host,
|
||||
)
|
||||
|
||||
@ -412,7 +412,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await CompaniesDBApi.findAllAutocomplete(
|
||||
@ -460,7 +460,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await CompaniesDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,753 @@
|
||||
const express = require('express');
|
||||
const db = require('../db/models');
|
||||
const wrapAsync = require('../helpers').wrapAsync;
|
||||
const { resolveTenantContextByHost } = require('../tenantContext');
|
||||
const { setRequestContextScope } = require('../requestContext');
|
||||
|
||||
const router = express.Router();
|
||||
const { Op } = db.Sequelize;
|
||||
|
||||
const SERVICE_CATALOG = {
|
||||
ai_ml: {
|
||||
lineNameFr: 'IA & Machine Learning',
|
||||
lineNameEn: 'AI & Machine Learning',
|
||||
lineDescriptionFr:
|
||||
'Automatisation, analytics et modèles sur mesure pour les entreprises africaines.',
|
||||
lineDescriptionEn:
|
||||
'Custom AI, analytics, and automation for African enterprises.',
|
||||
pricingModel: 'project_fixed',
|
||||
typicalTimeline: '16 semaines',
|
||||
serviceNameFr: 'Implémentation IA',
|
||||
serviceNameEn: 'AI Implementation',
|
||||
serviceDescriptionFr:
|
||||
'Discovery, préparation des données, modélisation, intégration et transfert.',
|
||||
serviceDescriptionEn:
|
||||
'Discovery, data preparation, modeling, integration and handover.',
|
||||
templateName: 'AI Implementation',
|
||||
templateDescription:
|
||||
'Discovery → Data → Model → Integration → Training → Handover',
|
||||
templateWeeks: 16,
|
||||
billingModel: 'fixed_price',
|
||||
},
|
||||
cybersecurity: {
|
||||
lineNameFr: 'Cybersécurité',
|
||||
lineNameEn: 'Cybersecurity',
|
||||
lineDescriptionFr:
|
||||
'Audits, remédiation et supervision pour sécuriser les SI critiques.',
|
||||
lineDescriptionEn:
|
||||
'Audits, remediation, and monitoring for critical information systems.',
|
||||
pricingModel: 'project_fixed',
|
||||
typicalTimeline: '4 semaines',
|
||||
serviceNameFr: 'Audit cybersécurité',
|
||||
serviceNameEn: 'Cybersecurity Audit',
|
||||
serviceDescriptionFr:
|
||||
'Scoping, assessment, remediation planning et reporting exécutif.',
|
||||
serviceDescriptionEn:
|
||||
'Scoping, assessment, remediation planning, and executive reporting.',
|
||||
templateName: 'Cybersecurity Audit',
|
||||
templateDescription: 'Scoping → Assessment → Remediation → Report',
|
||||
templateWeeks: 4,
|
||||
billingModel: 'fixed_price',
|
||||
},
|
||||
cloud: {
|
||||
lineNameFr: 'Cloud Solutions',
|
||||
lineNameEn: 'Cloud Solutions',
|
||||
lineDescriptionFr:
|
||||
'Architecture, migration et opérations cloud pour des équipes agiles.',
|
||||
lineDescriptionEn:
|
||||
'Architecture, migration, and cloud operations for agile teams.',
|
||||
pricingModel: 'hybrid',
|
||||
typicalTimeline: '8 semaines',
|
||||
serviceNameFr: 'Migration cloud',
|
||||
serviceNameEn: 'Cloud Migration',
|
||||
serviceDescriptionFr:
|
||||
'Audit, architecture cible, migration et validation des workloads.',
|
||||
serviceDescriptionEn:
|
||||
'Audit, target architecture, workload migration, and validation.',
|
||||
templateName: 'Cloud Migration',
|
||||
templateDescription: 'Audit → Architecture → Migration → Validation',
|
||||
templateWeeks: 8,
|
||||
billingModel: 'time_material',
|
||||
},
|
||||
hardware: {
|
||||
lineNameFr: 'Infrastructure matérielle',
|
||||
lineNameEn: 'Hardware Infrastructure',
|
||||
lineDescriptionFr:
|
||||
'Réseaux, équipements et déploiements terrain avec expertise locale.',
|
||||
lineDescriptionEn:
|
||||
'Networks, equipment, and field deployments with local expertise.',
|
||||
pricingModel: 'project_fixed',
|
||||
typicalTimeline: 'Variable',
|
||||
serviceNameFr: 'Déploiement hardware',
|
||||
serviceNameEn: 'Hardware Deployment',
|
||||
serviceDescriptionFr:
|
||||
'Approvisionnement, installation, tests et mise en production.',
|
||||
serviceDescriptionEn:
|
||||
'Procurement, installation, testing, and go-live.',
|
||||
templateName: 'Hardware Deployment',
|
||||
templateDescription: 'Procurement → Install → Test → Handover',
|
||||
templateWeeks: 6,
|
||||
billingModel: 'fixed_price',
|
||||
},
|
||||
web_mobile: {
|
||||
lineNameFr: 'Web & Mobile',
|
||||
lineNameEn: 'Web & Mobile',
|
||||
lineDescriptionFr:
|
||||
'Produits digitaux sur mesure, du cadrage jusqu’au déploiement.',
|
||||
lineDescriptionEn:
|
||||
'Custom digital products from discovery through deployment.',
|
||||
pricingModel: 'hybrid',
|
||||
typicalTimeline: '12 semaines',
|
||||
serviceNameFr: 'Développement web / mobile',
|
||||
serviceNameEn: 'Web / Mobile Development',
|
||||
serviceDescriptionFr:
|
||||
'Sprints agiles, UX, développement et stabilisation continue.',
|
||||
serviceDescriptionEn:
|
||||
'Agile sprints, UX, development, and continuous hardening.',
|
||||
templateName: 'Web/Mobile Development',
|
||||
templateDescription: 'Sprint setup → Build → QA → Launch',
|
||||
templateWeeks: 12,
|
||||
billingModel: 'time_material',
|
||||
},
|
||||
database: {
|
||||
lineNameFr: 'Database Management',
|
||||
lineNameEn: 'Database Management',
|
||||
lineDescriptionFr:
|
||||
'Architecture, migration et optimisation de données à grande échelle.',
|
||||
lineDescriptionEn:
|
||||
'Architecture, migration, and optimization for scalable data systems.',
|
||||
pricingModel: 'project_fixed',
|
||||
typicalTimeline: '6 semaines',
|
||||
serviceNameFr: 'Setup base de données',
|
||||
serviceNameEn: 'Database Setup',
|
||||
serviceDescriptionFr:
|
||||
'Modélisation, setup, migration et tuning des performances.',
|
||||
serviceDescriptionEn:
|
||||
'Modeling, setup, migration, and performance tuning.',
|
||||
templateName: 'Database Setup',
|
||||
templateDescription: 'Modeling → Setup → Migration → Optimization',
|
||||
templateWeeks: 6,
|
||||
billingModel: 'fixed_price',
|
||||
},
|
||||
};
|
||||
|
||||
const COMPANY_SIZE_LABELS = {
|
||||
'1_10': '1–10 employés',
|
||||
'11_50': '11–50 employés',
|
||||
'51_200': '51–200 employés',
|
||||
'201_500': '201–500 employés',
|
||||
'501_1000': '501–1000 employés',
|
||||
'1000_plus': '1000+ employés',
|
||||
};
|
||||
|
||||
const BUDGET_RANGE_LABELS = {
|
||||
lt_5k: '< 5 000 USD',
|
||||
'5_20k': '5 000–20 000 USD',
|
||||
'20_50k': '20 000–50 000 USD',
|
||||
'50k_plus': '50 000+ USD',
|
||||
};
|
||||
|
||||
const BUDGET_ESTIMATES = {
|
||||
lt_5k: 3000,
|
||||
'5_20k': 12500,
|
||||
'20_50k': 35000,
|
||||
'50k_plus': 60000,
|
||||
};
|
||||
|
||||
const CHANNEL_LABELS = {
|
||||
email: 'Email',
|
||||
phone: 'Téléphone',
|
||||
whatsapp: 'WhatsApp',
|
||||
};
|
||||
|
||||
const URGENCY_LABELS = {
|
||||
standard: 'Standard',
|
||||
urgent: 'Urgent',
|
||||
critical: 'Critique',
|
||||
};
|
||||
|
||||
const SALES_STAGE_BLUEPRINT = [
|
||||
{
|
||||
name: 'Nouveau besoin',
|
||||
probability_percent: 10,
|
||||
sort_order: 1,
|
||||
is_closed_won: false,
|
||||
is_closed_lost: false,
|
||||
},
|
||||
{
|
||||
name: 'Qualification',
|
||||
probability_percent: 30,
|
||||
sort_order: 2,
|
||||
is_closed_won: false,
|
||||
is_closed_lost: false,
|
||||
},
|
||||
{
|
||||
name: 'Proposition envoyée',
|
||||
probability_percent: 60,
|
||||
sort_order: 3,
|
||||
is_closed_won: false,
|
||||
is_closed_lost: false,
|
||||
},
|
||||
{
|
||||
name: 'Négociation',
|
||||
probability_percent: 80,
|
||||
sort_order: 4,
|
||||
is_closed_won: false,
|
||||
is_closed_lost: false,
|
||||
},
|
||||
];
|
||||
|
||||
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
function normalizeText(value) {
|
||||
if (typeof value !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return value.trim().replace(/\s+/g, ' ');
|
||||
}
|
||||
|
||||
function throwBadRequest(message) {
|
||||
const error = new Error(message);
|
||||
error.code = 400;
|
||||
throw error;
|
||||
}
|
||||
|
||||
function splitFullName(fullName) {
|
||||
const parts = fullName.split(' ').filter(Boolean);
|
||||
return {
|
||||
firstName: parts[0] || fullName,
|
||||
lastName: parts.slice(1).join(' ') || '-',
|
||||
};
|
||||
}
|
||||
|
||||
function getBudgetEstimate(range) {
|
||||
return BUDGET_ESTIMATES[range] || null;
|
||||
}
|
||||
|
||||
function getScore(payload) {
|
||||
let score = 35;
|
||||
|
||||
if (['11_50', '51_200', '201_500', '501_1000', '1000_plus'].includes(payload.company_size)) {
|
||||
score += 10;
|
||||
}
|
||||
|
||||
if (['51_200', '201_500', '501_1000', '1000_plus'].includes(payload.company_size)) {
|
||||
score += 5;
|
||||
}
|
||||
|
||||
if (payload.budget_range === '5_20k') {
|
||||
score += 10;
|
||||
}
|
||||
|
||||
if (payload.budget_range === '20_50k') {
|
||||
score += 15;
|
||||
}
|
||||
|
||||
if (payload.budget_range === '50k_plus') {
|
||||
score += 20;
|
||||
}
|
||||
|
||||
if (['ai_ml', 'cybersecurity', 'cloud'].includes(payload.service_line)) {
|
||||
score += 10;
|
||||
}
|
||||
|
||||
if (payload.urgency === 'urgent') {
|
||||
score += 5;
|
||||
}
|
||||
|
||||
if (payload.urgency === 'critical') {
|
||||
score += 10;
|
||||
}
|
||||
|
||||
if (payload.preferred_channel === 'whatsapp') {
|
||||
score += 5;
|
||||
}
|
||||
|
||||
if (payload.message.length > 120) {
|
||||
score += 5;
|
||||
}
|
||||
|
||||
return Math.min(score, 100);
|
||||
}
|
||||
|
||||
function getScope(tenant, organization) {
|
||||
const scope = {};
|
||||
|
||||
if (tenant?.id) {
|
||||
scope.tenantId = tenant.id;
|
||||
}
|
||||
|
||||
if (organization?.id) {
|
||||
scope.organizationsId = organization.id;
|
||||
}
|
||||
|
||||
return scope;
|
||||
}
|
||||
|
||||
function addDays(days) {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + days);
|
||||
return date;
|
||||
}
|
||||
|
||||
function buildLeadMessage(payload, catalog) {
|
||||
return [
|
||||
`Service demandé: ${catalog.lineNameFr}`,
|
||||
`Taille du client: ${COMPANY_SIZE_LABELS[payload.company_size]}`,
|
||||
`Budget estimé: ${BUDGET_RANGE_LABELS[payload.budget_range]}`,
|
||||
`Canal préféré: ${CHANNEL_LABELS[payload.preferred_channel]}`,
|
||||
`Urgence: ${URGENCY_LABELS[payload.urgency]}`,
|
||||
'',
|
||||
payload.message,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function buildCompanyNote(payload) {
|
||||
return [
|
||||
'Fiche enrichie automatiquement depuis la landing page publique TSC FleetOS.',
|
||||
`Taille: ${COMPANY_SIZE_LABELS[payload.company_size]}`,
|
||||
`Dernier canal préféré: ${CHANNEL_LABELS[payload.preferred_channel]}`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function buildContactNote(payload) {
|
||||
return [
|
||||
'Lead entrant web.',
|
||||
`Canal préféré: ${CHANNEL_LABELS[payload.preferred_channel]}`,
|
||||
`Urgence: ${URGENCY_LABELS[payload.urgency]}`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function buildDealNote(payload, score, lead, catalog) {
|
||||
return [
|
||||
`Référence lead: TSC-${lead.id.split('-')[0].toUpperCase()}`,
|
||||
`Score: ${score}/100`,
|
||||
`Service recommandé: ${catalog.serviceNameFr}`,
|
||||
`Template suggéré: ${catalog.templateName}`,
|
||||
`Budget visé: ${BUDGET_RANGE_LABELS[payload.budget_range]}`,
|
||||
`Canal préféré: ${CHANNEL_LABELS[payload.preferred_channel]}`,
|
||||
`Urgence: ${URGENCY_LABELS[payload.urgency]}`,
|
||||
'',
|
||||
payload.message,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
async function attachScope(instance, tenant, organization, transaction) {
|
||||
if (tenant && typeof instance.setTenant === 'function') {
|
||||
await instance.setTenant(tenant, { transaction });
|
||||
}
|
||||
|
||||
if (organization && typeof instance.setOrganizations === 'function') {
|
||||
await instance.setOrganizations(organization, { transaction });
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveTenantContext(req, transaction) {
|
||||
return resolveTenantContextByHost(req, {
|
||||
transaction,
|
||||
required: true,
|
||||
});
|
||||
}
|
||||
|
||||
async function ensureSalesPipeline(tenant, organization, transaction) {
|
||||
const scope = getScope(tenant, organization);
|
||||
|
||||
let pipeline = await db.pipelines.findOne({
|
||||
where: {
|
||||
pipeline_type: 'sales',
|
||||
...scope,
|
||||
},
|
||||
order: [
|
||||
['sort_order', 'ASC'],
|
||||
['createdAt', 'ASC'],
|
||||
],
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (!pipeline) {
|
||||
pipeline = await db.pipelines.create(
|
||||
{
|
||||
name: 'Pipeline commercial TSC',
|
||||
pipeline_type: 'sales',
|
||||
active: true,
|
||||
sort_order: 1,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await attachScope(pipeline, tenant, organization, transaction);
|
||||
}
|
||||
|
||||
let firstStage = null;
|
||||
|
||||
for (const stageBlueprint of SALES_STAGE_BLUEPRINT) {
|
||||
let stage = await db.deal_stages.findOne({
|
||||
where: {
|
||||
pipelineId: pipeline.id,
|
||||
name: stageBlueprint.name,
|
||||
...scope,
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (!stage) {
|
||||
stage = await db.deal_stages.create(stageBlueprint, { transaction });
|
||||
await attachScope(stage, tenant, organization, transaction);
|
||||
await stage.setPipeline(pipeline, { transaction });
|
||||
}
|
||||
|
||||
if (!firstStage || stage.sort_order < firstStage.sort_order) {
|
||||
firstStage = stage;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
pipeline,
|
||||
firstStage,
|
||||
};
|
||||
}
|
||||
|
||||
async function ensureCatalog(serviceLineKey, tenant, organization, transaction) {
|
||||
const catalog = SERVICE_CATALOG[serviceLineKey];
|
||||
|
||||
if (!catalog) {
|
||||
throwBadRequest('Service line invalide.');
|
||||
}
|
||||
|
||||
const scope = getScope(tenant, organization);
|
||||
|
||||
let serviceLine = await db.service_lines.findOne({
|
||||
where: {
|
||||
category: serviceLineKey,
|
||||
...scope,
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (!serviceLine) {
|
||||
serviceLine = await db.service_lines.create(
|
||||
{
|
||||
name_fr: catalog.lineNameFr,
|
||||
name_en: catalog.lineNameEn,
|
||||
description_fr: catalog.lineDescriptionFr,
|
||||
description_en: catalog.lineDescriptionEn,
|
||||
category: serviceLineKey,
|
||||
pricing_model: catalog.pricingModel,
|
||||
typical_timeline: catalog.typicalTimeline,
|
||||
active: true,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await attachScope(serviceLine, tenant, organization, transaction);
|
||||
}
|
||||
|
||||
let service = await db.services.findOne({
|
||||
where: {
|
||||
service_lineId: serviceLine.id,
|
||||
name_fr: catalog.serviceNameFr,
|
||||
...scope,
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (!service) {
|
||||
service = await db.services.create(
|
||||
{
|
||||
name_fr: catalog.serviceNameFr,
|
||||
name_en: catalog.serviceNameEn,
|
||||
description_fr: catalog.serviceDescriptionFr,
|
||||
description_en: catalog.serviceDescriptionEn,
|
||||
pricing_model: catalog.pricingModel,
|
||||
min_price: getBudgetEstimate('lt_5k'),
|
||||
max_price: getBudgetEstimate('50k_plus'),
|
||||
typical_duration_weeks: catalog.templateWeeks,
|
||||
active: true,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await attachScope(service, tenant, organization, transaction);
|
||||
await service.setService_line(serviceLine, { transaction });
|
||||
}
|
||||
|
||||
let projectTemplate = await db.project_templates.findOne({
|
||||
where: {
|
||||
service_lineId: serviceLine.id,
|
||||
name: catalog.templateName,
|
||||
...scope,
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (!projectTemplate) {
|
||||
projectTemplate = await db.project_templates.create(
|
||||
{
|
||||
name: catalog.templateName,
|
||||
description: catalog.templateDescription,
|
||||
default_duration_weeks: catalog.templateWeeks,
|
||||
default_budget: getBudgetEstimate('20_50k'),
|
||||
billing_model: catalog.billingModel,
|
||||
active: true,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await attachScope(projectTemplate, tenant, organization, transaction);
|
||||
await projectTemplate.setService_line(serviceLine, { transaction });
|
||||
}
|
||||
|
||||
return {
|
||||
serviceLine,
|
||||
service,
|
||||
projectTemplate,
|
||||
...catalog,
|
||||
};
|
||||
}
|
||||
|
||||
function validatePayload(payload) {
|
||||
if (!payload.full_name) {
|
||||
throwBadRequest('Le nom du contact est obligatoire.');
|
||||
}
|
||||
|
||||
if (!payload.company_name) {
|
||||
throwBadRequest('Le nom de la société est obligatoire.');
|
||||
}
|
||||
|
||||
if (!payload.email || !emailPattern.test(payload.email)) {
|
||||
throwBadRequest('Veuillez fournir un email professionnel valide.');
|
||||
}
|
||||
|
||||
if (!SERVICE_CATALOG[payload.service_line]) {
|
||||
throwBadRequest('Veuillez sélectionner une ligne de service valide.');
|
||||
}
|
||||
|
||||
if (!COMPANY_SIZE_LABELS[payload.company_size]) {
|
||||
throwBadRequest('Veuillez sélectionner la taille de votre entreprise.');
|
||||
}
|
||||
|
||||
if (!BUDGET_RANGE_LABELS[payload.budget_range]) {
|
||||
throwBadRequest('Veuillez sélectionner un budget estimatif.');
|
||||
}
|
||||
|
||||
if (!CHANNEL_LABELS[payload.preferred_channel]) {
|
||||
throwBadRequest('Veuillez sélectionner un canal de communication valide.');
|
||||
}
|
||||
|
||||
if (!URGENCY_LABELS[payload.urgency]) {
|
||||
throwBadRequest('Veuillez sélectionner un niveau d’urgence valide.');
|
||||
}
|
||||
|
||||
if (payload.preferred_channel !== 'email' && !payload.phone) {
|
||||
throwBadRequest('Le numéro de téléphone est requis pour un suivi par téléphone ou WhatsApp.');
|
||||
}
|
||||
|
||||
if (!payload.message || payload.message.length < 24) {
|
||||
throwBadRequest('Décrivez votre besoin en au moins 24 caractères.');
|
||||
}
|
||||
}
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
wrapAsync(async (req, res) => {
|
||||
const payload = {
|
||||
full_name: normalizeText(req.body.full_name),
|
||||
company_name: normalizeText(req.body.company_name),
|
||||
email: normalizeText(req.body.email).toLowerCase(),
|
||||
phone: normalizeText(req.body.phone),
|
||||
service_line: normalizeText(req.body.service_line),
|
||||
company_size: req.body.company_size,
|
||||
budget_range: req.body.budget_range,
|
||||
preferred_channel: normalizeText(req.body.preferred_channel).toLowerCase(),
|
||||
urgency: normalizeText(req.body.urgency).toLowerCase(),
|
||||
message: normalizeText(req.body.message),
|
||||
};
|
||||
|
||||
validatePayload(payload);
|
||||
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
const { tenant, organization } = await resolveTenantContext(req, transaction);
|
||||
|
||||
setRequestContextScope({
|
||||
currentOrg: organization.id,
|
||||
enforceOrganizationScope: true,
|
||||
});
|
||||
|
||||
await db.sequelize.query(
|
||||
"select set_config('app.current_org', :currentOrg, false), set_config('app.global_access', 'false', false)",
|
||||
{
|
||||
replacements: { currentOrg: organization.id },
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
|
||||
const scope = getScope(tenant, organization);
|
||||
const catalog = await ensureCatalog(payload.service_line, tenant, organization, transaction);
|
||||
const { pipeline, firstStage } = await ensureSalesPipeline(tenant, organization, transaction);
|
||||
const { firstName, lastName } = splitFullName(payload.full_name);
|
||||
const score = getScore(payload);
|
||||
const leadStatus = score >= 70 ? 'qualified' : 'new';
|
||||
|
||||
let company = await db.companies.findOne({
|
||||
where: {
|
||||
...scope,
|
||||
[Op.or]: [
|
||||
{ legal_name: { [Op.iLike]: payload.company_name } },
|
||||
{ trade_name: { [Op.iLike]: payload.company_name } },
|
||||
],
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (!company) {
|
||||
company = await db.companies.create(
|
||||
{
|
||||
legal_name: payload.company_name,
|
||||
trade_name: payload.company_name,
|
||||
size_band: payload.company_size,
|
||||
country: 'DRC',
|
||||
lifecycle_stage: 'lead',
|
||||
notes: buildCompanyNote(payload),
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await attachScope(company, tenant, organization, transaction);
|
||||
} else {
|
||||
await company.update(
|
||||
{
|
||||
size_band: company.size_band || payload.company_size,
|
||||
lifecycle_stage: company.lifecycle_stage || 'lead',
|
||||
notes: buildCompanyNote(payload),
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
}
|
||||
|
||||
let contact = await db.contacts.findOne({
|
||||
where: {
|
||||
...scope,
|
||||
email: { [Op.iLike]: payload.email },
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
|
||||
const contactPayload = {
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
email: payload.email,
|
||||
phone: payload.phone || null,
|
||||
whatsapp: payload.preferred_channel === 'whatsapp' ? payload.phone || null : null,
|
||||
preferred_channel: payload.preferred_channel,
|
||||
allow_email: payload.preferred_channel === 'email',
|
||||
allow_sms: payload.preferred_channel === 'phone',
|
||||
allow_whatsapp: payload.preferred_channel === 'whatsapp',
|
||||
lifecycle_stage: 'lead',
|
||||
notes: buildContactNote(payload),
|
||||
};
|
||||
|
||||
if (!contact) {
|
||||
contact = await db.contacts.create(contactPayload, { transaction });
|
||||
await attachScope(contact, tenant, organization, transaction);
|
||||
} else {
|
||||
await contact.update(contactPayload, { transaction });
|
||||
}
|
||||
|
||||
await contact.setCompany(company, { transaction });
|
||||
|
||||
let lead = await db.leads.findOne({
|
||||
where: {
|
||||
...scope,
|
||||
email: { [Op.iLike]: payload.email },
|
||||
company_name: { [Op.iLike]: payload.company_name },
|
||||
status: {
|
||||
[Op.in]: ['new', 'qualified'],
|
||||
},
|
||||
},
|
||||
order: [['updatedAt', 'DESC']],
|
||||
transaction,
|
||||
});
|
||||
|
||||
const leadPayload = {
|
||||
full_name: payload.full_name,
|
||||
email: payload.email,
|
||||
phone: payload.phone || null,
|
||||
company_name: payload.company_name,
|
||||
message: buildLeadMessage(payload, catalog),
|
||||
source: 'website_form',
|
||||
score,
|
||||
status: leadStatus,
|
||||
captured_at: new Date(),
|
||||
};
|
||||
|
||||
if (!lead) {
|
||||
lead = await db.leads.create(leadPayload, { transaction });
|
||||
await attachScope(lead, tenant, organization, transaction);
|
||||
} else {
|
||||
await lead.update(leadPayload, { transaction });
|
||||
}
|
||||
|
||||
await lead.setService_interest(catalog.service, { transaction });
|
||||
|
||||
let deal = await db.deals.findOne({
|
||||
where: {
|
||||
...scope,
|
||||
companyId: company.id,
|
||||
service_lineId: catalog.serviceLine.id,
|
||||
status: 'open',
|
||||
},
|
||||
order: [['updatedAt', 'DESC']],
|
||||
transaction,
|
||||
});
|
||||
|
||||
const dealPayload = {
|
||||
name: `Découverte ${payload.company_name} · ${catalog.serviceNameFr}`,
|
||||
amount: getBudgetEstimate(payload.budget_range),
|
||||
currency: 'USD',
|
||||
expected_close_at: addDays(14),
|
||||
status: 'open',
|
||||
notes: buildDealNote(payload, score, lead, catalog),
|
||||
};
|
||||
|
||||
if (!deal) {
|
||||
deal = await db.deals.create(dealPayload, { transaction });
|
||||
await attachScope(deal, tenant, organization, transaction);
|
||||
} else {
|
||||
await deal.update(dealPayload, { transaction });
|
||||
}
|
||||
|
||||
await deal.setCompany(company, { transaction });
|
||||
await deal.setPrimary_contact(contact, { transaction });
|
||||
await deal.setService_line(catalog.serviceLine, { transaction });
|
||||
await deal.setPipeline(pipeline, { transaction });
|
||||
|
||||
if (firstStage) {
|
||||
await deal.setStage(firstStage, { transaction });
|
||||
}
|
||||
|
||||
await transaction.commit();
|
||||
|
||||
res.status(200).send({
|
||||
success: true,
|
||||
reference: `TSC-${lead.id.split('-')[0].toUpperCase()}`,
|
||||
leadId: lead.id,
|
||||
dealId: deal.id,
|
||||
score,
|
||||
status: leadStatus,
|
||||
stageName: firstStage?.name || 'Nouveau besoin',
|
||||
pipelineName: pipeline?.name || 'Pipeline commercial TSC',
|
||||
templateSuggestion: catalog.projectTemplate?.name || catalog.templateName,
|
||||
});
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
router.use('/', require('../helpers').commonErrorHandler);
|
||||
|
||||
module.exports = router;
|
||||
@ -405,7 +405,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await ContactsDBApi.findAllAutocomplete(
|
||||
@ -453,7 +453,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await ContactsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -393,7 +393,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await ContractsDBApi.findAllAutocomplete(
|
||||
@ -441,7 +441,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await ContractsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -383,7 +383,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await Csat_surveysDBApi.findAllAutocomplete(
|
||||
@ -431,7 +431,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Csat_surveysDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -386,7 +386,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await Deal_stagesDBApi.findAllAutocomplete(
|
||||
@ -434,7 +434,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Deal_stagesDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -391,7 +391,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await DealsDBApi.findAllAutocomplete(
|
||||
@ -439,7 +439,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await DealsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -387,7 +387,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await DeliverablesDBApi.findAllAutocomplete(
|
||||
@ -435,7 +435,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await DeliverablesDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -393,7 +393,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await Email_templatesDBApi.findAllAutocomplete(
|
||||
@ -441,7 +441,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Email_templatesDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -391,7 +391,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await ImportsDBApi.findAllAutocomplete(
|
||||
@ -439,7 +439,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await ImportsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -389,7 +389,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await Invoice_line_itemsDBApi.findAllAutocomplete(
|
||||
@ -437,7 +437,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Invoice_line_itemsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -404,7 +404,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await InvoicesDBApi.findAllAutocomplete(
|
||||
@ -452,7 +452,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await InvoicesDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -397,7 +397,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await LeadsDBApi.findAllAutocomplete(
|
||||
@ -445,7 +445,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await LeadsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -382,7 +382,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await Leave_requestsDBApi.findAllAutocomplete(
|
||||
@ -430,7 +430,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Leave_requestsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -387,7 +387,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await MilestonesDBApi.findAllAutocomplete(
|
||||
@ -435,7 +435,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await MilestonesDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -388,7 +388,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await NotificationsDBApi.findAllAutocomplete(
|
||||
@ -436,7 +436,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await NotificationsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
|
||||
|
||||
|
||||
const express = require('express');
|
||||
|
||||
const OrganizationsDBApi = require('../db/api/organizations');
|
||||
const wrapAsync = require('../helpers').wrapAsync;
|
||||
const { resolveTenantContextByHost } = require('../tenantContext');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@ -37,19 +34,21 @@ const router = express.Router();
|
||||
router.get(
|
||||
'/',
|
||||
wrapAsync(async (req, res) => {
|
||||
const payload = await OrganizationsDBApi.findAll(req.query);
|
||||
const simplifiedPayload = payload.rows.map(org => ({
|
||||
id: org.id,
|
||||
name: org.name
|
||||
}));
|
||||
res.status(200).send(simplifiedPayload);
|
||||
|
||||
const { organization, tenant, subdomain } = await resolveTenantContextByHost(req, {
|
||||
required: true,
|
||||
});
|
||||
|
||||
res.status(200).send([
|
||||
{
|
||||
id: organization.id,
|
||||
name: organization.name,
|
||||
subdomain,
|
||||
tenantId: tenant.id,
|
||||
},
|
||||
]);
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
|
||||
router.use('/', require('../helpers').commonErrorHandler);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
|
||||
|
||||
@ -380,7 +380,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await OrganizationsDBApi.findAllAutocomplete(
|
||||
@ -428,7 +428,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await OrganizationsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -389,7 +389,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await PaymentsDBApi.findAllAutocomplete(
|
||||
@ -437,7 +437,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await PaymentsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -417,7 +417,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await PermissionsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -384,7 +384,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await PipelinesDBApi.findAllAutocomplete(
|
||||
@ -432,7 +432,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await PipelinesDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -391,7 +391,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await ProductsDBApi.findAllAutocomplete(
|
||||
@ -439,7 +439,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await ProductsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -389,7 +389,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await Project_risksDBApi.findAllAutocomplete(
|
||||
@ -437,7 +437,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Project_risksDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -390,7 +390,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await Project_status_reportsDBApi.findAllAutocomplete(
|
||||
@ -438,7 +438,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Project_status_reportsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -390,7 +390,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await Project_templatesDBApi.findAllAutocomplete(
|
||||
@ -438,7 +438,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Project_templatesDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -402,7 +402,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await ProjectsDBApi.findAllAutocomplete(
|
||||
@ -450,7 +450,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await ProjectsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -383,7 +383,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await Public_holidaysDBApi.findAllAutocomplete(
|
||||
@ -431,7 +431,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Public_holidaysDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -384,7 +384,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await Resource_allocationsDBApi.findAllAutocomplete(
|
||||
@ -432,7 +432,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Resource_allocationsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -426,7 +426,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await RolesDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -36,8 +36,8 @@ router.use(checkCrudPermissions('search'));
|
||||
*/
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
const { searchQuery , organizationId} = req.body;
|
||||
|
||||
const { searchQuery } = req.body;
|
||||
const organizationsId = req.currentUser?.organizationsId || null;
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
if (!searchQuery) {
|
||||
@ -45,7 +45,7 @@ router.post('/', async (req, res) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const foundMatches = await SearchService.search(searchQuery, req.currentUser , organizationId, globalAccess,);
|
||||
const foundMatches = await SearchService.search(searchQuery, req.currentUser, organizationsId, globalAccess);
|
||||
res.json(foundMatches);
|
||||
} catch (error) {
|
||||
console.error('Internal Server Error', error);
|
||||
|
||||
@ -400,7 +400,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await Service_linesDBApi.findAllAutocomplete(
|
||||
@ -448,7 +448,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Service_linesDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -405,7 +405,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await ServicesDBApi.findAllAutocomplete(
|
||||
@ -453,7 +453,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await ServicesDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -381,7 +381,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await SkillsDBApi.findAllAutocomplete(
|
||||
@ -429,7 +429,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await SkillsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -387,7 +387,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await Support_slasDBApi.findAllAutocomplete(
|
||||
@ -435,7 +435,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Support_slasDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -389,7 +389,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await Support_ticketsDBApi.findAllAutocomplete(
|
||||
@ -437,7 +437,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Support_ticketsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -383,7 +383,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await TagsDBApi.findAllAutocomplete(
|
||||
@ -431,7 +431,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await TagsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -377,7 +377,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await Task_dependenciesDBApi.findAllAutocomplete(
|
||||
@ -425,7 +425,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Task_dependenciesDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -394,7 +394,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await TasksDBApi.findAllAutocomplete(
|
||||
@ -442,7 +442,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await TasksDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -378,7 +378,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await Team_member_skillsDBApi.findAllAutocomplete(
|
||||
@ -426,7 +426,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Team_member_skillsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -389,7 +389,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await Team_membersDBApi.findAllAutocomplete(
|
||||
@ -437,7 +437,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Team_membersDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -415,7 +415,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await TenantsDBApi.findAllAutocomplete(
|
||||
@ -463,7 +463,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await TenantsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -381,7 +381,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await Ticket_commentsDBApi.findAllAutocomplete(
|
||||
@ -429,7 +429,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Ticket_commentsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -384,7 +384,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await Time_entriesDBApi.findAllAutocomplete(
|
||||
@ -432,7 +432,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Time_entriesDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -381,7 +381,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await TimesheetsDBApi.findAllAutocomplete(
|
||||
@ -429,7 +429,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await TimesheetsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -389,7 +389,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const organizationId = req.currentUser.organization?.id
|
||||
const organizationId = req.currentUser?.organizationsId
|
||||
|
||||
|
||||
const payload = await UsersDBApi.findAllAutocomplete(
|
||||
@ -437,7 +437,14 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await UsersDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
{ currentUser: req.currentUser },
|
||||
);
|
||||
|
||||
if (!payload) {
|
||||
const error = new Error('Item not found.');
|
||||
error.code = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
delete payload.password;
|
||||
|
||||
@ -10,8 +10,8 @@ const config = require('../config');
|
||||
const helpers = require('../helpers');
|
||||
|
||||
class Auth {
|
||||
static async signup(email, password, organizationId, options = {}, host) {
|
||||
const user = await UsersDBApi.findBy({email});
|
||||
static async signup(email, password, organizationsId, options = {}, host) {
|
||||
const user = await UsersDBApi.findAuthUserByEmail(email);
|
||||
|
||||
const hashedPassword = await bcrypt.hash(
|
||||
password,
|
||||
@ -54,14 +54,18 @@ class Auth {
|
||||
return helpers.jwtSign(data);
|
||||
}
|
||||
|
||||
if (!organizationsId) {
|
||||
const error = new Error('A valid tenant organization is required for signup.');
|
||||
error.code = 400;
|
||||
throw error;
|
||||
}
|
||||
|
||||
const newUser = await UsersDBApi.createFromAuth(
|
||||
{
|
||||
firstName: email.split('@')[0],
|
||||
password: hashedPassword,
|
||||
email: email,
|
||||
|
||||
organizationId: organizationId,
|
||||
|
||||
organizationsId,
|
||||
},
|
||||
options,
|
||||
);
|
||||
@ -84,7 +88,7 @@ class Auth {
|
||||
}
|
||||
|
||||
static async signin(email, password, options = {}) {
|
||||
const user = await UsersDBApi.findBy({email});
|
||||
const user = await UsersDBApi.findAuthUserByEmail(email);
|
||||
|
||||
if (!user) {
|
||||
throw new ValidationError(
|
||||
|
||||
@ -24,7 +24,7 @@ function buildWidgetResult(widget, queryResult, queryString) {
|
||||
async function executeQuery(queryString, currentUser) {
|
||||
try {
|
||||
return await db.sequelize.query(queryString, {
|
||||
replacements: { organizationId: currentUser.organizationId },
|
||||
replacements: { organizationId: currentUser.organizationsId },
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
@ -49,13 +49,13 @@ function insertWhereConditions(queryString, whereConditions) {
|
||||
}
|
||||
|
||||
function constructWhereConditions(mainTable, currentUser, replacements) {
|
||||
const { organizationId, app_role: { globalAccess } } = currentUser;
|
||||
const { organizationsId, app_role: { globalAccess } } = currentUser;
|
||||
const tablesWithoutOrgId = ['permissions', 'roles'];
|
||||
let whereConditions = '';
|
||||
|
||||
if (!globalAccess && !tablesWithoutOrgId.includes(mainTable)) {
|
||||
whereConditions += `"${mainTable}"."organizationId" = :organizationId`;
|
||||
replacements.organizationId = organizationId;
|
||||
whereConditions += `"${mainTable}"."organizationsId" = :organizationId`;
|
||||
replacements.organizationId = organizationsId;
|
||||
}
|
||||
|
||||
whereConditions += whereConditions ? ' AND ' : '';
|
||||
@ -362,7 +362,7 @@ module.exports = class RolesService {
|
||||
static async getRoleInfoByKey(key, roleId, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
const organizationId = currentUser.organizationId;
|
||||
const organizationId = currentUser.organizationsId;
|
||||
let globalAccess = currentUser.app_role?.globalAccess;
|
||||
let queryString = '';
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ async function checkPermissions(permission, currentUser) {
|
||||
}
|
||||
|
||||
module.exports = class SearchService {
|
||||
static async search(searchQuery, currentUser , organizationId, globalAccess) {
|
||||
static async search(searchQuery, currentUser, organizationsId, globalAccess) {
|
||||
try {
|
||||
if (!searchQuery) {
|
||||
throw new ValidationError('iam.errors.searchQueryRequired');
|
||||
@ -1008,8 +1008,8 @@ module.exports = class SearchService {
|
||||
};
|
||||
|
||||
|
||||
if (!globalAccess && tableName !== 'organizations' && organizationId) {
|
||||
whereCondition.organizationId = organizationId;
|
||||
if (!globalAccess && tableName !== 'organizations' && organizationsId) {
|
||||
whereCondition.organizationsId = organizationsId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user