Compare commits

...

2 Commits

Author SHA1 Message Date
Flatlogic Bot
151baf7125 Autosave: 20260624-155247 2026-06-24 15:52:42 +00:00
Flatlogic Bot
8156349b96 Autosave: 20260623-140927 2026-06-23 14:09:24 +00:00
200 changed files with 5499 additions and 2870 deletions

View File

@ -154,7 +154,7 @@ async function awaitResponse(aiRequestId, options = {}) {
const interval = Math.max(Number(options.interval ?? 5), 1); const interval = Math.max(Number(options.interval ?? 5), 1);
const deadline = Date.now() + Math.max(timeout, interval) * 1000; const deadline = Date.now() + Math.max(timeout, interval) * 1000;
while (true) { while (Date.now() < deadline) {
const statusResp = await fetchStatus(aiRequestId, { const statusResp = await fetchStatus(aiRequestId, {
headers: options.headers, headers: options.headers,
timeout: options.timeout_per_call, timeout: options.timeout_per_call,
@ -184,7 +184,9 @@ async function awaitResponse(aiRequestId, options = {}) {
return statusResp; return statusResp;
} }
if (Date.now() >= deadline) { await sleep(interval * 1000);
}
return { return {
success: false, success: false,
error: "timeout", error: "timeout",
@ -192,10 +194,6 @@ async function awaitResponse(aiRequestId, options = {}) {
}; };
} }
await sleep(interval * 1000);
}
}
function extractText(response) { function extractText(response) {
const payload = response && typeof response === "object" ? response.data || response : null; const payload = response && typeof response === "object" ? response.data || response : null;
if (!payload || typeof payload !== "object") { if (!payload || typeof payload !== "object") {

View File

@ -56,7 +56,7 @@ passport.use(new MicrosoftStrategy({
)); ));
function socialStrategy(email, profile, provider, done) { function socialStrategy(email, profile, provider, done) {
db.users.findOrCreate({where: {email, provider}}).then(([user, created]) => { db.users.findOrCreate({where: {email, provider}}).then(([user]) => {
const body = { const body = {
id: user.id, id: user.id,
email: user.email, email: user.email,

View File

@ -1,8 +1,7 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const { applySchoolScope, applySchoolScopeById, assertRecordInCurrentSchool, assertRelatedRecordInCurrentSchool, resolveSchoolIdForMutation } = require('./schoolScope');
@ -49,18 +48,21 @@ module.exports = class AssessmentsDBApi {
); );
await assessments.setSchool( data.school || null, { await assessments.setSchool(resolveSchoolIdForMutation(data.school, currentUser), {
transaction, transaction,
}); });
await assertRelatedRecordInCurrentSchool('students', data.student, currentUser, 'schoolId', { transaction });
await assessments.setStudent( data.student || null, { await assessments.setStudent( data.student || null, {
transaction, transaction,
}); });
await assertRelatedRecordInCurrentSchool('subjects', data.subject, currentUser, 'schoolId', { transaction });
await assessments.setSubject( data.subject || null, { await assessments.setSubject( data.subject || null, {
transaction, transaction,
}); });
await assertRelatedRecordInCurrentSchool('teachers', data.teacher, currentUser, 'schoolId', { transaction });
await assessments.setTeacher( data.teacher || null, { await assessments.setTeacher( data.teacher || null, {
transaction, transaction,
}); });
@ -105,6 +107,7 @@ module.exports = class AssessmentsDBApi {
importHash: item.importHash || null, importHash: item.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
schoolId: resolveSchoolIdForMutation(item.school, currentUser),
createdAt: new Date(Date.now() + index * 1000), createdAt: new Date(Date.now() + index * 1000),
})); }));
@ -120,9 +123,8 @@ module.exports = class AssessmentsDBApi {
static async update(id, data, options) { static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const assessments = await db.assessments.findByPk(id, {}, {transaction}); const assessments = await db.assessments.findByPk(id, {}, {transaction});
assertRecordInCurrentSchool(assessments, currentUser, 'schoolId');
@ -150,13 +152,14 @@ module.exports = class AssessmentsDBApi {
if (data.school !== undefined) { if (data.school !== undefined) {
await assessments.setSchool( await assessments.setSchool(
data.school, resolveSchoolIdForMutation(data.school, currentUser),
{ transaction } { transaction }
); );
} }
if (data.student !== undefined) { if (data.student !== undefined) {
await assertRelatedRecordInCurrentSchool('students', data.student, currentUser, 'schoolId', { transaction });
await assessments.setStudent( await assessments.setStudent(
data.student, data.student,
@ -166,6 +169,7 @@ module.exports = class AssessmentsDBApi {
} }
if (data.subject !== undefined) { if (data.subject !== undefined) {
await assertRelatedRecordInCurrentSchool('subjects', data.subject, currentUser, 'schoolId', { transaction });
await assessments.setSubject( await assessments.setSubject(
data.subject, data.subject,
@ -175,6 +179,7 @@ module.exports = class AssessmentsDBApi {
} }
if (data.teacher !== undefined) { if (data.teacher !== undefined) {
await assertRelatedRecordInCurrentSchool('teachers', data.teacher, currentUser, 'schoolId', { transaction });
await assessments.setTeacher( await assessments.setTeacher(
data.teacher, data.teacher,
@ -205,6 +210,8 @@ module.exports = class AssessmentsDBApi {
transaction, transaction,
}); });
assessments.forEach((record) => assertRecordInCurrentSchool(record, currentUser, 'schoolId'));
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of assessments) { for (const record of assessments) {
await record.update( await record.update(
@ -226,6 +233,7 @@ module.exports = class AssessmentsDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const assessments = await db.assessments.findByPk(id, options); const assessments = await db.assessments.findByPk(id, options);
assertRecordInCurrentSchool(assessments, currentUser, 'schoolId');
await assessments.update({ await assessments.update({
deletedBy: currentUser.id deletedBy: currentUser.id
@ -242,6 +250,9 @@ module.exports = class AssessmentsDBApi {
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const currentUser = (options && options.currentUser) || null;
applySchoolScope(where, currentUser?.app_role?.globalAccess, currentUser, 'schoolId');
const assessments = await db.assessments.findOne( const assessments = await db.assessments.findOne(
{ where }, { where },
@ -309,26 +320,11 @@ module.exports = class AssessmentsDBApi {
let offset = 0; let offset = 0;
let where = {}; let where = {};
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null; const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
if (userSchools) {
if (options?.currentUser?.schoolsId) {
where.schoolsId = options.currentUser.schoolsId;
}
}
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [
{ {
@ -530,11 +526,7 @@ module.exports = class AssessmentsDBApi {
} }
applySchoolScope(where, globalAccess, user, 'schoolId');
if (globalAccess) {
delete where.schoolsId;
}
const queryOptions = { const queryOptions = {
where, where,
@ -565,15 +557,10 @@ module.exports = class AssessmentsDBApi {
} }
} }
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId,) { static async findAllAutocomplete(query, limit, offset, globalAccess, schoolId,) {
let where = {}; let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) { if (query) {
where = { where = {
[Op.or]: [ [Op.or]: [
@ -587,6 +574,8 @@ module.exports = class AssessmentsDBApi {
}; };
} }
applySchoolScopeById(where, globalAccess, schoolId, 'schoolId');
const records = await db.assessments.findAll({ const records = await db.assessments.findAll({
attributes: [ 'id', 'tipo' ], attributes: [ 'id', 'tipo' ],
where, where,

View File

@ -1,8 +1,7 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const { applySchoolScope, applySchoolScopeById, assertRecordInCurrentSchool, assertRelatedRecordInCurrentSchool, resolveSchoolIdForMutation } = require('./schoolScope');
@ -45,10 +44,11 @@ module.exports = class AttendanceDBApi {
); );
await attendance.setSchool( data.school || null, { await attendance.setSchool(resolveSchoolIdForMutation(data.school, currentUser), {
transaction, transaction,
}); });
await assertRelatedRecordInCurrentSchool('students', data.student, currentUser, 'schoolId', { transaction });
await attendance.setStudent( data.student || null, { await attendance.setStudent( data.student || null, {
transaction, transaction,
}); });
@ -89,6 +89,7 @@ module.exports = class AttendanceDBApi {
importHash: item.importHash || null, importHash: item.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
schoolId: resolveSchoolIdForMutation(item.school, currentUser),
createdAt: new Date(Date.now() + index * 1000), createdAt: new Date(Date.now() + index * 1000),
})); }));
@ -104,9 +105,8 @@ module.exports = class AttendanceDBApi {
static async update(id, data, options) { static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const attendance = await db.attendance.findByPk(id, {}, {transaction}); const attendance = await db.attendance.findByPk(id, {}, {transaction});
assertRecordInCurrentSchool(attendance, currentUser, 'schoolId');
@ -131,13 +131,14 @@ module.exports = class AttendanceDBApi {
if (data.school !== undefined) { if (data.school !== undefined) {
await attendance.setSchool( await attendance.setSchool(
data.school, resolveSchoolIdForMutation(data.school, currentUser),
{ transaction } { transaction }
); );
} }
if (data.student !== undefined) { if (data.student !== undefined) {
await assertRelatedRecordInCurrentSchool('students', data.student, currentUser, 'schoolId', { transaction });
await attendance.setStudent( await attendance.setStudent(
data.student, data.student,
@ -168,6 +169,8 @@ module.exports = class AttendanceDBApi {
transaction, transaction,
}); });
attendance.forEach((record) => assertRecordInCurrentSchool(record, currentUser, 'schoolId'));
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of attendance) { for (const record of attendance) {
await record.update( await record.update(
@ -189,6 +192,7 @@ module.exports = class AttendanceDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const attendance = await db.attendance.findByPk(id, options); const attendance = await db.attendance.findByPk(id, options);
assertRecordInCurrentSchool(attendance, currentUser, 'schoolId');
await attendance.update({ await attendance.update({
deletedBy: currentUser.id deletedBy: currentUser.id
@ -205,6 +209,9 @@ module.exports = class AttendanceDBApi {
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const currentUser = (options && options.currentUser) || null;
applySchoolScope(where, currentUser?.app_role?.globalAccess, currentUser, 'schoolId');
const attendance = await db.attendance.findOne( const attendance = await db.attendance.findOne(
{ where }, { where },
@ -262,26 +269,11 @@ module.exports = class AttendanceDBApi {
let offset = 0; let offset = 0;
let where = {}; let where = {};
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null; const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
if (userSchools) {
if (options?.currentUser?.schoolsId) {
where.schoolsId = options.currentUser.schoolsId;
}
}
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [
{ {
@ -421,11 +413,7 @@ module.exports = class AttendanceDBApi {
} }
applySchoolScope(where, globalAccess, user, 'schoolId');
if (globalAccess) {
delete where.schoolsId;
}
const queryOptions = { const queryOptions = {
where, where,
@ -456,15 +444,10 @@ module.exports = class AttendanceDBApi {
} }
} }
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId,) { static async findAllAutocomplete(query, limit, offset, globalAccess, schoolId,) {
let where = {}; let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) { if (query) {
where = { where = {
[Op.or]: [ [Op.or]: [
@ -478,6 +461,8 @@ module.exports = class AttendanceDBApi {
}; };
} }
applySchoolScopeById(where, globalAccess, schoolId, 'schoolId');
const records = await db.attendance.findAll({ const records = await db.attendance.findAll({
attributes: [ 'id', 'observacao' ], attributes: [ 'id', 'observacao' ],
where, where,

View File

@ -1,8 +1,7 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const { applySchoolScope, applySchoolScopeById, assertRecordInCurrentSchool, assertRelatedRecordInCurrentSchool, resolveSchoolIdForMutation } = require('./schoolScope');
@ -54,14 +53,16 @@ module.exports = class Book_loansDBApi {
); );
await book_loans.setSchool( data.school || null, { await book_loans.setSchool(resolveSchoolIdForMutation(data.school, currentUser), {
transaction, transaction,
}); });
await assertRelatedRecordInCurrentSchool('books', data.book, currentUser, 'schoolId', { transaction });
await book_loans.setBook( data.book || null, { await book_loans.setBook( data.book || null, {
transaction, transaction,
}); });
await assertRelatedRecordInCurrentSchool('students', data.student, currentUser, 'schoolId', { transaction });
await book_loans.setStudent( data.student || null, { await book_loans.setStudent( data.student || null, {
transaction, transaction,
}); });
@ -111,6 +112,7 @@ module.exports = class Book_loansDBApi {
importHash: item.importHash || null, importHash: item.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
schoolId: resolveSchoolIdForMutation(item.school, currentUser),
createdAt: new Date(Date.now() + index * 1000), createdAt: new Date(Date.now() + index * 1000),
})); }));
@ -126,9 +128,8 @@ module.exports = class Book_loansDBApi {
static async update(id, data, options) { static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const book_loans = await db.book_loans.findByPk(id, {}, {transaction}); const book_loans = await db.book_loans.findByPk(id, {}, {transaction});
assertRecordInCurrentSchool(book_loans, currentUser, 'schoolId');
@ -159,13 +160,14 @@ module.exports = class Book_loansDBApi {
if (data.school !== undefined) { if (data.school !== undefined) {
await book_loans.setSchool( await book_loans.setSchool(
data.school, resolveSchoolIdForMutation(data.school, currentUser),
{ transaction } { transaction }
); );
} }
if (data.book !== undefined) { if (data.book !== undefined) {
await assertRelatedRecordInCurrentSchool('books', data.book, currentUser, 'schoolId', { transaction });
await book_loans.setBook( await book_loans.setBook(
data.book, data.book,
@ -175,6 +177,7 @@ module.exports = class Book_loansDBApi {
} }
if (data.student !== undefined) { if (data.student !== undefined) {
await assertRelatedRecordInCurrentSchool('students', data.student, currentUser, 'schoolId', { transaction });
await book_loans.setStudent( await book_loans.setStudent(
data.student, data.student,
@ -205,6 +208,8 @@ module.exports = class Book_loansDBApi {
transaction, transaction,
}); });
book_loans.forEach((record) => assertRecordInCurrentSchool(record, currentUser, 'schoolId'));
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of book_loans) { for (const record of book_loans) {
await record.update( await record.update(
@ -226,6 +231,7 @@ module.exports = class Book_loansDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const book_loans = await db.book_loans.findByPk(id, options); const book_loans = await db.book_loans.findByPk(id, options);
assertRecordInCurrentSchool(book_loans, currentUser, 'schoolId');
await book_loans.update({ await book_loans.update({
deletedBy: currentUser.id deletedBy: currentUser.id
@ -242,6 +248,9 @@ module.exports = class Book_loansDBApi {
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const currentUser = (options && options.currentUser) || null;
applySchoolScope(where, currentUser?.app_role?.globalAccess, currentUser, 'schoolId');
const book_loans = await db.book_loans.findOne( const book_loans = await db.book_loans.findOne(
{ where }, { where },
@ -304,26 +313,11 @@ module.exports = class Book_loansDBApi {
let offset = 0; let offset = 0;
let where = {}; let where = {};
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null; const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
if (userSchools) {
if (options?.currentUser?.schoolsId) {
where.schoolsId = options.currentUser.schoolsId;
}
}
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [
{ {
@ -561,11 +555,7 @@ module.exports = class Book_loansDBApi {
} }
applySchoolScope(where, globalAccess, user, 'schoolId');
if (globalAccess) {
delete where.schoolsId;
}
const queryOptions = { const queryOptions = {
where, where,
@ -596,15 +586,10 @@ module.exports = class Book_loansDBApi {
} }
} }
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId,) { static async findAllAutocomplete(query, limit, offset, globalAccess, schoolId,) {
let where = {}; let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) { if (query) {
where = { where = {
[Op.or]: [ [Op.or]: [
@ -618,6 +603,8 @@ module.exports = class Book_loansDBApi {
}; };
} }
applySchoolScopeById(where, globalAccess, schoolId, 'schoolId');
const records = await db.book_loans.findAll({ const records = await db.book_loans.findAll({
attributes: [ 'id', 'status' ], attributes: [ 'id', 'status' ],
where, where,

View File

@ -1,8 +1,7 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const { applySchoolScope, applySchoolScopeById, assertRecordInCurrentSchool, resolveSchoolIdForMutation } = require('./schoolScope');
@ -59,7 +58,7 @@ module.exports = class BooksDBApi {
); );
await books.setSchool( data.school || null, { await books.setSchool(resolveSchoolIdForMutation(data.school, currentUser), {
transaction, transaction,
}); });
@ -113,6 +112,7 @@ module.exports = class BooksDBApi {
importHash: item.importHash || null, importHash: item.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
schoolId: resolveSchoolIdForMutation(item.school, currentUser),
createdAt: new Date(Date.now() + index * 1000), createdAt: new Date(Date.now() + index * 1000),
})); }));
@ -128,9 +128,8 @@ module.exports = class BooksDBApi {
static async update(id, data, options) { static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const books = await db.books.findByPk(id, {}, {transaction}); const books = await db.books.findByPk(id, {}, {transaction});
assertRecordInCurrentSchool(books, currentUser, 'schoolId');
@ -164,7 +163,7 @@ module.exports = class BooksDBApi {
if (data.school !== undefined) { if (data.school !== undefined) {
await books.setSchool( await books.setSchool(
data.school, resolveSchoolIdForMutation(data.school, currentUser),
{ transaction } { transaction }
); );
@ -192,6 +191,8 @@ module.exports = class BooksDBApi {
transaction, transaction,
}); });
books.forEach((record) => assertRecordInCurrentSchool(record, currentUser, 'schoolId'));
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of books) { for (const record of books) {
await record.update( await record.update(
@ -213,6 +214,7 @@ module.exports = class BooksDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const books = await db.books.findByPk(id, options); const books = await db.books.findByPk(id, options);
assertRecordInCurrentSchool(books, currentUser, 'schoolId');
await books.update({ await books.update({
deletedBy: currentUser.id deletedBy: currentUser.id
@ -229,6 +231,9 @@ module.exports = class BooksDBApi {
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const currentUser = (options && options.currentUser) || null;
applySchoolScope(where, currentUser?.app_role?.globalAccess, currentUser, 'schoolId');
const books = await db.books.findOne( const books = await db.books.findOne(
{ where }, { where },
@ -285,26 +290,11 @@ module.exports = class BooksDBApi {
let offset = 0; let offset = 0;
let where = {}; let where = {};
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null; const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
if (userSchools) {
if (options?.currentUser?.schoolsId) {
where.schoolsId = options.currentUser.schoolsId;
}
}
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [
{ {
@ -471,11 +461,7 @@ module.exports = class BooksDBApi {
} }
applySchoolScope(where, globalAccess, user, 'schoolId');
if (globalAccess) {
delete where.schoolsId;
}
const queryOptions = { const queryOptions = {
where, where,
@ -506,15 +492,10 @@ module.exports = class BooksDBApi {
} }
} }
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId,) { static async findAllAutocomplete(query, limit, offset, globalAccess, schoolId,) {
let where = {}; let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) { if (query) {
where = { where = {
[Op.or]: [ [Op.or]: [
@ -528,6 +509,8 @@ module.exports = class BooksDBApi {
}; };
} }
applySchoolScopeById(where, globalAccess, schoolId, 'schoolId');
const records = await db.books.findAll({ const records = await db.books.findAll({
attributes: [ 'id', 'titulo' ], attributes: [ 'id', 'titulo' ],
where, where,

View File

@ -1,8 +1,7 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const { applySchoolScope, applySchoolScopeById, assertRecordInCurrentSchool, assertRelatedRecordInCurrentSchool, resolveSchoolIdForMutation } = require('./schoolScope');
@ -54,10 +53,11 @@ module.exports = class ClassesDBApi {
); );
await classes.setSchool( data.school || null, { await classes.setSchool(resolveSchoolIdForMutation(data.school, currentUser), {
transaction, transaction,
}); });
await assertRelatedRecordInCurrentSchool('grades', data.grade, currentUser, 'schoolId', { transaction });
await classes.setGrade( data.grade || null, { await classes.setGrade( data.grade || null, {
transaction, transaction,
}); });
@ -107,6 +107,7 @@ module.exports = class ClassesDBApi {
importHash: item.importHash || null, importHash: item.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
schoolId: resolveSchoolIdForMutation(item.school, currentUser),
createdAt: new Date(Date.now() + index * 1000), createdAt: new Date(Date.now() + index * 1000),
})); }));
@ -122,9 +123,8 @@ module.exports = class ClassesDBApi {
static async update(id, data, options) { static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const classes = await db.classes.findByPk(id, {}, {transaction}); const classes = await db.classes.findByPk(id, {}, {transaction});
assertRecordInCurrentSchool(classes, currentUser, 'schoolId');
@ -155,13 +155,14 @@ module.exports = class ClassesDBApi {
if (data.school !== undefined) { if (data.school !== undefined) {
await classes.setSchool( await classes.setSchool(
data.school, resolveSchoolIdForMutation(data.school, currentUser),
{ transaction } { transaction }
); );
} }
if (data.grade !== undefined) { if (data.grade !== undefined) {
await assertRelatedRecordInCurrentSchool('grades', data.grade, currentUser, 'schoolId', { transaction });
await classes.setGrade( await classes.setGrade(
data.grade, data.grade,
@ -192,6 +193,8 @@ module.exports = class ClassesDBApi {
transaction, transaction,
}); });
classes.forEach((record) => assertRecordInCurrentSchool(record, currentUser, 'schoolId'));
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of classes) { for (const record of classes) {
await record.update( await record.update(
@ -213,6 +216,7 @@ module.exports = class ClassesDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const classes = await db.classes.findByPk(id, options); const classes = await db.classes.findByPk(id, options);
assertRecordInCurrentSchool(classes, currentUser, 'schoolId');
await classes.update({ await classes.update({
deletedBy: currentUser.id deletedBy: currentUser.id
@ -229,6 +233,9 @@ module.exports = class ClassesDBApi {
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const currentUser = (options && options.currentUser) || null;
applySchoolScope(where, currentUser?.app_role?.globalAccess, currentUser, 'schoolId');
const classes = await db.classes.findOne( const classes = await db.classes.findOne(
{ where }, { where },
@ -290,26 +297,11 @@ module.exports = class ClassesDBApi {
let offset = 0; let offset = 0;
let where = {}; let where = {};
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null; const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
if (userSchools) {
if (options?.currentUser?.schoolsId) {
where.schoolsId = options.currentUser.schoolsId;
}
}
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [
{ {
@ -467,11 +459,7 @@ module.exports = class ClassesDBApi {
} }
applySchoolScope(where, globalAccess, user, 'schoolId');
if (globalAccess) {
delete where.schoolsId;
}
const queryOptions = { const queryOptions = {
where, where,
@ -502,15 +490,10 @@ module.exports = class ClassesDBApi {
} }
} }
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId,) { static async findAllAutocomplete(query, limit, offset, globalAccess, schoolId,) {
let where = {}; let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) { if (query) {
where = { where = {
[Op.or]: [ [Op.or]: [
@ -524,6 +507,8 @@ module.exports = class ClassesDBApi {
}; };
} }
applySchoolScopeById(where, globalAccess, schoolId, 'schoolId');
const records = await db.classes.findAll({ const records = await db.classes.findAll({
attributes: [ 'id', 'nome' ], attributes: [ 'id', 'nome' ],
where, where,

View File

@ -1,8 +1,7 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const { applySchoolScope, applySchoolScopeById, assertRecordInCurrentSchool, resolveSchoolIdForMutation } = require('./schoolScope');
@ -44,7 +43,7 @@ module.exports = class CoursesDBApi {
); );
await courses.setSchool( data.school || null, { await courses.setSchool(resolveSchoolIdForMutation(data.school, currentUser), {
transaction, transaction,
}); });
@ -83,6 +82,7 @@ module.exports = class CoursesDBApi {
importHash: item.importHash || null, importHash: item.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
schoolId: resolveSchoolIdForMutation(item.school, currentUser),
createdAt: new Date(Date.now() + index * 1000), createdAt: new Date(Date.now() + index * 1000),
})); }));
@ -98,9 +98,8 @@ module.exports = class CoursesDBApi {
static async update(id, data, options) { static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const courses = await db.courses.findByPk(id, {}, {transaction}); const courses = await db.courses.findByPk(id, {}, {transaction});
assertRecordInCurrentSchool(courses, currentUser, 'schoolId');
@ -125,7 +124,7 @@ module.exports = class CoursesDBApi {
if (data.school !== undefined) { if (data.school !== undefined) {
await courses.setSchool( await courses.setSchool(
data.school, resolveSchoolIdForMutation(data.school, currentUser),
{ transaction } { transaction }
); );
@ -153,6 +152,8 @@ module.exports = class CoursesDBApi {
transaction, transaction,
}); });
courses.forEach((record) => assertRecordInCurrentSchool(record, currentUser, 'schoolId'));
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of courses) { for (const record of courses) {
await record.update( await record.update(
@ -174,6 +175,7 @@ module.exports = class CoursesDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const courses = await db.courses.findByPk(id, options); const courses = await db.courses.findByPk(id, options);
assertRecordInCurrentSchool(courses, currentUser, 'schoolId');
await courses.update({ await courses.update({
deletedBy: currentUser.id deletedBy: currentUser.id
@ -190,6 +192,9 @@ module.exports = class CoursesDBApi {
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const currentUser = (options && options.currentUser) || null;
applySchoolScope(where, currentUser?.app_role?.globalAccess, currentUser, 'schoolId');
const courses = await db.courses.findOne( const courses = await db.courses.findOne(
{ where }, { where },
@ -242,26 +247,11 @@ module.exports = class CoursesDBApi {
let offset = 0; let offset = 0;
let where = {}; let where = {};
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null; const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
if (userSchools) {
if (options?.currentUser?.schoolsId) {
where.schoolsId = options.currentUser.schoolsId;
}
}
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [
{ {
@ -369,11 +359,7 @@ module.exports = class CoursesDBApi {
} }
applySchoolScope(where, globalAccess, user, 'schoolId');
if (globalAccess) {
delete where.schoolsId;
}
const queryOptions = { const queryOptions = {
where, where,
@ -404,15 +390,10 @@ module.exports = class CoursesDBApi {
} }
} }
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId,) { static async findAllAutocomplete(query, limit, offset, globalAccess, schoolId,) {
let where = {}; let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) { if (query) {
where = { where = {
[Op.or]: [ [Op.or]: [
@ -426,6 +407,8 @@ module.exports = class CoursesDBApi {
}; };
} }
applySchoolScopeById(where, globalAccess, schoolId, 'schoolId');
const records = await db.courses.findAll({ const records = await db.courses.findAll({
attributes: [ 'id', 'nome' ], attributes: [ 'id', 'nome' ],
where, where,

View File

@ -1,8 +1,7 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const { applySchoolScope, applySchoolScopeById, assertRecordInCurrentSchool, resolveSchoolIdForMutation } = require('./schoolScope');
@ -59,7 +58,7 @@ module.exports = class EmployeesDBApi {
); );
await employees.setSchool( data.school || null, { await employees.setSchool(resolveSchoolIdForMutation(data.school, currentUser), {
transaction, transaction,
}); });
@ -113,6 +112,7 @@ module.exports = class EmployeesDBApi {
importHash: item.importHash || null, importHash: item.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
schoolId: resolveSchoolIdForMutation(item.school, currentUser),
createdAt: new Date(Date.now() + index * 1000), createdAt: new Date(Date.now() + index * 1000),
})); }));
@ -128,9 +128,8 @@ module.exports = class EmployeesDBApi {
static async update(id, data, options) { static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const employees = await db.employees.findByPk(id, {}, {transaction}); const employees = await db.employees.findByPk(id, {}, {transaction});
assertRecordInCurrentSchool(employees, currentUser, 'schoolId');
@ -164,7 +163,7 @@ module.exports = class EmployeesDBApi {
if (data.school !== undefined) { if (data.school !== undefined) {
await employees.setSchool( await employees.setSchool(
data.school, resolveSchoolIdForMutation(data.school, currentUser),
{ transaction } { transaction }
); );
@ -192,6 +191,8 @@ module.exports = class EmployeesDBApi {
transaction, transaction,
}); });
employees.forEach((record) => assertRecordInCurrentSchool(record, currentUser, 'schoolId'));
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of employees) { for (const record of employees) {
await record.update( await record.update(
@ -213,6 +214,7 @@ module.exports = class EmployeesDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const employees = await db.employees.findByPk(id, options); const employees = await db.employees.findByPk(id, options);
assertRecordInCurrentSchool(employees, currentUser, 'schoolId');
await employees.update({ await employees.update({
deletedBy: currentUser.id deletedBy: currentUser.id
@ -229,6 +231,9 @@ module.exports = class EmployeesDBApi {
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const currentUser = (options && options.currentUser) || null;
applySchoolScope(where, currentUser?.app_role?.globalAccess, currentUser, 'schoolId');
const employees = await db.employees.findOne( const employees = await db.employees.findOne(
{ where }, { where },
@ -281,26 +286,11 @@ module.exports = class EmployeesDBApi {
let offset = 0; let offset = 0;
let where = {}; let where = {};
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null; const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
if (userSchools) {
if (options?.currentUser?.schoolsId) {
where.schoolsId = options.currentUser.schoolsId;
}
}
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [
{ {
@ -467,11 +457,7 @@ module.exports = class EmployeesDBApi {
} }
applySchoolScope(where, globalAccess, user, 'schoolId');
if (globalAccess) {
delete where.schoolsId;
}
const queryOptions = { const queryOptions = {
where, where,
@ -502,15 +488,10 @@ module.exports = class EmployeesDBApi {
} }
} }
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId,) { static async findAllAutocomplete(query, limit, offset, globalAccess, schoolId,) {
let where = {}; let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) { if (query) {
where = { where = {
[Op.or]: [ [Op.or]: [
@ -524,6 +505,8 @@ module.exports = class EmployeesDBApi {
}; };
} }
applySchoolScopeById(where, globalAccess, schoolId, 'schoolId');
const records = await db.employees.findAll({ const records = await db.employees.findAll({
attributes: [ 'id', 'nome' ], attributes: [ 'id', 'nome' ],
where, where,

View File

@ -1,8 +1,7 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const { applySchoolScope, applySchoolScopeById, assertRecordInCurrentSchool, assertRelatedRecordInCurrentSchool, resolveSchoolIdForMutation } = require('./schoolScope');
@ -44,14 +43,16 @@ module.exports = class EnrollmentsDBApi {
); );
await enrollments.setSchool( data.school || null, { await enrollments.setSchool(resolveSchoolIdForMutation(data.school, currentUser), {
transaction, transaction,
}); });
await assertRelatedRecordInCurrentSchool('students', data.student, currentUser, 'schoolId', { transaction });
await enrollments.setStudent( data.student || null, { await enrollments.setStudent( data.student || null, {
transaction, transaction,
}); });
await assertRelatedRecordInCurrentSchool('classes', data.class, currentUser, 'schoolId', { transaction });
await enrollments.setClass( data.class || null, { await enrollments.setClass( data.class || null, {
transaction, transaction,
}); });
@ -91,6 +92,7 @@ module.exports = class EnrollmentsDBApi {
importHash: item.importHash || null, importHash: item.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
schoolId: resolveSchoolIdForMutation(item.school, currentUser),
createdAt: new Date(Date.now() + index * 1000), createdAt: new Date(Date.now() + index * 1000),
})); }));
@ -106,9 +108,8 @@ module.exports = class EnrollmentsDBApi {
static async update(id, data, options) { static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const enrollments = await db.enrollments.findByPk(id, {}, {transaction}); const enrollments = await db.enrollments.findByPk(id, {}, {transaction});
assertRecordInCurrentSchool(enrollments, currentUser, 'schoolId');
@ -133,13 +134,14 @@ module.exports = class EnrollmentsDBApi {
if (data.school !== undefined) { if (data.school !== undefined) {
await enrollments.setSchool( await enrollments.setSchool(
data.school, resolveSchoolIdForMutation(data.school, currentUser),
{ transaction } { transaction }
); );
} }
if (data.student !== undefined) { if (data.student !== undefined) {
await assertRelatedRecordInCurrentSchool('students', data.student, currentUser, 'schoolId', { transaction });
await enrollments.setStudent( await enrollments.setStudent(
data.student, data.student,
@ -149,6 +151,7 @@ module.exports = class EnrollmentsDBApi {
} }
if (data.class !== undefined) { if (data.class !== undefined) {
await assertRelatedRecordInCurrentSchool('classes', data.class, currentUser, 'schoolId', { transaction });
await enrollments.setClass( await enrollments.setClass(
data.class, data.class,
@ -179,6 +182,8 @@ module.exports = class EnrollmentsDBApi {
transaction, transaction,
}); });
enrollments.forEach((record) => assertRecordInCurrentSchool(record, currentUser, 'schoolId'));
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of enrollments) { for (const record of enrollments) {
await record.update( await record.update(
@ -200,6 +205,7 @@ module.exports = class EnrollmentsDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const enrollments = await db.enrollments.findByPk(id, options); const enrollments = await db.enrollments.findByPk(id, options);
assertRecordInCurrentSchool(enrollments, currentUser, 'schoolId');
await enrollments.update({ await enrollments.update({
deletedBy: currentUser.id deletedBy: currentUser.id
@ -216,6 +222,9 @@ module.exports = class EnrollmentsDBApi {
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const currentUser = (options && options.currentUser) || null;
applySchoolScope(where, currentUser?.app_role?.globalAccess, currentUser, 'schoolId');
const enrollments = await db.enrollments.findOne( const enrollments = await db.enrollments.findOne(
{ where }, { where },
@ -278,26 +287,11 @@ module.exports = class EnrollmentsDBApi {
let offset = 0; let offset = 0;
let where = {}; let where = {};
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null; const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
if (userSchools) {
if (options?.currentUser?.schoolsId) {
where.schoolsId = options.currentUser.schoolsId;
}
}
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [
{ {
@ -456,11 +450,7 @@ module.exports = class EnrollmentsDBApi {
} }
applySchoolScope(where, globalAccess, user, 'schoolId');
if (globalAccess) {
delete where.schoolsId;
}
const queryOptions = { const queryOptions = {
where, where,
@ -491,15 +481,10 @@ module.exports = class EnrollmentsDBApi {
} }
} }
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId,) { static async findAllAutocomplete(query, limit, offset, globalAccess, schoolId,) {
let where = {}; let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) { if (query) {
where = { where = {
[Op.or]: [ [Op.or]: [
@ -513,6 +498,8 @@ module.exports = class EnrollmentsDBApi {
}; };
} }
applySchoolScopeById(where, globalAccess, schoolId, 'schoolId');
const records = await db.enrollments.findAll({ const records = await db.enrollments.findAll({
attributes: [ 'id', 'ano_lectivo' ], attributes: [ 'id', 'ano_lectivo' ],
where, where,

View File

@ -36,15 +36,17 @@ module.exports = class FileDBApi {
); );
for (const file of inexistentFiles) { for (const file of inexistentFiles) {
const safeFile = await services.validateFileMetadata(file, relation, { transaction });
await db.file.create( await db.file.create(
{ {
belongsTo: relation.belongsTo, belongsTo: relation.belongsTo,
belongsToColumn: relation.belongsToColumn, belongsToColumn: relation.belongsToColumn,
belongsToId: relation.belongsToId, belongsToId: relation.belongsToId,
name: file.name, name: safeFile.name,
sizeInBytes: file.sizeInBytes, sizeInBytes: safeFile.sizeInBytes,
privateUrl: file.privateUrl, privateUrl: safeFile.privateUrl,
publicUrl: file.publicUrl, publicUrl: safeFile.publicUrl,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
}, },

View File

@ -1,8 +1,7 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const { applySchoolScope, applySchoolScopeById, assertRecordInCurrentSchool, resolveSchoolIdForMutation } = require('./schoolScope');
@ -44,7 +43,7 @@ module.exports = class GradesDBApi {
); );
await grades.setSchool( data.school || null, { await grades.setSchool(resolveSchoolIdForMutation(data.school, currentUser), {
transaction, transaction,
}); });
@ -83,6 +82,7 @@ module.exports = class GradesDBApi {
importHash: item.importHash || null, importHash: item.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
schoolId: resolveSchoolIdForMutation(item.school, currentUser),
createdAt: new Date(Date.now() + index * 1000), createdAt: new Date(Date.now() + index * 1000),
})); }));
@ -98,9 +98,8 @@ module.exports = class GradesDBApi {
static async update(id, data, options) { static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const grades = await db.grades.findByPk(id, {}, {transaction}); const grades = await db.grades.findByPk(id, {}, {transaction});
assertRecordInCurrentSchool(grades, currentUser, 'schoolId');
@ -125,7 +124,7 @@ module.exports = class GradesDBApi {
if (data.school !== undefined) { if (data.school !== undefined) {
await grades.setSchool( await grades.setSchool(
data.school, resolveSchoolIdForMutation(data.school, currentUser),
{ transaction } { transaction }
); );
@ -153,6 +152,8 @@ module.exports = class GradesDBApi {
transaction, transaction,
}); });
grades.forEach((record) => assertRecordInCurrentSchool(record, currentUser, 'schoolId'));
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of grades) { for (const record of grades) {
await record.update( await record.update(
@ -174,6 +175,7 @@ module.exports = class GradesDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const grades = await db.grades.findByPk(id, options); const grades = await db.grades.findByPk(id, options);
assertRecordInCurrentSchool(grades, currentUser, 'schoolId');
await grades.update({ await grades.update({
deletedBy: currentUser.id deletedBy: currentUser.id
@ -190,6 +192,9 @@ module.exports = class GradesDBApi {
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const currentUser = (options && options.currentUser) || null;
applySchoolScope(where, currentUser?.app_role?.globalAccess, currentUser, 'schoolId');
const grades = await db.grades.findOne( const grades = await db.grades.findOne(
{ where }, { where },
@ -246,26 +251,11 @@ module.exports = class GradesDBApi {
let offset = 0; let offset = 0;
let where = {}; let where = {};
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null; const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
if (userSchools) {
if (options?.currentUser?.schoolsId) {
where.schoolsId = options.currentUser.schoolsId;
}
}
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [
{ {
@ -386,11 +376,7 @@ module.exports = class GradesDBApi {
} }
applySchoolScope(where, globalAccess, user, 'schoolId');
if (globalAccess) {
delete where.schoolsId;
}
const queryOptions = { const queryOptions = {
where, where,
@ -421,15 +407,10 @@ module.exports = class GradesDBApi {
} }
} }
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId,) { static async findAllAutocomplete(query, limit, offset, globalAccess, schoolId,) {
let where = {}; let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) { if (query) {
where = { where = {
[Op.or]: [ [Op.or]: [
@ -443,6 +424,8 @@ module.exports = class GradesDBApi {
}; };
} }
applySchoolScopeById(where, globalAccess, schoolId, 'schoolId');
const records = await db.grades.findAll({ const records = await db.grades.findAll({
attributes: [ 'id', 'nome' ], attributes: [ 'id', 'nome' ],
where, where,

View File

@ -1,8 +1,7 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const { applySchoolScope, applySchoolScopeById, assertRecordInCurrentSchool, resolveSchoolIdForMutation } = require('./schoolScope');
@ -49,7 +48,7 @@ module.exports = class GuardiansDBApi {
); );
await guardians.setSchool( data.school || null, { await guardians.setSchool(resolveSchoolIdForMutation(data.school, currentUser), {
transaction, transaction,
}); });
@ -93,6 +92,7 @@ module.exports = class GuardiansDBApi {
importHash: item.importHash || null, importHash: item.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
schoolId: resolveSchoolIdForMutation(item.school, currentUser),
createdAt: new Date(Date.now() + index * 1000), createdAt: new Date(Date.now() + index * 1000),
})); }));
@ -108,9 +108,8 @@ module.exports = class GuardiansDBApi {
static async update(id, data, options) { static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const guardians = await db.guardians.findByPk(id, {}, {transaction}); const guardians = await db.guardians.findByPk(id, {}, {transaction});
assertRecordInCurrentSchool(guardians, currentUser, 'schoolId');
@ -138,7 +137,7 @@ module.exports = class GuardiansDBApi {
if (data.school !== undefined) { if (data.school !== undefined) {
await guardians.setSchool( await guardians.setSchool(
data.school, resolveSchoolIdForMutation(data.school, currentUser),
{ transaction } { transaction }
); );
@ -166,6 +165,8 @@ module.exports = class GuardiansDBApi {
transaction, transaction,
}); });
guardians.forEach((record) => assertRecordInCurrentSchool(record, currentUser, 'schoolId'));
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of guardians) { for (const record of guardians) {
await record.update( await record.update(
@ -187,6 +188,7 @@ module.exports = class GuardiansDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const guardians = await db.guardians.findByPk(id, options); const guardians = await db.guardians.findByPk(id, options);
assertRecordInCurrentSchool(guardians, currentUser, 'schoolId');
await guardians.update({ await guardians.update({
deletedBy: currentUser.id deletedBy: currentUser.id
@ -203,6 +205,9 @@ module.exports = class GuardiansDBApi {
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const currentUser = (options && options.currentUser) || null;
applySchoolScope(where, currentUser?.app_role?.globalAccess, currentUser, 'schoolId');
const guardians = await db.guardians.findOne( const guardians = await db.guardians.findOne(
{ where }, { where },
@ -259,26 +264,11 @@ module.exports = class GuardiansDBApi {
let offset = 0; let offset = 0;
let where = {}; let where = {};
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null; const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
if (userSchools) {
if (options?.currentUser?.schoolsId) {
where.schoolsId = options.currentUser.schoolsId;
}
}
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [
{ {
@ -397,11 +387,7 @@ module.exports = class GuardiansDBApi {
} }
applySchoolScope(where, globalAccess, user, 'schoolId');
if (globalAccess) {
delete where.schoolsId;
}
const queryOptions = { const queryOptions = {
where, where,
@ -432,15 +418,10 @@ module.exports = class GuardiansDBApi {
} }
} }
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId,) { static async findAllAutocomplete(query, limit, offset, globalAccess, schoolId,) {
let where = {}; let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) { if (query) {
where = { where = {
[Op.or]: [ [Op.or]: [
@ -454,6 +435,8 @@ module.exports = class GuardiansDBApi {
}; };
} }
applySchoolScopeById(where, globalAccess, schoolId, 'schoolId');
const records = await db.guardians.findAll({ const records = await db.guardians.findAll({
attributes: [ 'id', 'nome' ], attributes: [ 'id', 'nome' ],
where, where,

View File

@ -1,8 +1,7 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const { applySchoolScope, applySchoolScopeById, assertRecordInCurrentSchool, assertRelatedRecordInCurrentSchool, resolveSchoolIdForMutation } = require('./schoolScope');
@ -59,10 +58,11 @@ module.exports = class InvoicesDBApi {
); );
await invoices.setSchool( data.school || null, { await invoices.setSchool(resolveSchoolIdForMutation(data.school, currentUser), {
transaction, transaction,
}); });
await assertRelatedRecordInCurrentSchool('students', data.student, currentUser, 'schoolId', { transaction });
await invoices.setStudent( data.student || null, { await invoices.setStudent( data.student || null, {
transaction, transaction,
}); });
@ -117,6 +117,7 @@ module.exports = class InvoicesDBApi {
importHash: item.importHash || null, importHash: item.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
schoolId: resolveSchoolIdForMutation(item.school, currentUser),
createdAt: new Date(Date.now() + index * 1000), createdAt: new Date(Date.now() + index * 1000),
})); }));
@ -132,9 +133,8 @@ module.exports = class InvoicesDBApi {
static async update(id, data, options) { static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const invoices = await db.invoices.findByPk(id, {}, {transaction}); const invoices = await db.invoices.findByPk(id, {}, {transaction});
assertRecordInCurrentSchool(invoices, currentUser, 'schoolId');
@ -168,13 +168,14 @@ module.exports = class InvoicesDBApi {
if (data.school !== undefined) { if (data.school !== undefined) {
await invoices.setSchool( await invoices.setSchool(
data.school, resolveSchoolIdForMutation(data.school, currentUser),
{ transaction } { transaction }
); );
} }
if (data.student !== undefined) { if (data.student !== undefined) {
await assertRelatedRecordInCurrentSchool('students', data.student, currentUser, 'schoolId', { transaction });
await invoices.setStudent( await invoices.setStudent(
data.student, data.student,
@ -205,6 +206,8 @@ module.exports = class InvoicesDBApi {
transaction, transaction,
}); });
invoices.forEach((record) => assertRecordInCurrentSchool(record, currentUser, 'schoolId'));
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of invoices) { for (const record of invoices) {
await record.update( await record.update(
@ -226,6 +229,7 @@ module.exports = class InvoicesDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const invoices = await db.invoices.findByPk(id, options); const invoices = await db.invoices.findByPk(id, options);
assertRecordInCurrentSchool(invoices, currentUser, 'schoolId');
await invoices.update({ await invoices.update({
deletedBy: currentUser.id deletedBy: currentUser.id
@ -242,6 +246,9 @@ module.exports = class InvoicesDBApi {
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const currentUser = (options && options.currentUser) || null;
applySchoolScope(where, currentUser?.app_role?.globalAccess, currentUser, 'schoolId');
const invoices = await db.invoices.findOne( const invoices = await db.invoices.findOne(
{ where }, { where },
@ -303,26 +310,11 @@ module.exports = class InvoicesDBApi {
let offset = 0; let offset = 0;
let where = {}; let where = {};
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null; const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
if (userSchools) {
if (options?.currentUser?.schoolsId) {
where.schoolsId = options.currentUser.schoolsId;
}
}
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [
{ {
@ -521,11 +513,7 @@ module.exports = class InvoicesDBApi {
} }
applySchoolScope(where, globalAccess, user, 'schoolId');
if (globalAccess) {
delete where.schoolsId;
}
const queryOptions = { const queryOptions = {
where, where,
@ -556,15 +544,10 @@ module.exports = class InvoicesDBApi {
} }
} }
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId,) { static async findAllAutocomplete(query, limit, offset, globalAccess, schoolId,) {
let where = {}; let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) { if (query) {
where = { where = {
[Op.or]: [ [Op.or]: [
@ -578,6 +561,8 @@ module.exports = class InvoicesDBApi {
}; };
} }
applySchoolScopeById(where, globalAccess, schoolId, 'schoolId');
const records = await db.invoices.findAll({ const records = await db.invoices.findAll({
attributes: [ 'id', 'referencia' ], attributes: [ 'id', 'referencia' ],
where, where,

View File

@ -1,8 +1,8 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file'); const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const { applySchoolScope, applySchoolScopeById, assertRecordInCurrentSchool, assertRelatedRecordInCurrentSchool, resolveSchoolIdForMutation } = require('./schoolScope');
@ -49,10 +49,11 @@ module.exports = class PaymentsDBApi {
); );
await payments.setSchool( data.school || null, { await payments.setSchool(resolveSchoolIdForMutation(data.school, currentUser), {
transaction, transaction,
}); });
await assertRelatedRecordInCurrentSchool('invoices', data.invoice, currentUser, 'schoolId', { transaction });
await payments.setInvoice( data.invoice || null, { await payments.setInvoice( data.invoice || null, {
transaction, transaction,
}); });
@ -107,6 +108,7 @@ module.exports = class PaymentsDBApi {
importHash: item.importHash || null, importHash: item.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
schoolId: resolveSchoolIdForMutation(item.school, currentUser),
createdAt: new Date(Date.now() + index * 1000), createdAt: new Date(Date.now() + index * 1000),
})); }));
@ -134,9 +136,8 @@ module.exports = class PaymentsDBApi {
static async update(id, data, options) { static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const payments = await db.payments.findByPk(id, {}, {transaction}); const payments = await db.payments.findByPk(id, {}, {transaction});
assertRecordInCurrentSchool(payments, currentUser, 'schoolId');
@ -164,13 +165,14 @@ module.exports = class PaymentsDBApi {
if (data.school !== undefined) { if (data.school !== undefined) {
await payments.setSchool( await payments.setSchool(
data.school, resolveSchoolIdForMutation(data.school, currentUser),
{ transaction } { transaction }
); );
} }
if (data.invoice !== undefined) { if (data.invoice !== undefined) {
await assertRelatedRecordInCurrentSchool('invoices', data.invoice, currentUser, 'schoolId', { transaction });
await payments.setInvoice( await payments.setInvoice(
data.invoice, data.invoice,
@ -184,6 +186,7 @@ module.exports = class PaymentsDBApi {
if (data.comprovativo !== undefined) {
await FileDBApi.replaceRelationFiles( await FileDBApi.replaceRelationFiles(
{ {
belongsTo: db.payments.getTableName(), belongsTo: db.payments.getTableName(),
@ -193,6 +196,7 @@ module.exports = class PaymentsDBApi {
data.comprovativo, data.comprovativo,
options, options,
); );
}
return payments; return payments;
@ -211,6 +215,8 @@ module.exports = class PaymentsDBApi {
transaction, transaction,
}); });
payments.forEach((record) => assertRecordInCurrentSchool(record, currentUser, 'schoolId'));
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of payments) { for (const record of payments) {
await record.update( await record.update(
@ -232,6 +238,7 @@ module.exports = class PaymentsDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const payments = await db.payments.findByPk(id, options); const payments = await db.payments.findByPk(id, options);
assertRecordInCurrentSchool(payments, currentUser, 'schoolId');
await payments.update({ await payments.update({
deletedBy: currentUser.id deletedBy: currentUser.id
@ -248,6 +255,9 @@ module.exports = class PaymentsDBApi {
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const currentUser = (options && options.currentUser) || null;
applySchoolScope(where, currentUser?.app_role?.globalAccess, currentUser, 'schoolId');
const payments = await db.payments.findOne( const payments = await db.payments.findOne(
{ where }, { where },
@ -310,26 +320,11 @@ module.exports = class PaymentsDBApi {
let offset = 0; let offset = 0;
let where = {}; let where = {};
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null; const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
if (userSchools) {
if (options?.currentUser?.schoolsId) {
where.schoolsId = options.currentUser.schoolsId;
}
}
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [
{ {
@ -498,11 +493,7 @@ module.exports = class PaymentsDBApi {
} }
applySchoolScope(where, globalAccess, user, 'schoolId');
if (globalAccess) {
delete where.schoolsId;
}
const queryOptions = { const queryOptions = {
where, where,
@ -533,15 +524,10 @@ module.exports = class PaymentsDBApi {
} }
} }
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId,) { static async findAllAutocomplete(query, limit, offset, globalAccess, schoolId,) {
let where = {}; let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) { if (query) {
where = { where = {
[Op.or]: [ [Op.or]: [
@ -555,6 +541,8 @@ module.exports = class PaymentsDBApi {
}; };
} }
applySchoolScopeById(where, globalAccess, schoolId, 'schoolId');
const records = await db.payments.findAll({ const records = await db.payments.findAll({
attributes: [ 'id', 'referencia_transacao' ], attributes: [ 'id', 'referencia_transacao' ],
where, where,

View File

@ -1,14 +1,20 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const ForbiddenError = require('../../services/notifications/errors/forbidden');
const Sequelize = db.Sequelize; const Sequelize = db.Sequelize;
const Op = Sequelize.Op; const Op = Sequelize.Op;
function assertGlobalAccess(currentUser) {
if (!currentUser?.app_role?.globalAccess) {
throw new ForbiddenError('auth.forbidden');
}
}
module.exports = class PermissionsDBApi { module.exports = class PermissionsDBApi {
@ -16,6 +22,7 @@ module.exports = class PermissionsDBApi {
static async create(data, options) { static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null }; const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
assertGlobalAccess(currentUser);
const permissions = await db.permissions.create( const permissions = await db.permissions.create(
{ {
@ -46,6 +53,7 @@ module.exports = class PermissionsDBApi {
static async bulkImport(data, options) { static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null }; const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
assertGlobalAccess(currentUser);
// Prepare data - wrapping individual data transformations in a map() method // Prepare data - wrapping individual data transformations in a map() method
const permissionsData = data.map((item, index) => ({ const permissionsData = data.map((item, index) => ({
@ -74,7 +82,7 @@ module.exports = class PermissionsDBApi {
static async update(id, data, options) { static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess; assertGlobalAccess(currentUser);
const permissions = await db.permissions.findByPk(id, {}, {transaction}); const permissions = await db.permissions.findByPk(id, {}, {transaction});
@ -104,6 +112,7 @@ module.exports = class PermissionsDBApi {
static async deleteByIds(ids, options) { static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null }; const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
assertGlobalAccess(currentUser);
const permissions = await db.permissions.findAll({ const permissions = await db.permissions.findAll({
where: { where: {
@ -133,6 +142,7 @@ module.exports = class PermissionsDBApi {
static async remove(id, options) { static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
assertGlobalAccess(currentUser);
const permissions = await db.permissions.findByPk(id, options); const permissions = await db.permissions.findByPk(id, options);
@ -200,18 +210,12 @@ module.exports = class PermissionsDBApi {
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [

View File

@ -1,8 +1,7 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const { applySchoolScope, applySchoolScopeById, assertRecordInCurrentSchool, resolveSchoolIdForMutation } = require('./schoolScope');
@ -54,7 +53,7 @@ module.exports = class ProductsDBApi {
); );
await products.setSchool( data.school || null, { await products.setSchool(resolveSchoolIdForMutation(data.school, currentUser), {
transaction, transaction,
}); });
@ -103,6 +102,7 @@ module.exports = class ProductsDBApi {
importHash: item.importHash || null, importHash: item.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
schoolId: resolveSchoolIdForMutation(item.school, currentUser),
createdAt: new Date(Date.now() + index * 1000), createdAt: new Date(Date.now() + index * 1000),
})); }));
@ -118,9 +118,8 @@ module.exports = class ProductsDBApi {
static async update(id, data, options) { static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const products = await db.products.findByPk(id, {}, {transaction}); const products = await db.products.findByPk(id, {}, {transaction});
assertRecordInCurrentSchool(products, currentUser, 'schoolId');
@ -151,7 +150,7 @@ module.exports = class ProductsDBApi {
if (data.school !== undefined) { if (data.school !== undefined) {
await products.setSchool( await products.setSchool(
data.school, resolveSchoolIdForMutation(data.school, currentUser),
{ transaction } { transaction }
); );
@ -179,6 +178,8 @@ module.exports = class ProductsDBApi {
transaction, transaction,
}); });
products.forEach((record) => assertRecordInCurrentSchool(record, currentUser, 'schoolId'));
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of products) { for (const record of products) {
await record.update( await record.update(
@ -200,6 +201,7 @@ module.exports = class ProductsDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const products = await db.products.findByPk(id, options); const products = await db.products.findByPk(id, options);
assertRecordInCurrentSchool(products, currentUser, 'schoolId');
await products.update({ await products.update({
deletedBy: currentUser.id deletedBy: currentUser.id
@ -216,6 +218,9 @@ module.exports = class ProductsDBApi {
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const currentUser = (options && options.currentUser) || null;
applySchoolScope(where, currentUser?.app_role?.globalAccess, currentUser, 'schoolId');
const products = await db.products.findOne( const products = await db.products.findOne(
{ where }, { where },
@ -268,26 +273,11 @@ module.exports = class ProductsDBApi {
let offset = 0; let offset = 0;
let where = {}; let where = {};
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null; const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
if (userSchools) {
if (options?.currentUser?.schoolsId) {
where.schoolsId = options.currentUser.schoolsId;
}
}
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [
{ {
@ -456,11 +446,7 @@ module.exports = class ProductsDBApi {
} }
applySchoolScope(where, globalAccess, user, 'schoolId');
if (globalAccess) {
delete where.schoolsId;
}
const queryOptions = { const queryOptions = {
where, where,
@ -491,15 +477,10 @@ module.exports = class ProductsDBApi {
} }
} }
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId,) { static async findAllAutocomplete(query, limit, offset, globalAccess, schoolId,) {
let where = {}; let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) { if (query) {
where = { where = {
[Op.or]: [ [Op.or]: [
@ -513,6 +494,8 @@ module.exports = class ProductsDBApi {
}; };
} }
applySchoolScopeById(where, globalAccess, schoolId, 'schoolId');
const records = await db.products.findAll({ const records = await db.products.findAll({
attributes: [ 'id', 'nome' ], attributes: [ 'id', 'nome' ],
where, where,

View File

@ -1,8 +1,7 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const ForbiddenError = require('../../services/notifications/errors/forbidden');
const config = require('../../config'); const config = require('../../config');
@ -11,6 +10,13 @@ const config = require('../../config');
const Sequelize = db.Sequelize; const Sequelize = db.Sequelize;
const Op = Sequelize.Op; const Op = Sequelize.Op;
function assertGlobalAccess(currentUser) {
if (!currentUser?.app_role?.globalAccess) {
throw new ForbiddenError('auth.forbidden');
}
}
module.exports = class RolesDBApi { module.exports = class RolesDBApi {
@ -18,6 +24,7 @@ module.exports = class RolesDBApi {
static async create(data, options) { static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null }; const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
assertGlobalAccess(currentUser);
const roles = await db.roles.create( const roles = await db.roles.create(
{ {
@ -63,6 +70,7 @@ module.exports = class RolesDBApi {
static async bulkImport(data, options) { static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null }; const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
assertGlobalAccess(currentUser);
// Prepare data - wrapping individual data transformations in a map() method // Prepare data - wrapping individual data transformations in a map() method
const rolesData = data.map((item, index) => ({ const rolesData = data.map((item, index) => ({
@ -102,7 +110,7 @@ module.exports = class RolesDBApi {
static async update(id, data, options) { static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess; assertGlobalAccess(currentUser);
const roles = await db.roles.findByPk(id, {}, {transaction}); const roles = await db.roles.findByPk(id, {}, {transaction});
@ -142,6 +150,7 @@ module.exports = class RolesDBApi {
static async deleteByIds(ids, options) { static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null }; const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
assertGlobalAccess(currentUser);
const roles = await db.roles.findAll({ const roles = await db.roles.findAll({
where: { where: {
@ -171,6 +180,7 @@ module.exports = class RolesDBApi {
static async remove(id, options) { static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
assertGlobalAccess(currentUser);
const roles = await db.roles.findByPk(id, options); const roles = await db.roles.findByPk(id, options);
@ -247,18 +257,12 @@ module.exports = class RolesDBApi {
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [

View File

@ -0,0 +1,103 @@
const ForbiddenError = require('../../services/notifications/errors/forbidden');
const db = require('../models');
function getCurrentUserSchoolId(currentUser) {
return currentUser?.schoolsId || currentUser?.schools?.id || null;
}
function applySchoolScopeById(where, globalAccess, schoolId, field = 'schoolId') {
if (globalAccess) {
return where;
}
if (!schoolId) {
where.id = null;
return where;
}
if (field === 'id' && where.id && String(where.id) !== String(schoolId)) {
where.id = null;
return where;
}
where[field] = schoolId;
return where;
}
function applySchoolScope(where, globalAccess, currentUser, field = 'schoolId') {
if (!currentUser || globalAccess) {
return where;
}
return applySchoolScopeById(where, false, getCurrentUserSchoolId(currentUser), field);
}
function resolveSchoolIdForMutation(inputSchoolId, currentUser) {
if (!currentUser || currentUser?.app_role?.globalAccess) {
return inputSchoolId || null;
}
const schoolId = getCurrentUserSchoolId(currentUser);
if (!schoolId) {
throw new ForbiddenError('auth.forbidden');
}
return schoolId;
}
function assertRecordInCurrentSchool(record, currentUser, field = 'schoolId') {
if (!record || !currentUser || currentUser?.app_role?.globalAccess) {
return;
}
const schoolId = getCurrentUserSchoolId(currentUser);
const recordSchoolId = field === 'id' ? record.id : record[field];
if (!schoolId || String(recordSchoolId || '') !== String(schoolId)) {
throw new ForbiddenError('auth.forbidden');
}
}
async function assertRelatedRecordInCurrentSchool(modelName, id, currentUser, field = 'schoolId', options = {}) {
if (!id || !currentUser || currentUser?.app_role?.globalAccess) {
return;
}
const model = db[modelName];
if (!model) {
throw new Error(`School scope model not found: ${modelName}`);
}
if (field === 'id') {
const schoolId = getCurrentUserSchoolId(currentUser);
if (!schoolId || String(id) !== String(schoolId)) {
throw new ForbiddenError('auth.forbidden');
}
return;
}
if (!model.rawAttributes?.[field]) {
throw new Error(`School scope field ${field} not found on model ${modelName}`);
}
const record = await model.findByPk(id, {
attributes: ['id', field],
transaction: options.transaction,
});
if (!record) {
throw new ForbiddenError('auth.forbidden');
}
assertRecordInCurrentSchool(record, currentUser, field);
}
module.exports = {
applySchoolScope,
applySchoolScopeById,
assertRecordInCurrentSchool,
assertRelatedRecordInCurrentSchool,
resolveSchoolIdForMutation,
getCurrentUserSchoolId,
};

View File

@ -1,8 +1,7 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const { applySchoolScope, applySchoolScopeById, assertRecordInCurrentSchool } = require('./schoolScope');
@ -25,6 +24,14 @@ module.exports = class SchoolsDBApi {
|| ||
null null
, ,
nif: data.nif || null,
phone: data.phone || null,
email: data.email || null,
province: data.province || null,
municipality: data.municipality || null,
address: data.address || null,
logoUrl: data.logoUrl || null,
status: data.status || 'active',
importHash: data.importHash || null, importHash: data.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
@ -55,6 +62,14 @@ module.exports = class SchoolsDBApi {
|| ||
null null
, ,
nif: item.nif || null,
phone: item.phone || null,
email: item.email || null,
province: item.province || null,
municipality: item.municipality || null,
address: item.address || null,
logoUrl: item.logoUrl || null,
status: item.status || 'active',
importHash: item.importHash || null, importHash: item.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
@ -74,9 +89,8 @@ module.exports = class SchoolsDBApi {
static async update(id, data, options) { static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const schools = await db.schools.findByPk(id, {}, {transaction}); const schools = await db.schools.findByPk(id, {}, {transaction});
assertRecordInCurrentSchool(schools, currentUser, 'id');
@ -84,6 +98,14 @@ module.exports = class SchoolsDBApi {
const updatePayload = {}; const updatePayload = {};
if (data.name !== undefined) updatePayload.name = data.name; if (data.name !== undefined) updatePayload.name = data.name;
if (data.nif !== undefined) updatePayload.nif = data.nif;
if (data.phone !== undefined) updatePayload.phone = data.phone;
if (data.email !== undefined) updatePayload.email = data.email;
if (data.province !== undefined) updatePayload.province = data.province;
if (data.municipality !== undefined) updatePayload.municipality = data.municipality;
if (data.address !== undefined) updatePayload.address = data.address;
if (data.logoUrl !== undefined) updatePayload.logoUrl = data.logoUrl;
if (data.status !== undefined) updatePayload.status = data.status;
updatePayload.updatedById = currentUser.id; updatePayload.updatedById = currentUser.id;
@ -114,6 +136,8 @@ module.exports = class SchoolsDBApi {
transaction, transaction,
}); });
schools.forEach((record) => assertRecordInCurrentSchool(record, currentUser, 'id'));
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of schools) { for (const record of schools) {
await record.update( await record.update(
@ -135,6 +159,7 @@ module.exports = class SchoolsDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const schools = await db.schools.findByPk(id, options); const schools = await db.schools.findByPk(id, options);
assertRecordInCurrentSchool(schools, currentUser, 'id');
await schools.update({ await schools.update({
deletedBy: currentUser.id deletedBy: currentUser.id
@ -151,6 +176,9 @@ module.exports = class SchoolsDBApi {
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const currentUser = (options && options.currentUser) || null;
applySchoolScope(where, currentUser?.app_role?.globalAccess, currentUser, 'id');
const schools = await db.schools.findOne( const schools = await db.schools.findOne(
{ where }, { where },
@ -270,26 +298,11 @@ module.exports = class SchoolsDBApi {
let offset = 0; let offset = 0;
let where = {}; let where = {};
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null; const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
if (userSchools) {
if (options?.currentUser?.schoolsId) {
where.schoolsId = options.currentUser.schoolsId;
}
}
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [
@ -316,6 +329,24 @@ module.exports = class SchoolsDBApi {
}; };
} }
if (filter.status) {
where = {
...where,
status: filter.status,
};
}
if (filter.province) {
where = {
...where,
[Op.and]: Utils.ilike(
'schools',
'province',
filter.province,
),
};
}
@ -360,11 +391,7 @@ module.exports = class SchoolsDBApi {
} }
applySchoolScope(where, globalAccess, user, 'id');
if (globalAccess) {
delete where.schoolsId;
}
const queryOptions = { const queryOptions = {
where, where,
@ -395,15 +422,10 @@ module.exports = class SchoolsDBApi {
} }
} }
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId,) { static async findAllAutocomplete(query, limit, offset, globalAccess, schoolId,) {
let where = {}; let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) { if (query) {
where = { where = {
[Op.or]: [ [Op.or]: [
@ -417,6 +439,8 @@ module.exports = class SchoolsDBApi {
}; };
} }
applySchoolScopeById(where, globalAccess, schoolId, 'id');
const records = await db.schools.findAll({ const records = await db.schools.findAll({
attributes: [ 'id', 'name' ], attributes: [ 'id', 'name' ],
where, where,

View File

@ -1,8 +1,7 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const { applySchoolScope, applySchoolScopeById, assertRecordInCurrentSchool, assertRelatedRecordInCurrentSchool, resolveSchoolIdForMutation } = require('./schoolScope');
@ -40,15 +39,17 @@ module.exports = class Student_guardiansDBApi {
); );
await assertRelatedRecordInCurrentSchool('students', data.student, currentUser, 'schoolId', { transaction });
await student_guardians.setStudent( data.student || null, { await student_guardians.setStudent( data.student || null, {
transaction, transaction,
}); });
await assertRelatedRecordInCurrentSchool('guardians', data.guardian, currentUser, 'schoolId', { transaction });
await student_guardians.setGuardian( data.guardian || null, { await student_guardians.setGuardian( data.guardian || null, {
transaction, transaction,
}); });
await student_guardians.setSchools( data.schools || null, { await student_guardians.setSchools(resolveSchoolIdForMutation(data.schools, currentUser), {
transaction, transaction,
}); });
@ -83,6 +84,7 @@ module.exports = class Student_guardiansDBApi {
importHash: item.importHash || null, importHash: item.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
schoolsId: resolveSchoolIdForMutation(item.schools, currentUser),
createdAt: new Date(Date.now() + index * 1000), createdAt: new Date(Date.now() + index * 1000),
})); }));
@ -98,9 +100,8 @@ module.exports = class Student_guardiansDBApi {
static async update(id, data, options) { static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const student_guardians = await db.student_guardians.findByPk(id, {}, {transaction}); const student_guardians = await db.student_guardians.findByPk(id, {}, {transaction});
assertRecordInCurrentSchool(student_guardians, currentUser, 'schoolsId');
@ -120,6 +121,7 @@ module.exports = class Student_guardiansDBApi {
if (data.student !== undefined) { if (data.student !== undefined) {
await assertRelatedRecordInCurrentSchool('students', data.student, currentUser, 'schoolId', { transaction });
await student_guardians.setStudent( await student_guardians.setStudent(
data.student, data.student,
@ -129,6 +131,7 @@ module.exports = class Student_guardiansDBApi {
} }
if (data.guardian !== undefined) { if (data.guardian !== undefined) {
await assertRelatedRecordInCurrentSchool('guardians', data.guardian, currentUser, 'schoolId', { transaction });
await student_guardians.setGuardian( await student_guardians.setGuardian(
data.guardian, data.guardian,
@ -140,7 +143,7 @@ module.exports = class Student_guardiansDBApi {
if (data.schools !== undefined) { if (data.schools !== undefined) {
await student_guardians.setSchools( await student_guardians.setSchools(
data.schools, resolveSchoolIdForMutation(data.schools, currentUser),
{ transaction } { transaction }
); );
@ -168,6 +171,8 @@ module.exports = class Student_guardiansDBApi {
transaction, transaction,
}); });
student_guardians.forEach((record) => assertRecordInCurrentSchool(record, currentUser, 'schoolsId'));
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of student_guardians) { for (const record of student_guardians) {
await record.update( await record.update(
@ -189,6 +194,7 @@ module.exports = class Student_guardiansDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const student_guardians = await db.student_guardians.findByPk(id, options); const student_guardians = await db.student_guardians.findByPk(id, options);
assertRecordInCurrentSchool(student_guardians, currentUser, 'schoolsId');
await student_guardians.update({ await student_guardians.update({
deletedBy: currentUser.id deletedBy: currentUser.id
@ -205,6 +211,9 @@ module.exports = class Student_guardiansDBApi {
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const currentUser = (options && options.currentUser) || null;
applySchoolScope(where, currentUser?.app_role?.globalAccess, currentUser, 'schoolsId');
const student_guardians = await db.student_guardians.findOne( const student_guardians = await db.student_guardians.findOne(
{ where }, { where },
@ -267,26 +276,11 @@ module.exports = class Student_guardiansDBApi {
let offset = 0; let offset = 0;
let where = {}; let where = {};
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null; const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
if (userSchools) {
if (options?.currentUser?.schoolsId) {
where.schoolsId = options.currentUser.schoolsId;
}
}
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [
{ {
@ -417,11 +411,7 @@ module.exports = class Student_guardiansDBApi {
} }
applySchoolScope(where, globalAccess, user, 'schoolsId');
if (globalAccess) {
delete where.schoolsId;
}
const queryOptions = { const queryOptions = {
where, where,
@ -452,15 +442,10 @@ module.exports = class Student_guardiansDBApi {
} }
} }
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId,) { static async findAllAutocomplete(query, limit, offset, globalAccess, schoolId,) {
let where = {}; let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) { if (query) {
where = { where = {
[Op.or]: [ [Op.or]: [
@ -474,6 +459,8 @@ module.exports = class Student_guardiansDBApi {
}; };
} }
applySchoolScopeById(where, globalAccess, schoolId, 'schoolsId');
const records = await db.student_guardians.findAll({ const records = await db.student_guardians.findAll({
attributes: [ 'id', 'tipo_responsabilidade' ], attributes: [ 'id', 'tipo_responsabilidade' ],
where, where,

View File

@ -1,8 +1,8 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file'); const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const { applySchoolScope, applySchoolScopeById, assertRecordInCurrentSchool, resolveSchoolIdForMutation } = require('./schoolScope');
@ -74,7 +74,7 @@ module.exports = class StudentsDBApi {
); );
await students.setSchool( data.school || null, { await students.setSchool(resolveSchoolIdForMutation(data.school, currentUser), {
transaction, transaction,
}); });
@ -153,6 +153,7 @@ module.exports = class StudentsDBApi {
importHash: item.importHash || null, importHash: item.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
schoolId: resolveSchoolIdForMutation(item.school, currentUser),
createdAt: new Date(Date.now() + index * 1000), createdAt: new Date(Date.now() + index * 1000),
})); }));
@ -180,9 +181,8 @@ module.exports = class StudentsDBApi {
static async update(id, data, options) { static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const students = await db.students.findByPk(id, {}, {transaction}); const students = await db.students.findByPk(id, {}, {transaction});
assertRecordInCurrentSchool(students, currentUser, 'schoolId');
@ -225,7 +225,7 @@ module.exports = class StudentsDBApi {
if (data.school !== undefined) { if (data.school !== undefined) {
await students.setSchool( await students.setSchool(
data.school, resolveSchoolIdForMutation(data.school, currentUser),
{ transaction } { transaction }
); );
@ -236,6 +236,7 @@ module.exports = class StudentsDBApi {
if (data.foto !== undefined) {
await FileDBApi.replaceRelationFiles( await FileDBApi.replaceRelationFiles(
{ {
belongsTo: db.students.getTableName(), belongsTo: db.students.getTableName(),
@ -245,6 +246,7 @@ module.exports = class StudentsDBApi {
data.foto, data.foto,
options, options,
); );
}
return students; return students;
@ -263,6 +265,8 @@ module.exports = class StudentsDBApi {
transaction, transaction,
}); });
students.forEach((record) => assertRecordInCurrentSchool(record, currentUser, 'schoolId'));
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of students) { for (const record of students) {
await record.update( await record.update(
@ -284,6 +288,7 @@ module.exports = class StudentsDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const students = await db.students.findByPk(id, options); const students = await db.students.findByPk(id, options);
assertRecordInCurrentSchool(students, currentUser, 'schoolId');
await students.update({ await students.update({
deletedBy: currentUser.id deletedBy: currentUser.id
@ -300,6 +305,9 @@ module.exports = class StudentsDBApi {
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const currentUser = (options && options.currentUser) || null;
applySchoolScope(where, currentUser?.app_role?.globalAccess, currentUser, 'schoolId');
const students = await db.students.findOne( const students = await db.students.findOne(
{ where }, { where },
@ -381,26 +389,11 @@ module.exports = class StudentsDBApi {
let offset = 0; let offset = 0;
let where = {}; let where = {};
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null; const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
if (userSchools) {
if (options?.currentUser?.schoolsId) {
where.schoolsId = options.currentUser.schoolsId;
}
}
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [
{ {
@ -588,11 +581,7 @@ module.exports = class StudentsDBApi {
} }
applySchoolScope(where, globalAccess, user, 'schoolId');
if (globalAccess) {
delete where.schoolsId;
}
const queryOptions = { const queryOptions = {
where, where,
@ -623,15 +612,10 @@ module.exports = class StudentsDBApi {
} }
} }
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId,) { static async findAllAutocomplete(query, limit, offset, globalAccess, schoolId,) {
let where = {}; let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) { if (query) {
where = { where = {
[Op.or]: [ [Op.or]: [
@ -645,6 +629,8 @@ module.exports = class StudentsDBApi {
}; };
} }
applySchoolScopeById(where, globalAccess, schoolId, 'schoolId');
const records = await db.students.findAll({ const records = await db.students.findAll({
attributes: [ 'id', 'nome_completo' ], attributes: [ 'id', 'nome_completo' ],
where, where,

View File

@ -1,8 +1,7 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const { applySchoolScope, applySchoolScopeById, assertRecordInCurrentSchool, resolveSchoolIdForMutation } = require('./schoolScope');
@ -39,7 +38,7 @@ module.exports = class SubjectsDBApi {
); );
await subjects.setSchool( data.school || null, { await subjects.setSchool(resolveSchoolIdForMutation(data.school, currentUser), {
transaction, transaction,
}); });
@ -73,6 +72,7 @@ module.exports = class SubjectsDBApi {
importHash: item.importHash || null, importHash: item.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
schoolId: resolveSchoolIdForMutation(item.school, currentUser),
createdAt: new Date(Date.now() + index * 1000), createdAt: new Date(Date.now() + index * 1000),
})); }));
@ -88,9 +88,8 @@ module.exports = class SubjectsDBApi {
static async update(id, data, options) { static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const subjects = await db.subjects.findByPk(id, {}, {transaction}); const subjects = await db.subjects.findByPk(id, {}, {transaction});
assertRecordInCurrentSchool(subjects, currentUser, 'schoolId');
@ -112,7 +111,7 @@ module.exports = class SubjectsDBApi {
if (data.school !== undefined) { if (data.school !== undefined) {
await subjects.setSchool( await subjects.setSchool(
data.school, resolveSchoolIdForMutation(data.school, currentUser),
{ transaction } { transaction }
); );
@ -140,6 +139,8 @@ module.exports = class SubjectsDBApi {
transaction, transaction,
}); });
subjects.forEach((record) => assertRecordInCurrentSchool(record, currentUser, 'schoolId'));
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of subjects) { for (const record of subjects) {
await record.update( await record.update(
@ -161,6 +162,7 @@ module.exports = class SubjectsDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const subjects = await db.subjects.findByPk(id, options); const subjects = await db.subjects.findByPk(id, options);
assertRecordInCurrentSchool(subjects, currentUser, 'schoolId');
await subjects.update({ await subjects.update({
deletedBy: currentUser.id deletedBy: currentUser.id
@ -177,6 +179,9 @@ module.exports = class SubjectsDBApi {
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const currentUser = (options && options.currentUser) || null;
applySchoolScope(where, currentUser?.app_role?.globalAccess, currentUser, 'schoolId');
const subjects = await db.subjects.findOne( const subjects = await db.subjects.findOne(
{ where }, { where },
@ -233,26 +238,11 @@ module.exports = class SubjectsDBApi {
let offset = 0; let offset = 0;
let where = {}; let where = {};
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null; const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
if (userSchools) {
if (options?.currentUser?.schoolsId) {
where.schoolsId = options.currentUser.schoolsId;
}
}
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [
{ {
@ -349,11 +339,7 @@ module.exports = class SubjectsDBApi {
} }
applySchoolScope(where, globalAccess, user, 'schoolId');
if (globalAccess) {
delete where.schoolsId;
}
const queryOptions = { const queryOptions = {
where, where,
@ -384,15 +370,10 @@ module.exports = class SubjectsDBApi {
} }
} }
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId,) { static async findAllAutocomplete(query, limit, offset, globalAccess, schoolId,) {
let where = {}; let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) { if (query) {
where = { where = {
[Op.or]: [ [Op.or]: [
@ -406,6 +387,8 @@ module.exports = class SubjectsDBApi {
}; };
} }
applySchoolScopeById(where, globalAccess, schoolId, 'schoolId');
const records = await db.subjects.findAll({ const records = await db.subjects.findAll({
attributes: [ 'id', 'nome' ], attributes: [ 'id', 'nome' ],
where, where,

View File

@ -1,8 +1,7 @@
const db = require('../models'); const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const { applySchoolScope, applySchoolScopeById, assertRecordInCurrentSchool, resolveSchoolIdForMutation } = require('./schoolScope');
@ -54,7 +53,7 @@ module.exports = class TeachersDBApi {
); );
await teachers.setSchool( data.school || null, { await teachers.setSchool(resolveSchoolIdForMutation(data.school, currentUser), {
transaction, transaction,
}); });
@ -103,6 +102,7 @@ module.exports = class TeachersDBApi {
importHash: item.importHash || null, importHash: item.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
schoolId: resolveSchoolIdForMutation(item.school, currentUser),
createdAt: new Date(Date.now() + index * 1000), createdAt: new Date(Date.now() + index * 1000),
})); }));
@ -118,9 +118,8 @@ module.exports = class TeachersDBApi {
static async update(id, data, options) { static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const teachers = await db.teachers.findByPk(id, {}, {transaction}); const teachers = await db.teachers.findByPk(id, {}, {transaction});
assertRecordInCurrentSchool(teachers, currentUser, 'schoolId');
@ -151,7 +150,7 @@ module.exports = class TeachersDBApi {
if (data.school !== undefined) { if (data.school !== undefined) {
await teachers.setSchool( await teachers.setSchool(
data.school, resolveSchoolIdForMutation(data.school, currentUser),
{ transaction } { transaction }
); );
@ -179,6 +178,8 @@ module.exports = class TeachersDBApi {
transaction, transaction,
}); });
teachers.forEach((record) => assertRecordInCurrentSchool(record, currentUser, 'schoolId'));
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of teachers) { for (const record of teachers) {
await record.update( await record.update(
@ -200,6 +201,7 @@ module.exports = class TeachersDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const teachers = await db.teachers.findByPk(id, options); const teachers = await db.teachers.findByPk(id, options);
assertRecordInCurrentSchool(teachers, currentUser, 'schoolId');
await teachers.update({ await teachers.update({
deletedBy: currentUser.id deletedBy: currentUser.id
@ -216,6 +218,9 @@ module.exports = class TeachersDBApi {
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const currentUser = (options && options.currentUser) || null;
applySchoolScope(where, currentUser?.app_role?.globalAccess, currentUser, 'schoolId');
const teachers = await db.teachers.findOne( const teachers = await db.teachers.findOne(
{ where }, { where },
@ -272,26 +277,11 @@ module.exports = class TeachersDBApi {
let offset = 0; let offset = 0;
let where = {}; let where = {};
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null; const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
if (userSchools) {
if (options?.currentUser?.schoolsId) {
where.schoolsId = options.currentUser.schoolsId;
}
}
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [
{ {
@ -421,11 +411,7 @@ module.exports = class TeachersDBApi {
} }
applySchoolScope(where, globalAccess, user, 'schoolId');
if (globalAccess) {
delete where.schoolsId;
}
const queryOptions = { const queryOptions = {
where, where,
@ -456,15 +442,10 @@ module.exports = class TeachersDBApi {
} }
} }
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId,) { static async findAllAutocomplete(query, limit, offset, globalAccess, schoolId,) {
let where = {}; let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) { if (query) {
where = { where = {
[Op.or]: [ [Op.or]: [
@ -478,6 +459,8 @@ module.exports = class TeachersDBApi {
}; };
} }
applySchoolScopeById(where, globalAccess, schoolId, 'schoolId');
const records = await db.teachers.findAll({ const records = await db.teachers.findAll({
attributes: [ 'id', 'nome' ], attributes: [ 'id', 'nome' ],
where, where,

View File

@ -3,6 +3,8 @@ const db = require('../models');
const FileDBApi = require('./file'); const FileDBApi = require('./file');
const crypto = require('crypto'); const crypto = require('crypto');
const Utils = require('../utils'); const Utils = require('../utils');
const ForbiddenError = require('../../services/notifications/errors/forbidden');
const { applySchoolScope, applySchoolScopeById, assertRecordInCurrentSchool, resolveSchoolIdForMutation } = require('./schoolScope');
const bcrypt = require('bcrypt'); const bcrypt = require('bcrypt');
const config = require('../../config'); const config = require('../../config');
@ -12,12 +14,71 @@ const config = require('../../config');
const Sequelize = db.Sequelize; const Sequelize = db.Sequelize;
const Op = Sequelize.Op; const Op = Sequelize.Op;
async function assertAssignableRole(roleId, currentUser, transaction) {
if (!roleId || currentUser?.app_role?.globalAccess) {
return;
}
const role = await db.roles.findByPk(roleId, { transaction });
if (!role || role.globalAccess) {
throw new ForbiddenError('auth.forbidden');
}
}
function assertSafeSelfUpdate(id, data, currentUser) {
if (
!currentUser?.id ||
String(currentUser.id) !== String(id) ||
currentUser?.app_role?.globalAccess
) {
return;
}
const restrictedFields = [
'app_role',
'custom_permissions',
'schools',
'disabled',
'email',
'emailVerified',
'emailVerificationToken',
'emailVerificationTokenExpiresAt',
'passwordResetToken',
'passwordResetTokenExpiresAt',
'provider',
];
const hasRestrictedField = restrictedFields.some((field) => (
Object.prototype.hasOwnProperty.call(data || {}, field) && data[field] !== undefined
));
if (hasRestrictedField) {
throw new ForbiddenError('auth.forbidden');
}
}
function assertCanAssignCustomPermissions(customPermissions, currentUser) {
if (currentUser?.app_role?.globalAccess || customPermissions === undefined || customPermissions === null) {
return;
}
if (Array.isArray(customPermissions) && customPermissions.length === 0) {
return;
}
throw new ForbiddenError('auth.forbidden');
}
module.exports = class UsersDBApi { module.exports = class UsersDBApi {
static async create(data,globalAccess, options) { static async create(data,globalAccess, options) {
const currentUser = (options && options.currentUser) || { id: null }; const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
await assertAssignableRole(data.data.app_role, currentUser, transaction);
assertCanAssignCustomPermissions(data.data.custom_permissions, currentUser);
const users = await db.users.create( const users = await db.users.create(
{ {
@ -112,7 +173,7 @@ module.exports = class UsersDBApi {
await users.setSchools( data.data.schools || null, { await users.setSchools(resolveSchoolIdForMutation(data.data.schools, currentUser), {
transaction, transaction,
}); });
@ -215,6 +276,7 @@ module.exports = class UsersDBApi {
importHash: item.importHash || null, importHash: item.importHash || null,
createdById: currentUser.id, createdById: currentUser.id,
updatedById: currentUser.id, updatedById: currentUser.id,
schoolsId: resolveSchoolIdForMutation(item.schools, currentUser),
createdAt: new Date(Date.now() + index * 1000), createdAt: new Date(Date.now() + index * 1000),
})); }));
@ -242,10 +304,20 @@ module.exports = class UsersDBApi {
static async update(id, data, globalAccess, options) { static async update(id, data, globalAccess, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
assertSafeSelfUpdate(id, data, currentUser);
const appRoleProvided = Object.prototype.hasOwnProperty.call(data || {}, 'app_role');
const customPermissionsProvided = Object.prototype.hasOwnProperty.call(data || {}, 'custom_permissions');
const users = await db.users.findByPk(id, {}, {transaction}); const users = await db.users.findByPk(id, {}, {transaction});
assertRecordInCurrentSchool(users, currentUser, 'schoolsId');
if (appRoleProvided) {
await assertAssignableRole(data.app_role, currentUser, transaction);
}
if (customPermissionsProvided) {
assertCanAssignCustomPermissions(data.custom_permissions, currentUser);
}
@ -324,7 +396,7 @@ module.exports = class UsersDBApi {
if (data.schools !== undefined) { if (data.schools !== undefined) {
await users.setSchools( await users.setSchools(
data.schools, resolveSchoolIdForMutation(data.schools, currentUser),
{ transaction } { transaction }
); );
@ -339,6 +411,7 @@ module.exports = class UsersDBApi {
if (data.avatar !== undefined) {
await FileDBApi.replaceRelationFiles( await FileDBApi.replaceRelationFiles(
{ {
belongsTo: db.users.getTableName(), belongsTo: db.users.getTableName(),
@ -348,6 +421,7 @@ module.exports = class UsersDBApi {
data.avatar, data.avatar,
options, options,
); );
}
return users; return users;
@ -366,6 +440,8 @@ module.exports = class UsersDBApi {
transaction, transaction,
}); });
users.forEach((record) => assertRecordInCurrentSchool(record, currentUser, 'schoolsId'));
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of users) { for (const record of users) {
await record.update( await record.update(
@ -387,6 +463,7 @@ module.exports = class UsersDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const users = await db.users.findByPk(id, options); const users = await db.users.findByPk(id, options);
assertRecordInCurrentSchool(users, currentUser, 'schoolsId');
await users.update({ await users.update({
deletedBy: currentUser.id deletedBy: currentUser.id
@ -403,6 +480,9 @@ module.exports = class UsersDBApi {
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const currentUser = (options && options.currentUser) || null;
applySchoolScope(where, currentUser?.app_role?.globalAccess, currentUser, 'schoolsId');
const users = await db.users.findOne( const users = await db.users.findOne(
{ where }, { where },
@ -476,26 +556,11 @@ module.exports = class UsersDBApi {
let offset = 0; let offset = 0;
let where = {}; let where = {};
const currentPage = +filter.page; const currentPage = +filter.page;
const user = (options && options.currentUser) || null; const user = (options && options.currentUser) || null;
const userSchools = (user && user.schools?.id) || null;
if (userSchools) {
if (options?.currentUser?.schoolsId) {
where.schoolsId = options.currentUser.schoolsId;
}
}
offset = currentPage * limit; offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [ let include = [
{ {
@ -777,11 +842,7 @@ module.exports = class UsersDBApi {
} }
applySchoolScope(where, globalAccess, user, 'schoolsId');
if (globalAccess) {
delete where.schoolsId;
}
const queryOptions = { const queryOptions = {
where, where,
@ -812,15 +873,10 @@ module.exports = class UsersDBApi {
} }
} }
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId,) { static async findAllAutocomplete(query, limit, offset, globalAccess, schoolId,) {
let where = {}; let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) { if (query) {
where = { where = {
[Op.or]: [ [Op.or]: [
@ -834,6 +890,8 @@ module.exports = class UsersDBApi {
}; };
} }
applySchoolScopeById(where, globalAccess, schoolId, 'schoolsId');
const records = await db.users.findAll({ const records = await db.users.findAll({
attributes: [ 'id', 'firstName' ], attributes: [ 'id', 'firstName' ],
where, where,
@ -858,7 +916,7 @@ module.exports = class UsersDBApi {
authenticationUid: data.authenticationUid, authenticationUid: data.authenticationUid,
password: data.password, password: data.password,
organizationId: data.organizationId, schoolsId: data.organizationId,
}, },
{ transaction }, { transaction },

View File

@ -2777,7 +2777,7 @@ module.exports = {
* @param {Sequelize} Sequelize * @param {Sequelize} Sequelize
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async down(queryInterface, Sequelize) { async down(queryInterface) {
/** /**
* @type {Transaction} * @type {Transaction}
*/ */

View File

@ -0,0 +1,50 @@
module.exports = {
async up(queryInterface, Sequelize) {
const transaction = await queryInterface.sequelize.transaction();
try {
await queryInterface.addColumn('schools', 'nif', { type: Sequelize.DataTypes.TEXT }, { transaction });
await queryInterface.addColumn('schools', 'phone', { type: Sequelize.DataTypes.TEXT }, { transaction });
await queryInterface.addColumn('schools', 'email', { type: Sequelize.DataTypes.TEXT }, { transaction });
await queryInterface.addColumn('schools', 'province', { type: Sequelize.DataTypes.TEXT }, { transaction });
await queryInterface.addColumn('schools', 'municipality', { type: Sequelize.DataTypes.TEXT }, { transaction });
await queryInterface.addColumn('schools', 'address', { type: Sequelize.DataTypes.TEXT }, { transaction });
await queryInterface.addColumn('schools', 'logoUrl', { type: Sequelize.DataTypes.TEXT }, { transaction });
await queryInterface.addColumn(
'schools',
'status',
{
type: Sequelize.DataTypes.TEXT,
allowNull: false,
defaultValue: 'active',
},
{ transaction },
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
},
async down(queryInterface) {
const transaction = await queryInterface.sequelize.transaction();
try {
await queryInterface.removeColumn('schools', 'status', { transaction });
await queryInterface.removeColumn('schools', 'logoUrl', { transaction });
await queryInterface.removeColumn('schools', 'address', { transaction });
await queryInterface.removeColumn('schools', 'municipality', { transaction });
await queryInterface.removeColumn('schools', 'province', { transaction });
await queryInterface.removeColumn('schools', 'email', { transaction });
await queryInterface.removeColumn('schools', 'phone', { transaction });
await queryInterface.removeColumn('schools', 'nif', { transaction });
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
},
};

View File

@ -1,8 +1,3 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const assessments = sequelize.define( const assessments = sequelize.define(

View File

@ -1,8 +1,3 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const attendance = sequelize.define( const attendance = sequelize.define(

View File

@ -1,8 +1,3 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const book_loans = sequelize.define( const book_loans = sequelize.define(

View File

@ -1,8 +1,3 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const books = sequelize.define( const books = sequelize.define(

View File

@ -1,8 +1,3 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const classes = sequelize.define( const classes = sequelize.define(

View File

@ -1,8 +1,3 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const courses = sequelize.define( const courses = sequelize.define(

View File

@ -1,8 +1,3 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const employees = sequelize.define( const employees = sequelize.define(

View File

@ -1,8 +1,3 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const enrollments = sequelize.define( const enrollments = sequelize.define(

View File

@ -1,8 +1,3 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const grades = sequelize.define( const grades = sequelize.define(

View File

@ -1,8 +1,3 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const guardians = sequelize.define( const guardians = sequelize.define(

View File

@ -1,8 +1,3 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const invoices = sequelize.define( const invoices = sequelize.define(

View File

@ -1,8 +1,3 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const payments = sequelize.define( const payments = sequelize.define(

View File

@ -1,8 +1,3 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const permissions = sequelize.define( const permissions = sequelize.define(

View File

@ -1,8 +1,3 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const products = sequelize.define( const products = sequelize.define(

View File

@ -1,8 +1,3 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const roles = sequelize.define( const roles = sequelize.define(

View File

@ -1,9 +1,3 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const schools = sequelize.define( const schools = sequelize.define(
'schools', 'schools',
@ -19,6 +13,63 @@ name: {
},
nif: {
type: DataTypes.TEXT,
},
phone: {
type: DataTypes.TEXT,
},
email: {
type: DataTypes.TEXT,
},
province: {
type: DataTypes.TEXT,
},
municipality: {
type: DataTypes.TEXT,
},
address: {
type: DataTypes.TEXT,
},
logoUrl: {
type: DataTypes.TEXT,
},
status: {
type: DataTypes.TEXT,
allowNull: false,
defaultValue: 'active',
}, },
importHash: { importHash: {

View File

@ -1,8 +1,3 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const student_guardians = sequelize.define( const student_guardians = sequelize.define(

View File

@ -1,8 +1,3 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const students = sequelize.define( const students = sequelize.define(

View File

@ -1,8 +1,3 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const subjects = sequelize.define( const subjects = sequelize.define(

View File

@ -1,8 +1,3 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const teachers = sequelize.define( const teachers = sequelize.define(

View File

@ -2,7 +2,6 @@ const config = require('../../config');
const providers = config.providers; const providers = config.providers;
const crypto = require('crypto'); const crypto = require('crypto');
const bcrypt = require('bcrypt'); const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const users = sequelize.define( const users = sequelize.define(
@ -206,13 +205,13 @@ provider: {
}; };
users.beforeCreate((users, options) => { users.beforeCreate((user) => {
users = trimStringFields(users); trimStringFields(user);
if (users.provider !== providers.LOCAL && Object.values(providers).indexOf(users.provider) > -1) { if (user.provider !== providers.LOCAL && Object.values(providers).indexOf(user.provider) > -1) {
users.emailVerified = true; user.emailVerified = true;
if (!users.password) { if (!user.password) {
const password = crypto const password = crypto
.randomBytes(20) .randomBytes(20)
.toString('hex'); .toString('hex');
@ -222,13 +221,13 @@ provider: {
config.bcrypt.saltRounds, config.bcrypt.saltRounds,
); );
users.password = hashedPassword user.password = hashedPassword
} }
} }
}); });
users.beforeUpdate((users, options) => { users.beforeUpdate((user) => {
users = trimStringFields(users); trimStringFields(user);
}); });

View File

@ -10,7 +10,7 @@ const ids = [
] ]
module.exports = { module.exports = {
up: async (queryInterface, Sequelize) => { up: async (queryInterface) => {
let admin_hash = bcrypt.hashSync(config.admin_pass, config.bcrypt.saltRounds); let admin_hash = bcrypt.hashSync(config.admin_pass, config.bcrypt.saltRounds);
let user_hash = bcrypt.hashSync(config.user_pass, config.bcrypt.saltRounds); let user_hash = bcrypt.hashSync(config.user_pass, config.bcrypt.saltRounds);

View File

@ -63,7 +63,7 @@ module.exports = {
} }
const entities = [ const entities = [
"users","roles","permissions","schools","students","guardians","student_guardians","teachers","courses","grades","classes","subjects","enrollments","assessments","attendance","invoices","payments","employees","products","books","book_loans",, "users","roles","permissions","schools","students","guardians","student_guardians","teachers","courses","grades","classes","subjects","enrollments","assessments","attendance","invoices","payments","employees","products","books","book_loans",
]; ];
await queryInterface.bulkInsert("permissions", entities.flatMap(createPermissions)); await queryInterface.bulkInsert("permissions", entities.flatMap(createPermissions));
await queryInterface.bulkInsert("permissions", [{ id: getId(`READ_API_DOCS`), createdAt, updatedAt, name: `READ_API_DOCS` }]); await queryInterface.bulkInsert("permissions", [{ id: getId(`READ_API_DOCS`), createdAt, updatedAt, name: `READ_API_DOCS` }]);

View File

@ -4450,7 +4450,7 @@ const BookLoansData = [
module.exports = { module.exports = {
up: async (queryInterface, Sequelize) => { up: async () => {
@ -4954,7 +4954,7 @@ module.exports = {
}, },
down: async (queryInterface, Sequelize) => { down: async (queryInterface) => {

View File

@ -8,7 +8,7 @@ module.exports = class Helpers {
}; };
} }
static commonErrorHandler(error, req, res, next) { static commonErrorHandler(error, req, res) {
if ([400, 403, 404].includes(error.code)) { if ([400, 403, 404].includes(error.code)) {
return res.status(error.code).send(error.message); return res.status(error.code).send(error.message);
} }
@ -19,5 +19,5 @@ module.exports = class Helpers {
static jwtSign(data) { static jwtSign(data) {
return jwt.sign(data, config.secret_key, {expiresIn: '6h'}); return jwt.sign(data, config.secret_key, {expiresIn: '6h'});
}; }
}; };

View File

@ -6,7 +6,6 @@ const passport = require('passport');
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
const bodyParser = require('body-parser'); const bodyParser = require('body-parser');
const db = require('./db/models');
const config = require('./config'); const config = require('./config');
const swaggerUI = require('swagger-ui-express'); const swaggerUI = require('swagger-ui-express');
const swaggerJsDoc = require('swagger-jsdoc'); const swaggerJsDoc = require('swagger-jsdoc');

View File

@ -42,9 +42,12 @@ function checkPermissions(permission) {
return async (req, res, next) => { return async (req, res, next) => {
const { currentUser } = req; const { currentUser } = req;
// 1. Check self-access bypass (only if the user is authenticated) // 1. Check self-access bypass for reading the authenticated user's own record only.
if (currentUser && (currentUser.id === req.params.id || currentUser.id === req.body.id)) { const isSelfAccess = currentUser && (
return next(); // User has access to their own resource currentUser.id === req.params.id || currentUser.id === req.body?.id
);
if (isSelfAccess && permission === 'READ_USERS') {
return next();
} }
// 2. Check Custom Permissions (only if the user is authenticated) // 2. Check Custom Permissions (only if the user is authenticated)
@ -135,9 +138,10 @@ const METHOD_MAP = {
*/ */
function checkCrudPermissions(name) { function checkCrudPermissions(name) {
return (req, res, next) => { return (req, res, next) => {
// Dynamically determine the permission name (e.g., 'READ_USERS') const methodAction = req.path.replace(/\/$/, '') === '/deleteByIds'
const permissionName = `${METHOD_MAP[req.method]}_${name.toUpperCase()}`; ? 'DELETE'
// Call the checkPermissions middleware with the determined permission : METHOD_MAP[req.method];
const permissionName = `${methodAction}_${name.toUpperCase()}`;
checkPermissions(permissionName)(req, res, next); checkPermissions(permissionName)(req, res, next);
}; };
} }

View File

@ -3,10 +3,9 @@ const express = require('express');
const AssessmentsService = require('../services/assessments'); const AssessmentsService = require('../services/assessments');
const AssessmentsDBApi = require('../db/api/assessments'); const AssessmentsDBApi = require('../db/api/assessments');
const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -309,6 +308,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);
@ -384,14 +384,14 @@ router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess; const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id const schoolId = getCurrentUserSchoolId(req.currentUser);
const payload = await AssessmentsDBApi.findAllAutocomplete( const payload = await AssessmentsDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
globalAccess, organizationId, globalAccess, schoolId,
); );
res.status(200).send(payload); res.status(200).send(payload);
@ -432,6 +432,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await AssessmentsDBApi.findBy( const payload = await AssessmentsDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
{ currentUser: req.currentUser },
); );

View File

@ -3,10 +3,9 @@ const express = require('express');
const AttendanceService = require('../services/attendance'); const AttendanceService = require('../services/attendance');
const AttendanceDBApi = require('../db/api/attendance'); const AttendanceDBApi = require('../db/api/attendance');
const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -305,6 +304,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);
@ -380,14 +380,14 @@ router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess; const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id const schoolId = getCurrentUserSchoolId(req.currentUser);
const payload = await AttendanceDBApi.findAllAutocomplete( const payload = await AttendanceDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
globalAccess, organizationId, globalAccess, schoolId,
); );
res.status(200).send(payload); res.status(200).send(payload);
@ -428,6 +428,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await AttendanceDBApi.findBy( const payload = await AttendanceDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
{ currentUser: req.currentUser },
); );

View File

@ -3,10 +3,9 @@ const express = require('express');
const Book_loansService = require('../services/book_loans'); const Book_loansService = require('../services/book_loans');
const Book_loansDBApi = require('../db/api/book_loans'); const Book_loansDBApi = require('../db/api/book_loans');
const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -306,6 +305,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);
@ -381,14 +381,14 @@ router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess; const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id const schoolId = getCurrentUserSchoolId(req.currentUser);
const payload = await Book_loansDBApi.findAllAutocomplete( const payload = await Book_loansDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
globalAccess, organizationId, globalAccess, schoolId,
); );
res.status(200).send(payload); res.status(200).send(payload);
@ -429,6 +429,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await Book_loansDBApi.findBy( const payload = await Book_loansDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
{ currentUser: req.currentUser },
); );

View File

@ -3,10 +3,9 @@ const express = require('express');
const BooksService = require('../services/books'); const BooksService = require('../services/books');
const BooksDBApi = require('../db/api/books'); const BooksDBApi = require('../db/api/books');
const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -318,6 +317,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);
@ -393,14 +393,14 @@ router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess; const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id const schoolId = getCurrentUserSchoolId(req.currentUser);
const payload = await BooksDBApi.findAllAutocomplete( const payload = await BooksDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
globalAccess, organizationId, globalAccess, schoolId,
); );
res.status(200).send(payload); res.status(200).send(payload);
@ -441,6 +441,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await BooksDBApi.findBy( const payload = await BooksDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
{ currentUser: req.currentUser },
); );

View File

@ -3,10 +3,9 @@ const express = require('express');
const ClassesService = require('../services/classes'); const ClassesService = require('../services/classes');
const ClassesDBApi = require('../db/api/classes'); const ClassesDBApi = require('../db/api/classes');
const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -313,6 +312,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);
@ -388,14 +388,14 @@ router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess; const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id const schoolId = getCurrentUserSchoolId(req.currentUser);
const payload = await ClassesDBApi.findAllAutocomplete( const payload = await ClassesDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
globalAccess, organizationId, globalAccess, schoolId,
); );
res.status(200).send(payload); res.status(200).send(payload);
@ -436,6 +436,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await ClassesDBApi.findBy( const payload = await ClassesDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
{ currentUser: req.currentUser },
); );

View File

@ -3,10 +3,9 @@ const express = require('express');
const CoursesService = require('../services/courses'); const CoursesService = require('../services/courses');
const CoursesDBApi = require('../db/api/courses'); const CoursesDBApi = require('../db/api/courses');
const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -309,6 +308,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);
@ -384,14 +384,14 @@ router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess; const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id const schoolId = getCurrentUserSchoolId(req.currentUser);
const payload = await CoursesDBApi.findAllAutocomplete( const payload = await CoursesDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
globalAccess, organizationId, globalAccess, schoolId,
); );
res.status(200).send(payload); res.status(200).send(payload);
@ -432,6 +432,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await CoursesDBApi.findBy( const payload = await CoursesDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
{ currentUser: req.currentUser },
); );

View File

@ -3,10 +3,9 @@ const express = require('express');
const EmployeesService = require('../services/employees'); const EmployeesService = require('../services/employees');
const EmployeesDBApi = require('../db/api/employees'); const EmployeesDBApi = require('../db/api/employees');
const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -315,6 +314,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);
@ -390,14 +390,14 @@ router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess; const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id const schoolId = getCurrentUserSchoolId(req.currentUser);
const payload = await EmployeesDBApi.findAllAutocomplete( const payload = await EmployeesDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
globalAccess, organizationId, globalAccess, schoolId,
); );
res.status(200).send(payload); res.status(200).send(payload);
@ -438,6 +438,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await EmployeesDBApi.findBy( const payload = await EmployeesDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
{ currentUser: req.currentUser },
); );

View File

@ -3,10 +3,9 @@ const express = require('express');
const EnrollmentsService = require('../services/enrollments'); const EnrollmentsService = require('../services/enrollments');
const EnrollmentsDBApi = require('../db/api/enrollments'); const EnrollmentsDBApi = require('../db/api/enrollments');
const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -306,6 +305,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);
@ -381,14 +381,14 @@ router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess; const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id const schoolId = getCurrentUserSchoolId(req.currentUser);
const payload = await EnrollmentsDBApi.findAllAutocomplete( const payload = await EnrollmentsDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
globalAccess, organizationId, globalAccess, schoolId,
); );
res.status(200).send(payload); res.status(200).send(payload);
@ -429,6 +429,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await EnrollmentsDBApi.findBy( const payload = await EnrollmentsDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
{ currentUser: req.currentUser },
); );

View File

@ -1,21 +1,24 @@
const express = require('express'); const express = require('express');
const config = require('../config');
const path = require('path');
const passport = require('passport'); const passport = require('passport');
const services = require('../services/file'); const services = require('../services/file');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router(); const router = express.Router();
router.get('/download', (req, res) => { router.get('/download', wrapAsync(async (req, res) => {
if (process.env.NODE_ENV == "production" || process.env.NEXT_PUBLIC_BACK_API) { if (process.env.NODE_ENV == "production" || process.env.NEXT_PUBLIC_BACK_API) {
services.downloadGCloud(req, res); await services.downloadGCloud(req, res);
} }
else { else {
services.downloadLocal(req, res); await services.downloadLocal(req, res);
} }
}); }));
router.post('/upload/:table/:field', passport.authenticate('jwt', {session: false}), (req, res) => { router.post('/upload/:table/:field', passport.authenticate('jwt', {session: false}), (req, res) => {
const fileName = `${req.params.table}/${req.params.field}`; const fileName = services.normalizeFolder(`${req.params.table}/${req.params.field}`);
if (!fileName) {
return res.status(400).send({ message: 'Invalid upload path.' });
}
if (process.env.NODE_ENV == "production" || process.env.NEXT_PUBLIC_BACK_API) { if (process.env.NODE_ENV == "production" || process.env.NEXT_PUBLIC_BACK_API) {
services.uploadGCloud(fileName, req, res); services.uploadGCloud(fileName, req, res);
@ -29,4 +32,7 @@ router.post('/upload/:table/:field', passport.authenticate('jwt', {session: fals
} }
}); });
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router; module.exports = router;

View File

@ -3,10 +3,9 @@ const express = require('express');
const GradesService = require('../services/grades'); const GradesService = require('../services/grades');
const GradesDBApi = require('../db/api/grades'); const GradesDBApi = require('../db/api/grades');
const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -309,6 +308,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);
@ -384,14 +384,14 @@ router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess; const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id const schoolId = getCurrentUserSchoolId(req.currentUser);
const payload = await GradesDBApi.findAllAutocomplete( const payload = await GradesDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
globalAccess, organizationId, globalAccess, schoolId,
); );
res.status(200).send(payload); res.status(200).send(payload);
@ -432,6 +432,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await GradesDBApi.findBy( const payload = await GradesDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
{ currentUser: req.currentUser },
); );

View File

@ -3,10 +3,9 @@ const express = require('express');
const GuardiansService = require('../services/guardians'); const GuardiansService = require('../services/guardians');
const GuardiansDBApi = require('../db/api/guardians'); const GuardiansDBApi = require('../db/api/guardians');
const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -312,6 +311,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);
@ -387,14 +387,14 @@ router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess; const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id const schoolId = getCurrentUserSchoolId(req.currentUser);
const payload = await GuardiansDBApi.findAllAutocomplete( const payload = await GuardiansDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
globalAccess, organizationId, globalAccess, schoolId,
); );
res.status(200).send(payload); res.status(200).send(payload);
@ -435,6 +435,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await GuardiansDBApi.findBy( const payload = await GuardiansDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
{ currentUser: req.currentUser },
); );

View File

@ -3,10 +3,9 @@ const express = require('express');
const InvoicesService = require('../services/invoices'); const InvoicesService = require('../services/invoices');
const InvoicesDBApi = require('../db/api/invoices'); const InvoicesDBApi = require('../db/api/invoices');
const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -312,6 +311,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);
@ -387,14 +387,14 @@ router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess; const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id const schoolId = getCurrentUserSchoolId(req.currentUser);
const payload = await InvoicesDBApi.findAllAutocomplete( const payload = await InvoicesDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
globalAccess, organizationId, globalAccess, schoolId,
); );
res.status(200).send(payload); res.status(200).send(payload);
@ -435,6 +435,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await InvoicesDBApi.findBy( const payload = await InvoicesDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
{ currentUser: req.currentUser },
); );

View File

@ -5,6 +5,109 @@ const router = express.Router();
const sjs = require('sequelize-json-schema'); const sjs = require('sequelize-json-schema');
const { getWidget, askGpt } = require('../services/openai'); const { getWidget, askGpt } = require('../services/openai');
const { LocalAIApi } = require('../ai/LocalAIApi'); const { LocalAIApi } = require('../ai/LocalAIApi');
const ForbiddenError = require('../services/notifications/errors/forbidden');
const { checkPermissions } = require('../middlewares/check-permissions');
const WIDGET_CUSTOMIZATION_KEY = 'widgets';
const UUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
function badRequest(message) {
const error = new Error(message);
error.code = 400;
return error;
}
function normalizeWidgetKey(key) {
if (key !== WIDGET_CUSTOMIZATION_KEY) {
throw badRequest('Invalid role customization key.');
}
return key;
}
function normalizeUuid(value, label) {
if (typeof value !== 'string' || !UUID_REGEX.test(value)) {
throw badRequest(`${label} must be a valid UUID.`);
}
return value;
}
function normalizeText(value, label, maxLength) {
if (typeof value !== 'string' || !value.trim()) {
throw badRequest(`${label} is required.`);
}
const normalized = value.trim();
if (normalized.length > maxLength) {
throw badRequest(`${label} is too long. Maximum length is ${maxLength} characters.`);
}
return normalized;
}
function normalizeNumberOption(value, defaultValue, minValue, maxValue) {
const parsed = Number(value);
if (!Number.isFinite(parsed)) {
return defaultValue;
}
return Math.min(Math.max(parsed, minValue), maxValue);
}
function normalizeAiProxyOptions(options = {}) {
return {
poll_interval: normalizeNumberOption(options.poll_interval, 5, 1, 30),
poll_timeout: normalizeNumberOption(options.poll_timeout, 300, 5, 300),
timeout: normalizeNumberOption(options.timeout, 30, 5, 60),
};
}
function assertGlobalRoleAccess(currentUser) {
if (!currentUser?.app_role?.globalAccess) {
throw new ForbiddenError('auth.forbidden');
}
}
async function hasRolePermission(currentUser, permission) {
if (!currentUser) {
return false;
}
const customPermissions = Array.isArray(currentUser.custom_permissions)
? currentUser.custom_permissions
: [];
if (customPermissions.find((cp) => cp.name === permission)) {
return true;
}
if (!currentUser.app_role) {
return false;
}
const permissions = typeof currentUser.app_role.getPermissions === 'function'
? await currentUser.app_role.getPermissions()
: currentUser.app_role.permissions || [];
return !!permissions.find((p) => p.name === permission);
}
async function assertCanReadRoleWidgets(roleId, currentUser) {
if (roleId === currentUser?.app_role?.id) {
return;
}
if (!(await hasRolePermission(currentUser, 'READ_ROLES'))) {
throw new ForbiddenError('auth.forbidden');
}
}
async function canGenerateRoleWidgets(currentUser) {
return !!currentUser?.app_role?.globalAccess
&& await hasRolePermission(currentUser, 'UPDATE_ROLES');
}
const loadRolesModules = () => { const loadRolesModules = () => {
try { try {
@ -71,12 +174,17 @@ const loadRolesModules = () => {
router.delete( router.delete(
'/roles-info/:infoId', '/roles-info/:infoId',
checkPermissions('UPDATE_ROLES'),
wrapAsync(async (req, res) => { wrapAsync(async (req, res) => {
assertGlobalRoleAccess(req.currentUser);
const { RolesService } = loadRolesModules(); const { RolesService } = loadRolesModules();
const key = normalizeWidgetKey(req.query.key);
const infoId = normalizeUuid(req.query.infoId || req.params.infoId, 'Widget ID');
const roleId = normalizeUuid(req.query.roleId, 'Role ID');
const role = await RolesService.removeRoleInfoById( const role = await RolesService.removeRoleInfoById(
req.query.infoId, infoId,
req.query.roleId, roleId,
req.query.key, key,
req.currentUser, req.currentUser,
); );
@ -131,33 +239,46 @@ router.get(
'/info-by-key', '/info-by-key',
wrapAsync(async (req, res) => { wrapAsync(async (req, res) => {
const { RolesService, RolesDBApi } = loadRolesModules(); const { RolesService, RolesDBApi } = loadRolesModules();
const roleId = req.query.roleId;
const key = req.query.key;
const currentUser = req.currentUser; const currentUser = req.currentUser;
const key = normalizeWidgetKey(req.query.key);
const roleId = normalizeUuid(req.query.roleId || currentUser?.app_role?.id, 'Role ID');
await assertCanReadRoleWidgets(roleId, currentUser);
const role = await RolesDBApi.findBy({ id: roleId });
if (!role) {
return res.status(404).send('Role not found');
}
let info = await RolesService.getRoleInfoByKey( let info = await RolesService.getRoleInfoByKey(
key, key,
roleId, roleId,
currentUser, currentUser,
); );
const role = await RolesDBApi.findBy({ id: roleId });
if (!role?.role_customization) { if (!role.role_customization && await canGenerateRoleWidgets(currentUser)) {
await Promise.all(["pie", "bar"].map(async (e) => {
const schema = await sjs.getSequelizeSchema(db.sequelize, {}); const schema = await sjs.getSequelizeSchema(db.sequelize, {});
const widgetResults = await Promise.allSettled(['pie', 'bar'].map(async (chartType) => {
const payload = { const payload = {
description: `Create some cool ${e} chart`, description: `Create some cool ${chartType} chart`,
modelDefinition: schema.definitions, modelDefinition: schema.definitions,
}; };
const widgetId = await getWidget(payload, currentUser?.id, roleId); const widgetId = await getWidget(payload);
if (widgetId) { if (typeof widgetId === 'string') {
await RolesService.addRoleInfo( await RolesService.addRoleInfo(
roleId, roleId,
currentUser?.id, currentUser?.id,
'widgets', WIDGET_CUSTOMIZATION_KEY,
widgetId, widgetId,
req.currentUser, req.currentUser,
); );
} }
})) }));
widgetResults
.filter((result) => result.status === 'rejected')
.forEach((result) => console.error('Default widget creation failed:', result.reason));
info = await RolesService.getRoleInfoByKey( info = await RolesService.getRoleInfoByKey(
key, key,
roleId, roleId,
@ -170,9 +291,13 @@ router.get(
router.post( router.post(
'/create_widget', '/create_widget',
checkPermissions('UPDATE_ROLES'),
wrapAsync(async (req, res) => { wrapAsync(async (req, res) => {
assertGlobalRoleAccess(req.currentUser);
const { RolesService } = loadRolesModules(); const { RolesService } = loadRolesModules();
const { description, userId, roleId } = req.body; const { userId } = req.body || {};
const description = normalizeText(req.body?.description, 'Description', 1000);
const roleId = normalizeUuid(req.body?.roleId, 'Role ID');
const currentUser = req.currentUser; const currentUser = req.currentUser;
const schema = await sjs.getSequelizeSchema(db.sequelize, {}); const schema = await sjs.getSequelizeSchema(db.sequelize, {});
@ -181,13 +306,13 @@ router.post(
modelDefinition: schema.definitions, modelDefinition: schema.definitions,
}; };
const widgetId = await getWidget(payload, userId, roleId); const widgetId = await getWidget(payload);
if (widgetId) { if (typeof widgetId === 'string') {
await RolesService.addRoleInfo( await RolesService.addRoleInfo(
roleId, roleId,
userId, userId,
'widgets', WIDGET_CUSTOMIZATION_KEY,
widgetId, widgetId,
currentUser, currentUser,
); );
@ -247,9 +372,10 @@ router.post(
'/response', '/response',
wrapAsync(async (req, res) => { wrapAsync(async (req, res) => {
const body = req.body || {}; const body = req.body || {};
const options = body.options || {}; const options = normalizeAiProxyOptions(body.options || {});
const payload = { ...body }; const payload = { ...body };
delete payload.options; delete payload.options;
delete payload.project_uuid;
const response = await LocalAIApi.createResponse(payload, options); const response = await LocalAIApi.createResponse(payload, options);
@ -306,13 +432,7 @@ router.post(
router.post( router.post(
'/ask-gpt', '/ask-gpt',
wrapAsync(async (req, res) => { wrapAsync(async (req, res) => {
const { prompt } = req.body; const prompt = normalizeText(req.body?.prompt, 'Prompt', 4000);
if (!prompt) {
return res.status(400).send({
success: false,
error: 'Prompt is required',
});
}
const response = await askGpt(prompt); const response = await askGpt(prompt);
@ -325,4 +445,6 @@ router.post(
); );
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router; module.exports = router;

View File

@ -3,10 +3,9 @@ const express = require('express');
const PaymentsService = require('../services/payments'); const PaymentsService = require('../services/payments');
const PaymentsDBApi = require('../db/api/payments'); const PaymentsDBApi = require('../db/api/payments');
const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -309,6 +308,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);
@ -384,14 +384,14 @@ router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess; const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id const schoolId = getCurrentUserSchoolId(req.currentUser);
const payload = await PaymentsDBApi.findAllAutocomplete( const payload = await PaymentsDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
globalAccess, organizationId, globalAccess, schoolId,
); );
res.status(200).send(payload); res.status(200).send(payload);
@ -432,6 +432,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await PaymentsDBApi.findBy( const payload = await PaymentsDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
{ currentUser: req.currentUser },
); );

View File

@ -301,6 +301,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);

View File

@ -3,10 +3,9 @@ const express = require('express');
const ProductsService = require('../services/products'); const ProductsService = require('../services/products');
const ProductsDBApi = require('../db/api/products'); const ProductsDBApi = require('../db/api/products');
const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -315,6 +314,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);
@ -390,14 +390,14 @@ router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess; const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id const schoolId = getCurrentUserSchoolId(req.currentUser);
const payload = await ProductsDBApi.findAllAutocomplete( const payload = await ProductsDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
globalAccess, organizationId, globalAccess, schoolId,
); );
res.status(200).send(payload); res.status(200).send(payload);
@ -438,6 +438,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await ProductsDBApi.findBy( const payload = await ProductsDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
{ currentUser: req.currentUser },
); );

View File

@ -5,8 +5,6 @@ const RolesService = require('../services/roles');
const RolesDBApi = require('../db/api/roles'); const RolesDBApi = require('../db/api/roles');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -305,6 +303,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);

View File

@ -3,10 +3,9 @@ const express = require('express');
const SchoolsService = require('../services/schools'); const SchoolsService = require('../services/schools');
const SchoolsDBApi = require('../db/api/schools'); const SchoolsDBApi = require('../db/api/schools');
const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -31,9 +30,22 @@ router.use(checkCrudPermissions('schools'));
* name: * name:
* type: string * type: string
* default: name * default: name
* nif:
* type: string
* phone:
* type: string
* email:
* type: string
* province:
* type: string
* municipality:
* type: string
* address:
* type: string
* logoUrl:
* type: string
* status:
* type: string
*/ */
/** /**
@ -292,11 +304,7 @@ router.get('/', wrapAsync(async (req, res) => {
req.query, globalAccess, { currentUser } req.query, globalAccess, { currentUser }
); );
if (filetype && filetype === 'csv') { if (filetype && filetype === 'csv') {
const fields = ['id','name', const fields = ['id','name','nif','phone','email','province','municipality','address','logoUrl','status'];
];
const opts = { fields }; const opts = { fields };
try { try {
const csv = parse(payload.rows, opts); const csv = parse(payload.rows, opts);
@ -305,6 +313,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);
@ -380,14 +389,14 @@ router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess; const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id const schoolId = getCurrentUserSchoolId(req.currentUser);
const payload = await SchoolsDBApi.findAllAutocomplete( const payload = await SchoolsDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
globalAccess, organizationId, globalAccess, schoolId,
); );
res.status(200).send(payload); res.status(200).send(payload);
@ -428,6 +437,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await SchoolsDBApi.findBy( const payload = await SchoolsDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
{ currentUser: req.currentUser },
); );

View File

@ -1,8 +1,6 @@
const express = require('express'); const express = require('express');
const SearchService = require('../services/search'); const SearchService = require('../services/search');
const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -35,22 +33,14 @@ router.use(checkCrudPermissions('search'));
* description: Internal server error * description: Internal server error
*/ */
router.post('/', async (req, res) => { router.post('/', wrapAsync(async (req, res) => {
const { searchQuery , organizationId} = req.body; const { searchQuery } = req.body || {};
const globalAccess = !!req.currentUser?.app_role?.globalAccess;
const globalAccess = req.currentUser.app_role.globalAccess; const foundMatches = await SearchService.search(searchQuery, req.currentUser, globalAccess);
if (!searchQuery) {
return res.status(400).json({ error: 'Please enter a search query' });
}
try {
const foundMatches = await SearchService.search(searchQuery, req.currentUser , organizationId, globalAccess,);
res.json(foundMatches); res.json(foundMatches);
} catch (error) { }));
console.error('Internal Server Error', error);
res.status(500).json({ error: 'Internal Server Error' }); router.use('/', require('../helpers').commonErrorHandler);
}
});
module.exports = router; module.exports = router;

View File

@ -1,6 +1,7 @@
const express = require('express'); const express = require('express');
const db = require('../db/models');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const ForbiddenError = require('../services/notifications/errors/forbidden');
const { executeReadOnlySelect } = require('../services/sqlSafety');
const router = express.Router(); const router = express.Router();
@ -30,32 +31,27 @@ const router = express.Router();
* description: Invalid SQL * description: Invalid SQL
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 403:
* description: Requires global access
* 500: * 500:
* description: Internal server error * description: Internal server error
*/ */
router.post( router.post(
'/', '/',
wrapAsync(async (req, res) => { wrapAsync(async (req, res) => {
const { sql } = req.body; if (!req.currentUser?.app_role?.globalAccess) {
if (typeof sql !== 'string' || !sql.trim()) { throw new ForbiddenError('auth.forbidden');
return res.status(400).json({ error: 'SQL is required' });
} }
const normalized = sql.trim().replace(/;+\s*$/, ''); const body = req.body || {};
if (!/^select\b/i.test(normalized)) { const rows = await executeReadOnlySelect(body.sql, {
return res.status(400).json({ error: 'Only SELECT statements are allowed' }); limit: body.limit,
}
if (normalized.includes(';')) {
return res.status(400).json({ error: 'Only a single SELECT statement is allowed' });
}
const rows = await db.sequelize.query(normalized, {
type: db.Sequelize.QueryTypes.SELECT,
}); });
return res.status(200).json({ rows }); return res.status(200).json({ rows });
}), }),
); );
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router; module.exports = router;

View File

@ -3,10 +3,9 @@ const express = require('express');
const Student_guardiansService = require('../services/student_guardians'); const Student_guardiansService = require('../services/student_guardians');
const Student_guardiansDBApi = require('../db/api/student_guardians'); const Student_guardiansDBApi = require('../db/api/student_guardians');
const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -303,6 +302,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);
@ -378,14 +378,14 @@ router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess; const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id const schoolId = getCurrentUserSchoolId(req.currentUser);
const payload = await Student_guardiansDBApi.findAllAutocomplete( const payload = await Student_guardiansDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
globalAccess, organizationId, globalAccess, schoolId,
); );
res.status(200).send(payload); res.status(200).send(payload);
@ -426,6 +426,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await Student_guardiansDBApi.findBy( const payload = await Student_guardiansDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
{ currentUser: req.currentUser },
); );

View File

@ -3,10 +3,9 @@ const express = require('express');
const StudentsService = require('../services/students'); const StudentsService = require('../services/students');
const StudentsDBApi = require('../db/api/students'); const StudentsDBApi = require('../db/api/students');
const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -322,6 +321,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);
@ -397,14 +397,14 @@ router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess; const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id const schoolId = getCurrentUserSchoolId(req.currentUser);
const payload = await StudentsDBApi.findAllAutocomplete( const payload = await StudentsDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
globalAccess, organizationId, globalAccess, schoolId,
); );
res.status(200).send(payload); res.status(200).send(payload);
@ -445,6 +445,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await StudentsDBApi.findBy( const payload = await StudentsDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
{ currentUser: req.currentUser },
); );

View File

@ -3,10 +3,9 @@ const express = require('express');
const SubjectsService = require('../services/subjects'); const SubjectsService = require('../services/subjects');
const SubjectsDBApi = require('../db/api/subjects'); const SubjectsDBApi = require('../db/api/subjects');
const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -306,6 +305,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);
@ -381,14 +381,14 @@ router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess; const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id const schoolId = getCurrentUserSchoolId(req.currentUser);
const payload = await SubjectsDBApi.findAllAutocomplete( const payload = await SubjectsDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
globalAccess, organizationId, globalAccess, schoolId,
); );
res.status(200).send(payload); res.status(200).send(payload);
@ -429,6 +429,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await SubjectsDBApi.findBy( const payload = await SubjectsDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
{ currentUser: req.currentUser },
); );

View File

@ -3,10 +3,9 @@ const express = require('express');
const TeachersService = require('../services/teachers'); const TeachersService = require('../services/teachers');
const TeachersDBApi = require('../db/api/teachers'); const TeachersDBApi = require('../db/api/teachers');
const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -315,6 +314,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);
@ -390,14 +390,14 @@ router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess; const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id const schoolId = getCurrentUserSchoolId(req.currentUser);
const payload = await TeachersDBApi.findAllAutocomplete( const payload = await TeachersDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
globalAccess, organizationId, globalAccess, schoolId,
); );
res.status(200).send(payload); res.status(200).send(payload);
@ -438,6 +438,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await TeachersDBApi.findBy( const payload = await TeachersDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
{ currentUser: req.currentUser },
); );

View File

@ -3,10 +3,9 @@ const express = require('express');
const UsersService = require('../services/users'); const UsersService = require('../services/users');
const UsersDBApi = require('../db/api/users'); const UsersDBApi = require('../db/api/users');
const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
const config = require('../config');
const router = express.Router(); const router = express.Router();
@ -314,6 +313,7 @@ router.get('/', wrapAsync(async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err;
} }
} else { } else {
res.status(200).send(payload); res.status(200).send(payload);
@ -389,14 +389,14 @@ router.get('/autocomplete', async (req, res) => {
const globalAccess = req.currentUser.app_role.globalAccess; const globalAccess = req.currentUser.app_role.globalAccess;
const organizationId = req.currentUser.organization?.id const schoolId = getCurrentUserSchoolId(req.currentUser);
const payload = await UsersDBApi.findAllAutocomplete( const payload = await UsersDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
globalAccess, organizationId, globalAccess, schoolId,
); );
res.status(200).send(payload); res.status(200).send(payload);
@ -437,6 +437,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await UsersDBApi.findBy( const payload = await UsersDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
{ currentUser: req.currentUser },
); );

View File

@ -1,11 +1,7 @@
const db = require('../db/models'); const db = require('../db/models');
const AssessmentsDBApi = require('../db/api/assessments'); const AssessmentsDBApi = require('../db/api/assessments');
const processFile = require("../middlewares/upload"); const parseImportFile = require('./importFileParser');
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -28,28 +24,13 @@ module.exports = class AssessmentsService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async bulkImport(req, res, sendInvitationEmails = true, host) { static async bulkImport(req, res) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await processFile(req, res); const results = await parseImportFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await AssessmentsDBApi.bulkImport(results, { await AssessmentsDBApi.bulkImport(results, {
transaction, transaction,
@ -70,7 +51,7 @@ module.exports = class AssessmentsService {
try { try {
let assessments = await AssessmentsDBApi.findBy( let assessments = await AssessmentsDBApi.findBy(
{id}, {id},
{transaction}, { transaction, currentUser },
); );
if (!assessments) { if (!assessments) {
@ -95,7 +76,7 @@ module.exports = class AssessmentsService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async deleteByIds(ids, currentUser) { static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();

View File

@ -1,11 +1,7 @@
const db = require('../db/models'); const db = require('../db/models');
const AttendanceDBApi = require('../db/api/attendance'); const AttendanceDBApi = require('../db/api/attendance');
const processFile = require("../middlewares/upload"); const parseImportFile = require('./importFileParser');
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -28,28 +24,13 @@ module.exports = class AttendanceService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async bulkImport(req, res, sendInvitationEmails = true, host) { static async bulkImport(req, res) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await processFile(req, res); const results = await parseImportFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await AttendanceDBApi.bulkImport(results, { await AttendanceDBApi.bulkImport(results, {
transaction, transaction,
@ -70,7 +51,7 @@ module.exports = class AttendanceService {
try { try {
let attendance = await AttendanceDBApi.findBy( let attendance = await AttendanceDBApi.findBy(
{id}, {id},
{transaction}, { transaction, currentUser },
); );
if (!attendance) { if (!attendance) {
@ -95,7 +76,7 @@ module.exports = class AttendanceService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async deleteByIds(ids, currentUser) { static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();

View File

@ -1,4 +1,5 @@
const UsersDBApi = require('../db/api/users'); const UsersDBApi = require('../db/api/users');
const db = require('../db/models');
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const ForbiddenError = require('./notifications/errors/forbidden'); const ForbiddenError = require('./notifications/errors/forbidden');
const bcrypt = require('bcrypt'); const bcrypt = require('bcrypt');
@ -83,7 +84,7 @@ class Auth {
return helpers.jwtSign(data); return helpers.jwtSign(data);
} }
static async signin(email, password, options = {}) { static async signin(email, password) {
const user = await UsersDBApi.findBy({email}); const user = await UsersDBApi.findBy({email});
if (!user) { if (!user) {
@ -290,12 +291,21 @@ class Auth {
try { try {
await UsersDBApi.findBy( await UsersDBApi.findBy(
{id: currentUser.id}, {id: currentUser.id},
{transaction}, { transaction, currentUser },
); );
const safeProfile = {
firstName: data?.firstName,
lastName: data?.lastName,
phoneNumber: data?.phoneNumber,
password: data?.password,
avatar: data?.avatar,
};
await UsersDBApi.update( await UsersDBApi.update(
currentUser.id, currentUser.id,
data, safeProfile,
currentUser.app_role?.globalAccess,
{ {
currentUser, currentUser,
transaction transaction

View File

@ -1,11 +1,7 @@
const db = require('../db/models'); const db = require('../db/models');
const Book_loansDBApi = require('../db/api/book_loans'); const Book_loansDBApi = require('../db/api/book_loans');
const processFile = require("../middlewares/upload"); const parseImportFile = require('./importFileParser');
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -28,28 +24,13 @@ module.exports = class Book_loansService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async bulkImport(req, res, sendInvitationEmails = true, host) { static async bulkImport(req, res) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await processFile(req, res); const results = await parseImportFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await Book_loansDBApi.bulkImport(results, { await Book_loansDBApi.bulkImport(results, {
transaction, transaction,
@ -70,7 +51,7 @@ module.exports = class Book_loansService {
try { try {
let book_loans = await Book_loansDBApi.findBy( let book_loans = await Book_loansDBApi.findBy(
{id}, {id},
{transaction}, { transaction, currentUser },
); );
if (!book_loans) { if (!book_loans) {
@ -95,7 +76,7 @@ module.exports = class Book_loansService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async deleteByIds(ids, currentUser) { static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();

View File

@ -1,11 +1,7 @@
const db = require('../db/models'); const db = require('../db/models');
const BooksDBApi = require('../db/api/books'); const BooksDBApi = require('../db/api/books');
const processFile = require("../middlewares/upload"); const parseImportFile = require('./importFileParser');
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -28,28 +24,13 @@ module.exports = class BooksService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async bulkImport(req, res, sendInvitationEmails = true, host) { static async bulkImport(req, res) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await processFile(req, res); const results = await parseImportFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await BooksDBApi.bulkImport(results, { await BooksDBApi.bulkImport(results, {
transaction, transaction,
@ -70,7 +51,7 @@ module.exports = class BooksService {
try { try {
let books = await BooksDBApi.findBy( let books = await BooksDBApi.findBy(
{id}, {id},
{transaction}, { transaction, currentUser },
); );
if (!books) { if (!books) {
@ -95,7 +76,7 @@ module.exports = class BooksService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async deleteByIds(ids, currentUser) { static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();

View File

@ -1,11 +1,7 @@
const db = require('../db/models'); const db = require('../db/models');
const ClassesDBApi = require('../db/api/classes'); const ClassesDBApi = require('../db/api/classes');
const processFile = require("../middlewares/upload"); const parseImportFile = require('./importFileParser');
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -28,28 +24,13 @@ module.exports = class ClassesService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async bulkImport(req, res, sendInvitationEmails = true, host) { static async bulkImport(req, res) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await processFile(req, res); const results = await parseImportFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await ClassesDBApi.bulkImport(results, { await ClassesDBApi.bulkImport(results, {
transaction, transaction,
@ -70,7 +51,7 @@ module.exports = class ClassesService {
try { try {
let classes = await ClassesDBApi.findBy( let classes = await ClassesDBApi.findBy(
{id}, {id},
{transaction}, { transaction, currentUser },
); );
if (!classes) { if (!classes) {
@ -95,7 +76,7 @@ module.exports = class ClassesService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async deleteByIds(ids, currentUser) { static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();

View File

@ -1,11 +1,7 @@
const db = require('../db/models'); const db = require('../db/models');
const CoursesDBApi = require('../db/api/courses'); const CoursesDBApi = require('../db/api/courses');
const processFile = require("../middlewares/upload"); const parseImportFile = require('./importFileParser');
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -28,28 +24,13 @@ module.exports = class CoursesService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async bulkImport(req, res, sendInvitationEmails = true, host) { static async bulkImport(req, res) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await processFile(req, res); const results = await parseImportFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await CoursesDBApi.bulkImport(results, { await CoursesDBApi.bulkImport(results, {
transaction, transaction,
@ -70,7 +51,7 @@ module.exports = class CoursesService {
try { try {
let courses = await CoursesDBApi.findBy( let courses = await CoursesDBApi.findBy(
{id}, {id},
{transaction}, { transaction, currentUser },
); );
if (!courses) { if (!courses) {
@ -95,7 +76,7 @@ module.exports = class CoursesService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async deleteByIds(ids, currentUser) { static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();

View File

@ -1,11 +1,7 @@
const db = require('../db/models'); const db = require('../db/models');
const EmployeesDBApi = require('../db/api/employees'); const EmployeesDBApi = require('../db/api/employees');
const processFile = require("../middlewares/upload"); const parseImportFile = require('./importFileParser');
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -28,28 +24,13 @@ module.exports = class EmployeesService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async bulkImport(req, res, sendInvitationEmails = true, host) { static async bulkImport(req, res) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await processFile(req, res); const results = await parseImportFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await EmployeesDBApi.bulkImport(results, { await EmployeesDBApi.bulkImport(results, {
transaction, transaction,
@ -70,7 +51,7 @@ module.exports = class EmployeesService {
try { try {
let employees = await EmployeesDBApi.findBy( let employees = await EmployeesDBApi.findBy(
{id}, {id},
{transaction}, { transaction, currentUser },
); );
if (!employees) { if (!employees) {
@ -95,7 +76,7 @@ module.exports = class EmployeesService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async deleteByIds(ids, currentUser) { static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();

View File

@ -1,11 +1,7 @@
const db = require('../db/models'); const db = require('../db/models');
const EnrollmentsDBApi = require('../db/api/enrollments'); const EnrollmentsDBApi = require('../db/api/enrollments');
const processFile = require("../middlewares/upload"); const parseImportFile = require('./importFileParser');
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -28,28 +24,13 @@ module.exports = class EnrollmentsService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async bulkImport(req, res, sendInvitationEmails = true, host) { static async bulkImport(req, res) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await processFile(req, res); const results = await parseImportFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await EnrollmentsDBApi.bulkImport(results, { await EnrollmentsDBApi.bulkImport(results, {
transaction, transaction,
@ -70,7 +51,7 @@ module.exports = class EnrollmentsService {
try { try {
let enrollments = await EnrollmentsDBApi.findBy( let enrollments = await EnrollmentsDBApi.findBy(
{id}, {id},
{transaction}, { transaction, currentUser },
); );
if (!enrollments) { if (!enrollments) {
@ -95,7 +76,7 @@ module.exports = class EnrollmentsService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async deleteByIds(ids, currentUser) { static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();

View File

@ -2,7 +2,172 @@ const formidable = require('formidable');
const fs = require('fs'); const fs = require('fs');
const config = require('../config'); const config = require('../config');
const path = require('path'); const path = require('path');
const { format } = require("util"); const crypto = require('crypto');
const db = require('../db/models');
const POSIX_SEPARATOR = '/';
const getFirstValue = (value) => {
if (Array.isArray(value)) {
return value[0];
}
return value;
};
const normalizeStoragePath = (value) => {
if (typeof value !== 'string') {
return null;
}
const trimmed = value.trim();
if (!trimmed || trimmed.includes('\0') || trimmed.includes('\\')) {
return null;
}
const normalized = path.posix.normalize(trimmed);
if (
normalized === '.' ||
path.posix.isAbsolute(normalized) ||
normalized === '..' ||
normalized.startsWith(`..${POSIX_SEPARATOR}`) ||
normalized.split(POSIX_SEPARATOR).includes('..') ||
normalized !== trimmed
) {
return null;
}
return normalized;
};
const normalizeFolder = (folder) => normalizeStoragePath(folder);
const normalizePrivateUrl = (privateUrl) => normalizeStoragePath(privateUrl);
const normalizeFilename = (filename) => {
if (typeof filename !== 'string') {
return null;
}
const trimmed = filename.trim();
if (
!trimmed ||
trimmed.includes('\0') ||
trimmed.includes(POSIX_SEPARATOR) ||
trimmed.includes('\\') ||
trimmed === '.' ||
trimmed === '..' ||
path.basename(trimmed) !== trimmed
) {
return null;
}
return trimmed;
};
const createDownloadToken = (privateUrl) => (
crypto
.createHmac('sha256', config.secret_key)
.update(privateUrl)
.digest('hex')
);
const isValidDownloadToken = (privateUrl, token) => {
if (typeof token !== 'string' || !token) {
return false;
}
const expected = createDownloadToken(privateUrl);
const tokenBuffer = Buffer.from(token, 'hex');
const expectedBuffer = Buffer.from(expected, 'hex');
return (
tokenBuffer.length === expectedBuffer.length &&
crypto.timingSafeEqual(tokenBuffer, expectedBuffer)
);
};
const buildPublicDownloadUrl = (privateUrl) => (
`/api/file/download?privateUrl=${encodeURIComponent(privateUrl)}&token=${createDownloadToken(privateUrl)}`
);
const ensureKnownPrivateUrl = async (privateUrl) => {
const file = await db.file.findOne({
where: { privateUrl },
attributes: ['id'],
});
return !!file;
};
const canDownloadPrivateUrl = async (privateUrl, token) => {
if (isValidDownloadToken(privateUrl, token)) {
return true;
}
return ensureKnownPrivateUrl(privateUrl);
};
const assertSafeLocalPath = (privateUrl) => {
const baseDir = path.resolve(config.uploadDir);
const targetPath = path.resolve(baseDir, privateUrl);
if (targetPath !== baseDir && !targetPath.startsWith(`${baseDir}${path.sep}`)) {
return null;
}
return targetPath;
};
const validateFileMetadata = async (file, relation, options = {}) => {
const privateUrl = normalizePrivateUrl(file && file.privateUrl);
const expectedFolder = normalizeFolder(
`${String(relation.belongsTo)}/${String(relation.belongsToColumn)}`,
);
if (!privateUrl || !expectedFolder) {
const error = new Error('Invalid file path.');
error.code = 400;
throw error;
}
const expectedPrefix = `${expectedFolder}/`;
if (!privateUrl.startsWith(expectedPrefix)) {
const error = new Error('Invalid file relation path.');
error.code = 400;
throw error;
}
const filename = privateUrl.slice(expectedPrefix.length);
if (!normalizeFilename(filename)) {
const error = new Error('Invalid file name.');
error.code = 400;
throw error;
}
const existingFile = await db.file.findOne({
where: { privateUrl },
attributes: ['id'],
transaction: options.transaction,
});
if (existingFile) {
const error = new Error('File already belongs to a record.');
error.code = 403;
throw error;
}
return {
...file,
privateUrl,
publicUrl: buildPublicDownloadUrl(privateUrl),
};
};
const ensureDirectoryExistence = (filePath) => { const ensureDirectoryExistence = (filePath) => {
const dirname = path.dirname(filePath); const dirname = path.dirname(filePath);
@ -36,20 +201,29 @@ const uploadLocal = (
return; return;
} }
let uploadFolder = folder;
if (validations.folderIncludesAuthenticationUid) { if (validations.folderIncludesAuthenticationUid) {
folder = folder.replace( uploadFolder = uploadFolder.replace(
':userId', ':userId',
req.currentUser.authenticationUid, req.currentUser.authenticationUid,
); );
if ( if (
!req.currentUser.authenticationUid || !req.currentUser.authenticationUid ||
!folder.includes(req.currentUser.authenticationUid) !uploadFolder.includes(req.currentUser.authenticationUid)
) { ) {
res.sendStatus(403); res.sendStatus(403);
return; return;
} }
} }
uploadFolder = normalizeFolder(uploadFolder);
if (!uploadFolder) {
res.status(400).send({ message: 'Invalid upload path.' });
return;
}
const form = new formidable.IncomingForm(); const form = new formidable.IncomingForm();
form.uploadDir = config.uploadDir; form.uploadDir = config.uploadDir;
@ -58,44 +232,83 @@ const uploadLocal = (
} }
form.parse(req, function (err, fields, files) { form.parse(req, function (err, fields, files) {
const filename = String(fields.filename); if (err) {
const fileTempUrl = files.file.path; console.error('Upload parse error:', err);
res.status(500).send(err);
if (!filename) {
fs.unlinkSync(fileTempUrl);
res.sendStatus(500);
return; return;
} }
const privateUrl = path.join( const filename = normalizeFilename(String(getFirstValue(fields.filename) || ''));
form.uploadDir, const uploadedFile = getFirstValue(files.file);
folder, const fileTempUrl = uploadedFile && uploadedFile.path;
filename,
if (!filename || !fileTempUrl) {
if (fileTempUrl && fs.existsSync(fileTempUrl)) {
fs.unlinkSync(fileTempUrl);
}
res.status(400).send({ message: 'Invalid uploaded file.' });
return;
}
const privateUrl = assertSafeLocalPath(
path.posix.join(uploadFolder, filename),
); );
if (!privateUrl) {
fs.unlinkSync(fileTempUrl);
res.status(400).send({ message: 'Invalid upload path.' });
return;
}
ensureDirectoryExistence(privateUrl); ensureDirectoryExistence(privateUrl);
fs.renameSync(fileTempUrl, privateUrl); fs.renameSync(fileTempUrl, privateUrl);
res.sendStatus(200); res.status(200).send({
url: buildPublicDownloadUrl(path.posix.join(uploadFolder, filename)),
});
}); });
form.on('error', function (err) { form.on('error', function (err) {
console.error('Upload form error:', err);
res.status(500).send(err); res.status(500).send(err);
}); });
} }
} }
const downloadLocal = async (req, res) => { const downloadLocal = async (req, res) => {
const privateUrl = req.query.privateUrl; const privateUrl = normalizePrivateUrl(getFirstValue(req.query.privateUrl));
if (!privateUrl) { if (!privateUrl) {
return res.sendStatus(404); return res.sendStatus(404);
} }
res.download(path.join(config.uploadDir, privateUrl));
const canDownload = await canDownloadPrivateUrl(
privateUrl,
getFirstValue(req.query.token),
);
if (!canDownload) {
return res.sendStatus(404);
}
const privatePath = assertSafeLocalPath(privateUrl);
if (!privatePath) {
return res.sendStatus(404);
}
res.download(privatePath, (err) => {
if (!err) {
return;
}
console.error('Download error:', err);
if (!res.headersSent) {
res.sendStatus(404);
}
});
} }
const initGCloud = () => { const initGCloud = () => {
const processFile = require("../middlewares/upload"); const processFile = require("../middlewares/upload");
const { Storage } = require("@google-cloud/storage"); const { Storage } = require("@google-cloud/storage");
const crypto = require('crypto')
const hash = config.gcloud.hash const hash = config.gcloud.hash
const privateKey = process.env.GC_PRIVATE_KEY.replace(/\\\n/g, "\n"); const privateKey = process.env.GC_PRIVATE_KEY.replace(/\\\n/g, "\n");
@ -116,14 +329,20 @@ const uploadGCloud = async (folder, req, res) => {
try { try {
const {hash, bucket, processFile} = initGCloud(); const {hash, bucket, processFile} = initGCloud();
await processFile(req, res); await processFile(req, res);
let buffer = await req.file.buffer; const uploadFolder = normalizeFolder(folder);
let filename = await req.body.filename;
if (!req.file) { if (!req.file) {
return res.status(400).send({ message: "Please upload a file!" }); return res.status(400).send({ message: "Please upload a file!" });
} }
let path = `${hash}/${folder}/${filename}`; let buffer = await req.file.buffer;
let filename = normalizeFilename(await req.body.filename);
if (!uploadFolder || !filename) {
return res.status(400).send({ message: 'Invalid upload path.' });
}
let path = `${hash}/${uploadFolder}/${filename}`;
let blob = bucket.file(path); let blob = bucket.file(path);
console.log(path); console.log(path);
@ -140,10 +359,9 @@ const uploadGCloud = async (folder, req, res) => {
console.log(`https://storage.googleapis.com/${bucket.name}/${blob.name}`); console.log(`https://storage.googleapis.com/${bucket.name}/${blob.name}`);
blobStream.on("finish", async (data) => { blobStream.on("finish", async () => {
const publicUrl = format( const privateUrl = path.posix.join(uploadFolder, filename);
`https://storage.googleapis.com/${bucket.name}/${blob.name}` const publicUrl = buildPublicDownloadUrl(privateUrl);
);
res.status(200).send({ res.status(200).send({
message: "Uploaded the file successfully: " + path, message: "Uploaded the file successfully: " + path,
@ -163,9 +381,21 @@ const uploadGCloud = async (folder, req, res) => {
const downloadGCloud = async (req, res) => { const downloadGCloud = async (req, res) => {
try { try {
const {hash, bucket, processFile} = initGCloud(); const {hash, bucket} = initGCloud();
const privateUrl = normalizePrivateUrl(getFirstValue(await req.query.privateUrl));
if (!privateUrl) {
return res.sendStatus(404);
}
const canDownload = await canDownloadPrivateUrl(
privateUrl,
getFirstValue(req.query.token),
);
if (!canDownload) {
return res.sendStatus(404);
}
const privateUrl = await req.query.privateUrl;
const filePath = `${hash}/${privateUrl}`; const filePath = `${hash}/${privateUrl}`;
const file = bucket.file(filePath) const file = bucket.file(filePath)
const fileExists = await file.exists(); const fileExists = await file.exists();
@ -176,7 +406,7 @@ const downloadGCloud = async (req, res) => {
} }
else { else {
res.status(404).send({ res.status(404).send({
message: "Could not download the file. " + err, message: "Could not download the file.",
}); });
} }
} catch (err) { } catch (err) {
@ -188,8 +418,13 @@ const downloadGCloud = async (req, res) => {
const deleteGCloud = async (privateUrl) => { const deleteGCloud = async (privateUrl) => {
try { try {
const {hash, bucket, processFile} = initGCloud(); const normalizedPrivateUrl = normalizePrivateUrl(privateUrl);
const filePath = `${hash}/${privateUrl}`; if (!normalizedPrivateUrl) {
return;
}
const {hash, bucket} = initGCloud();
const filePath = `${hash}/${normalizedPrivateUrl}`;
const file = bucket.file(filePath) const file = bucket.file(filePath)
const fileExists = await file.exists(); const fileExists = await file.exists();
@ -208,6 +443,13 @@ module.exports = {
downloadLocal, downloadLocal,
deleteGCloud, deleteGCloud,
uploadGCloud, uploadGCloud,
downloadGCloud downloadGCloud,
normalizePrivateUrl,
normalizeFilename,
normalizeFolder,
buildPublicDownloadUrl,
validateFileMetadata,
createDownloadToken,
isValidDownloadToken,
} }

View File

@ -1,11 +1,7 @@
const db = require('../db/models'); const db = require('../db/models');
const GradesDBApi = require('../db/api/grades'); const GradesDBApi = require('../db/api/grades');
const processFile = require("../middlewares/upload"); const parseImportFile = require('./importFileParser');
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -28,28 +24,13 @@ module.exports = class GradesService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async bulkImport(req, res, sendInvitationEmails = true, host) { static async bulkImport(req, res) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await processFile(req, res); const results = await parseImportFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await GradesDBApi.bulkImport(results, { await GradesDBApi.bulkImport(results, {
transaction, transaction,
@ -70,7 +51,7 @@ module.exports = class GradesService {
try { try {
let grades = await GradesDBApi.findBy( let grades = await GradesDBApi.findBy(
{id}, {id},
{transaction}, { transaction, currentUser },
); );
if (!grades) { if (!grades) {
@ -95,7 +76,7 @@ module.exports = class GradesService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async deleteByIds(ids, currentUser) { static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();

View File

@ -1,11 +1,7 @@
const db = require('../db/models'); const db = require('../db/models');
const GuardiansDBApi = require('../db/api/guardians'); const GuardiansDBApi = require('../db/api/guardians');
const processFile = require("../middlewares/upload"); const parseImportFile = require('./importFileParser');
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -28,28 +24,13 @@ module.exports = class GuardiansService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async bulkImport(req, res, sendInvitationEmails = true, host) { static async bulkImport(req, res) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await processFile(req, res); const results = await parseImportFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await GuardiansDBApi.bulkImport(results, { await GuardiansDBApi.bulkImport(results, {
transaction, transaction,
@ -70,7 +51,7 @@ module.exports = class GuardiansService {
try { try {
let guardians = await GuardiansDBApi.findBy( let guardians = await GuardiansDBApi.findBy(
{id}, {id},
{transaction}, { transaction, currentUser },
); );
if (!guardians) { if (!guardians) {
@ -95,7 +76,7 @@ module.exports = class GuardiansService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async deleteByIds(ids, currentUser) { static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();

View File

@ -0,0 +1,380 @@
const csv = require('csv-parser');
const stream = require('stream');
const zlib = require('zlib');
const processFile = require('../middlewares/upload');
function createBadRequest(message) {
const error = new Error(message);
error.code = 400;
return error;
}
function getExtension(filename) {
if (!filename) {
return '';
}
const match = String(filename).match(/\.([^.]+)$/);
return match ? match[1].toLowerCase() : '';
}
async function parseCsvBuffer(buffer) {
const bufferStream = new stream.PassThrough();
const results = [];
bufferStream.end(Buffer.from(buffer, 'utf-8'));
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', resolve)
.on('error', reject);
});
return results;
}
function findEndOfCentralDirectory(buffer) {
const signature = 0x06054b50;
const minOffset = Math.max(0, buffer.length - 0xffff - 22);
for (let offset = buffer.length - 22; offset >= minOffset; offset -= 1) {
if (buffer.readUInt32LE(offset) === signature) {
return offset;
}
}
throw createBadRequest('Invalid XLSX file.');
}
function readZipEntries(buffer) {
const centralDirectorySignature = 0x02014b50;
const localFileSignature = 0x04034b50;
const endOffset = findEndOfCentralDirectory(buffer);
const entriesCount = buffer.readUInt16LE(endOffset + 10);
let offset = buffer.readUInt32LE(endOffset + 16);
const entries = {};
for (let index = 0; index < entriesCount; index += 1) {
if (buffer.readUInt32LE(offset) !== centralDirectorySignature) {
throw createBadRequest('Invalid XLSX file.');
}
const compressionMethod = buffer.readUInt16LE(offset + 10);
const compressedSize = buffer.readUInt32LE(offset + 20);
const fileNameLength = buffer.readUInt16LE(offset + 28);
const extraFieldLength = buffer.readUInt16LE(offset + 30);
const fileCommentLength = buffer.readUInt16LE(offset + 32);
const localHeaderOffset = buffer.readUInt32LE(offset + 42);
const fileName = buffer.toString('utf8', offset + 46, offset + 46 + fileNameLength);
if (buffer.readUInt32LE(localHeaderOffset) !== localFileSignature) {
throw createBadRequest('Invalid XLSX file.');
}
const localFileNameLength = buffer.readUInt16LE(localHeaderOffset + 26);
const localExtraFieldLength = buffer.readUInt16LE(localHeaderOffset + 28);
const dataOffset = localHeaderOffset + 30 + localFileNameLength + localExtraFieldLength;
const compressedData = buffer.slice(dataOffset, dataOffset + compressedSize);
let data;
if (compressionMethod === 0) {
data = compressedData;
} else if (compressionMethod === 8) {
data = zlib.inflateRawSync(compressedData);
} else {
throw createBadRequest('Unsupported XLSX compression method.');
}
entries[fileName] = data;
offset += 46 + fileNameLength + extraFieldLength + fileCommentLength;
}
return entries;
}
function xmlText(entries, filename) {
return entries[filename] ? entries[filename].toString('utf8') : '';
}
function decodeXml(value) {
if (value === undefined || value === null) {
return '';
}
return String(value)
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&apos;/g, "'")
.replace(/&amp;/g, '&');
}
function getAttribute(source, name) {
const escapedName = name.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
const match = source.match(new RegExp(`(?:^|\\s)${escapedName}=(['"])(.*?)\\1`));
return match ? decodeXml(match[2]) : null;
}
function extractTextFromXmlFragment(fragment) {
const parts = [];
const textRegex = /<t\b[^>]*>([\s\S]*?)<\/t>/g;
let textMatch;
while ((textMatch = textRegex.exec(fragment)) !== null) {
parts.push(decodeXml(textMatch[1]));
}
if (parts.length) {
return parts.join('');
}
return decodeXml(fragment.replace(/<[^>]+>/g, ''));
}
function parseSharedStrings(sharedStringsXml) {
const sharedStrings = [];
const itemRegex = /<si\b[^>]*>([\s\S]*?)<\/si>/g;
let itemMatch;
while ((itemMatch = itemRegex.exec(sharedStringsXml)) !== null) {
sharedStrings.push(extractTextFromXmlFragment(itemMatch[1]));
}
return sharedStrings;
}
function parseDateStyleIndexes(stylesXml) {
if (!stylesXml) {
return new Set();
}
const builtInDateFormats = new Set([
14, 15, 16, 17, 18, 19, 20, 21, 22,
27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
45, 46, 47, 50, 51, 52, 53, 54, 55, 56, 57, 58,
]);
const customDateFormats = new Set();
const numberFormatRegex = /<numFmt\b([^>]*)\/>/g;
let numberFormatMatch;
while ((numberFormatMatch = numberFormatRegex.exec(stylesXml)) !== null) {
const numberFormatId = Number(getAttribute(numberFormatMatch[1], 'numFmtId'));
const formatCode = getAttribute(numberFormatMatch[1], 'formatCode') || '';
if (Number.isFinite(numberFormatId) && /[dy]/i.test(formatCode)) {
customDateFormats.add(numberFormatId);
}
}
const cellFormatsMatch = stylesXml.match(/<cellXfs\b[^>]*>([\s\S]*?)<\/cellXfs>/);
if (!cellFormatsMatch) {
return new Set();
}
const styleIndexes = new Set();
const formatRegex = /<xf\b([^>]*)\/?>(?:<\/xf>)?/g;
let styleIndex = 0;
let formatMatch;
while ((formatMatch = formatRegex.exec(cellFormatsMatch[1])) !== null) {
const numberFormatId = Number(getAttribute(formatMatch[1], 'numFmtId'));
if (builtInDateFormats.has(numberFormatId) || customDateFormats.has(numberFormatId)) {
styleIndexes.add(styleIndex);
}
styleIndex += 1;
}
return styleIndexes;
}
function excelSerialDateToIsoDate(value) {
const serial = Number(value);
if (!Number.isFinite(serial)) {
return value;
}
const milliseconds = Math.round((serial - 25569) * 86400 * 1000);
const date = new Date(milliseconds);
if (Number.isNaN(date.getTime())) {
return value;
}
return date.toISOString().slice(0, 10);
}
function columnNameToIndex(columnName) {
return columnName
.toUpperCase()
.split('')
.reduce((result, char) => result * 26 + char.charCodeAt(0) - 64, 0) - 1;
}
function getFirstSheetPath(entries) {
const workbookXml = xmlText(entries, 'xl/workbook.xml');
const relationshipsXml = xmlText(entries, 'xl/_rels/workbook.xml.rels');
if (!workbookXml || !relationshipsXml) {
return entries['xl/worksheets/sheet1.xml'] ? 'xl/worksheets/sheet1.xml' : null;
}
const sheetMatch = workbookXml.match(/<sheet\b([^>]*)\/?>/);
const relationshipId = sheetMatch ? getAttribute(sheetMatch[1], 'r:id') : null;
if (!relationshipId) {
return entries['xl/worksheets/sheet1.xml'] ? 'xl/worksheets/sheet1.xml' : null;
}
const relationshipRegex = /<Relationship\b([^>]*)\/?>/g;
let relationshipMatch;
while ((relationshipMatch = relationshipRegex.exec(relationshipsXml)) !== null) {
if (getAttribute(relationshipMatch[1], 'Id') === relationshipId) {
const target = getAttribute(relationshipMatch[1], 'Target');
if (!target) {
break;
}
if (target.startsWith('/')) {
return target.replace(/^\//, '');
}
return `xl/${target}`.replace(/\/\.\//g, '/');
}
}
return entries['xl/worksheets/sheet1.xml'] ? 'xl/worksheets/sheet1.xml' : null;
}
function readCellValue(cellAttributes, cellBody, sharedStrings, dateStyleIndexes) {
const cellType = getAttribute(cellAttributes, 't');
const styleIndex = Number(getAttribute(cellAttributes, 's'));
if (cellType === 'inlineStr') {
return extractTextFromXmlFragment(cellBody);
}
const valueMatch = cellBody.match(/<v\b[^>]*>([\s\S]*?)<\/v>/);
const rawValue = valueMatch ? decodeXml(valueMatch[1]) : '';
if (cellType === 's') {
return sharedStrings[Number(rawValue)] || '';
}
if (cellType === 'b') {
return rawValue === '1' ? 'true' : 'false';
}
if (dateStyleIndexes.has(styleIndex) && rawValue !== '') {
return excelSerialDateToIsoDate(rawValue);
}
return rawValue;
}
function parseWorksheetRows(sheetXml, sharedStrings, dateStyleIndexes) {
const rows = [];
const rowRegex = /<row\b[^>]*>([\s\S]*?)<\/row>/g;
let rowMatch;
while ((rowMatch = rowRegex.exec(sheetXml)) !== null) {
const row = [];
const cellRegex = /<c\b([^>]*?)>([\s\S]*?)<\/c>|<c\b([^>]*?)\/>/g;
let cellMatch;
let nextColumnIndex = 0;
while ((cellMatch = cellRegex.exec(rowMatch[1])) !== null) {
const cellAttributes = cellMatch[1] || cellMatch[3] || '';
const cellBody = cellMatch[2] || '';
const cellReference = getAttribute(cellAttributes, 'r');
const columnMatch = cellReference ? cellReference.match(/[A-Z]+/i) : null;
const columnIndex = columnMatch ? columnNameToIndex(columnMatch[0]) : nextColumnIndex;
row[columnIndex] = readCellValue(cellAttributes, cellBody, sharedStrings, dateStyleIndexes);
nextColumnIndex = columnIndex + 1;
}
if (row.some((value) => value !== undefined && value !== '')) {
rows.push(row);
}
}
return rows;
}
function rowsToObjects(rows) {
if (!rows.length) {
return [];
}
const headers = rows[0].map((header) => String(header || '').replace(/^\uFEFF/, '').trim());
return rows.slice(1).reduce((items, row) => {
const item = {};
headers.forEach((header, index) => {
if (!header) {
return;
}
const value = row[index];
item[header] = value === undefined ? '' : value;
});
if (Object.values(item).some((value) => value !== '')) {
items.push(item);
}
return items;
}, []);
}
function parseXlsxBuffer(buffer) {
const entries = readZipEntries(buffer);
const sheetPath = getFirstSheetPath(entries);
if (!sheetPath || !entries[sheetPath]) {
throw createBadRequest('XLSX file does not contain a worksheet.');
}
const sharedStrings = parseSharedStrings(xmlText(entries, 'xl/sharedStrings.xml'));
const dateStyleIndexes = parseDateStyleIndexes(xmlText(entries, 'xl/styles.xml'));
const rows = parseWorksheetRows(xmlText(entries, sheetPath), sharedStrings, dateStyleIndexes);
return rowsToObjects(rows);
}
async function parseImportFile(req, res) {
await processFile(req, res);
if (!req.file || !req.file.buffer) {
throw createBadRequest('No import file was uploaded.');
}
const filename = req.file.originalname || req.body.filename || '';
const extension = getExtension(filename);
if (extension === 'csv') {
return parseCsvBuffer(req.file.buffer);
}
if (extension === 'xlsx') {
return parseXlsxBuffer(req.file.buffer);
}
if (extension === 'xls') {
throw createBadRequest('Legacy .xls imports are not supported yet. Please save the spreadsheet as .xlsx or .csv.');
}
throw createBadRequest('Invalid import format. Allowed formats: .csv, .xlsx.');
}
module.exports = parseImportFile;
module.exports.parseCsvBuffer = parseCsvBuffer;
module.exports.parseXlsxBuffer = parseXlsxBuffer;

View File

@ -1,11 +1,7 @@
const db = require('../db/models'); const db = require('../db/models');
const InvoicesDBApi = require('../db/api/invoices'); const InvoicesDBApi = require('../db/api/invoices');
const processFile = require("../middlewares/upload"); const parseImportFile = require('./importFileParser');
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -28,28 +24,13 @@ module.exports = class InvoicesService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async bulkImport(req, res, sendInvitationEmails = true, host) { static async bulkImport(req, res) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await processFile(req, res); const results = await parseImportFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await InvoicesDBApi.bulkImport(results, { await InvoicesDBApi.bulkImport(results, {
transaction, transaction,
@ -70,7 +51,7 @@ module.exports = class InvoicesService {
try { try {
let invoices = await InvoicesDBApi.findBy( let invoices = await InvoicesDBApi.findBy(
{id}, {id},
{transaction}, { transaction, currentUser },
); );
if (!invoices) { if (!invoices) {
@ -95,7 +76,7 @@ module.exports = class InvoicesService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async deleteByIds(ids, currentUser) { static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();

View File

@ -2,20 +2,10 @@ const axios = require('axios');
const config = require('../config'); const config = require('../config');
const { LocalAIApi } = require('../ai/LocalAIApi'); const { LocalAIApi } = require('../ai/LocalAIApi');
const loadRoleService = () => {
try {
return require('./roles');
} catch (error) {
console.error('Role service is missing. Advanced roles are required for this operation.', error);
const err = new Error('Role service is missing. Advanced roles are required for this operation.');
err.originalError = error;
throw err;
}
};
module.exports = class OpenAiService { module.exports = class OpenAiService {
static async getWidget(payload, userId, roleId) { static async getWidget(payload) {
const RoleService = loadRoleService();
const response = await axios.post( const response = await axios.post(
`${config.flHost}/${config.project_uuid}/project_customization_widgets.json`, `${config.flHost}/${config.project_uuid}/project_customization_widgets.json`,
payload, payload,
@ -23,7 +13,6 @@ module.exports = class OpenAiService {
if (response.status >= 200 && response.status < 300) { if (response.status >= 200 && response.status < 300) {
const { widget_id } = await response.data; const { widget_id } = await response.data;
await RoleService.addRoleInfo(roleId, userId, 'widgets', widget_id);
return widget_id; return widget_id;
} else { } else {
console.error('=======error=======', response.data); console.error('=======error=======', response.data);

View File

@ -1,11 +1,7 @@
const db = require('../db/models'); const db = require('../db/models');
const PaymentsDBApi = require('../db/api/payments'); const PaymentsDBApi = require('../db/api/payments');
const processFile = require("../middlewares/upload"); const parseImportFile = require('./importFileParser');
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -28,28 +24,13 @@ module.exports = class PaymentsService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async bulkImport(req, res, sendInvitationEmails = true, host) { static async bulkImport(req, res) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await processFile(req, res); const results = await parseImportFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await PaymentsDBApi.bulkImport(results, { await PaymentsDBApi.bulkImport(results, {
transaction, transaction,
@ -70,7 +51,7 @@ module.exports = class PaymentsService {
try { try {
let payments = await PaymentsDBApi.findBy( let payments = await PaymentsDBApi.findBy(
{id}, {id},
{transaction}, { transaction, currentUser },
); );
if (!payments) { if (!payments) {
@ -95,7 +76,7 @@ module.exports = class PaymentsService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async deleteByIds(ids, currentUser) { static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();

View File

@ -1,11 +1,7 @@
const db = require('../db/models'); const db = require('../db/models');
const PermissionsDBApi = require('../db/api/permissions'); const PermissionsDBApi = require('../db/api/permissions');
const processFile = require("../middlewares/upload"); const parseImportFile = require('./importFileParser');
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -28,28 +24,13 @@ module.exports = class PermissionsService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async bulkImport(req, res, sendInvitationEmails = true, host) { static async bulkImport(req, res) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await processFile(req, res); const results = await parseImportFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await PermissionsDBApi.bulkImport(results, { await PermissionsDBApi.bulkImport(results, {
transaction, transaction,
@ -95,7 +76,7 @@ module.exports = class PermissionsService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async deleteByIds(ids, currentUser) { static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();

View File

@ -1,11 +1,7 @@
const db = require('../db/models'); const db = require('../db/models');
const ProductsDBApi = require('../db/api/products'); const ProductsDBApi = require('../db/api/products');
const processFile = require("../middlewares/upload"); const parseImportFile = require('./importFileParser');
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -28,28 +24,13 @@ module.exports = class ProductsService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async bulkImport(req, res, sendInvitationEmails = true, host) { static async bulkImport(req, res) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await processFile(req, res); const results = await parseImportFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await ProductsDBApi.bulkImport(results, { await ProductsDBApi.bulkImport(results, {
transaction, transaction,
@ -70,7 +51,7 @@ module.exports = class ProductsService {
try { try {
let products = await ProductsDBApi.findBy( let products = await ProductsDBApi.findBy(
{id}, {id},
{transaction}, { transaction, currentUser },
); );
if (!products) { if (!products) {
@ -95,7 +76,7 @@ module.exports = class ProductsService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async deleteByIds(ids, currentUser) { static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();

View File

@ -1,19 +1,41 @@
const db = require('../db/models'); const db = require('../db/models');
const RolesDBApi = require('../db/api/roles'); const RolesDBApi = require('../db/api/roles');
const processFile = require("../middlewares/upload"); const parseImportFile = require('./importFileParser');
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios'); const axios = require('axios');
const config = require('../config'); const config = require('../config');
const stream = require('stream'); const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const { executeReadOnlySelect } = require('./sqlSafety');
const WIDGET_CUSTOMIZATION_KEY = 'widgets';
const UUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
function badRequest(message) {
const error = new Error(message);
error.code = 400;
return error;
}
function assertWidgetKey(key) {
if (key !== WIDGET_CUSTOMIZATION_KEY) {
throw badRequest('Invalid role customization key.');
}
}
function assertUuid(value, label) {
if (typeof value !== 'string' || !UUID_REGEX.test(value)) {
throw badRequest(`${label} must be a valid UUID.`);
}
}
function buildWidgetResult(widget, queryResult, queryString) {
if (queryResult[0] && queryResult[0].length) { function buildWidgetResult(widget, rows, queryString) {
const key = Object.keys(queryResult[0][0])[0]; if (Array.isArray(rows) && rows.length) {
const value = widget.widget_type === 'scalar' ? queryResult[0][0][key] : queryResult[0]; const key = Object.keys(rows[0])[0];
const value = widget.widget_type === 'scalar' ? rows[0][key] : rows;
const widgetData = JSON.parse(widget.data); const widgetData = JSON.parse(widget.data);
return { ...widget, ...widgetData, value, query: queryString }; return { ...widget, ...widgetData, value, query: queryString };
} else { } else {
@ -21,14 +43,16 @@ function buildWidgetResult(widget, queryResult, queryString) {
} }
} }
async function executeQuery(queryString, currentUser) { async function executeQuery(queryString, replacements) {
try { try {
return await db.sequelize.query(queryString, { return await executeReadOnlySelect(queryString, {
replacements: { organizationId: currentUser.organizationId }, replacements,
maxRows: 1000,
timeoutMs: 5000,
}); });
} catch (e) { } catch (e) {
console.log(e); console.error('Widget SQL execution failed:', e);
return []; throw e;
} }
} }
@ -49,19 +73,36 @@ function insertWhereConditions(queryString, whereConditions) {
} }
function constructWhereConditions(mainTable, currentUser, replacements) { function constructWhereConditions(mainTable, currentUser, replacements) {
const { organizationId, app_role: { globalAccess } } = currentUser; const globalAccess = currentUser?.app_role?.globalAccess;
const tablesWithoutOrgId = ['permissions', 'roles']; const tablesWithoutSchoolScope = ['permissions', 'roles'];
let whereConditions = ''; const model = db[mainTable];
const rawAttributes = model?.rawAttributes || {};
const conditions = [];
if (!globalAccess && !tablesWithoutOrgId.includes(mainTable)) { if (!globalAccess && !tablesWithoutSchoolScope.includes(mainTable)) {
whereConditions += `"${mainTable}"."organizationId" = :organizationId`; const schoolId = getCurrentUserSchoolId(currentUser);
replacements.organizationId = organizationId;
if (!schoolId) {
conditions.push('1 = 0');
} else if (mainTable === 'schools') {
conditions.push(`"${mainTable}"."id" = :schoolId`);
replacements.schoolId = schoolId;
} else if (rawAttributes.schoolId) {
conditions.push(`"${mainTable}"."schoolId" = :schoolId`);
replacements.schoolId = schoolId;
} else if (rawAttributes.schoolsId) {
conditions.push(`"${mainTable}"."schoolsId" = :schoolId`);
replacements.schoolId = schoolId;
} else {
conditions.push('1 = 0');
}
} }
whereConditions += whereConditions ? ' AND ' : ''; if (rawAttributes.deletedAt) {
whereConditions += `"${mainTable}"."deletedAt" IS NULL`; conditions.push(`"${mainTable}"."deletedAt" IS NULL`);
}
return whereConditions; return conditions.join(' AND ');
} }
function extractTableName(queryString) { function extractTableName(queryString) {
@ -70,23 +111,22 @@ function extractTableName(queryString) {
return match ? match[2] : null; return match ? match[2] : null;
} }
function buildQueryString(widget, currentUser) { function buildQuery(widget, currentUser) {
let queryString = widget?.query || ''; let queryString = widget?.query || '';
const tableName = extractTableName(queryString); const tableName = extractTableName(queryString);
const mainTable = JSON.parse(widget?.data)?.main_table || tableName; const mainTable = JSON.parse(widget?.data)?.main_table || tableName;
const replacements = {}; const replacements = {};
const whereConditions = constructWhereConditions(mainTable, currentUser, replacements); const whereConditions = constructWhereConditions(mainTable, currentUser, replacements);
queryString = insertWhereConditions(queryString, whereConditions); queryString = insertWhereConditions(queryString, whereConditions);
console.log(queryString, 'queryString'); return { queryString, replacements };
return queryString;
} }
async function constructWidgetsResults(widgets, currentUser) { async function constructWidgetsResults(widgets, currentUser) {
const widgetsResults = []; const widgetsResults = [];
for (const widget of widgets) { for (const widget of widgets) {
if (!widget) continue; if (!widget) continue;
const queryString = buildQueryString(widget, currentUser); const { queryString, replacements } = buildQuery(widget, currentUser);
const queryResult = await executeQuery(queryString, currentUser); const queryResult = await executeQuery(queryString, replacements);
widgetsResults.push(buildWidgetResult(widget, queryResult, queryString)); widgetsResults.push(buildWidgetResult(widget, queryResult, queryString));
} }
return widgetsResults; return widgetsResults;
@ -97,6 +137,10 @@ async function fetchWidgetsData(widgets) {
axios.get(`${config.flHost}/${config.project_uuid}/project_customization_widgets/${widgetId}.json`) axios.get(`${config.flHost}/${config.project_uuid}/project_customization_widgets/${widgetId}.json`)
); );
const widgetResults = widgetPromises ? await Promise.allSettled(widgetPromises) : []; const widgetResults = widgetPromises ? await Promise.allSettled(widgetPromises) : [];
widgetResults
.filter(result => result.status === 'rejected')
.forEach(result => console.error('Widget fetch failed:', result.reason));
return widgetResults return widgetResults
.filter(result => result.status === 'fulfilled') .filter(result => result.status === 'fulfilled')
.map(result => result.value.data); .map(result => result.value.data);
@ -116,7 +160,7 @@ function parseCustomization(role) {
} }
} }
async function findRole(roleId, currentUser) { async function findRole(roleId) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
const role = roleId const role = roleId
@ -148,28 +192,13 @@ module.exports = class RolesService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async bulkImport(req, res, sendInvitationEmails = true, host) { static async bulkImport(req, res) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await processFile(req, res); const results = await parseImportFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await RolesDBApi.bulkImport(results, { await RolesDBApi.bulkImport(results, {
transaction, transaction,
@ -215,7 +244,7 @@ module.exports = class RolesService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async deleteByIds(ids, currentUser) { static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
@ -254,10 +283,11 @@ module.exports = class RolesService {
static async addRoleInfo(roleId, userId, key, widgetId, currentUser) { static async addRoleInfo(roleId, userId, key, widgetId, currentUser) {
const regexExpForUuid = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/gi; assertWidgetKey(key);
const widgetIdIsUUID = regexExpForUuid.test(widgetId); assertUuid(widgetId, 'Widget ID');
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try {
let role; let role;
if (roleId) { if (roleId) {
role = await RolesDBApi.findBy({ id: roleId }, { transaction }); role = await RolesDBApi.findBy({ id: roleId }, { transaction });
@ -269,7 +299,6 @@ module.exports = class RolesService {
throw new ValidationError('rolesNotFound'); throw new ValidationError('rolesNotFound');
} }
try {
let customization = {}; let customization = {};
try { try {
customization = JSON.parse(role.role_customization || '{}'); customization = JSON.parse(role.role_customization || '{}');
@ -277,12 +306,12 @@ module.exports = class RolesService {
console.log(e); console.log(e);
} }
if (widgetIdIsUUID && Array.isArray(customization[key])) { if (Array.isArray(customization[key])) {
const el = customization[key].find((e) => e === widgetId); const el = customization[key].find((e) => e === widgetId);
!el ? customization[key].unshift(widgetId) : null; if (!el) {
customization[key].unshift(widgetId);
} }
} else {
if (widgetIdIsUUID && !customization[key]) {
customization[key] = [widgetId]; customization[key] = [widgetId];
} }
@ -310,8 +339,11 @@ module.exports = class RolesService {
} }
static async removeRoleInfoById(infoId, roleId, key, currentUser) { static async removeRoleInfoById(infoId, roleId, key, currentUser) {
const transaction = await db.sequelize.transaction(); assertWidgetKey(key);
assertUuid(infoId, 'Widget ID');
const transaction = await db.sequelize.transaction();
try {
let role; let role;
if (roleId) { if (roleId) {
role = await RolesDBApi.findBy({ id: roleId }, { transaction }); role = await RolesDBApi.findBy({ id: roleId }, { transaction });
@ -319,7 +351,6 @@ module.exports = class RolesService {
role = await RolesDBApi.findBy({ name: 'User' }, { transaction }); role = await RolesDBApi.findBy({ name: 'User' }, { transaction });
} }
if (!role) { if (!role) {
await transaction.rollback();
throw new ValidationError('rolesNotFound'); throw new ValidationError('rolesNotFound');
} }
@ -330,13 +361,15 @@ module.exports = class RolesService {
console.log(e); console.log(e);
} }
if (!Array.isArray(customization[key])) {
throw badRequest('Widget customization not found.');
}
customization[key] = customization[key].filter( customization[key] = customization[key].filter(
(item) => item !== infoId, (item) => item !== infoId,
); );
const response = await axios.delete(`${config.flHost}/${config.project_uuid}/project_customization_widgets/${infoId}.json`); await axios.delete(`${config.flHost}/${config.project_uuid}/project_customization_widgets/${infoId}.json`);
const { status } = await response;
try {
const result = await RolesDBApi.update( const result = await RolesDBApi.update(
role.id, role.id,
{ {
@ -362,14 +395,8 @@ module.exports = class RolesService {
static async getRoleInfoByKey(key, roleId, currentUser) { static async getRoleInfoByKey(key, roleId, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
const organizationId = currentUser.organizationId;
let globalAccess = currentUser.app_role?.globalAccess;
let queryString = '';
try { try {
const role = await findRole(roleId, currentUser); const role = await findRole(roleId);
const customization = parseCustomization(role); const customization = parseCustomization(role);
let result; let result;
@ -384,16 +411,11 @@ module.exports = class RolesService {
} catch (error) { } catch (error) {
console.error(error); console.error(error);
await transaction.rollback(); await transaction.rollback();
} finally { throw error;
if (transaction.finished !== 'commit') {
await transaction.rollback();
} }
} }
}
}; };

View File

@ -1,22 +1,50 @@
const db = require('../db/models'); const db = require('../db/models');
const SchoolsDBApi = require('../db/api/schools'); const SchoolsDBApi = require('../db/api/schools');
const processFile = require("../middlewares/upload"); const parseImportFile = require('./importFileParser');
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
const SCHOOL_STATUSES = new Set(['active', 'setup', 'suspended']);
function cleanString(value) {
if (value === undefined || value === null) return null;
const text = String(value).trim();
return text || null;
}
function normalizeSchoolData(data = {}) {
const normalized = {
name: cleanString(data.name),
nif: cleanString(data.nif),
phone: cleanString(data.phone),
email: cleanString(data.email),
province: cleanString(data.province),
municipality: cleanString(data.municipality),
address: cleanString(data.address),
logoUrl: cleanString(data.logoUrl),
status: cleanString(data.status) || 'active',
};
if (!normalized.name) {
throw new ValidationError('schoolsNameRequired');
}
if (normalized.email && !normalized.email.includes('@')) {
throw new ValidationError('schoolsEmailInvalid');
}
if (!SCHOOL_STATUSES.has(normalized.status)) {
throw new ValidationError('schoolsStatusInvalid');
}
return normalized;
}
module.exports = class SchoolsService { module.exports = class SchoolsService {
static async create(data, currentUser) { static async create(data, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await SchoolsDBApi.create( await SchoolsDBApi.create(
data, normalizeSchoolData(data),
{ {
currentUser, currentUser,
transaction, transaction,
@ -28,28 +56,13 @@ module.exports = class SchoolsService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async bulkImport(req, res, sendInvitationEmails = true, host) { static async bulkImport(req, res) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await processFile(req, res); const results = await parseImportFile(req, res);
const bufferStream = new stream.PassThrough();
const results = [];
await bufferStream.end(Buffer.from(req.file.buffer, "utf-8")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await SchoolsDBApi.bulkImport(results, { await SchoolsDBApi.bulkImport(results, {
transaction, transaction,
@ -70,7 +83,7 @@ module.exports = class SchoolsService {
try { try {
let schools = await SchoolsDBApi.findBy( let schools = await SchoolsDBApi.findBy(
{id}, {id},
{transaction}, { transaction, currentUser },
); );
if (!schools) { if (!schools) {
@ -81,7 +94,7 @@ module.exports = class SchoolsService {
const updatedSchools = await SchoolsDBApi.update( const updatedSchools = await SchoolsDBApi.update(
id, id,
data, normalizeSchoolData(data),
{ {
currentUser, currentUser,
transaction, transaction,
@ -95,7 +108,7 @@ module.exports = class SchoolsService {
await transaction.rollback(); await transaction.rollback();
throw error; throw error;
} }
}; }
static async deleteByIds(ids, currentUser) { static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();

View File

@ -3,6 +3,35 @@ const ValidationError = require('./notifications/errors/validation');
const Sequelize = db.Sequelize; const Sequelize = db.Sequelize;
const Op = Sequelize.Op; const Op = Sequelize.Op;
const { getCurrentUserSchoolId } = require('../db/api/schoolScope');
const MAX_SEARCH_QUERY_LENGTH = 100;
const MAX_RESULTS_PER_TABLE = 10;
const MAX_TOTAL_SEARCH_RESULTS = 100;
function normalizeSearchQuery(searchQuery) {
if (typeof searchQuery !== 'string') {
throw new ValidationError('iam.errors.searchQueryRequired');
}
const normalized = searchQuery.trim();
if (!normalized) {
throw new ValidationError('iam.errors.searchQueryRequired');
}
if (normalized.length > MAX_SEARCH_QUERY_LENGTH) {
const error = new Error(`Search query is too long. Maximum length is ${MAX_SEARCH_QUERY_LENGTH} characters.`);
error.code = 400;
throw error;
}
return normalized;
}
function escapeLikePattern(value) {
return value.replace(/[\\%_]/g, '\\$&');
}
/** /**
* @param {string} permission * @param {string} permission
@ -14,7 +43,10 @@ async function checkPermissions(permission, currentUser) {
throw new ValidationError('auth.unauthorized'); throw new ValidationError('auth.unauthorized');
} }
const userPermission = currentUser.custom_permissions.find( const customPermissions = Array.isArray(currentUser.custom_permissions)
? currentUser.custom_permissions
: [];
const userPermission = customPermissions.find(
(cp) => cp.name === permission, (cp) => cp.name === permission,
); );
@ -22,7 +54,6 @@ async function checkPermissions(permission, currentUser) {
return true; return true;
} }
try {
if (!currentUser.app_role) { if (!currentUser.app_role) {
throw new ValidationError('auth.forbidden'); throw new ValidationError('auth.forbidden');
} }
@ -30,17 +61,14 @@ async function checkPermissions(permission, currentUser) {
const permissions = await currentUser.app_role.getPermissions(); const permissions = await currentUser.app_role.getPermissions();
return !!permissions.find((p) => p.name === permission); return !!permissions.find((p) => p.name === permission);
} catch (e) {
throw e;
}
} }
module.exports = class SearchService { module.exports = class SearchService {
static async search(searchQuery, currentUser , organizationId, globalAccess) { static async search(searchQuery, currentUser, globalAccess) {
try { const normalizedSearchQuery = normalizeSearchQuery(searchQuery);
if (!searchQuery) { const likeSearchQuery = `%${escapeLikePattern(normalizedSearchQuery)}%`;
throw new ValidationError('iam.errors.searchQueryRequired'); const lowerSearchQuery = normalizedSearchQuery.toLowerCase();
}
const tableColumns = { const tableColumns = {
@ -427,54 +455,70 @@ module.exports = class SearchService {
let allFoundRecords = []; let allFoundRecords = [];
for (const tableName in tableColumns) { for (const [tableName, attributesToSearch] of Object.entries(tableColumns)) {
if (tableColumns.hasOwnProperty(tableName)) { const model = db[tableName];
const attributesToSearch = tableColumns[tableName]; if (!model) {
continue;
}
const attributesIntToSearch = columnsInt[tableName] || []; const attributesIntToSearch = columnsInt[tableName] || [];
const whereCondition = { const whereCondition = {
[Op.or]: [ [Op.or]: [
...attributesToSearch.map(attribute => ({ ...attributesToSearch.map(attribute => ({
[attribute]: { [attribute]: {
[Op.iLike] : `%${searchQuery}%`, [Op.iLike]: likeSearchQuery,
}, },
})), })),
...attributesIntToSearch.map(attribute => ( ...attributesIntToSearch.map(attribute => (
Sequelize.where( Sequelize.where(
Sequelize.cast(Sequelize.col(`${tableName}.${attribute}`), 'varchar'), Sequelize.cast(Sequelize.col(`${tableName}.${attribute}`), 'varchar'),
{ [Op.iLike]: `%${searchQuery}%` } { [Op.iLike]: likeSearchQuery }
) )
)), )),
], ],
}; };
if (!globalAccess) {
const schoolId = getCurrentUserSchoolId(currentUser);
const rawAttributes = model.rawAttributes || {};
if (!globalAccess && tableName !== 'organizations' && organizationId) { if (!schoolId) {
whereCondition.organizationId = organizationId; whereCondition.id = null;
} else if (tableName === 'schools') {
whereCondition.id = schoolId;
} else if (rawAttributes.schoolId) {
whereCondition.schoolId = schoolId;
} else if (rawAttributes.schoolsId) {
whereCondition.schoolsId = schoolId;
} else {
whereCondition.id = null;
}
} }
const hasPermission = await checkPermissions(`READ_${tableName.toUpperCase()}`, currentUser); const hasPermission = await checkPermissions(`READ_${tableName.toUpperCase()}`, currentUser);
if (!hasPermission) { if (!hasPermission) {
continue; continue;
} }
const foundRecords = await db[tableName].findAll({ const foundRecords = await model.findAll({
where: whereCondition, where: whereCondition,
attributes: [...tableColumns[tableName], 'id', ...attributesIntToSearch], attributes: [...new Set([...attributesToSearch, 'id', ...attributesIntToSearch])],
limit: MAX_RESULTS_PER_TABLE,
}); });
const modifiedRecords = foundRecords.map((record) => { const modifiedRecords = foundRecords.map((record) => {
const matchAttribute = []; const matchAttribute = [];
for (const attribute of attributesToSearch) { for (const attribute of attributesToSearch) {
if (record[attribute]?.toLowerCase()?.includes(searchQuery.toLowerCase())) { const value = record[attribute];
if (typeof value === 'string' && value.toLowerCase().includes(lowerSearchQuery)) {
matchAttribute.push(attribute); matchAttribute.push(attribute);
} }
} }
for (const attribute of attributesIntToSearch) { for (const attribute of attributesIntToSearch) {
const castedValue = String(record[attribute]); const castedValue = String(record[attribute] ?? '');
if (castedValue && castedValue.toLowerCase().includes(searchQuery.toLowerCase())) { if (castedValue.toLowerCase().includes(lowerSearchQuery)) {
matchAttribute.push(attribute); matchAttribute.push(attribute);
} }
} }
@ -487,12 +531,11 @@ module.exports = class SearchService {
}); });
allFoundRecords = allFoundRecords.concat(modifiedRecords); allFoundRecords = allFoundRecords.concat(modifiedRecords);
if (allFoundRecords.length >= MAX_TOTAL_SEARCH_RESULTS) {
break;
} }
} }
return allFoundRecords; return allFoundRecords.slice(0, MAX_TOTAL_SEARCH_RESULTS);
} catch (error) {
throw error;
}
} }
} }

Some files were not shown because too many files have changed in this diff Show More