Compare commits

..

No commits in common. "ai-dev" and "master" have entirely different histories.

200 changed files with 2869 additions and 5498 deletions

View File

@ -154,7 +154,7 @@ async function awaitResponse(aiRequestId, options = {}) {
const interval = Math.max(Number(options.interval ?? 5), 1);
const deadline = Date.now() + Math.max(timeout, interval) * 1000;
while (Date.now() < deadline) {
while (true) {
const statusResp = await fetchStatus(aiRequestId, {
headers: options.headers,
timeout: options.timeout_per_call,
@ -184,14 +184,16 @@ async function awaitResponse(aiRequestId, options = {}) {
return statusResp;
}
if (Date.now() >= deadline) {
return {
success: false,
error: "timeout",
message: "Timed out waiting for AI response.",
};
}
await sleep(interval * 1000);
}
return {
success: false,
error: "timeout",
message: "Timed out waiting for AI response.",
};
}
function extractText(response) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,8 +3,6 @@ const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const ForbiddenError = require('../../services/notifications/errors/forbidden');
const { applySchoolScope, applySchoolScopeById, assertRecordInCurrentSchool, resolveSchoolIdForMutation } = require('./schoolScope');
const bcrypt = require('bcrypt');
const config = require('../../config');
@ -14,71 +12,12 @@ const config = require('../../config');
const Sequelize = db.Sequelize;
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 {
static async create(data,globalAccess, options) {
const currentUser = (options && options.currentUser) || { id: null };
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(
{
@ -173,7 +112,7 @@ module.exports = class UsersDBApi {
await users.setSchools(resolveSchoolIdForMutation(data.data.schools, currentUser), {
await users.setSchools( data.data.schools || null, {
transaction,
});
@ -276,7 +215,6 @@ module.exports = class UsersDBApi {
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
schoolsId: resolveSchoolIdForMutation(item.schools, currentUser),
createdAt: new Date(Date.now() + index * 1000),
}));
@ -304,20 +242,10 @@ module.exports = class UsersDBApi {
static async update(id, data, globalAccess, options) {
const currentUser = (options && options.currentUser) || {id: null};
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});
assertRecordInCurrentSchool(users, currentUser, 'schoolsId');
if (appRoleProvided) {
await assertAssignableRole(data.app_role, currentUser, transaction);
}
if (customPermissionsProvided) {
assertCanAssignCustomPermissions(data.custom_permissions, currentUser);
}
@ -396,7 +324,7 @@ module.exports = class UsersDBApi {
if (data.schools !== undefined) {
await users.setSchools(
resolveSchoolIdForMutation(data.schools, currentUser),
data.schools,
{ transaction }
);
@ -411,17 +339,15 @@ module.exports = class UsersDBApi {
if (data.avatar !== undefined) {
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.users.getTableName(),
belongsToColumn: 'avatar',
belongsToId: users.id,
},
data.avatar,
options,
);
}
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.users.getTableName(),
belongsToColumn: 'avatar',
belongsToId: users.id,
},
data.avatar,
options,
);
return users;
@ -440,8 +366,6 @@ module.exports = class UsersDBApi {
transaction,
});
users.forEach((record) => assertRecordInCurrentSchool(record, currentUser, 'schoolsId'));
await db.sequelize.transaction(async (transaction) => {
for (const record of users) {
await record.update(
@ -463,7 +387,6 @@ module.exports = class UsersDBApi {
const transaction = (options && options.transaction) || undefined;
const users = await db.users.findByPk(id, options);
assertRecordInCurrentSchool(users, currentUser, 'schoolsId');
await users.update({
deletedBy: currentUser.id
@ -480,9 +403,6 @@ module.exports = class UsersDBApi {
static async findBy(where, options) {
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(
{ where },
@ -556,11 +476,26 @@ module.exports = class UsersDBApi {
let offset = 0;
let where = {};
const currentPage = +filter.page;
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;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
{
@ -842,7 +777,11 @@ module.exports = class UsersDBApi {
}
applySchoolScope(where, globalAccess, user, 'schoolsId');
if (globalAccess) {
delete where.schoolsId;
}
const queryOptions = {
where,
@ -873,9 +812,14 @@ module.exports = class UsersDBApi {
}
}
static async findAllAutocomplete(query, limit, offset, globalAccess, schoolId,) {
static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId,) {
let where = {};
if (!globalAccess && organizationId) {
where.organizationId = organizationId;
}
if (query) {
where = {
@ -890,8 +834,6 @@ module.exports = class UsersDBApi {
};
}
applySchoolScopeById(where, globalAccess, schoolId, 'schoolsId');
const records = await db.users.findAll({
attributes: [ 'id', 'firstName' ],
where,
@ -916,7 +858,7 @@ module.exports = class UsersDBApi {
authenticationUid: data.authenticationUid,
password: data.password,
schoolsId: data.organizationId,
organizationId: data.organizationId,
},
{ transaction },

View File

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

View File

@ -1,50 +0,0 @@
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,3 +1,8 @@
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) {
const assessments = sequelize.define(

View File

@ -1,3 +1,8 @@
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) {
const attendance = sequelize.define(

View File

@ -1,3 +1,8 @@
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) {
const book_loans = sequelize.define(

View File

@ -1,3 +1,8 @@
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) {
const books = sequelize.define(

View File

@ -1,3 +1,8 @@
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) {
const classes = sequelize.define(

View File

@ -1,3 +1,8 @@
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) {
const courses = sequelize.define(

View File

@ -1,3 +1,8 @@
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) {
const employees = sequelize.define(

View File

@ -1,3 +1,8 @@
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) {
const enrollments = sequelize.define(

View File

@ -1,3 +1,8 @@
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) {
const grades = sequelize.define(

View File

@ -1,3 +1,8 @@
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) {
const guardians = sequelize.define(

View File

@ -1,3 +1,8 @@
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) {
const invoices = sequelize.define(

View File

@ -1,3 +1,8 @@
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) {
const payments = sequelize.define(

View File

@ -1,3 +1,8 @@
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) {
const permissions = sequelize.define(

View File

@ -1,3 +1,8 @@
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) {
const products = sequelize.define(

View File

@ -1,3 +1,8 @@
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) {
const roles = sequelize.define(

View File

@ -1,3 +1,9 @@
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) {
const schools = sequelize.define(
'schools',
@ -13,63 +19,6 @@ 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: {

View File

@ -1,3 +1,8 @@
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) {
const student_guardians = sequelize.define(

View File

@ -1,3 +1,8 @@
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) {
const students = sequelize.define(

View File

@ -1,3 +1,8 @@
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) {
const subjects = sequelize.define(

View File

@ -1,3 +1,8 @@
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) {
const teachers = sequelize.define(

View File

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

View File

@ -10,7 +10,7 @@ const ids = [
]
module.exports = {
up: async (queryInterface) => {
up: async (queryInterface, Sequelize) => {
let admin_hash = bcrypt.hashSync(config.admin_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 = [
"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", [{ id: getId(`READ_API_DOCS`), createdAt, updatedAt, name: `READ_API_DOCS` }]);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,109 +5,6 @@ const router = express.Router();
const sjs = require('sequelize-json-schema');
const { getWidget, askGpt } = require('../services/openai');
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 = () => {
try {
@ -174,17 +71,12 @@ const loadRolesModules = () => {
router.delete(
'/roles-info/:infoId',
checkPermissions('UPDATE_ROLES'),
wrapAsync(async (req, res) => {
assertGlobalRoleAccess(req.currentUser);
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(
infoId,
roleId,
key,
req.query.infoId,
req.query.roleId,
req.query.key,
req.currentUser,
);
@ -239,46 +131,33 @@ router.get(
'/info-by-key',
wrapAsync(async (req, res) => {
const { RolesService, RolesDBApi } = loadRolesModules();
const roleId = req.query.roleId;
const key = req.query.key;
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(
key,
roleId,
currentUser,
);
if (!role.role_customization && await canGenerateRoleWidgets(currentUser)) {
const schema = await sjs.getSequelizeSchema(db.sequelize, {});
const widgetResults = await Promise.allSettled(['pie', 'bar'].map(async (chartType) => {
const role = await RolesDBApi.findBy({ id: roleId });
if (!role?.role_customization) {
await Promise.all(["pie", "bar"].map(async (e) => {
const schema = await sjs.getSequelizeSchema(db.sequelize, {});
const payload = {
description: `Create some cool ${chartType} chart`,
description: `Create some cool ${e} chart`,
modelDefinition: schema.definitions,
};
const widgetId = await getWidget(payload);
if (typeof widgetId === 'string') {
const widgetId = await getWidget(payload, currentUser?.id, roleId);
if (widgetId) {
await RolesService.addRoleInfo(
roleId,
currentUser?.id,
WIDGET_CUSTOMIZATION_KEY,
'widgets',
widgetId,
req.currentUser,
);
}
}));
widgetResults
.filter((result) => result.status === 'rejected')
.forEach((result) => console.error('Default widget creation failed:', result.reason));
}))
info = await RolesService.getRoleInfoByKey(
key,
roleId,
@ -291,13 +170,9 @@ router.get(
router.post(
'/create_widget',
checkPermissions('UPDATE_ROLES'),
wrapAsync(async (req, res) => {
assertGlobalRoleAccess(req.currentUser);
const { RolesService } = loadRolesModules();
const { userId } = req.body || {};
const description = normalizeText(req.body?.description, 'Description', 1000);
const roleId = normalizeUuid(req.body?.roleId, 'Role ID');
const { description, userId, roleId } = req.body;
const currentUser = req.currentUser;
const schema = await sjs.getSequelizeSchema(db.sequelize, {});
@ -306,13 +181,13 @@ router.post(
modelDefinition: schema.definitions,
};
const widgetId = await getWidget(payload);
const widgetId = await getWidget(payload, userId, roleId);
if (typeof widgetId === 'string') {
if (widgetId) {
await RolesService.addRoleInfo(
roleId,
userId,
WIDGET_CUSTOMIZATION_KEY,
'widgets',
widgetId,
currentUser,
);
@ -372,10 +247,9 @@ router.post(
'/response',
wrapAsync(async (req, res) => {
const body = req.body || {};
const options = normalizeAiProxyOptions(body.options || {});
const options = body.options || {};
const payload = { ...body };
delete payload.options;
delete payload.project_uuid;
const response = await LocalAIApi.createResponse(payload, options);
@ -432,7 +306,13 @@ router.post(
router.post(
'/ask-gpt',
wrapAsync(async (req, res) => {
const prompt = normalizeText(req.body?.prompt, 'Prompt', 4000);
const { prompt } = req.body;
if (!prompt) {
return res.status(400).send({
success: false,
error: 'Prompt is required',
});
}
const response = await askGpt(prompt);
@ -445,6 +325,4 @@ router.post(
);
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,11 @@
const db = require('../db/models');
const AssessmentsDBApi = require('../db/api/assessments');
const parseImportFile = require('./importFileParser');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -24,13 +28,28 @@ module.exports = class AssessmentsService {
await transaction.rollback();
throw error;
}
}
};
static async bulkImport(req, res) {
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
const results = await parseImportFile(req, res);
await processFile(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, {
transaction,
@ -51,7 +70,7 @@ module.exports = class AssessmentsService {
try {
let assessments = await AssessmentsDBApi.findBy(
{id},
{ transaction, currentUser },
{transaction},
);
if (!assessments) {
@ -76,7 +95,7 @@ module.exports = class AssessmentsService {
await transaction.rollback();
throw error;
}
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();

View File

@ -1,7 +1,11 @@
const db = require('../db/models');
const AttendanceDBApi = require('../db/api/attendance');
const parseImportFile = require('./importFileParser');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -24,13 +28,28 @@ module.exports = class AttendanceService {
await transaction.rollback();
throw error;
}
}
};
static async bulkImport(req, res) {
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
const results = await parseImportFile(req, res);
await processFile(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, {
transaction,
@ -51,7 +70,7 @@ module.exports = class AttendanceService {
try {
let attendance = await AttendanceDBApi.findBy(
{id},
{ transaction, currentUser },
{transaction},
);
if (!attendance) {
@ -76,7 +95,7 @@ module.exports = class AttendanceService {
await transaction.rollback();
throw error;
}
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();

View File

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

View File

@ -1,7 +1,11 @@
const db = require('../db/models');
const Book_loansDBApi = require('../db/api/book_loans');
const parseImportFile = require('./importFileParser');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -24,13 +28,28 @@ module.exports = class Book_loansService {
await transaction.rollback();
throw error;
}
}
};
static async bulkImport(req, res) {
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
const results = await parseImportFile(req, res);
await processFile(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, {
transaction,
@ -51,7 +70,7 @@ module.exports = class Book_loansService {
try {
let book_loans = await Book_loansDBApi.findBy(
{id},
{ transaction, currentUser },
{transaction},
);
if (!book_loans) {
@ -76,7 +95,7 @@ module.exports = class Book_loansService {
await transaction.rollback();
throw error;
}
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();

View File

@ -1,7 +1,11 @@
const db = require('../db/models');
const BooksDBApi = require('../db/api/books');
const parseImportFile = require('./importFileParser');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -24,13 +28,28 @@ module.exports = class BooksService {
await transaction.rollback();
throw error;
}
}
};
static async bulkImport(req, res) {
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
const results = await parseImportFile(req, res);
await processFile(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, {
transaction,
@ -51,7 +70,7 @@ module.exports = class BooksService {
try {
let books = await BooksDBApi.findBy(
{id},
{ transaction, currentUser },
{transaction},
);
if (!books) {
@ -76,7 +95,7 @@ module.exports = class BooksService {
await transaction.rollback();
throw error;
}
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();

View File

@ -1,7 +1,11 @@
const db = require('../db/models');
const ClassesDBApi = require('../db/api/classes');
const parseImportFile = require('./importFileParser');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -24,13 +28,28 @@ module.exports = class ClassesService {
await transaction.rollback();
throw error;
}
}
};
static async bulkImport(req, res) {
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
const results = await parseImportFile(req, res);
await processFile(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, {
transaction,
@ -51,7 +70,7 @@ module.exports = class ClassesService {
try {
let classes = await ClassesDBApi.findBy(
{id},
{ transaction, currentUser },
{transaction},
);
if (!classes) {
@ -76,7 +95,7 @@ module.exports = class ClassesService {
await transaction.rollback();
throw error;
}
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();

View File

@ -1,7 +1,11 @@
const db = require('../db/models');
const CoursesDBApi = require('../db/api/courses');
const parseImportFile = require('./importFileParser');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -24,13 +28,28 @@ module.exports = class CoursesService {
await transaction.rollback();
throw error;
}
}
};
static async bulkImport(req, res) {
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
const results = await parseImportFile(req, res);
await processFile(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, {
transaction,
@ -51,7 +70,7 @@ module.exports = class CoursesService {
try {
let courses = await CoursesDBApi.findBy(
{id},
{ transaction, currentUser },
{transaction},
);
if (!courses) {
@ -76,7 +95,7 @@ module.exports = class CoursesService {
await transaction.rollback();
throw error;
}
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();

View File

@ -1,7 +1,11 @@
const db = require('../db/models');
const EmployeesDBApi = require('../db/api/employees');
const parseImportFile = require('./importFileParser');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -24,13 +28,28 @@ module.exports = class EmployeesService {
await transaction.rollback();
throw error;
}
}
};
static async bulkImport(req, res) {
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
const results = await parseImportFile(req, res);
await processFile(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, {
transaction,
@ -51,7 +70,7 @@ module.exports = class EmployeesService {
try {
let employees = await EmployeesDBApi.findBy(
{id},
{ transaction, currentUser },
{transaction},
);
if (!employees) {
@ -76,7 +95,7 @@ module.exports = class EmployeesService {
await transaction.rollback();
throw error;
}
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();

View File

@ -1,7 +1,11 @@
const db = require('../db/models');
const EnrollmentsDBApi = require('../db/api/enrollments');
const parseImportFile = require('./importFileParser');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -24,13 +28,28 @@ module.exports = class EnrollmentsService {
await transaction.rollback();
throw error;
}
}
};
static async bulkImport(req, res) {
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
const results = await parseImportFile(req, res);
await processFile(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, {
transaction,
@ -51,7 +70,7 @@ module.exports = class EnrollmentsService {
try {
let enrollments = await EnrollmentsDBApi.findBy(
{id},
{ transaction, currentUser },
{transaction},
);
if (!enrollments) {
@ -76,7 +95,7 @@ module.exports = class EnrollmentsService {
await transaction.rollback();
throw error;
}
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();

View File

@ -2,172 +2,7 @@ const formidable = require('formidable');
const fs = require('fs');
const config = require('../config');
const path = require('path');
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 { format } = require("util");
const ensureDirectoryExistence = (filePath) => {
const dirname = path.dirname(filePath);
@ -201,29 +36,20 @@ const uploadLocal = (
return;
}
let uploadFolder = folder;
if (validations.folderIncludesAuthenticationUid) {
uploadFolder = uploadFolder.replace(
folder = folder.replace(
':userId',
req.currentUser.authenticationUid,
);
if (
!req.currentUser.authenticationUid ||
!uploadFolder.includes(req.currentUser.authenticationUid)
!folder.includes(req.currentUser.authenticationUid)
) {
res.sendStatus(403);
return;
}
}
uploadFolder = normalizeFolder(uploadFolder);
if (!uploadFolder) {
res.status(400).send({ message: 'Invalid upload path.' });
return;
}
const form = new formidable.IncomingForm();
form.uploadDir = config.uploadDir;
@ -232,83 +58,44 @@ const uploadLocal = (
}
form.parse(req, function (err, fields, files) {
if (err) {
console.error('Upload parse error:', err);
res.status(500).send(err);
return;
}
const filename = String(fields.filename);
const fileTempUrl = files.file.path;
const filename = normalizeFilename(String(getFirstValue(fields.filename) || ''));
const uploadedFile = getFirstValue(files.file);
const fileTempUrl = uploadedFile && uploadedFile.path;
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) {
if (!filename) {
fs.unlinkSync(fileTempUrl);
res.status(400).send({ message: 'Invalid upload path.' });
res.sendStatus(500);
return;
}
const privateUrl = path.join(
form.uploadDir,
folder,
filename,
);
ensureDirectoryExistence(privateUrl);
fs.renameSync(fileTempUrl, privateUrl);
res.status(200).send({
url: buildPublicDownloadUrl(path.posix.join(uploadFolder, filename)),
});
res.sendStatus(200);
});
form.on('error', function (err) {
console.error('Upload form error:', err);
res.status(500).send(err);
});
}
}
const downloadLocal = async (req, res) => {
const privateUrl = normalizePrivateUrl(getFirstValue(req.query.privateUrl));
const privateUrl = 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 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);
}
});
res.download(path.join(config.uploadDir, privateUrl));
}
const initGCloud = () => {
const processFile = require("../middlewares/upload");
const { Storage } = require("@google-cloud/storage");
const crypto = require('crypto')
const hash = config.gcloud.hash
const privateKey = process.env.GC_PRIVATE_KEY.replace(/\\\n/g, "\n");
@ -329,20 +116,14 @@ const uploadGCloud = async (folder, req, res) => {
try {
const {hash, bucket, processFile} = initGCloud();
await processFile(req, res);
const uploadFolder = normalizeFolder(folder);
let buffer = await req.file.buffer;
let filename = await req.body.filename;
if (!req.file) {
return res.status(400).send({ message: "Please upload a file!" });
}
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 path = `${hash}/${folder}/${filename}`;
let blob = bucket.file(path);
console.log(path);
@ -359,9 +140,10 @@ const uploadGCloud = async (folder, req, res) => {
console.log(`https://storage.googleapis.com/${bucket.name}/${blob.name}`);
blobStream.on("finish", async () => {
const privateUrl = path.posix.join(uploadFolder, filename);
const publicUrl = buildPublicDownloadUrl(privateUrl);
blobStream.on("finish", async (data) => {
const publicUrl = format(
`https://storage.googleapis.com/${bucket.name}/${blob.name}`
);
res.status(200).send({
message: "Uploaded the file successfully: " + path,
@ -381,21 +163,9 @@ const uploadGCloud = async (folder, req, res) => {
const downloadGCloud = async (req, res) => {
try {
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 {hash, bucket, processFile} = initGCloud();
const privateUrl = await req.query.privateUrl;
const filePath = `${hash}/${privateUrl}`;
const file = bucket.file(filePath)
const fileExists = await file.exists();
@ -406,7 +176,7 @@ const downloadGCloud = async (req, res) => {
}
else {
res.status(404).send({
message: "Could not download the file.",
message: "Could not download the file. " + err,
});
}
} catch (err) {
@ -418,13 +188,8 @@ const downloadGCloud = async (req, res) => {
const deleteGCloud = async (privateUrl) => {
try {
const normalizedPrivateUrl = normalizePrivateUrl(privateUrl);
if (!normalizedPrivateUrl) {
return;
}
const {hash, bucket} = initGCloud();
const filePath = `${hash}/${normalizedPrivateUrl}`;
const {hash, bucket, processFile} = initGCloud();
const filePath = `${hash}/${privateUrl}`;
const file = bucket.file(filePath)
const fileExists = await file.exists();
@ -443,13 +208,6 @@ module.exports = {
downloadLocal,
deleteGCloud,
uploadGCloud,
downloadGCloud,
normalizePrivateUrl,
normalizeFilename,
normalizeFolder,
buildPublicDownloadUrl,
validateFileMetadata,
createDownloadToken,
isValidDownloadToken,
downloadGCloud
}

View File

@ -1,7 +1,11 @@
const db = require('../db/models');
const GradesDBApi = require('../db/api/grades');
const parseImportFile = require('./importFileParser');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -24,13 +28,28 @@ module.exports = class GradesService {
await transaction.rollback();
throw error;
}
}
};
static async bulkImport(req, res) {
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
const results = await parseImportFile(req, res);
await processFile(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, {
transaction,
@ -51,7 +70,7 @@ module.exports = class GradesService {
try {
let grades = await GradesDBApi.findBy(
{id},
{ transaction, currentUser },
{transaction},
);
if (!grades) {
@ -76,7 +95,7 @@ module.exports = class GradesService {
await transaction.rollback();
throw error;
}
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();

View File

@ -1,7 +1,11 @@
const db = require('../db/models');
const GuardiansDBApi = require('../db/api/guardians');
const parseImportFile = require('./importFileParser');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -24,13 +28,28 @@ module.exports = class GuardiansService {
await transaction.rollback();
throw error;
}
}
};
static async bulkImport(req, res) {
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
const results = await parseImportFile(req, res);
await processFile(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, {
transaction,
@ -51,7 +70,7 @@ module.exports = class GuardiansService {
try {
let guardians = await GuardiansDBApi.findBy(
{id},
{ transaction, currentUser },
{transaction},
);
if (!guardians) {
@ -76,7 +95,7 @@ module.exports = class GuardiansService {
await transaction.rollback();
throw error;
}
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();

View File

@ -1,380 +0,0 @@
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,7 +1,11 @@
const db = require('../db/models');
const InvoicesDBApi = require('../db/api/invoices');
const parseImportFile = require('./importFileParser');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -24,13 +28,28 @@ module.exports = class InvoicesService {
await transaction.rollback();
throw error;
}
}
};
static async bulkImport(req, res) {
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
const results = await parseImportFile(req, res);
await processFile(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, {
transaction,
@ -51,7 +70,7 @@ module.exports = class InvoicesService {
try {
let invoices = await InvoicesDBApi.findBy(
{id},
{ transaction, currentUser },
{transaction},
);
if (!invoices) {
@ -76,7 +95,7 @@ module.exports = class InvoicesService {
await transaction.rollback();
throw error;
}
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();

View File

@ -2,10 +2,20 @@ const axios = require('axios');
const config = require('../config');
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 {
static async getWidget(payload) {
static async getWidget(payload, userId, roleId) {
const RoleService = loadRoleService();
const response = await axios.post(
`${config.flHost}/${config.project_uuid}/project_customization_widgets.json`,
payload,
@ -13,6 +23,7 @@ module.exports = class OpenAiService {
if (response.status >= 200 && response.status < 300) {
const { widget_id } = await response.data;
await RoleService.addRoleInfo(roleId, userId, 'widgets', widget_id);
return widget_id;
} else {
console.error('=======error=======', response.data);

View File

@ -1,7 +1,11 @@
const db = require('../db/models');
const PaymentsDBApi = require('../db/api/payments');
const parseImportFile = require('./importFileParser');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -24,13 +28,28 @@ module.exports = class PaymentsService {
await transaction.rollback();
throw error;
}
}
};
static async bulkImport(req, res) {
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
const results = await parseImportFile(req, res);
await processFile(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, {
transaction,
@ -51,7 +70,7 @@ module.exports = class PaymentsService {
try {
let payments = await PaymentsDBApi.findBy(
{id},
{ transaction, currentUser },
{transaction},
);
if (!payments) {
@ -76,7 +95,7 @@ module.exports = class PaymentsService {
await transaction.rollback();
throw error;
}
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();

View File

@ -1,7 +1,11 @@
const db = require('../db/models');
const PermissionsDBApi = require('../db/api/permissions');
const parseImportFile = require('./importFileParser');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -24,13 +28,28 @@ module.exports = class PermissionsService {
await transaction.rollback();
throw error;
}
}
};
static async bulkImport(req, res) {
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
const results = await parseImportFile(req, res);
await processFile(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, {
transaction,
@ -76,7 +95,7 @@ module.exports = class PermissionsService {
await transaction.rollback();
throw error;
}
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();

View File

@ -1,7 +1,11 @@
const db = require('../db/models');
const ProductsDBApi = require('../db/api/products');
const parseImportFile = require('./importFileParser');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
@ -24,13 +28,28 @@ module.exports = class ProductsService {
await transaction.rollback();
throw error;
}
}
};
static async bulkImport(req, res) {
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
const results = await parseImportFile(req, res);
await processFile(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, {
transaction,
@ -51,7 +70,7 @@ module.exports = class ProductsService {
try {
let products = await ProductsDBApi.findBy(
{id},
{ transaction, currentUser },
{transaction},
);
if (!products) {
@ -76,7 +95,7 @@ module.exports = class ProductsService {
await transaction.rollback();
throw error;
}
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();

View File

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

View File

@ -1,50 +1,22 @@
const db = require('../db/models');
const SchoolsDBApi = require('../db/api/schools');
const parseImportFile = require('./importFileParser');
const processFile = require("../middlewares/upload");
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 {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await SchoolsDBApi.create(
normalizeSchoolData(data),
data,
{
currentUser,
transaction,
@ -56,13 +28,28 @@ module.exports = class SchoolsService {
await transaction.rollback();
throw error;
}
}
};
static async bulkImport(req, res) {
static async bulkImport(req, res, sendInvitationEmails = true, host) {
const transaction = await db.sequelize.transaction();
try {
const results = await parseImportFile(req, res);
await processFile(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, {
transaction,
@ -83,7 +70,7 @@ module.exports = class SchoolsService {
try {
let schools = await SchoolsDBApi.findBy(
{id},
{ transaction, currentUser },
{transaction},
);
if (!schools) {
@ -94,7 +81,7 @@ module.exports = class SchoolsService {
const updatedSchools = await SchoolsDBApi.update(
id,
normalizeSchoolData(data),
data,
{
currentUser,
transaction,
@ -108,7 +95,7 @@ module.exports = class SchoolsService {
await transaction.rollback();
throw error;
}
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();

View File

@ -3,35 +3,6 @@ const ValidationError = require('./notifications/errors/validation');
const Sequelize = db.Sequelize;
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
@ -43,10 +14,7 @@ async function checkPermissions(permission, currentUser) {
throw new ValidationError('auth.unauthorized');
}
const customPermissions = Array.isArray(currentUser.custom_permissions)
? currentUser.custom_permissions
: [];
const userPermission = customPermissions.find(
const userPermission = currentUser.custom_permissions.find(
(cp) => cp.name === permission,
);
@ -54,21 +22,25 @@ async function checkPermissions(permission, currentUser) {
return true;
}
if (!currentUser.app_role) {
throw new ValidationError('auth.forbidden');
try {
if (!currentUser.app_role) {
throw new ValidationError('auth.forbidden');
}
const permissions = await currentUser.app_role.getPermissions();
return !!permissions.find((p) => p.name === permission);
} catch (e) {
throw e;
}
const permissions = await currentUser.app_role.getPermissions();
return !!permissions.find((p) => p.name === permission);
}
module.exports = class SearchService {
static async search(searchQuery, currentUser, globalAccess) {
const normalizedSearchQuery = normalizeSearchQuery(searchQuery);
const likeSearchQuery = `%${escapeLikePattern(normalizedSearchQuery)}%`;
const lowerSearchQuery = normalizedSearchQuery.toLowerCase();
static async search(searchQuery, currentUser , organizationId, globalAccess) {
try {
if (!searchQuery) {
throw new ValidationError('iam.errors.searchQueryRequired');
}
const tableColumns = {
@ -455,87 +427,72 @@ module.exports = class SearchService {
let allFoundRecords = [];
for (const [tableName, attributesToSearch] of Object.entries(tableColumns)) {
const model = db[tableName];
if (!model) {
continue;
}
const attributesIntToSearch = columnsInt[tableName] || [];
const whereCondition = {
[Op.or]: [
...attributesToSearch.map(attribute => ({
[attribute]: {
[Op.iLike]: likeSearchQuery,
},
})),
...attributesIntToSearch.map(attribute => (
Sequelize.where(
Sequelize.cast(Sequelize.col(`${tableName}.${attribute}`), 'varchar'),
{ [Op.iLike]: likeSearchQuery }
)
)),
],
};
if (!globalAccess) {
const schoolId = getCurrentUserSchoolId(currentUser);
const rawAttributes = model.rawAttributes || {};
if (!schoolId) {
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);
if (!hasPermission) {
continue;
}
const foundRecords = await model.findAll({
where: whereCondition,
attributes: [...new Set([...attributesToSearch, 'id', ...attributesIntToSearch])],
limit: MAX_RESULTS_PER_TABLE,
});
const modifiedRecords = foundRecords.map((record) => {
const matchAttribute = [];
for (const attribute of attributesToSearch) {
const value = record[attribute];
if (typeof value === 'string' && value.toLowerCase().includes(lowerSearchQuery)) {
matchAttribute.push(attribute);
}
}
for (const attribute of attributesIntToSearch) {
const castedValue = String(record[attribute] ?? '');
if (castedValue.toLowerCase().includes(lowerSearchQuery)) {
matchAttribute.push(attribute);
}
}
return {
...record.get(),
matchAttribute,
tableName,
for (const tableName in tableColumns) {
if (tableColumns.hasOwnProperty(tableName)) {
const attributesToSearch = tableColumns[tableName];
const attributesIntToSearch = columnsInt[tableName] || [];
const whereCondition = {
[Op.or]: [
...attributesToSearch.map(attribute => ({
[attribute]: {
[Op.iLike] : `%${searchQuery}%`,
},
})),
...attributesIntToSearch.map(attribute => (
Sequelize.where(
Sequelize.cast(Sequelize.col(`${tableName}.${attribute}`), 'varchar'),
{ [Op.iLike]: `%${searchQuery}%` }
)
)),
],
};
});
allFoundRecords = allFoundRecords.concat(modifiedRecords);
if (allFoundRecords.length >= MAX_TOTAL_SEARCH_RESULTS) {
break;
if (!globalAccess && tableName !== 'organizations' && organizationId) {
whereCondition.organizationId = organizationId;
}
const hasPermission = await checkPermissions(`READ_${tableName.toUpperCase()}`, currentUser);
if (!hasPermission) {
continue;
}
const foundRecords = await db[tableName].findAll({
where: whereCondition,
attributes: [...tableColumns[tableName], 'id', ...attributesIntToSearch],
});
const modifiedRecords = foundRecords.map((record) => {
const matchAttribute = [];
for (const attribute of attributesToSearch) {
if (record[attribute]?.toLowerCase()?.includes(searchQuery.toLowerCase())) {
matchAttribute.push(attribute);
}
}
for (const attribute of attributesIntToSearch) {
const castedValue = String(record[attribute]);
if (castedValue && castedValue.toLowerCase().includes(searchQuery.toLowerCase())) {
matchAttribute.push(attribute);
}
}
return {
...record.get(),
matchAttribute,
tableName,
};
});
allFoundRecords = allFoundRecords.concat(modifiedRecords);
}
}
return allFoundRecords.slice(0, MAX_TOTAL_SEARCH_RESULTS);
return allFoundRecords;
} catch (error) {
throw error;
}
}
}

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