diff --git a/backend/src/db/api/test.js b/backend/src/db/api/test.js new file mode 100644 index 0000000..5c2732b --- /dev/null +++ b/backend/src/db/api/test.js @@ -0,0 +1,257 @@ +const db = require('../models'); +const FileDBApi = require('./file'); +const crypto = require('crypto'); +const Utils = require('../utils'); + +const Sequelize = db.Sequelize; +const Op = Sequelize.Op; + +module.exports = class TestDBApi { + static async create(data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const test = await db.test.create( + { + id: data.id || undefined, + + name: data.name || null, + amount: data.amount || null, + importHash: data.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + }, + { transaction }, + ); + + return test; + } + + static async bulkImport(data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + // Prepare data - wrapping individual data transformations in a map() method + const testData = data.map((item, index) => ({ + id: item.id || undefined, + + name: item.name || null, + amount: item.amount || null, + importHash: item.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + createdAt: new Date(Date.now() + index * 1000), + })); + + // Bulk create items + const test = await db.test.bulkCreate(testData, { transaction }); + + // For each item created, replace relation files + + return test; + } + + static async update(id, data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const test = await db.test.findByPk(id, {}, { transaction }); + + const updatePayload = {}; + + if (data.name !== undefined) updatePayload.name = data.name; + + if (data.amount !== undefined) updatePayload.amount = data.amount; + + updatePayload.updatedById = currentUser.id; + + await test.update(updatePayload, { transaction }); + + return test; + } + + static async deleteByIds(ids, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const test = await db.test.findAll({ + where: { + id: { + [Op.in]: ids, + }, + }, + transaction, + }); + + await db.sequelize.transaction(async (transaction) => { + for (const record of test) { + await record.update({ deletedBy: currentUser.id }, { transaction }); + } + for (const record of test) { + await record.destroy({ transaction }); + } + }); + + return test; + } + + static async remove(id, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const test = await db.test.findByPk(id, options); + + await test.update( + { + deletedBy: currentUser.id, + }, + { + transaction, + }, + ); + + await test.destroy({ + transaction, + }); + + return test; + } + + static async findBy(where, options) { + const transaction = (options && options.transaction) || undefined; + + const test = await db.test.findOne({ where }, { transaction }); + + if (!test) { + return test; + } + + const output = test.get({ plain: true }); + + return output; + } + + static async findAll(filter, options) { + const limit = filter.limit || 0; + let offset = 0; + let where = {}; + const currentPage = +filter.page; + + offset = currentPage * limit; + + const orderBy = null; + + const transaction = (options && options.transaction) || undefined; + + let include = []; + + if (filter) { + if (filter.id) { + where = { + ...where, + ['id']: Utils.uuid(filter.id), + }; + } + + if (filter.name) { + where = { + ...where, + [Op.and]: Utils.ilike('test', 'name', filter.name), + }; + } + + if (filter.amount) { + where = { + ...where, + [Op.and]: Utils.ilike('test', 'amount', filter.amount), + }; + } + + if (filter.active !== undefined) { + where = { + ...where, + active: filter.active === true || filter.active === 'true', + }; + } + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + + if (start !== undefined && start !== null && start !== '') { + where = { + ...where, + ['createdAt']: { + ...where.createdAt, + [Op.gte]: start, + }, + }; + } + + if (end !== undefined && end !== null && end !== '') { + where = { + ...where, + ['createdAt']: { + ...where.createdAt, + [Op.lte]: end, + }, + }; + } + } + } + + const queryOptions = { + where, + include, + distinct: true, + order: + filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options?.transaction, + logging: console.log, + }; + + if (!options?.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await db.test.findAndCountAll(queryOptions); + + return { + rows: options?.countOnly ? [] : rows, + count: count, + }; + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } + + static async findAllAutocomplete(query, limit, offset) { + let where = {}; + + if (query) { + where = { + [Op.or]: [ + { ['id']: Utils.uuid(query) }, + Utils.ilike('test', 'id', query), + ], + }; + } + + const records = await db.test.findAll({ + attributes: ['id', 'id'], + where, + limit: limit ? Number(limit) : undefined, + offset: offset ? Number(offset) : undefined, + orderBy: [['id', 'ASC']], + }); + + return records.map((record) => ({ + id: record.id, + label: record.id, + })); + } +}; diff --git a/backend/src/db/migrations/1773412167384.js b/backend/src/db/migrations/1773412167384.js new file mode 100644 index 0000000..4810d6d --- /dev/null +++ b/backend/src/db/migrations/1773412167384.js @@ -0,0 +1,94 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.createTable( + 'test', + { + id: { + type: Sequelize.DataTypes.UUID, + defaultValue: Sequelize.DataTypes.UUIDV4, + primaryKey: true, + }, + createdById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + updatedById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + importHash: { + type: Sequelize.DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { transaction }, + ); + + await queryInterface.addColumn( + 'test', + 'name', + { + type: Sequelize.DataTypes.TEXT, + }, + { transaction }, + ); + + await queryInterface.addColumn( + 'test', + 'amount', + { + type: Sequelize.DataTypes.TEXT, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('test', 'amount', { transaction }); + + await queryInterface.removeColumn('test', 'name', { transaction }); + + await queryInterface.dropTable('test', { transaction }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/models/test.js b/backend/src/db/models/test.js new file mode 100644 index 0000000..978b9b1 --- /dev/null +++ b/backend/src/db/models/test.js @@ -0,0 +1,53 @@ +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 test = sequelize.define( + 'test', + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + + name: { + type: DataTypes.TEXT, + }, + + amount: { + type: DataTypes.TEXT, + }, + + importHash: { + type: DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + + test.associate = (db) => { + /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity + + //end loop + + db.test.belongsTo(db.users, { + as: 'createdBy', + }); + + db.test.belongsTo(db.users, { + as: 'updatedBy', + }); + }; + + return test; +}; diff --git a/backend/src/db/seeders/20200430130760-user-roles.js b/backend/src/db/seeders/20200430130760-user-roles.js index 7d091b5..b2f42ec 100644 --- a/backend/src/db/seeders/20200430130760-user-roles.js +++ b/backend/src/db/seeders/20200430130760-user-roles.js @@ -1,5 +1,4 @@ - -const { v4: uuid } = require("uuid"); +const { v4: uuid } = require('uuid'); module.exports = { /** @@ -26,26 +25,45 @@ module.exports = { return id; } - await queryInterface.bulkInsert("roles", [ - - - { id: getId("Administrator"), name: "Administrator", createdAt, updatedAt }, - - - - { id: getId("PlatformOwner"), name: "Platform Owner", createdAt, updatedAt }, - - { id: getId("AcademicDirector"), name: "Academic Director", createdAt, updatedAt }, - - { id: getId("LeadInstructor"), name: "Lead Instructor", createdAt, updatedAt }, - - { id: getId("TeachingAssistant"), name: "Teaching Assistant", createdAt, updatedAt }, - - { id: getId("Learner"), name: "Learner", createdAt, updatedAt }, - - - - { id: getId("Public"), name: "Public", createdAt, updatedAt }, + await queryInterface.bulkInsert('roles', [ + { + id: getId('Administrator'), + name: 'Administrator', + createdAt, + updatedAt, + }, + + { + id: getId('PlatformOwner'), + name: 'Platform Owner', + createdAt, + updatedAt, + }, + + { + id: getId('AcademicDirector'), + name: 'Academic Director', + createdAt, + updatedAt, + }, + + { + id: getId('LeadInstructor'), + name: 'Lead Instructor', + createdAt, + updatedAt, + }, + + { + id: getId('TeachingAssistant'), + name: 'Teaching Assistant', + createdAt, + updatedAt, + }, + + { id: getId('Learner'), name: 'Learner', createdAt, updatedAt }, + + { id: getId('Public'), name: 'Public', createdAt, updatedAt }, ]); /** @@ -53,22 +71,69 @@ module.exports = { */ function createPermissions(name) { return [ - { id: getId(`CREATE_${name.toUpperCase()}`), createdAt, updatedAt, name: `CREATE_${name.toUpperCase()}` }, - { id: getId(`READ_${name.toUpperCase()}`), createdAt, updatedAt, name: `READ_${name.toUpperCase()}` }, - { id: getId(`UPDATE_${name.toUpperCase()}`), createdAt, updatedAt, name: `UPDATE_${name.toUpperCase()}` }, - { id: getId(`DELETE_${name.toUpperCase()}`), createdAt, updatedAt, name: `DELETE_${name.toUpperCase()}` } + { + id: getId(`CREATE_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `CREATE_${name.toUpperCase()}`, + }, + { + id: getId(`READ_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `READ_${name.toUpperCase()}`, + }, + { + id: getId(`UPDATE_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `UPDATE_${name.toUpperCase()}`, + }, + { + id: getId(`DELETE_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `DELETE_${name.toUpperCase()}`, + }, ]; } const entities = [ - "users","roles","permissions","courses","lessons","enrollments","lesson_progress","announcements","course_resources",, + 'users', + 'roles', + 'permissions', + 'courses', + 'lessons', + 'enrollments', + 'lesson_progress', + 'announcements', + 'course_resources', + 'test', + , ]; -await queryInterface.bulkInsert("permissions", entities.flatMap(createPermissions)); -await queryInterface.bulkInsert("permissions", [{ id: getId(`READ_API_DOCS`), createdAt, updatedAt, name: `READ_API_DOCS` }]); -await queryInterface.bulkInsert("permissions", [{ id: getId(`CREATE_SEARCH`), createdAt, updatedAt, name: `CREATE_SEARCH`}]); + await queryInterface.bulkInsert( + 'permissions', + entities.flatMap(createPermissions), + ); + await queryInterface.bulkInsert('permissions', [ + { + id: getId(`READ_API_DOCS`), + createdAt, + updatedAt, + name: `READ_API_DOCS`, + }, + ]); + await queryInterface.bulkInsert('permissions', [ + { + id: getId(`CREATE_SEARCH`), + createdAt, + updatedAt, + name: `CREATE_SEARCH`, + }, + ]); - -await queryInterface.sequelize.query(`create table "rolesPermissionsPermissions" + await queryInterface.sequelize + .query(`create table "rolesPermissionsPermissions" ( "createdAt" timestamp with time zone not null, "updatedAt" timestamp with time zone not null, @@ -77,804 +142,970 @@ await queryInterface.sequelize.query(`create table "rolesPermissionsPermissions" primary key ("roles_permissionsId", "permissionId") );`); + await queryInterface.bulkInsert('rolesPermissionsPermissions', [ + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('CREATE_USERS'), + }, -await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('CREATE_USERS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('READ_USERS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('UPDATE_USERS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('DELETE_USERS') }, - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('READ_USERS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('UPDATE_USERS') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('READ_USERS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('UPDATE_USERS') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('READ_USERS') }, - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("Learner"), permissionId: getId('READ_USERS') }, - - - - - - - - - - - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('CREATE_COURSES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('READ_COURSES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('UPDATE_COURSES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('DELETE_COURSES') }, - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('CREATE_COURSES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('READ_COURSES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('UPDATE_COURSES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('DELETE_COURSES') }, - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('CREATE_COURSES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('READ_COURSES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('UPDATE_COURSES') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('READ_COURSES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('UPDATE_COURSES') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("Learner"), permissionId: getId('READ_COURSES') }, - - - - - - - - - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('CREATE_LESSONS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('READ_LESSONS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('UPDATE_LESSONS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('DELETE_LESSONS') }, - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('CREATE_LESSONS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('READ_LESSONS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('UPDATE_LESSONS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('DELETE_LESSONS') }, - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('CREATE_LESSONS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('READ_LESSONS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('UPDATE_LESSONS') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('READ_LESSONS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('UPDATE_LESSONS') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("Learner"), permissionId: getId('READ_LESSONS') }, - - - - - - - - - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('CREATE_ENROLLMENTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('READ_ENROLLMENTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('UPDATE_ENROLLMENTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('DELETE_ENROLLMENTS') }, - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('CREATE_ENROLLMENTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('READ_ENROLLMENTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('UPDATE_ENROLLMENTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('DELETE_ENROLLMENTS') }, - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('READ_ENROLLMENTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('UPDATE_ENROLLMENTS') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('READ_ENROLLMENTS') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("Learner"), permissionId: getId('CREATE_ENROLLMENTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("Learner"), permissionId: getId('READ_ENROLLMENTS') }, - - - - - - - - - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('CREATE_LESSON_PROGRESS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('READ_LESSON_PROGRESS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('UPDATE_LESSON_PROGRESS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('DELETE_LESSON_PROGRESS') }, - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('READ_LESSON_PROGRESS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('UPDATE_LESSON_PROGRESS') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('READ_LESSON_PROGRESS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('UPDATE_LESSON_PROGRESS') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('READ_LESSON_PROGRESS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('UPDATE_LESSON_PROGRESS') }, - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("Learner"), permissionId: getId('CREATE_LESSON_PROGRESS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("Learner"), permissionId: getId('READ_LESSON_PROGRESS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("Learner"), permissionId: getId('UPDATE_LESSON_PROGRESS') }, - - - - - - - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('CREATE_ANNOUNCEMENTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('READ_ANNOUNCEMENTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('UPDATE_ANNOUNCEMENTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('DELETE_ANNOUNCEMENTS') }, - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('CREATE_ANNOUNCEMENTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('READ_ANNOUNCEMENTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('UPDATE_ANNOUNCEMENTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('DELETE_ANNOUNCEMENTS') }, - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('CREATE_ANNOUNCEMENTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('READ_ANNOUNCEMENTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('UPDATE_ANNOUNCEMENTS') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('READ_ANNOUNCEMENTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('UPDATE_ANNOUNCEMENTS') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("Learner"), permissionId: getId('READ_ANNOUNCEMENTS') }, - - - - - - - - - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('CREATE_COURSE_RESOURCES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('READ_COURSE_RESOURCES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('UPDATE_COURSE_RESOURCES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('DELETE_COURSE_RESOURCES') }, - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('CREATE_COURSE_RESOURCES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('READ_COURSE_RESOURCES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('UPDATE_COURSE_RESOURCES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('DELETE_COURSE_RESOURCES') }, - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('CREATE_COURSE_RESOURCES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('READ_COURSE_RESOURCES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('UPDATE_COURSE_RESOURCES') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('READ_COURSE_RESOURCES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('UPDATE_COURSE_RESOURCES') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("Learner"), permissionId: getId('READ_COURSE_RESOURCES') }, - - - - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('CREATE_SEARCH') }, - - { createdAt, updatedAt, roles_permissionsId: getId("AcademicDirector"), permissionId: getId('CREATE_SEARCH') }, - - { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('CREATE_SEARCH') }, - - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('CREATE_SEARCH') }, - - { createdAt, updatedAt, roles_permissionsId: getId("Learner"), permissionId: getId('CREATE_SEARCH') }, - - - - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_USERS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_USERS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_USERS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_USERS') }, - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_ROLES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_ROLES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_ROLES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_ROLES') }, - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_PERMISSIONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_PERMISSIONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_PERMISSIONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_PERMISSIONS') }, - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_COURSES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_COURSES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_COURSES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_COURSES') }, - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_LESSONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_LESSONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_LESSONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_LESSONS') }, - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_ENROLLMENTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_ENROLLMENTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_ENROLLMENTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_ENROLLMENTS') }, - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_LESSON_PROGRESS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_LESSON_PROGRESS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_LESSON_PROGRESS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_LESSON_PROGRESS') }, - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_ANNOUNCEMENTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_ANNOUNCEMENTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_ANNOUNCEMENTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_ANNOUNCEMENTS') }, - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_COURSE_RESOURCES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_COURSE_RESOURCES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_COURSE_RESOURCES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_COURSE_RESOURCES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_API_DOCS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_SEARCH') }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('READ_USERS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('UPDATE_USERS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('DELETE_USERS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('READ_USERS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('UPDATE_USERS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('LeadInstructor'), + permissionId: getId('READ_USERS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('LeadInstructor'), + permissionId: getId('UPDATE_USERS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('TeachingAssistant'), + permissionId: getId('READ_USERS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Learner'), + permissionId: getId('READ_USERS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('CREATE_COURSES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('READ_COURSES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('UPDATE_COURSES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('DELETE_COURSES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('CREATE_COURSES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('READ_COURSES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('UPDATE_COURSES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('DELETE_COURSES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('LeadInstructor'), + permissionId: getId('CREATE_COURSES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('LeadInstructor'), + permissionId: getId('READ_COURSES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('LeadInstructor'), + permissionId: getId('UPDATE_COURSES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('TeachingAssistant'), + permissionId: getId('READ_COURSES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('TeachingAssistant'), + permissionId: getId('UPDATE_COURSES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Learner'), + permissionId: getId('READ_COURSES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('CREATE_LESSONS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('READ_LESSONS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('UPDATE_LESSONS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('DELETE_LESSONS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('CREATE_LESSONS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('READ_LESSONS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('UPDATE_LESSONS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('DELETE_LESSONS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('LeadInstructor'), + permissionId: getId('CREATE_LESSONS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('LeadInstructor'), + permissionId: getId('READ_LESSONS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('LeadInstructor'), + permissionId: getId('UPDATE_LESSONS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('TeachingAssistant'), + permissionId: getId('READ_LESSONS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('TeachingAssistant'), + permissionId: getId('UPDATE_LESSONS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Learner'), + permissionId: getId('READ_LESSONS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('CREATE_ENROLLMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('READ_ENROLLMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('UPDATE_ENROLLMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('DELETE_ENROLLMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('CREATE_ENROLLMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('READ_ENROLLMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('UPDATE_ENROLLMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('DELETE_ENROLLMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('LeadInstructor'), + permissionId: getId('READ_ENROLLMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('LeadInstructor'), + permissionId: getId('UPDATE_ENROLLMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('TeachingAssistant'), + permissionId: getId('READ_ENROLLMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Learner'), + permissionId: getId('CREATE_ENROLLMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Learner'), + permissionId: getId('READ_ENROLLMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('CREATE_LESSON_PROGRESS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('READ_LESSON_PROGRESS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('UPDATE_LESSON_PROGRESS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('DELETE_LESSON_PROGRESS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('READ_LESSON_PROGRESS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('UPDATE_LESSON_PROGRESS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('LeadInstructor'), + permissionId: getId('READ_LESSON_PROGRESS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('LeadInstructor'), + permissionId: getId('UPDATE_LESSON_PROGRESS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('TeachingAssistant'), + permissionId: getId('READ_LESSON_PROGRESS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('TeachingAssistant'), + permissionId: getId('UPDATE_LESSON_PROGRESS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Learner'), + permissionId: getId('CREATE_LESSON_PROGRESS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Learner'), + permissionId: getId('READ_LESSON_PROGRESS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Learner'), + permissionId: getId('UPDATE_LESSON_PROGRESS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('CREATE_ANNOUNCEMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('READ_ANNOUNCEMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('UPDATE_ANNOUNCEMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('DELETE_ANNOUNCEMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('CREATE_ANNOUNCEMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('READ_ANNOUNCEMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('UPDATE_ANNOUNCEMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('DELETE_ANNOUNCEMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('LeadInstructor'), + permissionId: getId('CREATE_ANNOUNCEMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('LeadInstructor'), + permissionId: getId('READ_ANNOUNCEMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('LeadInstructor'), + permissionId: getId('UPDATE_ANNOUNCEMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('TeachingAssistant'), + permissionId: getId('READ_ANNOUNCEMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('TeachingAssistant'), + permissionId: getId('UPDATE_ANNOUNCEMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Learner'), + permissionId: getId('READ_ANNOUNCEMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('CREATE_COURSE_RESOURCES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('READ_COURSE_RESOURCES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('UPDATE_COURSE_RESOURCES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('DELETE_COURSE_RESOURCES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('CREATE_COURSE_RESOURCES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('READ_COURSE_RESOURCES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('UPDATE_COURSE_RESOURCES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('DELETE_COURSE_RESOURCES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('LeadInstructor'), + permissionId: getId('CREATE_COURSE_RESOURCES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('LeadInstructor'), + permissionId: getId('READ_COURSE_RESOURCES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('LeadInstructor'), + permissionId: getId('UPDATE_COURSE_RESOURCES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('TeachingAssistant'), + permissionId: getId('READ_COURSE_RESOURCES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('TeachingAssistant'), + permissionId: getId('UPDATE_COURSE_RESOURCES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Learner'), + permissionId: getId('READ_COURSE_RESOURCES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('PlatformOwner'), + permissionId: getId('CREATE_SEARCH'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('AcademicDirector'), + permissionId: getId('CREATE_SEARCH'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('LeadInstructor'), + permissionId: getId('CREATE_SEARCH'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('TeachingAssistant'), + permissionId: getId('CREATE_SEARCH'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Learner'), + permissionId: getId('CREATE_SEARCH'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('CREATE_USERS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('READ_USERS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('UPDATE_USERS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('DELETE_USERS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('CREATE_ROLES'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('READ_ROLES'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('UPDATE_ROLES'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('DELETE_ROLES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('CREATE_PERMISSIONS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('READ_PERMISSIONS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('UPDATE_PERMISSIONS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('DELETE_PERMISSIONS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('CREATE_COURSES'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('READ_COURSES'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('UPDATE_COURSES'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('DELETE_COURSES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('CREATE_LESSONS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('READ_LESSONS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('UPDATE_LESSONS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('DELETE_LESSONS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('CREATE_ENROLLMENTS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('READ_ENROLLMENTS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('UPDATE_ENROLLMENTS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('DELETE_ENROLLMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('CREATE_LESSON_PROGRESS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('READ_LESSON_PROGRESS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('UPDATE_LESSON_PROGRESS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('DELETE_LESSON_PROGRESS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('CREATE_ANNOUNCEMENTS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('READ_ANNOUNCEMENTS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('UPDATE_ANNOUNCEMENTS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('DELETE_ANNOUNCEMENTS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('CREATE_COURSE_RESOURCES'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('READ_COURSE_RESOURCES'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('UPDATE_COURSE_RESOURCES'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('DELETE_COURSE_RESOURCES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('CREATE_TEST'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('READ_TEST'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('UPDATE_TEST'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('DELETE_TEST'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('READ_API_DOCS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('CREATE_SEARCH'), + }, ]); + await queryInterface.sequelize.query( + `UPDATE "users" SET "app_roleId"='${getId( + 'SuperAdmin', + )}' WHERE "email"='super_admin@flatlogic.com'`, + ); + await queryInterface.sequelize.query( + `UPDATE "users" SET "app_roleId"='${getId( + 'Administrator', + )}' WHERE "email"='admin@flatlogic.com'`, + ); - await queryInterface.sequelize.query(`UPDATE "users" SET "app_roleId"='${getId("SuperAdmin")}' WHERE "email"='super_admin@flatlogic.com'`); - await queryInterface.sequelize.query(`UPDATE "users" SET "app_roleId"='${getId("Administrator")}' WHERE "email"='admin@flatlogic.com'`); - - - - - - - await queryInterface.sequelize.query(`UPDATE "users" SET "app_roleId"='${getId("PlatformOwner")}' WHERE "email"='client@hello.com'`); - await queryInterface.sequelize.query(`UPDATE "users" SET "app_roleId"='${getId("AcademicDirector")}' WHERE "email"='john@doe.com'`); - - - - -} + await queryInterface.sequelize.query( + `UPDATE "users" SET "app_roleId"='${getId( + 'PlatformOwner', + )}' WHERE "email"='client@hello.com'`, + ); + await queryInterface.sequelize.query( + `UPDATE "users" SET "app_roleId"='${getId( + 'AcademicDirector', + )}' WHERE "email"='john@doe.com'`, + ); + }, }; - diff --git a/backend/src/db/seeders/20231127130745-sample-data.js b/backend/src/db/seeders/20231127130745-sample-data.js index 2a34ee9..22f6d33 100644 --- a/backend/src/db/seeders/20231127130745-sample-data.js +++ b/backend/src/db/seeders/20231127130745-sample-data.js @@ -1,33 +1,6 @@ - - - - - - - - - - - - - - - - - - - - - - const db = require('../models'); const Users = db.users; - - - - - const Courses = db.courses; const Lessons = db.lessons; @@ -40,3084 +13,757 @@ const Announcements = db.announcements; const CourseResources = db.course_resources; - - - - - +const Test = db.test; const CoursesData = [ - - { - - - - - "title": "Grace Hopper", - - - - - - - "subtitle": "Alan Turing", - - - - - - - "description": "Alan Turing", - - - - - - - // type code here for "images" field - - - - - - - "status": "archived", - - - - - - - "level": "beginner", - - - - - - - "category": "Alan Turing", - - - - - - - "language": "Grace Hopper", - - - - - - - "price": 3.85, - - - - - - - "is_free": true, - - - - - - - "published_at": new Date(Date.now()), - - - - - - - // type code here for "relation_one" field - - - - }, - - { - - - - - "title": "Marie Curie", - - - - - - - "subtitle": "Ada Lovelace", - - - - - - - "description": "Marie Curie", - - - - - - - // type code here for "images" field - - - - - - - "status": "draft", - - - - - - - "level": "beginner", - - - - - - - "category": "Marie Curie", - - - - - - - "language": "Alan Turing", - - - - - - - "price": 1.77, - - - - - - - "is_free": true, - - - - - - - "published_at": new Date(Date.now()), - - - - - - - // type code here for "relation_one" field - - - - }, - - { - - - - - "title": "Marie Curie", - - - - - - - "subtitle": "Grace Hopper", - - - - - - - "description": "Alan Turing", - - - - - - - // type code here for "images" field - - - - - - - "status": "draft", - - - - - - - "level": "all_levels", - - - - - - - "category": "Marie Curie", - - - - - - - "language": "Marie Curie", - - - - - - - "price": 9.42, - - - - - - - "is_free": false, - - - - - - - "published_at": new Date(Date.now()), - - - - - - - // type code here for "relation_one" field - - - - }, - - { - - - - - "title": "Marie Curie", - - - - - - - "subtitle": "Alan Turing", - - - - - - - "description": "Ada Lovelace", - - - - - - - // type code here for "images" field - - - - - - - "status": "draft", - - - - - - - "level": "intermediate", - - - - - - - "category": "Ada Lovelace", - - - - - - - "language": "Grace Hopper", - - - - - - - "price": 3.07, - - - - - - - "is_free": true, - - - - - - - "published_at": new Date(Date.now()), - - - - - - - // type code here for "relation_one" field - - - - }, - - { - - - - - "title": "Ada Lovelace", - - - - - - - "subtitle": "Ada Lovelace", - - - - - - - "description": "Marie Curie", - - - - - - - // type code here for "images" field - - - - - - - "status": "published", - - - - - - - "level": "beginner", - - - - - - - "category": "Ada Lovelace", - - - - - - - "language": "Ada Lovelace", - - - - - - - "price": 1.28, - - - - - - - "is_free": false, - - - - - - - "published_at": new Date(Date.now()), - - - - - - - // type code here for "relation_one" field - - - - }, - + { + title: 'Frederick Sanger', + + subtitle: 'Willard Libby', + + description: 'Isaac Newton', + + // type code here for "images" field + + status: 'archived', + + level: 'all_levels', + + category: 'Jonas Salk', + + language: 'Louis Victor de Broglie', + + price: 35.36, + + is_free: true, + + published_at: new Date(Date.now()), + + // type code here for "relation_one" field + }, + + { + title: 'Max von Laue', + + subtitle: 'Albert Einstein', + + description: 'Sigmund Freud', + + // type code here for "images" field + + status: 'archived', + + level: 'all_levels', + + category: 'Johannes Kepler', + + language: 'Paul Dirac', + + price: 42.83, + + is_free: false, + + published_at: new Date(Date.now()), + + // type code here for "relation_one" field + }, + + { + title: 'Carl Linnaeus', + + subtitle: 'Jean Baptiste Lamarck', + + description: 'Arthur Eddington', + + // type code here for "images" field + + status: 'published', + + level: 'all_levels', + + category: 'Pierre Simon de Laplace', + + language: 'James Watson', + + price: 39.75, + + is_free: false, + + published_at: new Date(Date.now()), + + // type code here for "relation_one" field + }, ]; - - const LessonsData = [ - - { - - - - - // type code here for "relation_one" field - - - - - - - "title": "Alan Turing", - - - - - - - "summary": "Ada Lovelace", - - - - - - - "content_type": "quiz", - - - - - - - "content": "Alan Turing", - - - - - - - "video_url": "Ada Lovelace", - - - - - - - // type code here for "files" field - - - - - - - "external_url": "Grace Hopper", - - - - - - - "sort_order": 1, - - - - - - - "estimated_minutes": 7, - - - - - - - "status": "draft", - - - - - - - "is_preview": false, - - - - }, - - { - - - - - // type code here for "relation_one" field - - - - - - - "title": "Alan Turing", - - - - - - - "summary": "Marie Curie", - - - - - - - "content_type": "file", - - - - - - - "content": "Alan Turing", - - - - - - - "video_url": "Alan Turing", - - - - - - - // type code here for "files" field - - - - - - - "external_url": "Marie Curie", - - - - - - - "sort_order": 2, - - - - - - - "estimated_minutes": 6, - - - - - - - "status": "archived", - - - - - - - "is_preview": true, - - - - }, - - { - - - - - // type code here for "relation_one" field - - - - - - - "title": "Ada Lovelace", - - - - - - - "summary": "Grace Hopper", - - - - - - - "content_type": "link", - - - - - - - "content": "Ada Lovelace", - - - - - - - "video_url": "Marie Curie", - - - - - - - // type code here for "files" field - - - - - - - "external_url": "Ada Lovelace", - - - - - - - "sort_order": 6, - - - - - - - "estimated_minutes": 3, - - - - - - - "status": "draft", - - - - - - - "is_preview": false, - - - - }, - - { - - - - - // type code here for "relation_one" field - - - - - - - "title": "Ada Lovelace", - - - - - - - "summary": "Alan Turing", - - - - - - - "content_type": "text", - - - - - - - "content": "Alan Turing", - - - - - - - "video_url": "Alan Turing", - - - - - - - // type code here for "files" field - - - - - - - "external_url": "Alan Turing", - - - - - - - "sort_order": 4, - - - - - - - "estimated_minutes": 9, - - - - - - - "status": "draft", - - - - - - - "is_preview": true, - - - - }, - - { - - - - - // type code here for "relation_one" field - - - - - - - "title": "Marie Curie", - - - - - - - "summary": "Marie Curie", - - - - - - - "content_type": "link", - - - - - - - "content": "Ada Lovelace", - - - - - - - "video_url": "Marie Curie", - - - - - - - // type code here for "files" field - - - - - - - "external_url": "Marie Curie", - - - - - - - "sort_order": 2, - - - - - - - "estimated_minutes": 5, - - - - - - - "status": "draft", - - - - - - - "is_preview": false, - - - - }, - + { + // type code here for "relation_one" field + + title: 'Louis Pasteur', + + summary: 'Murray Gell-Mann', + + content_type: 'text', + + content: 'Frederick Gowland Hopkins', + + video_url: 'Jean Piaget', + + // type code here for "files" field + + external_url: 'Jean Piaget', + + sort_order: 2, + + estimated_minutes: 8, + + status: 'draft', + + is_preview: true, + }, + + { + // type code here for "relation_one" field + + title: 'Willard Libby', + + summary: 'Johannes Kepler', + + content_type: 'quiz', + + content: 'Edward Teller', + + video_url: 'Paul Dirac', + + // type code here for "files" field + + external_url: 'Anton van Leeuwenhoek', + + sort_order: 8, + + estimated_minutes: 3, + + status: 'archived', + + is_preview: true, + }, + + { + // type code here for "relation_one" field + + title: 'Rudolf Virchow', + + summary: 'Emil Fischer', + + content_type: 'quiz', + + content: 'Jonas Salk', + + video_url: 'William Harvey', + + // type code here for "files" field + + external_url: 'Charles Darwin', + + sort_order: 9, + + estimated_minutes: 8, + + status: 'draft', + + is_preview: true, + }, ]; - - const EnrollmentsData = [ - - { - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "status": "active", - - - - - - - "enrolled_at": new Date(Date.now()), - - - - - - - "completed_at": new Date(Date.now()), - - - - - - - "progress_percent": 3.5, - - - - - - - "last_activity_at": new Date(Date.now()), - - - - }, - - { - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "status": "completed", - - - - - - - "enrolled_at": new Date(Date.now()), - - - - - - - "completed_at": new Date(Date.now()), - - - - - - - "progress_percent": 3.65, - - - - - - - "last_activity_at": new Date(Date.now()), - - - - }, - - { - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "status": "active", - - - - - - - "enrolled_at": new Date(Date.now()), - - - - - - - "completed_at": new Date(Date.now()), - - - - - - - "progress_percent": 0.91, - - - - - - - "last_activity_at": new Date(Date.now()), - - - - }, - - { - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "status": "active", - - - - - - - "enrolled_at": new Date(Date.now()), - - - - - - - "completed_at": new Date(Date.now()), - - - - - - - "progress_percent": 8.1, - - - - - - - "last_activity_at": new Date(Date.now()), - - - - }, - - { - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "status": "cancelled", - - - - - - - "enrolled_at": new Date(Date.now()), - - - - - - - "completed_at": new Date(Date.now()), - - - - - - - "progress_percent": 8.27, - - - - - - - "last_activity_at": new Date(Date.now()), - - - - }, - + { + // type code here for "relation_one" field + + // type code here for "relation_one" field + + status: 'completed', + + enrolled_at: new Date(Date.now()), + + completed_at: new Date(Date.now()), + + progress_percent: 86.99, + + last_activity_at: new Date(Date.now()), + }, + + { + // type code here for "relation_one" field + + // type code here for "relation_one" field + + status: 'cancelled', + + enrolled_at: new Date(Date.now()), + + completed_at: new Date(Date.now()), + + progress_percent: 93.52, + + last_activity_at: new Date(Date.now()), + }, + + { + // type code here for "relation_one" field + + // type code here for "relation_one" field + + status: 'cancelled', + + enrolled_at: new Date(Date.now()), + + completed_at: new Date(Date.now()), + + progress_percent: 49.84, + + last_activity_at: new Date(Date.now()), + }, ]; - - const LessonProgressData = [ - - { - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "status": "completed", - - - - - - - "progress_percent": 1.32, - - - - - - - "started_at": new Date(Date.now()), - - - - - - - "completed_at": new Date(Date.now()), - - - - - - - "last_viewed_at": new Date(Date.now()), - - - - - - - "time_spent_seconds": 5, - - - - }, - - { - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "status": "completed", - - - - - - - "progress_percent": 4.84, - - - - - - - "started_at": new Date(Date.now()), - - - - - - - "completed_at": new Date(Date.now()), - - - - - - - "last_viewed_at": new Date(Date.now()), - - - - - - - "time_spent_seconds": 3, - - - - }, - - { - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "status": "in_progress", - - - - - - - "progress_percent": 8.04, - - - - - - - "started_at": new Date(Date.now()), - - - - - - - "completed_at": new Date(Date.now()), - - - - - - - "last_viewed_at": new Date(Date.now()), - - - - - - - "time_spent_seconds": 5, - - - - }, - - { - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "status": "completed", - - - - - - - "progress_percent": 5.93, - - - - - - - "started_at": new Date(Date.now()), - - - - - - - "completed_at": new Date(Date.now()), - - - - - - - "last_viewed_at": new Date(Date.now()), - - - - - - - "time_spent_seconds": 6, - - - - }, - - { - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "status": "not_started", - - - - - - - "progress_percent": 1.12, - - - - - - - "started_at": new Date(Date.now()), - - - - - - - "completed_at": new Date(Date.now()), - - - - - - - "last_viewed_at": new Date(Date.now()), - - - - - - - "time_spent_seconds": 5, - - - - }, - + { + // type code here for "relation_one" field + + // type code here for "relation_one" field + + status: 'in_progress', + + progress_percent: 37.73, + + started_at: new Date(Date.now()), + + completed_at: new Date(Date.now()), + + last_viewed_at: new Date(Date.now()), + + time_spent_seconds: 1, + }, + + { + // type code here for "relation_one" field + + // type code here for "relation_one" field + + status: 'in_progress', + + progress_percent: 99.73, + + started_at: new Date(Date.now()), + + completed_at: new Date(Date.now()), + + last_viewed_at: new Date(Date.now()), + + time_spent_seconds: 8, + }, + + { + // type code here for "relation_one" field + + // type code here for "relation_one" field + + status: 'completed', + + progress_percent: 72.44, + + started_at: new Date(Date.now()), + + completed_at: new Date(Date.now()), + + last_viewed_at: new Date(Date.now()), + + time_spent_seconds: 3, + }, ]; - - const AnnouncementsData = [ - - { - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "title": "Grace Hopper", - - - - - - - "message": "Marie Curie", - - - - - - - "published_at": new Date(Date.now()), - - - - - - - "is_pinned": true, - - - - }, - - { - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "title": "Ada Lovelace", - - - - - - - "message": "Marie Curie", - - - - - - - "published_at": new Date(Date.now()), - - - - - - - "is_pinned": false, - - - - }, - - { - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "title": "Grace Hopper", - - - - - - - "message": "Ada Lovelace", - - - - - - - "published_at": new Date(Date.now()), - - - - - - - "is_pinned": true, - - - - }, - - { - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "title": "Grace Hopper", - - - - - - - "message": "Alan Turing", - - - - - - - "published_at": new Date(Date.now()), - - - - - - - "is_pinned": true, - - - - }, - - { - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "title": "Marie Curie", - - - - - - - "message": "Grace Hopper", - - - - - - - "published_at": new Date(Date.now()), - - - - - - - "is_pinned": true, - - - - }, - + { + // type code here for "relation_one" field + + // type code here for "relation_one" field + + title: 'John von Neumann', + + message: 'Hans Bethe', + + published_at: new Date(Date.now()), + + is_pinned: false, + }, + + { + // type code here for "relation_one" field + + // type code here for "relation_one" field + + title: 'Sigmund Freud', + + message: 'John Dalton', + + published_at: new Date(Date.now()), + + is_pinned: false, + }, + + { + // type code here for "relation_one" field + + // type code here for "relation_one" field + + title: 'Lucretius', + + message: 'Marie Curie', + + published_at: new Date(Date.now()), + + is_pinned: false, + }, ]; - - const CourseResourcesData = [ - - { - - - - - // type code here for "relation_one" field - - - - - - - "title": "Marie Curie", - - - - - - - "description": "Alan Turing", - - - - - - - // type code here for "files" field - - - - - - - "url": "Ada Lovelace", - - - - - - - "resource_type": "link", - - - - - - - "sort_order": 7, - - - - }, - - { - - - - - // type code here for "relation_one" field - - - - - - - "title": "Marie Curie", - - - - - - - "description": "Marie Curie", - - - - - - - // type code here for "files" field - - - - - - - "url": "Alan Turing", - - - - - - - "resource_type": "link", - - - - - - - "sort_order": 1, - - - - }, - - { - - - - - // type code here for "relation_one" field - - - - - - - "title": "Grace Hopper", - - - - - - - "description": "Marie Curie", - - - - - - - // type code here for "files" field - - - - - - - "url": "Ada Lovelace", - - - - - - - "resource_type": "link", - - - - - - - "sort_order": 6, - - - - }, - - { - - - - - // type code here for "relation_one" field - - - - - - - "title": "Marie Curie", - - - - - - - "description": "Ada Lovelace", - - - - - - - // type code here for "files" field - - - - - - - "url": "Ada Lovelace", - - - - - - - "resource_type": "link", - - - - - - - "sort_order": 9, - - - - }, - - { - - - - - // type code here for "relation_one" field - - - - - - - "title": "Marie Curie", - - - - - - - "description": "Marie Curie", - - - - - - - // type code here for "files" field - - - - - - - "url": "Alan Turing", - - - - - - - "resource_type": "file", - - - - - - - "sort_order": 8, - - - - }, - + { + // type code here for "relation_one" field + + title: 'J. Robert Oppenheimer', + + description: 'Frederick Gowland Hopkins', + + // type code here for "files" field + + url: 'Nicolaus Copernicus', + + resource_type: 'link', + + sort_order: 7, + }, + + { + // type code here for "relation_one" field + + title: 'J. Robert Oppenheimer', + + description: 'August Kekule', + + // type code here for "files" field + + url: 'Stephen Hawking', + + resource_type: 'link', + + sort_order: 5, + }, + + { + // type code here for "relation_one" field + + title: 'J. Robert Oppenheimer', + + description: 'Andreas Vesalius', + + // type code here for "files" field + + url: 'Albert Einstein', + + resource_type: 'link', + + sort_order: 9, + }, ]; +const TestData = [ + { + name: 'Johannes Kepler', + amount: 'Francis Galton', + }, + { + name: 'Ernst Haeckel', - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Similar logic for "relation_many" - - + amount: 'William Bayliss', + }, - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - async function associateCoursWithInstructor() { - - const relatedInstructor0 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Cours0 = await Courses.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Cours0?.setInstructor) - { - await - Cours0. - setInstructor(relatedInstructor0); - } - - const relatedInstructor1 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Cours1 = await Courses.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Cours1?.setInstructor) - { - await - Cours1. - setInstructor(relatedInstructor1); - } - - const relatedInstructor2 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Cours2 = await Courses.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Cours2?.setInstructor) - { - await - Cours2. - setInstructor(relatedInstructor2); - } - - const relatedInstructor3 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Cours3 = await Courses.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Cours3?.setInstructor) - { - await - Cours3. - setInstructor(relatedInstructor3); - } - - const relatedInstructor4 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Cours4 = await Courses.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Cours4?.setInstructor) - { - await - Cours4. - setInstructor(relatedInstructor4); - } - - } - - + { + name: 'Konrad Lorenz', - - - - - - async function associateLessonWithCourse() { - - const relatedCourse0 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), - }); - const Lesson0 = await Lessons.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Lesson0?.setCourse) - { - await - Lesson0. - setCourse(relatedCourse0); - } - - const relatedCourse1 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), - }); - const Lesson1 = await Lessons.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Lesson1?.setCourse) - { - await - Lesson1. - setCourse(relatedCourse1); - } - - const relatedCourse2 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), - }); - const Lesson2 = await Lessons.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Lesson2?.setCourse) - { - await - Lesson2. - setCourse(relatedCourse2); - } - - const relatedCourse3 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), - }); - const Lesson3 = await Lessons.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Lesson3?.setCourse) - { - await - Lesson3. - setCourse(relatedCourse3); - } - - const relatedCourse4 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), - }); - const Lesson4 = await Lessons.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Lesson4?.setCourse) - { - await - Lesson4. - setCourse(relatedCourse4); - } - - } - - - - - - - - - - - - - - - - - - - - - - - - + amount: 'Archimedes', + }, +]; - - - - - - async function associateEnrollmentWithStudent() { - - const relatedStudent0 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Enrollment0 = await Enrollments.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Enrollment0?.setStudent) - { - await - Enrollment0. - setStudent(relatedStudent0); - } - - const relatedStudent1 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Enrollment1 = await Enrollments.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Enrollment1?.setStudent) - { - await - Enrollment1. - setStudent(relatedStudent1); - } - - const relatedStudent2 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Enrollment2 = await Enrollments.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Enrollment2?.setStudent) - { - await - Enrollment2. - setStudent(relatedStudent2); - } - - const relatedStudent3 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Enrollment3 = await Enrollments.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Enrollment3?.setStudent) - { - await - Enrollment3. - setStudent(relatedStudent3); - } - - const relatedStudent4 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Enrollment4 = await Enrollments.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Enrollment4?.setStudent) - { - await - Enrollment4. - setStudent(relatedStudent4); - } - - } - - - - - async function associateEnrollmentWithCourse() { - - const relatedCourse0 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), - }); - const Enrollment0 = await Enrollments.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Enrollment0?.setCourse) - { - await - Enrollment0. - setCourse(relatedCourse0); - } - - const relatedCourse1 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), - }); - const Enrollment1 = await Enrollments.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Enrollment1?.setCourse) - { - await - Enrollment1. - setCourse(relatedCourse1); - } - - const relatedCourse2 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), - }); - const Enrollment2 = await Enrollments.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Enrollment2?.setCourse) - { - await - Enrollment2. - setCourse(relatedCourse2); - } - - const relatedCourse3 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), - }); - const Enrollment3 = await Enrollments.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Enrollment3?.setCourse) - { - await - Enrollment3. - setCourse(relatedCourse3); - } - - const relatedCourse4 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), - }); - const Enrollment4 = await Enrollments.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Enrollment4?.setCourse) - { - await - Enrollment4. - setCourse(relatedCourse4); - } - - } - - - - - - - - - - - - +// Similar logic for "relation_many" - - - - - - async function associateLessonProgresWithEnrollment() { - - const relatedEnrollment0 = await Enrollments.findOne({ - offset: Math.floor(Math.random() * (await Enrollments.count())), - }); - const LessonProgres0 = await LessonProgress.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (LessonProgres0?.setEnrollment) - { - await - LessonProgres0. - setEnrollment(relatedEnrollment0); - } - - const relatedEnrollment1 = await Enrollments.findOne({ - offset: Math.floor(Math.random() * (await Enrollments.count())), - }); - const LessonProgres1 = await LessonProgress.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (LessonProgres1?.setEnrollment) - { - await - LessonProgres1. - setEnrollment(relatedEnrollment1); - } - - const relatedEnrollment2 = await Enrollments.findOne({ - offset: Math.floor(Math.random() * (await Enrollments.count())), - }); - const LessonProgres2 = await LessonProgress.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (LessonProgres2?.setEnrollment) - { - await - LessonProgres2. - setEnrollment(relatedEnrollment2); - } - - const relatedEnrollment3 = await Enrollments.findOne({ - offset: Math.floor(Math.random() * (await Enrollments.count())), - }); - const LessonProgres3 = await LessonProgress.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (LessonProgres3?.setEnrollment) - { - await - LessonProgres3. - setEnrollment(relatedEnrollment3); - } - - const relatedEnrollment4 = await Enrollments.findOne({ - offset: Math.floor(Math.random() * (await Enrollments.count())), - }); - const LessonProgres4 = await LessonProgress.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (LessonProgres4?.setEnrollment) - { - await - LessonProgres4. - setEnrollment(relatedEnrollment4); - } - - } - - - - - async function associateLessonProgresWithLesson() { - - const relatedLesson0 = await Lessons.findOne({ - offset: Math.floor(Math.random() * (await Lessons.count())), - }); - const LessonProgres0 = await LessonProgress.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (LessonProgres0?.setLesson) - { - await - LessonProgres0. - setLesson(relatedLesson0); - } - - const relatedLesson1 = await Lessons.findOne({ - offset: Math.floor(Math.random() * (await Lessons.count())), - }); - const LessonProgres1 = await LessonProgress.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (LessonProgres1?.setLesson) - { - await - LessonProgres1. - setLesson(relatedLesson1); - } - - const relatedLesson2 = await Lessons.findOne({ - offset: Math.floor(Math.random() * (await Lessons.count())), - }); - const LessonProgres2 = await LessonProgress.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (LessonProgres2?.setLesson) - { - await - LessonProgres2. - setLesson(relatedLesson2); - } - - const relatedLesson3 = await Lessons.findOne({ - offset: Math.floor(Math.random() * (await Lessons.count())), - }); - const LessonProgres3 = await LessonProgress.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (LessonProgres3?.setLesson) - { - await - LessonProgres3. - setLesson(relatedLesson3); - } - - const relatedLesson4 = await Lessons.findOne({ - offset: Math.floor(Math.random() * (await Lessons.count())), - }); - const LessonProgres4 = await LessonProgress.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (LessonProgres4?.setLesson) - { - await - LessonProgres4. - setLesson(relatedLesson4); - } - - } - - - - - - - - - - - - - - +async function associateCourseWithInstructor() { + const relatedInstructor0 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Course0 = await Courses.findOne({ + order: [['id', 'ASC']], + offset: 0, + }); + if (Course0?.setInstructor) { + await Course0.setInstructor(relatedInstructor0); + } - - - - - - async function associateAnnouncementWithCourse() { - - const relatedCourse0 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), - }); - const Announcement0 = await Announcements.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Announcement0?.setCourse) - { - await - Announcement0. - setCourse(relatedCourse0); - } - - const relatedCourse1 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), - }); - const Announcement1 = await Announcements.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Announcement1?.setCourse) - { - await - Announcement1. - setCourse(relatedCourse1); - } - - const relatedCourse2 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), - }); - const Announcement2 = await Announcements.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Announcement2?.setCourse) - { - await - Announcement2. - setCourse(relatedCourse2); - } - - const relatedCourse3 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), - }); - const Announcement3 = await Announcements.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Announcement3?.setCourse) - { - await - Announcement3. - setCourse(relatedCourse3); - } - - const relatedCourse4 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), - }); - const Announcement4 = await Announcements.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Announcement4?.setCourse) - { - await - Announcement4. - setCourse(relatedCourse4); - } - - } - - - - - async function associateAnnouncementWithAuthor() { - - const relatedAuthor0 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Announcement0 = await Announcements.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Announcement0?.setAuthor) - { - await - Announcement0. - setAuthor(relatedAuthor0); - } - - const relatedAuthor1 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Announcement1 = await Announcements.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Announcement1?.setAuthor) - { - await - Announcement1. - setAuthor(relatedAuthor1); - } - - const relatedAuthor2 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Announcement2 = await Announcements.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Announcement2?.setAuthor) - { - await - Announcement2. - setAuthor(relatedAuthor2); - } - - const relatedAuthor3 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Announcement3 = await Announcements.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Announcement3?.setAuthor) - { - await - Announcement3. - setAuthor(relatedAuthor3); - } - - const relatedAuthor4 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Announcement4 = await Announcements.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Announcement4?.setAuthor) - { - await - Announcement4. - setAuthor(relatedAuthor4); - } - - } - - - - - - - - - - + const relatedInstructor1 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Course1 = await Courses.findOne({ + order: [['id', 'ASC']], + offset: 1, + }); + if (Course1?.setInstructor) { + await Course1.setInstructor(relatedInstructor1); + } - - - - - - async function associateCourseResourceWithCourse() { - - const relatedCourse0 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), - }); - const CourseResource0 = await CourseResources.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (CourseResource0?.setCourse) - { - await - CourseResource0. - setCourse(relatedCourse0); - } - - const relatedCourse1 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), - }); - const CourseResource1 = await CourseResources.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (CourseResource1?.setCourse) - { - await - CourseResource1. - setCourse(relatedCourse1); - } - - const relatedCourse2 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), - }); - const CourseResource2 = await CourseResources.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (CourseResource2?.setCourse) - { - await - CourseResource2. - setCourse(relatedCourse2); - } - - const relatedCourse3 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), - }); - const CourseResource3 = await CourseResources.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (CourseResource3?.setCourse) - { - await - CourseResource3. - setCourse(relatedCourse3); - } - - const relatedCourse4 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), - }); - const CourseResource4 = await CourseResources.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (CourseResource4?.setCourse) - { - await - CourseResource4. - setCourse(relatedCourse4); - } - - } - - - - - - - - - - - - - - + const relatedInstructor2 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Course2 = await Courses.findOne({ + order: [['id', 'ASC']], + offset: 2, + }); + if (Course2?.setInstructor) { + await Course2.setInstructor(relatedInstructor2); + } +} +async function associateLessonWithCourse() { + const relatedCourse0 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const Lesson0 = await Lessons.findOne({ + order: [['id', 'ASC']], + offset: 0, + }); + if (Lesson0?.setCourse) { + await Lesson0.setCourse(relatedCourse0); + } + + const relatedCourse1 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const Lesson1 = await Lessons.findOne({ + order: [['id', 'ASC']], + offset: 1, + }); + if (Lesson1?.setCourse) { + await Lesson1.setCourse(relatedCourse1); + } + + const relatedCourse2 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const Lesson2 = await Lessons.findOne({ + order: [['id', 'ASC']], + offset: 2, + }); + if (Lesson2?.setCourse) { + await Lesson2.setCourse(relatedCourse2); + } +} + +async function associateEnrollmentWithStudent() { + const relatedStudent0 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Enrollment0 = await Enrollments.findOne({ + order: [['id', 'ASC']], + offset: 0, + }); + if (Enrollment0?.setStudent) { + await Enrollment0.setStudent(relatedStudent0); + } + + const relatedStudent1 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Enrollment1 = await Enrollments.findOne({ + order: [['id', 'ASC']], + offset: 1, + }); + if (Enrollment1?.setStudent) { + await Enrollment1.setStudent(relatedStudent1); + } + + const relatedStudent2 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Enrollment2 = await Enrollments.findOne({ + order: [['id', 'ASC']], + offset: 2, + }); + if (Enrollment2?.setStudent) { + await Enrollment2.setStudent(relatedStudent2); + } +} + +async function associateEnrollmentWithCourse() { + const relatedCourse0 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const Enrollment0 = await Enrollments.findOne({ + order: [['id', 'ASC']], + offset: 0, + }); + if (Enrollment0?.setCourse) { + await Enrollment0.setCourse(relatedCourse0); + } + + const relatedCourse1 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const Enrollment1 = await Enrollments.findOne({ + order: [['id', 'ASC']], + offset: 1, + }); + if (Enrollment1?.setCourse) { + await Enrollment1.setCourse(relatedCourse1); + } + + const relatedCourse2 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const Enrollment2 = await Enrollments.findOne({ + order: [['id', 'ASC']], + offset: 2, + }); + if (Enrollment2?.setCourse) { + await Enrollment2.setCourse(relatedCourse2); + } +} + +async function associateLessonProgressWithEnrollment() { + const relatedEnrollment0 = await Enrollments.findOne({ + offset: Math.floor(Math.random() * (await Enrollments.count())), + }); + const LessonProgress0 = await LessonProgress.findOne({ + order: [['id', 'ASC']], + offset: 0, + }); + if (LessonProgress0?.setEnrollment) { + await LessonProgress0.setEnrollment(relatedEnrollment0); + } + + const relatedEnrollment1 = await Enrollments.findOne({ + offset: Math.floor(Math.random() * (await Enrollments.count())), + }); + const LessonProgress1 = await LessonProgress.findOne({ + order: [['id', 'ASC']], + offset: 1, + }); + if (LessonProgress1?.setEnrollment) { + await LessonProgress1.setEnrollment(relatedEnrollment1); + } + + const relatedEnrollment2 = await Enrollments.findOne({ + offset: Math.floor(Math.random() * (await Enrollments.count())), + }); + const LessonProgress2 = await LessonProgress.findOne({ + order: [['id', 'ASC']], + offset: 2, + }); + if (LessonProgress2?.setEnrollment) { + await LessonProgress2.setEnrollment(relatedEnrollment2); + } +} + +async function associateLessonProgressWithLesson() { + const relatedLesson0 = await Lessons.findOne({ + offset: Math.floor(Math.random() * (await Lessons.count())), + }); + const LessonProgress0 = await LessonProgress.findOne({ + order: [['id', 'ASC']], + offset: 0, + }); + if (LessonProgress0?.setLesson) { + await LessonProgress0.setLesson(relatedLesson0); + } + + const relatedLesson1 = await Lessons.findOne({ + offset: Math.floor(Math.random() * (await Lessons.count())), + }); + const LessonProgress1 = await LessonProgress.findOne({ + order: [['id', 'ASC']], + offset: 1, + }); + if (LessonProgress1?.setLesson) { + await LessonProgress1.setLesson(relatedLesson1); + } + + const relatedLesson2 = await Lessons.findOne({ + offset: Math.floor(Math.random() * (await Lessons.count())), + }); + const LessonProgress2 = await LessonProgress.findOne({ + order: [['id', 'ASC']], + offset: 2, + }); + if (LessonProgress2?.setLesson) { + await LessonProgress2.setLesson(relatedLesson2); + } +} + +async function associateAnnouncementWithCourse() { + const relatedCourse0 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const Announcement0 = await Announcements.findOne({ + order: [['id', 'ASC']], + offset: 0, + }); + if (Announcement0?.setCourse) { + await Announcement0.setCourse(relatedCourse0); + } + + const relatedCourse1 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const Announcement1 = await Announcements.findOne({ + order: [['id', 'ASC']], + offset: 1, + }); + if (Announcement1?.setCourse) { + await Announcement1.setCourse(relatedCourse1); + } + + const relatedCourse2 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const Announcement2 = await Announcements.findOne({ + order: [['id', 'ASC']], + offset: 2, + }); + if (Announcement2?.setCourse) { + await Announcement2.setCourse(relatedCourse2); + } +} + +async function associateAnnouncementWithAuthor() { + const relatedAuthor0 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Announcement0 = await Announcements.findOne({ + order: [['id', 'ASC']], + offset: 0, + }); + if (Announcement0?.setAuthor) { + await Announcement0.setAuthor(relatedAuthor0); + } + + const relatedAuthor1 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Announcement1 = await Announcements.findOne({ + order: [['id', 'ASC']], + offset: 1, + }); + if (Announcement1?.setAuthor) { + await Announcement1.setAuthor(relatedAuthor1); + } + + const relatedAuthor2 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Announcement2 = await Announcements.findOne({ + order: [['id', 'ASC']], + offset: 2, + }); + if (Announcement2?.setAuthor) { + await Announcement2.setAuthor(relatedAuthor2); + } +} + +async function associateCourseResourceWithCourse() { + const relatedCourse0 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const CourseResource0 = await CourseResources.findOne({ + order: [['id', 'ASC']], + offset: 0, + }); + if (CourseResource0?.setCourse) { + await CourseResource0.setCourse(relatedCourse0); + } + + const relatedCourse1 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const CourseResource1 = await CourseResources.findOne({ + order: [['id', 'ASC']], + offset: 1, + }); + if (CourseResource1?.setCourse) { + await CourseResource1.setCourse(relatedCourse1); + } + + const relatedCourse2 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const CourseResource2 = await CourseResources.findOne({ + order: [['id', 'ASC']], + offset: 2, + }); + if (CourseResource2?.setCourse) { + await CourseResource2.setCourse(relatedCourse2); + } +} module.exports = { - up: async (queryInterface, Sequelize) => { - - - - - - - - await Courses.bulkCreate(CoursesData); - - - - - await Lessons.bulkCreate(LessonsData); - - - - - await Enrollments.bulkCreate(EnrollmentsData); - - - - - await LessonProgress.bulkCreate(LessonProgressData); - - - - - await Announcements.bulkCreate(AnnouncementsData); - - - - - await CourseResources.bulkCreate(CourseResourcesData); - - - await Promise.all([ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Similar logic for "relation_many" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - await associateCoursWithInstructor(), - - - - - - - - await associateLessonWithCourse(), - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - await associateEnrollmentWithStudent(), - - - - - await associateEnrollmentWithCourse(), - - - - - - - - - - - - - - - - - - await associateLessonProgresWithEnrollment(), - - - - - await associateLessonProgresWithLesson(), - - - - - - - - - - - - - - - - - - - - await associateAnnouncementWithCourse(), - - - - - await associateAnnouncementWithAuthor(), - - - - - - - - - - - - - - - - await associateCourseResourceWithCourse(), - - - - - - - - - - - - - - - - ]); - - }, + up: async (queryInterface, Sequelize) => { + await Courses.bulkCreate(CoursesData); - down: async (queryInterface, Sequelize) => { - - - - - - - await queryInterface.bulkDelete('courses', null, {}); - - - await queryInterface.bulkDelete('lessons', null, {}); - - - await queryInterface.bulkDelete('enrollments', null, {}); - - - await queryInterface.bulkDelete('lesson_progress', null, {}); - - - await queryInterface.bulkDelete('announcements', null, {}); - - - await queryInterface.bulkDelete('course_resources', null, {}); - - - }, -}; \ No newline at end of file + await Lessons.bulkCreate(LessonsData); + + await Enrollments.bulkCreate(EnrollmentsData); + + await LessonProgress.bulkCreate(LessonProgressData); + + await Announcements.bulkCreate(AnnouncementsData); + + await CourseResources.bulkCreate(CourseResourcesData); + + await Test.bulkCreate(TestData); + + await Promise.all([ + // Similar logic for "relation_many" + + await associateCourseWithInstructor(), + + await associateLessonWithCourse(), + + await associateEnrollmentWithStudent(), + + await associateEnrollmentWithCourse(), + + await associateLessonProgressWithEnrollment(), + + await associateLessonProgressWithLesson(), + + await associateAnnouncementWithCourse(), + + await associateAnnouncementWithAuthor(), + + await associateCourseResourceWithCourse(), + ]); + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete('courses', null, {}); + + await queryInterface.bulkDelete('lessons', null, {}); + + await queryInterface.bulkDelete('enrollments', null, {}); + + await queryInterface.bulkDelete('lesson_progress', null, {}); + + await queryInterface.bulkDelete('announcements', null, {}); + + await queryInterface.bulkDelete('course_resources', null, {}); + + await queryInterface.bulkDelete('test', null, {}); + }, +}; diff --git a/backend/src/db/seeders/20260313142927.js b/backend/src/db/seeders/20260313142927.js new file mode 100644 index 0000000..fe32966 --- /dev/null +++ b/backend/src/db/seeders/20260313142927.js @@ -0,0 +1,87 @@ +const { v4: uuid } = require('uuid'); +const db = require('../models'); +const Sequelize = require('sequelize'); +const config = require('../../config'); + +module.exports = { + /** + * @param{import("sequelize").QueryInterface} queryInterface + * @return {Promise} + */ + async up(queryInterface) { + const createdAt = new Date(); + const updatedAt = new Date(); + + /** @type {Map} */ + const idMap = new Map(); + + /** + * @param {string} key + * @return {string} + */ + function getId(key) { + if (idMap.has(key)) { + return idMap.get(key); + } + const id = uuid(); + idMap.set(key, id); + return id; + } + + /** + * @param {string} name + */ + function createPermissions(name) { + return [ + { + id: getId(`CREATE_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `CREATE_${name.toUpperCase()}`, + }, + { + id: getId(`READ_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `READ_${name.toUpperCase()}`, + }, + { + id: getId(`UPDATE_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `UPDATE_${name.toUpperCase()}`, + }, + { + id: getId(`DELETE_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `DELETE_${name.toUpperCase()}`, + }, + ]; + } + + const entities = ['test']; + + const createdPermissions = entities.flatMap(createPermissions); + + // Add permissions to database + await queryInterface.bulkInsert('permissions', createdPermissions); + // Get permissions ids + const permissionsIds = createdPermissions.map((p) => p.id); + // Get admin role + const adminRole = await db.roles.findOne({ + where: { name: config.roles.admin }, + }); + + if (adminRole) { + // Add permissions to admin role if it exists + await adminRole.addPermissions(permissionsIds); + } + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete( + 'permissions', + entities.flatMap(createPermissions), + ); + }, +}; diff --git a/backend/src/index.js b/backend/src/index.js index 56d0101..d26cd63 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -1,4 +1,3 @@ - const express = require('express'); const cors = require('cors'); const app = express(); @@ -14,13 +13,10 @@ const swaggerJsDoc = require('swagger-jsdoc'); const authRoutes = require('./routes/auth'); const fileRoutes = require('./routes/file'); const searchRoutes = require('./routes/search'); -const sqlRoutes = require('./routes/sql'); const pexelsRoutes = require('./routes/pexels'); const openaiRoutes = require('./routes/openai'); - - const usersRoutes = require('./routes/users'); const rolesRoutes = require('./routes/roles'); @@ -39,6 +35,7 @@ const announcementsRoutes = require('./routes/announcements'); const course_resourcesRoutes = require('./routes/course_resources'); +const testRoutes = require('./routes/test'); const getBaseUrl = (url) => { if (!url) return ''; @@ -47,17 +44,18 @@ const getBaseUrl = (url) => { const options = { definition: { - openapi: "3.0.0", - info: { - version: "1.0.0", - title: "Course LMS", - description: "Course LMS Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.", - }, + openapi: '3.0.0', + info: { + version: '1.0.0', + title: 'Course LMS', + description: + 'Course LMS Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.', + }, servers: [ { url: getBaseUrl(process.env.NEXT_PUBLIC_BACK_API) || config.swaggerUrl, - description: "Development server", - } + description: 'Development server', + }, ], components: { securitySchemes: { @@ -65,28 +63,36 @@ const options = { type: 'http', scheme: 'bearer', bearerFormat: 'JWT', - } + }, }, responses: { UnauthorizedError: { - description: "Access token is missing or invalid" - } - } + description: 'Access token is missing or invalid', + }, + }, }, - security: [{ - bearerAuth: [] - }] + security: [ + { + bearerAuth: [], + }, + ], }, - apis: ["./src/routes/*.js"], + apis: ['./src/routes/*.js'], }; const specs = swaggerJsDoc(options); -app.use('/api-docs', function (req, res, next) { - swaggerUI.host = getBaseUrl(process.env.NEXT_PUBLIC_BACK_API) || req.get('host'); - next() - }, swaggerUI.serve, swaggerUI.setup(specs)) +app.use( + '/api-docs', + function (req, res, next) { + swaggerUI.host = + getBaseUrl(process.env.NEXT_PUBLIC_BACK_API) || req.get('host'); + next(); + }, + swaggerUI.serve, + swaggerUI.setup(specs), +); -app.use(cors({origin: true})); +app.use(cors({ origin: true })); require('./auth/auth'); app.use(bodyParser.json()); @@ -96,65 +102,94 @@ app.use('/api/file', fileRoutes); app.use('/api/pexels', pexelsRoutes); app.enable('trust proxy'); - -app.use('/api/users', passport.authenticate('jwt', {session: false}), usersRoutes); - -app.use('/api/roles', passport.authenticate('jwt', {session: false}), rolesRoutes); - -app.use('/api/permissions', passport.authenticate('jwt', {session: false}), permissionsRoutes); - -app.use('/api/courses', passport.authenticate('jwt', {session: false}), coursesRoutes); - -app.use('/api/lessons', passport.authenticate('jwt', {session: false}), lessonsRoutes); - -app.use('/api/enrollments', passport.authenticate('jwt', {session: false}), enrollmentsRoutes); - -app.use('/api/lesson_progress', passport.authenticate('jwt', {session: false}), lesson_progressRoutes); - -app.use('/api/announcements', passport.authenticate('jwt', {session: false}), announcementsRoutes); - -app.use('/api/course_resources', passport.authenticate('jwt', {session: false}), course_resourcesRoutes); - app.use( - '/api/openai', - passport.authenticate('jwt', { session: false }), - openaiRoutes, + '/api/users', + passport.authenticate('jwt', { session: false }), + usersRoutes, ); + app.use( - '/api/ai', - passport.authenticate('jwt', { session: false }), - openaiRoutes, + '/api/roles', + passport.authenticate('jwt', { session: false }), + rolesRoutes, +); + +app.use( + '/api/permissions', + passport.authenticate('jwt', { session: false }), + permissionsRoutes, +); + +app.use( + '/api/courses', + passport.authenticate('jwt', { session: false }), + coursesRoutes, +); + +app.use( + '/api/lessons', + passport.authenticate('jwt', { session: false }), + lessonsRoutes, +); + +app.use( + '/api/enrollments', + passport.authenticate('jwt', { session: false }), + enrollmentsRoutes, +); + +app.use( + '/api/lesson_progress', + passport.authenticate('jwt', { session: false }), + lesson_progressRoutes, +); + +app.use( + '/api/announcements', + passport.authenticate('jwt', { session: false }), + announcementsRoutes, +); + +app.use( + '/api/course_resources', + passport.authenticate('jwt', { session: false }), + course_resourcesRoutes, +); + +app.use( + '/api/test', + passport.authenticate('jwt', { session: false }), + testRoutes, +); + +app.use( + '/api/openai', + passport.authenticate('jwt', { session: false }), + openaiRoutes, ); app.use( '/api/search', passport.authenticate('jwt', { session: false }), - searchRoutes); -app.use( - '/api/sql', - passport.authenticate('jwt', { session: false }), - sqlRoutes); - - -const publicDir = path.join( - __dirname, - '../public', + searchRoutes, ); +const publicDir = path.join(__dirname, '../public'); + if (fs.existsSync(publicDir)) { app.use('/', express.static(publicDir)); - app.get('*', function(request, response) { - response.sendFile( - path.resolve(publicDir, 'index.html'), - ); + app.get('*', function (request, response) { + response.sendFile(path.resolve(publicDir, 'index.html')); }); } const PORT = process.env.NODE_ENV === 'dev_stage' ? 3000 : 8080; +db.sequelize.sync().then(function () { app.listen(PORT, () => { console.log(`Listening on port ${PORT}`); }); +}); module.exports = app; diff --git a/backend/src/routes/test.js b/backend/src/routes/test.js new file mode 100644 index 0000000..1657c33 --- /dev/null +++ b/backend/src/routes/test.js @@ -0,0 +1,436 @@ +const express = require('express'); + +const TestService = require('../services/test'); +const TestDBApi = require('../db/api/test'); +const wrapAsync = require('../helpers').wrapAsync; + +const router = express.Router(); + +const { parse } = require('json2csv'); + +const { checkCrudPermissions } = require('../middlewares/check-permissions'); + +router.use(checkCrudPermissions('test')); + +/** + * @swagger + * components: + * schemas: + * Test: + * type: object + * properties: + + * name: + * type: string + * default: name + * amount: + * type: string + * default: amount + + */ + +/** + * @swagger + * tags: + * name: Test + * description: The Test managing API + */ + +/** + * @swagger + * /api/test: + * post: + * security: + * - bearerAuth: [] + * tags: [Test] + * summary: Add new item + * description: Add new item + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * description: Data of the updated item + * type: object + * $ref: "#/components/schemas/Test" + * responses: + * 200: + * description: The item was successfully added + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Test" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 405: + * description: Invalid input data + * 500: + * description: Some server error + */ +router.post( + '/', + wrapAsync(async (req, res) => { + const referer = + req.headers.referer || + `${req.protocol}://${req.hostname}${req.originalUrl}`; + const link = new URL(referer); + await TestService.create(req.body.data, req.currentUser, true, link.host); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/budgets/bulk-import: + * post: + * security: + * - bearerAuth: [] + * tags: [Test] + * summary: Bulk import items + * description: Bulk import items + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * description: Data of the updated items + * type: array + * items: + * $ref: "#/components/schemas/Test" + * responses: + * 200: + * description: The items were successfully imported + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Test" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 405: + * description: Invalid input data + * 500: + * description: Some server error + * + */ +router.post( + '/bulk-import', + wrapAsync(async (req, res) => { + const referer = + req.headers.referer || + `${req.protocol}://${req.hostname}${req.originalUrl}`; + const link = new URL(referer); + await TestService.bulkImport(req, res, true, link.host); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/test/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Test] + * summary: Update the data of the selected item + * description: Update the data of the selected item + * parameters: + * - in: path + * name: id + * description: Item ID to update + * required: true + * schema: + * type: string + * requestBody: + * description: Set new item data + * required: true + * content: + * application/json: + * schema: + * properties: + * id: + * description: ID of the updated item + * type: string + * data: + * description: Data of the updated item + * type: object + * $ref: "#/components/schemas/Test" + * required: + * - id + * responses: + * 200: + * description: The item data was successfully updated + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Test" + * 400: + * description: Invalid ID supplied + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ +router.put( + '/:id', + wrapAsync(async (req, res) => { + await TestService.update(req.body.data, req.body.id, req.currentUser); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/test/{id}: + * delete: + * security: + * - bearerAuth: [] + * tags: [Test] + * summary: Delete the selected item + * description: Delete the selected item + * parameters: + * - in: path + * name: id + * description: Item ID to delete + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The item was successfully deleted + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Test" + * 400: + * description: Invalid ID supplied + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ +router.delete( + '/:id', + wrapAsync(async (req, res) => { + await TestService.remove(req.params.id, req.currentUser); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/test/deleteByIds: + * post: + * security: + * - bearerAuth: [] + * tags: [Test] + * summary: Delete the selected item list + * description: Delete the selected item list + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * ids: + * description: IDs of the updated items + * type: array + * responses: + * 200: + * description: The items was successfully deleted + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Test" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Items not found + * 500: + * description: Some server error + */ +router.post( + '/deleteByIds', + wrapAsync(async (req, res) => { + await TestService.deleteByIds(req.body.data, req.currentUser); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/test: + * get: + * security: + * - bearerAuth: [] + * tags: [Test] + * summary: Get all test + * description: Get all test + * responses: + * 200: + * description: Test list successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Test" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Data not found + * 500: + * description: Some server error + */ +router.get( + '/', + wrapAsync(async (req, res) => { + const filetype = req.query.filetype; + + const currentUser = req.currentUser; + const payload = await TestDBApi.findAll(req.query, { currentUser }); + if (filetype && filetype === 'csv') { + const fields = ['id', 'name', 'amount']; + const opts = { fields }; + try { + const csv = parse(payload.rows, opts); + res.status(200).attachment(csv); + res.send(csv); + } catch (err) { + console.error(err); + } + } else { + res.status(200).send(payload); + } + }), +); + +/** + * @swagger + * /api/test/count: + * get: + * security: + * - bearerAuth: [] + * tags: [Test] + * summary: Count all test + * description: Count all test + * responses: + * 200: + * description: Test count successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Test" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Data not found + * 500: + * description: Some server error + */ +router.get( + '/count', + wrapAsync(async (req, res) => { + const currentUser = req.currentUser; + const payload = await TestDBApi.findAll(req.query, null, { + countOnly: true, + currentUser, + }); + + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/test/autocomplete: + * get: + * security: + * - bearerAuth: [] + * tags: [Test] + * summary: Find all test that match search criteria + * description: Find all test that match search criteria + * responses: + * 200: + * description: Test list successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Test" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Data not found + * 500: + * description: Some server error + */ +router.get('/autocomplete', async (req, res) => { + const payload = await TestDBApi.findAllAutocomplete( + req.query.query, + req.query.limit, + req.query.offset, + ); + + res.status(200).send(payload); +}); + +/** + * @swagger + * /api/test/{id}: + * get: + * security: + * - bearerAuth: [] + * tags: [Test] + * summary: Get selected item + * description: Get selected item + * parameters: + * - in: path + * name: id + * description: ID of item to get + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Selected item successfully received + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Test" + * 400: + * description: Invalid ID supplied + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ +router.get( + '/:id', + wrapAsync(async (req, res) => { + const payload = await TestDBApi.findBy({ id: req.params.id }); + + res.status(200).send(payload); + }), +); + +router.use('/', require('../helpers').commonErrorHandler); + +module.exports = router; diff --git a/backend/src/services/search.js b/backend/src/services/search.js index f6b1548..9b3535c 100644 --- a/backend/src/services/search.js +++ b/backend/src/services/search.js @@ -9,7 +9,6 @@ const Op = Sequelize.Op; * @param {object} currentUser */ async function checkPermissions(permission, currentUser) { - if (!currentUser) { throw new ValidationError('auth.unauthorized'); } @@ -36,175 +35,34 @@ async function checkPermissions(permission, currentUser) { } module.exports = class SearchService { - static async search(searchQuery, currentUser ) { + static async search(searchQuery, currentUser) { try { if (!searchQuery) { throw new ValidationError('iam.errors.searchQueryRequired'); } const tableColumns = { - - - + users: ['firstName', 'lastName', 'phoneNumber', 'email'], - - "users": [ - - "firstName", - - "lastName", - - "phoneNumber", - - "email", - - ], - - - - - - + courses: ['title', 'subtitle', 'description', 'category', 'language'], - - "courses": [ - - "title", - - "subtitle", - - "description", - - "category", - - "language", - - ], - - - - + lessons: ['title', 'summary', 'content', 'video_url', 'external_url'], - - "lessons": [ - - "title", - - "summary", - - "content", - - "video_url", - - "external_url", - - ], - - - - + announcements: ['title', 'message'], - - - - + course_resources: ['title', 'description', 'url'], - - - - - - - "announcements": [ - - "title", - - "message", - - ], - - - - - - - "course_resources": [ - - "title", - - "description", - - "url", - - ], - - + test: ['name', 'amount'], }; const columnsInt = { - - - - - - - - - - - "courses": [ - - "price", - - ], - - - - - - "lessons": [ - - "sort_order", - - "estimated_minutes", - - ], - - - - - - "enrollments": [ - - "progress_percent", - - ], - - - - - - "lesson_progress": [ - - "progress_percent", - - "time_spent_seconds", - - ], - - - - - - - - - - "course_resources": [ - - "sort_order", - - ], - - + courses: ['price'], + + lessons: ['sort_order', 'estimated_minutes'], + + enrollments: ['progress_percent'], + + lesson_progress: ['progress_percent', 'time_spent_seconds'], + + course_resources: ['sort_order'], }; let allFoundRecords = []; @@ -215,48 +73,63 @@ module.exports = class SearchService { const attributesIntToSearch = columnsInt[tableName] || []; const whereCondition = { [Op.or]: [ - ...attributesToSearch.map(attribute => ({ + ...attributesToSearch.map((attribute) => ({ [attribute]: { - [Op.iLike] : `%${searchQuery}%`, + [Op.iLike]: `%${searchQuery}%`, }, })), - ...attributesIntToSearch.map(attribute => ( + ...attributesIntToSearch.map((attribute) => Sequelize.where( - Sequelize.cast(Sequelize.col(`${tableName}.${attribute}`), 'varchar'), - { [Op.iLike]: `%${searchQuery}%` } - ) - )), + Sequelize.cast( + Sequelize.col(`${tableName}.${attribute}`), + 'varchar', + ), + { [Op.iLike]: `%${searchQuery}%` }, + ), + ), ], }; - - - const hasPermission = await checkPermissions(`READ_${tableName.toUpperCase()}`, currentUser); + const hasPermission = await checkPermissions( + `READ_${tableName.toUpperCase()}`, + currentUser, + ); if (!hasPermission) { continue; } const foundRecords = await db[tableName].findAll({ where: whereCondition, - attributes: [...tableColumns[tableName], 'id', ...attributesIntToSearch], + attributes: [ + ...tableColumns[tableName], + 'id', + ...attributesIntToSearch, + ], }); - + const modifiedRecords = foundRecords.map((record) => { const matchAttribute = []; - + for (const attribute of attributesToSearch) { - if (record[attribute]?.toLowerCase()?.includes(searchQuery.toLowerCase())) { + 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())) { + if ( + castedValue && + castedValue.toLowerCase().includes(searchQuery.toLowerCase()) + ) { matchAttribute.push(attribute); } } - + return { ...record.get(), matchAttribute, @@ -273,4 +146,4 @@ module.exports = class SearchService { throw error; } } -} \ No newline at end of file +}; diff --git a/backend/src/services/test.js b/backend/src/services/test.js new file mode 100644 index 0000000..7b0c74b --- /dev/null +++ b/backend/src/services/test.js @@ -0,0 +1,114 @@ +const db = require('../db/models'); +const TestDBApi = require('../db/api/test'); +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'); + +module.exports = class TestService { + static async create(data, currentUser) { + const transaction = await db.sequelize.transaction(); + try { + await TestDBApi.create(data, { + currentUser, + transaction, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async bulkImport(req, res, sendInvitationEmails = true, host) { + const transaction = await db.sequelize.transaction(); + + try { + 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 TestDBApi.bulkImport(results, { + transaction, + ignoreDuplicates: true, + validate: true, + currentUser: req.currentUser, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async update(data, id, currentUser) { + const transaction = await db.sequelize.transaction(); + try { + let test = await TestDBApi.findBy({ id }, { transaction }); + + if (!test) { + throw new ValidationError('testNotFound'); + } + + const updatedTest = await TestDBApi.update(id, data, { + currentUser, + transaction, + }); + + await transaction.commit(); + return updatedTest; + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async deleteByIds(ids, currentUser) { + const transaction = await db.sequelize.transaction(); + + try { + await TestDBApi.deleteByIds(ids, { + currentUser, + transaction, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async remove(id, currentUser) { + const transaction = await db.sequelize.transaction(); + + try { + await TestDBApi.remove(id, { + currentUser, + transaction, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } +}; diff --git a/frontend/src/components/Test/CardTest.tsx b/frontend/src/components/Test/CardTest.tsx new file mode 100644 index 0000000..1db038e --- /dev/null +++ b/frontend/src/components/Test/CardTest.tsx @@ -0,0 +1,116 @@ +import React from 'react'; +import ImageField from '../ImageField'; +import ListActionsPopover from '../ListActionsPopover'; +import { useAppSelector } from '../../stores/hooks'; +import dataFormatter from '../../helpers/dataFormatter'; +import { Pagination } from '../Pagination'; +import { saveFile } from '../../helpers/fileSaver'; +import LoadingSpinner from '../LoadingSpinner'; +import Link from 'next/link'; + +import { hasPermission } from '../../helpers/userPermissions'; + +type Props = { + test: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; +}; + +const CardTest = ({ + test, + loading, + onDelete, + currentPage, + numPages, + onPageChange, +}: Props) => { + const asideScrollbarsStyle = useAppSelector( + (state) => state.style.asideScrollbarsStyle, + ); + const bgColor = useAppSelector((state) => state.style.cardsColor); + const darkMode = useAppSelector((state) => state.style.darkMode); + const corners = useAppSelector((state) => state.style.corners); + const focusRing = useAppSelector((state) => state.style.focusRingColor); + + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_TEST'); + + return ( +
+ {loading && } +
    + {!loading && + test.map((item, index) => ( +
  • +
    + + {item.id} + + +
    + +
    +
    +
    +
    +
    Name
    +
    +
    {item.name}
    +
    +
    + +
    +
    + Amount +
    +
    +
    + {item.amount} +
    +
    +
    +
    +
  • + ))} + {!loading && test.length === 0 && ( +
    +

    No data to display

    +
    + )} +
+
+ +
+
+ ); +}; + +export default CardTest; diff --git a/frontend/src/components/Test/ListTest.tsx b/frontend/src/components/Test/ListTest.tsx new file mode 100644 index 0000000..9419657 --- /dev/null +++ b/frontend/src/components/Test/ListTest.tsx @@ -0,0 +1,92 @@ +import React from 'react'; +import CardBox from '../CardBox'; +import ImageField from '../ImageField'; +import dataFormatter from '../../helpers/dataFormatter'; +import { saveFile } from '../../helpers/fileSaver'; +import ListActionsPopover from '../ListActionsPopover'; +import { useAppSelector } from '../../stores/hooks'; +import { Pagination } from '../Pagination'; +import LoadingSpinner from '../LoadingSpinner'; +import Link from 'next/link'; + +import { hasPermission } from '../../helpers/userPermissions'; + +type Props = { + test: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; +}; + +const ListTest = ({ + test, + loading, + onDelete, + currentPage, + numPages, + onPageChange, +}: Props) => { + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_TEST'); + + const corners = useAppSelector((state) => state.style.corners); + const bgColor = useAppSelector((state) => state.style.cardsColor); + + return ( + <> +
+ {loading && } + {!loading && + test.map((item) => ( +
+ +
+ dark:divide-dark-700 overflow-x-auto' + } + > +
+

Name

+

{item.name}

+
+ +
+

Amount

+

{item.amount}

+
+ + +
+
+
+ ))} + {!loading && test.length === 0 && ( +
+

No data to display

+
+ )} +
+
+ +
+ + ); +}; + +export default ListTest; diff --git a/frontend/src/components/Test/TableTest.tsx b/frontend/src/components/Test/TableTest.tsx new file mode 100644 index 0000000..9f0edec --- /dev/null +++ b/frontend/src/components/Test/TableTest.tsx @@ -0,0 +1,481 @@ +import React, { useEffect, useState, useMemo } from 'react'; +import { createPortal } from 'react-dom'; +import { ToastContainer, toast } from 'react-toastify'; +import BaseButton from '../BaseButton'; +import CardBoxModal from '../CardBoxModal'; +import CardBox from '../CardBox'; +import { + fetch, + update, + deleteItem, + setRefetch, + deleteItemsByIds, +} from '../../stores/test/testSlice'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { useRouter } from 'next/router'; +import { Field, Form, Formik } from 'formik'; +import { DataGrid, GridColDef } from '@mui/x-data-grid'; +import { loadColumns } from './configureTestCols'; +import _ from 'lodash'; +import dataFormatter from '../../helpers/dataFormatter'; +import { dataGridStyles } from '../../styles'; + +const perPage = 10; + +const TableSampleTest = ({ + filterItems, + setFilterItems, + filters, + showGrid, +}) => { + const notify = (type, msg) => toast(msg, { type, position: 'bottom-center' }); + + const dispatch = useAppDispatch(); + const router = useRouter(); + + const pagesList = []; + const [id, setId] = useState(null); + const [currentPage, setCurrentPage] = useState(0); + const [filterRequest, setFilterRequest] = React.useState(''); + const [columns, setColumns] = useState([]); + const [selectedRows, setSelectedRows] = useState([]); + const [sortModel, setSortModel] = useState([ + { + field: '', + sort: 'desc', + }, + ]); + + const { + test, + loading, + count, + notify: testNotify, + refetch, + } = useAppSelector((state) => state.test); + const { currentUser } = useAppSelector((state) => state.auth); + const focusRing = useAppSelector((state) => state.style.focusRingColor); + const bgColor = useAppSelector((state) => state.style.bgLayoutColor); + const corners = useAppSelector((state) => state.style.corners); + const numPages = + Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); + for (let i = 0; i < numPages; i++) { + pagesList.push(i); + } + + const loadData = async (page = currentPage, request = filterRequest) => { + if (page !== currentPage) setCurrentPage(page); + if (request !== filterRequest) setFilterRequest(request); + const { sort, field } = sortModel[0]; + + const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; + dispatch(fetch({ limit: perPage, page, query })); + }; + + useEffect(() => { + if (testNotify.showNotification) { + notify(testNotify.typeNotification, testNotify.textNotification); + } + }, [testNotify.showNotification]); + + useEffect(() => { + if (!currentUser) return; + loadData(); + }, [sortModel, currentUser]); + + useEffect(() => { + if (refetch) { + loadData(0); + dispatch(setRefetch(false)); + } + }, [refetch, dispatch]); + + const [isModalInfoActive, setIsModalInfoActive] = useState(false); + const [isModalTrashActive, setIsModalTrashActive] = useState(false); + + const handleModalAction = () => { + setIsModalInfoActive(false); + setIsModalTrashActive(false); + }; + + const handleDeleteModalAction = (id: string) => { + setId(id); + setIsModalTrashActive(true); + }; + const handleDeleteAction = async () => { + if (id) { + await dispatch(deleteItem(id)); + await loadData(0); + setIsModalTrashActive(false); + } + }; + + const generateFilterRequests = useMemo(() => { + let request = '&'; + filterItems.forEach((item) => { + const isRangeFilter = filters.find( + (filter) => + filter.title === item.fields.selectedField && + (filter.number || filter.date), + ); + + if (isRangeFilter) { + const from = item.fields.filterValueFrom; + const to = item.fields.filterValueTo; + if (from) { + request += `${item.fields.selectedField}Range=${from}&`; + } + if (to) { + request += `${item.fields.selectedField}Range=${to}&`; + } + } else { + const value = item.fields.filterValue; + if (value) { + request += `${item.fields.selectedField}=${value}&`; + } + } + }); + return request; + }, [filterItems, filters]); + + const deleteFilter = (value) => { + const newItems = filterItems.filter((item) => item.id !== value); + + if (newItems.length) { + setFilterItems(newItems); + } else { + loadData(0, ''); + + setFilterItems(newItems); + } + }; + + const handleSubmit = () => { + loadData(0, generateFilterRequests); + }; + + const handleChange = (id) => (e) => { + const value = e.target.value; + const name = e.target.name; + + setFilterItems( + filterItems.map((item) => { + if (item.id !== id) return item; + if (name === 'selectedField') return { id, fields: { [name]: value } }; + + return { id, fields: { ...item.fields, [name]: value } }; + }), + ); + }; + + const handleReset = () => { + setFilterItems([]); + loadData(0, ''); + }; + + const onPageChange = (page: number) => { + loadData(page); + setCurrentPage(page); + }; + + useEffect(() => { + if (!currentUser) return; + + loadColumns(handleDeleteModalAction, `test`, currentUser).then((newCols) => + setColumns(newCols), + ); + }, [currentUser]); + + const handleTableSubmit = async (id: string, data) => { + if (!_.isEmpty(data)) { + await dispatch(update({ id, data })) + .unwrap() + .then((res) => res) + .catch((err) => { + throw new Error(err); + }); + } + }; + + const onDeleteRows = async (selectedRows) => { + await dispatch(deleteItemsByIds(selectedRows)); + await loadData(0); + }; + + const controlClasses = + 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + + ` ${bgColor} ${focusRing} ${corners} ` + + 'dark:bg-slate-800 border'; + + const dataGrid = ( +
+ `datagrid--row`} + rows={test ?? []} + columns={columns} + initialState={{ + pagination: { + paginationModel: { + pageSize: 10, + }, + }, + }} + disableRowSelectionOnClick + onProcessRowUpdateError={(params) => { + console.log('Error', params); + }} + processRowUpdate={async (newRow, oldRow) => { + const data = dataFormatter.dataGridEditFormatter(newRow); + + try { + await handleTableSubmit(newRow.id, data); + return newRow; + } catch { + return oldRow; + } + }} + sortingMode={'server'} + checkboxSelection + onRowSelectionModelChange={(ids) => { + setSelectedRows(ids); + }} + onSortModelChange={(params) => { + params.length + ? setSortModel(params) + : setSortModel([{ field: '', sort: 'desc' }]); + }} + rowCount={count} + pageSizeOptions={[10]} + paginationMode={'server'} + loading={loading} + onPaginationModelChange={(params) => { + onPageChange(params.page); + }} + /> +
+ ); + + return ( + <> + {filterItems && Array.isArray(filterItems) && filterItems.length ? ( + + null} + > +
+ <> + {filterItems && + filterItems.map((filterItem) => { + return ( +
+
+
+ Filter +
+ + {filters.map((selectOption) => ( + + ))} + +
+ {filters.find( + (filter) => + filter.title === filterItem?.fields?.selectedField, + )?.type === 'enum' ? ( +
+
Value
+ + + {filters + .find( + (filter) => + filter.title === + filterItem?.fields?.selectedField, + ) + ?.options?.map((option) => ( + + ))} + +
+ ) : filters.find( + (filter) => + filter.title === + filterItem?.fields?.selectedField, + )?.number ? ( +
+
+
+ From +
+ +
+
+
+ To +
+ +
+
+ ) : filters.find( + (filter) => + filter.title === + filterItem?.fields?.selectedField, + )?.date ? ( +
+
+
+ From +
+ +
+
+
+ To +
+ +
+
+ ) : ( +
+
+ Contains +
+ +
+ )} +
+
+ Action +
+ { + deleteFilter(filterItem.id); + }} + /> +
+
+ ); + })} +
+ + +
+ +
+
+
+ ) : null} + +

Are you sure you want to delete this item?

+
+ + {dataGrid} + + {selectedRows.length > 0 && + createPortal( + onDeleteRows(selectedRows)} + />, + document.getElementById('delete-rows-button'), + )} + + + ); +}; + +export default TableSampleTest; diff --git a/frontend/src/components/Test/configureTestCols.tsx b/frontend/src/components/Test/configureTestCols.tsx new file mode 100644 index 0000000..d9046c0 --- /dev/null +++ b/frontend/src/components/Test/configureTestCols.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import BaseIcon from '../BaseIcon'; +import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; +import axios from 'axios'; +import { + GridActionsCellItem, + GridRowParams, + GridValueGetterParams, +} from '@mui/x-data-grid'; +import ImageField from '../ImageField'; +import { saveFile } from '../../helpers/fileSaver'; +import dataFormatter from '../../helpers/dataFormatter'; +import DataGridMultiSelect from '../DataGridMultiSelect'; +import ListActionsPopover from '../ListActionsPopover'; + +import { hasPermission } from '../../helpers/userPermissions'; + +type Params = (id: string) => void; + +export const loadColumns = async ( + onDelete: Params, + entityName: string, + + user, +) => { + async function callOptionsApi(entityName: string) { + if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; + + try { + const data = await axios(`/${entityName}/autocomplete?limit=100`); + return data.data; + } catch (error) { + console.log(error); + return []; + } + } + + const hasUpdatePermission = hasPermission(user, 'UPDATE_TEST'); + + return [ + { + field: 'name', + headerName: 'Name', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'amount', + headerName: 'Amount', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'actions', + type: 'actions', + minWidth: 30, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + getActions: (params: GridRowParams) => { + return [ +
+ +
, + ]; + }, + }, + ]; +}; diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts index 2cafd41..7ad2a89 100644 --- a/frontend/src/menuAside.ts +++ b/frontend/src/menuAside.ts @@ -1,6 +1,5 @@ import * as icon from '@mdi/js'; -import { MenuAsideItem } from './interfaces' -import { LMS_ROLE_GROUPS } from './helpers/roleVisibility'; +import { MenuAsideItem } from './interfaces'; const menuAside: MenuAsideItem[] = [ { @@ -8,7 +7,7 @@ const menuAside: MenuAsideItem[] = [ icon: icon.mdiViewDashboardOutline, label: 'Dashboard', }, - + { href: '/users/users-list', label: 'Users', @@ -16,7 +15,6 @@ const menuAside: MenuAsideItem[] = [ // @ts-ignore icon: icon.mdiAccountGroup ?? icon.mdiTable, permissions: 'READ_USERS', - roles: LMS_ROLE_GROUPS.instructorOrAdmin, }, { href: '/roles/roles-list', @@ -25,7 +23,6 @@ const menuAside: MenuAsideItem[] = [ // @ts-ignore icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable, permissions: 'READ_ROLES', - roles: LMS_ROLE_GROUPS.admin, }, { href: '/permissions/permissions-list', @@ -34,61 +31,80 @@ const menuAside: MenuAsideItem[] = [ // @ts-ignore icon: icon.mdiShieldAccountOutline ?? icon.mdiTable, permissions: 'READ_PERMISSIONS', - roles: LMS_ROLE_GROUPS.admin, }, { href: '/courses/courses-list', label: 'Courses', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: 'mdiSchool' in icon ? icon['mdiSchool' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + icon: + 'mdiSchool' in icon + ? icon['mdiSchool' as keyof typeof icon] + : icon.mdiTable ?? icon.mdiTable, permissions: 'READ_COURSES', - roles: LMS_ROLE_GROUPS.allLms, }, { href: '/lessons/lessons-list', label: 'Lessons', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: 'mdiBookOpenPageVariant' in icon ? icon['mdiBookOpenPageVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + icon: + 'mdiBookOpenPageVariant' in icon + ? icon['mdiBookOpenPageVariant' as keyof typeof icon] + : icon.mdiTable ?? icon.mdiTable, permissions: 'READ_LESSONS', - roles: LMS_ROLE_GROUPS.allLms, }, { href: '/enrollments/enrollments-list', label: 'Enrollments', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: 'mdiAccountSchool' in icon ? icon['mdiAccountSchool' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + icon: + 'mdiAccountSchool' in icon + ? icon['mdiAccountSchool' as keyof typeof icon] + : icon.mdiTable ?? icon.mdiTable, permissions: 'READ_ENROLLMENTS', - roles: LMS_ROLE_GROUPS.allLms, }, { href: '/lesson_progress/lesson_progress-list', label: 'Lesson progress', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: 'mdiProgressCheck' in icon ? icon['mdiProgressCheck' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + icon: + 'mdiProgressCheck' in icon + ? icon['mdiProgressCheck' as keyof typeof icon] + : icon.mdiTable ?? icon.mdiTable, permissions: 'READ_LESSON_PROGRESS', - roles: LMS_ROLE_GROUPS.allLms, }, { href: '/announcements/announcements-list', label: 'Announcements', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: 'mdiBullhorn' in icon ? icon['mdiBullhorn' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + icon: + 'mdiBullhorn' in icon + ? icon['mdiBullhorn' as keyof typeof icon] + : icon.mdiTable ?? icon.mdiTable, permissions: 'READ_ANNOUNCEMENTS', - roles: LMS_ROLE_GROUPS.allLms, }, { href: '/course_resources/course_resources-list', label: 'Course resources', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: 'mdiFolderOpen' in icon ? icon['mdiFolderOpen' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + icon: + 'mdiFolderOpen' in icon + ? icon['mdiFolderOpen' as keyof typeof icon] + : icon.mdiTable ?? icon.mdiTable, permissions: 'READ_COURSE_RESOURCES', - roles: LMS_ROLE_GROUPS.allLms, + }, + { + href: '/test/test-list', + label: 'Test', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_TEST', }, { href: '/profile', @@ -96,15 +112,13 @@ const menuAside: MenuAsideItem[] = [ icon: icon.mdiAccountCircle, }, - { href: '/api-docs', target: '_blank', label: 'Swagger API', icon: icon.mdiFileCode, permissions: 'READ_API_DOCS', - roles: LMS_ROLE_GROUPS.admin, }, -] +]; -export default menuAside +export default menuAside; diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx index d5c9e40..a49c02a 100644 --- a/frontend/src/pages/dashboard.tsx +++ b/frontend/src/pages/dashboard.tsx @@ -1,575 +1,542 @@ import * as icon from '@mdi/js'; -import Head from 'next/head' -import React from 'react' +import Head from 'next/head'; +import React from 'react'; import axios from 'axios'; -import type { ReactElement } from 'react' -import LayoutAuthenticated from '../layouts/Authenticated' -import SectionMain from '../components/SectionMain' -import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton' -import BaseIcon from "../components/BaseIcon"; -import { getPageTitle } from '../config' -import Link from "next/link"; +import type { ReactElement } from 'react'; +import LayoutAuthenticated from '../layouts/Authenticated'; +import SectionMain from '../components/SectionMain'; +import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; +import BaseIcon from '../components/BaseIcon'; +import { getPageTitle } from '../config'; +import Link from 'next/link'; +import { useTranslation } from 'next-i18next'; -import { hasPermission } from "../helpers/userPermissions"; -import { LMS_ROLE_GROUPS, userHasAnyRole } from '../helpers/roleVisibility'; +import { hasPermission } from '../helpers/userPermissions'; import { fetchWidgets } from '../stores/roles/rolesSlice'; import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator'; import { SmartWidget } from '../components/SmartWidget/SmartWidget'; import { useAppDispatch, useAppSelector } from '../stores/hooks'; - -type LearnerMetricsState = { - enrolledCourses: string | number | null; - completedCourses: string | number | null; - avgCourseProgress: string | number | null; - lessonsInProgress: string | number | null; - lessonsCompleted: string | number | null; - hoursSpent: string | number | null; -}; - const Dashboard = () => { - const dispatch = useAppDispatch(); - const iconsColor = useAppSelector((state) => state.style.iconsColor); - const corners = useAppSelector((state) => state.style.corners); - const cardsStyle = useAppSelector((state) => state.style.cardsStyle); + const { t } = useTranslation('common'); + const dispatch = useAppDispatch(); + const iconsColor = useAppSelector((state) => state.style.iconsColor); + const corners = useAppSelector((state) => state.style.corners); + const cardsStyle = useAppSelector((state) => state.style.cardsStyle); - const loadingMessage = 'Loading...'; + const loadingMessage = t('pages.dashboard.loading', { + defaultValue: 'Loading...', + }); - - const [users, setUsers] = React.useState(loadingMessage); - const [roles, setRoles] = React.useState(loadingMessage); - const [permissions, setPermissions] = React.useState(loadingMessage); - const [courses, setCourses] = React.useState(loadingMessage); - const [lessons, setLessons] = React.useState(loadingMessage); - const [enrollments, setEnrollments] = React.useState(loadingMessage); - const [lesson_progress, setLesson_progress] = React.useState(loadingMessage); - const [announcements, setAnnouncements] = React.useState(loadingMessage); - const [course_resources, setCourse_resources] = React.useState(loadingMessage); - const [learnerMetrics, setLearnerMetrics] = React.useState({ - enrolledCourses: loadingMessage, - completedCourses: loadingMessage, - avgCourseProgress: loadingMessage, - lessonsInProgress: loadingMessage, - lessonsCompleted: loadingMessage, - hoursSpent: loadingMessage, + const [users, setUsers] = React.useState(loadingMessage); + const [roles, setRoles] = React.useState(loadingMessage); + const [permissions, setPermissions] = React.useState(loadingMessage); + const [courses, setCourses] = React.useState(loadingMessage); + const [lessons, setLessons] = React.useState(loadingMessage); + const [enrollments, setEnrollments] = React.useState(loadingMessage); + const [lesson_progress, setLesson_progress] = React.useState(loadingMessage); + const [announcements, setAnnouncements] = React.useState(loadingMessage); + const [course_resources, setCourse_resources] = + React.useState(loadingMessage); + const [test, setTest] = React.useState(loadingMessage); + + const [widgetsRole, setWidgetsRole] = React.useState({ + role: { value: '', label: '' }, + }); + const { currentUser } = useAppSelector((state) => state.auth); + const { isFetchingQuery } = useAppSelector((state) => state.openAi); + + const { rolesWidgets, loading } = useAppSelector((state) => state.roles); + + async function loadData() { + const entities = [ + 'users', + 'roles', + 'permissions', + 'courses', + 'lessons', + 'enrollments', + 'lesson_progress', + 'announcements', + 'course_resources', + 'test', + ]; + const fns = [ + setUsers, + setRoles, + setPermissions, + setCourses, + setLessons, + setEnrollments, + setLesson_progress, + setAnnouncements, + setCourse_resources, + setTest, + ]; + + const requests = entities.map((entity, index) => { + if (hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) { + return axios.get(`/${entity.toLowerCase()}/count`); + } else { + fns[index](null); + return Promise.resolve({ data: { count: null } }); + } }); - - const [widgetsRole, setWidgetsRole] = React.useState({ - role: { value: '', label: '' }, - }); - const { currentUser } = useAppSelector((state) => state.auth); - const { isFetchingQuery } = useAppSelector((state) => state.openAi); - - const { rolesWidgets, loading } = useAppSelector((state) => state.roles); - const isAdmin = userHasAnyRole(currentUser, LMS_ROLE_GROUPS.admin); - const isInstructor = userHasAnyRole(currentUser, LMS_ROLE_GROUPS.instructor); - const isLearner = userHasAnyRole(currentUser, LMS_ROLE_GROUPS.learner); - const overviewTitle = isLearner ? 'My Learning Overview' : isInstructor ? 'Instructor Overview' : 'Platform Overview'; - - - async function loadData() { - const entities = ['users','roles','permissions','courses','lessons','enrollments','lesson_progress','announcements','course_resources',]; - const fns = [setUsers,setRoles,setPermissions,setCourses,setLessons,setEnrollments,setLesson_progress,setAnnouncements,setCourse_resources,]; - - const requests = entities.map((entity, index) => { - - if(hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) { - return axios.get(`/${entity.toLowerCase()}/count`); - } else { - fns[index](null); - return Promise.resolve({data: {count: null}}); - } - - }); - - Promise.allSettled(requests).then((results) => { - results.forEach((result, i) => { - if (result.status === 'fulfilled') { - fns[i](result.value.data.count); - } else { - fns[i](result.reason.message); - } - }); - }); - } - - async function loadLearnerMetrics() { - if (!currentUser?.id || !isLearner) return; - - try { - const canReadEnrollments = hasPermission(currentUser, 'READ_ENROLLMENTS'); - const canReadLessonProgress = hasPermission(currentUser, 'READ_LESSON_PROGRESS'); - - const enrollmentsResponse = canReadEnrollments - ? await axios.get('/enrollments', { - params: { - page: 0, - limit: 500, - student: currentUser.id, - }, - }) - : { data: { rows: [] } }; - - const enrollmentRows = Array.isArray(enrollmentsResponse?.data?.rows) - ? enrollmentsResponse.data.rows - : []; - const enrolledCourses = enrollmentRows.length; - const completedCourses = enrollmentRows.filter((item) => item?.status === 'completed').length; - const avgCourseProgress = enrollmentRows.length - ? Math.round( - enrollmentRows.reduce((sum, item) => sum + Number(item?.progress_percent || 0), 0) / - enrollmentRows.length, - ) - : 0; - - const enrollmentIds = enrollmentRows.map((item) => item?.id).filter(Boolean); - const lessonProgressResponse = - canReadLessonProgress && enrollmentIds.length - ? await axios.get('/lesson_progress', { - params: { - page: 0, - limit: 1000, - enrollment: enrollmentIds.join('|'), - }, - }) - : { data: { rows: [] } }; - const lessonProgressRows = Array.isArray(lessonProgressResponse?.data?.rows) - ? lessonProgressResponse.data.rows - : []; - - const lessonsInProgress = lessonProgressRows.filter((item) => item?.status === 'in_progress').length; - const lessonsCompleted = lessonProgressRows.filter((item) => item?.status === 'completed').length; - const hoursSpent = - Math.round( - (lessonProgressRows.reduce((sum, item) => sum + Number(item?.time_spent_seconds || 0), 0) / 3600) * - 10, - ) / 10; - - setLearnerMetrics({ - enrolledCourses, - completedCourses, - avgCourseProgress: `${avgCourseProgress}%`, - lessonsInProgress, - lessonsCompleted, - hoursSpent, - }); - } catch (error) { - console.error('Failed to load learner metrics:', error); - setLearnerMetrics({ - enrolledCourses: 'Error', - completedCourses: 'Error', - avgCourseProgress: 'Error', - lessonsInProgress: 'Error', - lessonsCompleted: 'Error', - hoursSpent: 'Error', - }); + Promise.allSettled(requests).then((results) => { + results.forEach((result, i) => { + if (result.status === 'fulfilled') { + fns[i](result.value.data.count); + } else { + fns[i](result.reason.message); } - } - - async function getWidgets(roleId) { - await dispatch(fetchWidgets(roleId)); - } - React.useEffect(() => { - if (!currentUser) return; - loadData().then(); - loadLearnerMetrics().then(); - setWidgetsRole({ role: { value: currentUser?.app_role?.id, label: currentUser?.app_role?.name } }); - }, [currentUser, isLearner]); + }); + }); + } + + async function getWidgets(roleId) { + await dispatch(fetchWidgets(roleId)); + } + React.useEffect(() => { + if (!currentUser) return; + loadData().then(); + setWidgetsRole({ + role: { + value: currentUser?.app_role?.id, + label: currentUser?.app_role?.name, + }, + }); + }, [currentUser]); + + React.useEffect(() => { + if (!currentUser || !widgetsRole?.role?.value) return; + getWidgets(widgetsRole?.role?.value || '').then(); + }, [widgetsRole?.role?.value]); - React.useEffect(() => { - if (!currentUser || !widgetsRole?.role?.value) return; - getWidgets(widgetsRole?.role?.value || '').then(); - }, [widgetsRole?.role?.value]); - return ( <> - {getPageTitle('Overview')} + {getPageTitle( + t('pages.dashboard.pageTitle', { defaultValue: 'Overview' }), + )} + icon={icon.mdiChartTimelineVariant} + title={t('pages.dashboard.overview', { defaultValue: 'Overview' })} + main + > {''} - - {hasPermission(currentUser, 'CREATE_ROLES') && } + /> + )} {!!rolesWidgets.length && - hasPermission(currentUser, 'CREATE_ROLES') && ( -

- {`${widgetsRole?.role?.label || 'Users'}'s widgets`} -

- )} + hasPermission(currentUser, 'CREATE_ROLES') && ( +

+ {`${widgetsRole?.role?.label || 'Users'}'s widgets`} +

+ )}
- {(isFetchingQuery || loading) && ( -
- {' '} - Loading widgets... -
- )} + {(isFetchingQuery || loading) && ( +
+ {' '} + {t('pages.dashboard.loadingWidgets', { + defaultValue: 'Loading widgets...', + })} +
+ )} - { rolesWidgets && - rolesWidgets.map((widget) => ( - + {rolesWidgets && + rolesWidgets.map((widget) => ( + ))}
{!!rolesWidgets.length &&
} - {isLearner && ( - <> -
My Courses & Progress
-
- -
-
My courses
-
{learnerMetrics.enrolledCourses}
-
- - -
-
- Completed courses -
-
{learnerMetrics.completedCourses}
-
- - -
-
- Avg course progress -
-
{learnerMetrics.avgCourseProgress}
-
- - -
-
- Lessons in progress -
-
- {learnerMetrics.lessonsInProgress} -
-
- - -
-
- Lessons completed -
-
- {learnerMetrics.lessonsCompleted} -
-
- - -
-
Hours spent
-
{learnerMetrics.hoursSpent}
-
- -
- - )} - -
- - - {hasPermission(currentUser, 'READ_USERS') && !isLearner && -
-
-
-
- Users -
-
- {users} -
-
-
- -
+
+ {hasPermission(currentUser, 'READ_USERS') && ( + +
+
+
+
+ Users
-
- } - - {hasPermission(currentUser, 'READ_ROLES') && isAdmin && -
-
-
-
- Roles -
-
- {roles} -
-
-
- -
+
+ {users}
+
+
+ +
- } - - {hasPermission(currentUser, 'READ_PERMISSIONS') && isAdmin && -
-
-
-
- Permissions -
-
- {permissions} -
-
-
- -
+
+ + )} + + {hasPermission(currentUser, 'READ_ROLES') && ( + +
+
+
+
+ Roles
-
- } - - {hasPermission(currentUser, 'READ_COURSES') && -
-
-
-
- Courses -
-
- {courses} -
-
-
- -
+
+ {roles}
+
+
+ +
- } - - {hasPermission(currentUser, 'READ_LESSONS') && -
-
-
-
- Lessons -
-
- {lessons} -
-
-
- -
+
+ + )} + + {hasPermission(currentUser, 'READ_PERMISSIONS') && ( + +
+
+
+
+ Permissions
-
- } - - {hasPermission(currentUser, 'READ_ENROLLMENTS') && -
-
-
-
- Enrollments -
-
- {enrollments} -
-
-
- -
+
+ {permissions}
+
+
+ +
- } - - {hasPermission(currentUser, 'READ_LESSON_PROGRESS') && -
-
-
-
- Lesson progress -
-
- {lesson_progress} -
-
-
- -
+
+ + )} + + {hasPermission(currentUser, 'READ_COURSES') && ( + +
+
+
+
+ Courses
-
- } - - {hasPermission(currentUser, 'READ_ANNOUNCEMENTS') && -
-
-
-
- Announcements -
-
- {announcements} -
-
-
- -
+
+ {courses}
+
+
+ +
- } - - {hasPermission(currentUser, 'READ_COURSE_RESOURCES') && -
-
-
-
- Course resources -
-
- {course_resources} -
-
-
- -
+
+ + )} + + {hasPermission(currentUser, 'READ_LESSONS') && ( + +
+
+
+
+ Lessons
+
+ {lessons} +
+
+
+ +
- } - - +
+ + )} + + {hasPermission(currentUser, 'READ_ENROLLMENTS') && ( + +
+
+
+
+ Enrollments +
+
+ {enrollments} +
+
+
+ +
+
+
+ + )} + + {hasPermission(currentUser, 'READ_LESSON_PROGRESS') && ( + +
+
+
+
+ Lesson progress +
+
+ {lesson_progress} +
+
+
+ +
+
+
+ + )} + + {hasPermission(currentUser, 'READ_ANNOUNCEMENTS') && ( + +
+
+
+
+ Announcements +
+
+ {announcements} +
+
+
+ +
+
+
+ + )} + + {hasPermission(currentUser, 'READ_COURSE_RESOURCES') && ( + +
+
+
+
+ Course resources +
+
+ {course_resources} +
+
+
+ +
+
+
+ + )} + + {hasPermission(currentUser, 'READ_TEST') && ( + +
+
+
+
+ Test +
+
+ {test} +
+
+
+ +
+
+
+ + )}
- ) -} + ); +}; Dashboard.getLayout = function getLayout(page: ReactElement) { - return {page} -} + return {page}; +}; -export default Dashboard +export default Dashboard; diff --git a/frontend/src/pages/test/[testId].tsx b/frontend/src/pages/test/[testId].tsx new file mode 100644 index 0000000..5a4a723 --- /dev/null +++ b/frontend/src/pages/test/[testId].tsx @@ -0,0 +1,128 @@ +import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; +import Head from 'next/head'; +import React, { ReactElement, useEffect, useState } from 'react'; +import DatePicker from 'react-datepicker'; +import 'react-datepicker/dist/react-datepicker.css'; +import dayjs from 'dayjs'; + +import CardBox from '../../components/CardBox'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../../config'; + +import { Field, Form, Formik } from 'formik'; +import FormField from '../../components/FormField'; +import BaseDivider from '../../components/BaseDivider'; +import BaseButtons from '../../components/BaseButtons'; +import BaseButton from '../../components/BaseButton'; +import FormCheckRadio from '../../components/FormCheckRadio'; +import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; +import FormFilePicker from '../../components/FormFilePicker'; +import FormImagePicker from '../../components/FormImagePicker'; +import { SelectField } from '../../components/SelectField'; +import { SelectFieldMany } from '../../components/SelectFieldMany'; +import { SwitchField } from '../../components/SwitchField'; +import { RichTextField } from '../../components/RichTextField'; + +import { update, fetch } from '../../stores/test/testSlice'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { useRouter } from 'next/router'; +import { saveFile } from '../../helpers/fileSaver'; +import dataFormatter from '../../helpers/dataFormatter'; +import ImageField from '../../components/ImageField'; + +const EditTest = () => { + const router = useRouter(); + const dispatch = useAppDispatch(); + const initVals = { + name: '', + + amount: '', + }; + const [initialValues, setInitialValues] = useState(initVals); + + const { test } = useAppSelector((state) => state.test); + + const { testId } = router.query; + + useEffect(() => { + dispatch(fetch({ id: testId })); + }, [testId]); + + useEffect(() => { + if (typeof test === 'object') { + setInitialValues(test); + } + }, [test]); + + useEffect(() => { + if (typeof test === 'object') { + const newInitialVal = { ...initVals }; + + Object.keys(initVals).forEach((el) => (newInitialVal[el] = test[el])); + + setInitialValues(newInitialVal); + } + }, [test]); + + const handleSubmit = async (data) => { + await dispatch(update({ id: testId, data })); + await router.push('/test/test-list'); + }; + + return ( + <> + + {getPageTitle('Edit test')} + + + + {''} + + + handleSubmit(values)} + > +
+ + + + + + + + + + + + + router.push('/test/test-list')} + /> + + +
+
+
+ + ); +}; + +EditTest.getLayout = function getLayout(page: ReactElement) { + return ( + {page} + ); +}; + +export default EditTest; diff --git a/frontend/src/pages/test/test-edit.tsx b/frontend/src/pages/test/test-edit.tsx new file mode 100644 index 0000000..e93881c --- /dev/null +++ b/frontend/src/pages/test/test-edit.tsx @@ -0,0 +1,126 @@ +import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; +import Head from 'next/head'; +import React, { ReactElement, useEffect, useState } from 'react'; +import DatePicker from 'react-datepicker'; +import 'react-datepicker/dist/react-datepicker.css'; +import dayjs from 'dayjs'; + +import CardBox from '../../components/CardBox'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../../config'; + +import { Field, Form, Formik } from 'formik'; +import FormField from '../../components/FormField'; +import BaseDivider from '../../components/BaseDivider'; +import BaseButtons from '../../components/BaseButtons'; +import BaseButton from '../../components/BaseButton'; +import FormCheckRadio from '../../components/FormCheckRadio'; +import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; +import FormFilePicker from '../../components/FormFilePicker'; +import FormImagePicker from '../../components/FormImagePicker'; +import { SelectField } from '../../components/SelectField'; +import { SelectFieldMany } from '../../components/SelectFieldMany'; +import { SwitchField } from '../../components/SwitchField'; +import { RichTextField } from '../../components/RichTextField'; + +import { update, fetch } from '../../stores/test/testSlice'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { useRouter } from 'next/router'; +import { saveFile } from '../../helpers/fileSaver'; +import dataFormatter from '../../helpers/dataFormatter'; +import ImageField from '../../components/ImageField'; + +const EditTestPage = () => { + const router = useRouter(); + const dispatch = useAppDispatch(); + const initVals = { + name: '', + + amount: '', + }; + const [initialValues, setInitialValues] = useState(initVals); + + const { test } = useAppSelector((state) => state.test); + + const { id } = router.query; + + useEffect(() => { + dispatch(fetch({ id: id })); + }, [id]); + + useEffect(() => { + if (typeof test === 'object') { + setInitialValues(test); + } + }, [test]); + + useEffect(() => { + if (typeof test === 'object') { + const newInitialVal = { ...initVals }; + Object.keys(initVals).forEach((el) => (newInitialVal[el] = test[el])); + setInitialValues(newInitialVal); + } + }, [test]); + + const handleSubmit = async (data) => { + await dispatch(update({ id: id, data })); + await router.push('/test/test-list'); + }; + + return ( + <> + + {getPageTitle('Edit test')} + + + + {''} + + + handleSubmit(values)} + > +
+ + + + + + + + + + + + + router.push('/test/test-list')} + /> + + +
+
+
+ + ); +}; + +EditTestPage.getLayout = function getLayout(page: ReactElement) { + return ( + {page} + ); +}; + +export default EditTestPage; diff --git a/frontend/src/pages/test/test-list.tsx b/frontend/src/pages/test/test-list.tsx new file mode 100644 index 0000000..13a7363 --- /dev/null +++ b/frontend/src/pages/test/test-list.tsx @@ -0,0 +1,163 @@ +import { mdiChartTimelineVariant } from '@mdi/js'; +import Head from 'next/head'; +import { uniqueId } from 'lodash'; +import React, { ReactElement, useState } from 'react'; +import CardBox from '../../components/CardBox'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../../config'; +import TableTest from '../../components/Test/TableTest'; +import BaseButton from '../../components/BaseButton'; +import axios from 'axios'; +import Link from 'next/link'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import CardBoxModal from '../../components/CardBoxModal'; +import DragDropFilePicker from '../../components/DragDropFilePicker'; +import { setRefetch, uploadCsv } from '../../stores/test/testSlice'; + +import { hasPermission } from '../../helpers/userPermissions'; + +const TestTablesPage = () => { + const [filterItems, setFilterItems] = useState([]); + const [csvFile, setCsvFile] = useState(null); + const [isModalActive, setIsModalActive] = useState(false); + const [showTableView, setShowTableView] = useState(false); + + const { currentUser } = useAppSelector((state) => state.auth); + + const dispatch = useAppDispatch(); + + const [filters] = useState([ + { label: 'Name', title: 'name' }, + { label: 'Amount', title: 'amount' }, + ]); + + const hasCreatePermission = + currentUser && hasPermission(currentUser, 'CREATE_TEST'); + + const addFilter = () => { + const newItem = { + id: uniqueId(), + fields: { + filterValue: '', + filterValueFrom: '', + filterValueTo: '', + selectedField: '', + }, + }; + newItem.fields.selectedField = filters[0].title; + setFilterItems([...filterItems, newItem]); + }; + + const getTestCSV = async () => { + const response = await axios({ + url: '/test?filetype=csv', + method: 'GET', + responseType: 'blob', + }); + const type = response.headers['content-type']; + const blob = new Blob([response.data], { type: type }); + const link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = 'testCSV.csv'; + link.click(); + }; + + const onModalConfirm = async () => { + if (!csvFile) return; + await dispatch(uploadCsv(csvFile)); + dispatch(setRefetch(true)); + setCsvFile(null); + setIsModalActive(false); + }; + + const onModalCancel = () => { + setCsvFile(null); + setIsModalActive(false); + }; + + return ( + <> + + {getPageTitle('Test')} + + + + {''} + + + {hasCreatePermission && ( + + )} + + + + + {hasCreatePermission && ( + setIsModalActive(true)} + /> + )} + +
+
+
+
+ + + + +
+ + + + + ); +}; + +TestTablesPage.getLayout = function getLayout(page: ReactElement) { + return ( + {page} + ); +}; + +export default TestTablesPage; diff --git a/frontend/src/pages/test/test-new.tsx b/frontend/src/pages/test/test-new.tsx new file mode 100644 index 0000000..649bd6f --- /dev/null +++ b/frontend/src/pages/test/test-new.tsx @@ -0,0 +1,102 @@ +import { + mdiAccount, + mdiChartTimelineVariant, + mdiMail, + mdiUpload, +} from '@mdi/js'; +import Head from 'next/head'; +import React, { ReactElement } from 'react'; +import CardBox from '../../components/CardBox'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../../config'; + +import { Field, Form, Formik } from 'formik'; +import FormField from '../../components/FormField'; +import BaseDivider from '../../components/BaseDivider'; +import BaseButtons from '../../components/BaseButtons'; +import BaseButton from '../../components/BaseButton'; +import FormCheckRadio from '../../components/FormCheckRadio'; +import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; +import FormFilePicker from '../../components/FormFilePicker'; +import FormImagePicker from '../../components/FormImagePicker'; +import { SwitchField } from '../../components/SwitchField'; + +import { SelectField } from '../../components/SelectField'; +import { SelectFieldMany } from '../../components/SelectFieldMany'; +import { RichTextField } from '../../components/RichTextField'; + +import { create } from '../../stores/test/testSlice'; +import { useAppDispatch } from '../../stores/hooks'; +import { useRouter } from 'next/router'; +import moment from 'moment'; + +const initialValues = { + name: '', + + amount: '', +}; + +const TestNew = () => { + const router = useRouter(); + const dispatch = useAppDispatch(); + + const handleSubmit = async (data) => { + await dispatch(create(data)); + await router.push('/test/test-list'); + }; + return ( + <> + + {getPageTitle('New Item')} + + + + {''} + + + handleSubmit(values)} + > +
+ + + + + + + + + + + + + router.push('/test/test-list')} + /> + + +
+
+
+ + ); +}; + +TestNew.getLayout = function getLayout(page: ReactElement) { + return ( + {page} + ); +}; + +export default TestNew; diff --git a/frontend/src/pages/test/test-table.tsx b/frontend/src/pages/test/test-table.tsx new file mode 100644 index 0000000..20d3c21 --- /dev/null +++ b/frontend/src/pages/test/test-table.tsx @@ -0,0 +1,162 @@ +import { mdiChartTimelineVariant } from '@mdi/js'; +import Head from 'next/head'; +import { uniqueId } from 'lodash'; +import React, { ReactElement, useState } from 'react'; +import CardBox from '../../components/CardBox'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../../config'; +import TableTest from '../../components/Test/TableTest'; +import BaseButton from '../../components/BaseButton'; +import axios from 'axios'; +import Link from 'next/link'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import CardBoxModal from '../../components/CardBoxModal'; +import DragDropFilePicker from '../../components/DragDropFilePicker'; +import { setRefetch, uploadCsv } from '../../stores/test/testSlice'; + +import { hasPermission } from '../../helpers/userPermissions'; + +const TestTablesPage = () => { + const [filterItems, setFilterItems] = useState([]); + const [csvFile, setCsvFile] = useState(null); + const [isModalActive, setIsModalActive] = useState(false); + const [showTableView, setShowTableView] = useState(false); + + const { currentUser } = useAppSelector((state) => state.auth); + + const dispatch = useAppDispatch(); + + const [filters] = useState([ + { label: 'Name', title: 'name' }, + { label: 'Amount', title: 'amount' }, + ]); + + const hasCreatePermission = + currentUser && hasPermission(currentUser, 'CREATE_TEST'); + + const addFilter = () => { + const newItem = { + id: uniqueId(), + fields: { + filterValue: '', + filterValueFrom: '', + filterValueTo: '', + selectedField: '', + }, + }; + newItem.fields.selectedField = filters[0].title; + setFilterItems([...filterItems, newItem]); + }; + + const getTestCSV = async () => { + const response = await axios({ + url: '/test?filetype=csv', + method: 'GET', + responseType: 'blob', + }); + const type = response.headers['content-type']; + const blob = new Blob([response.data], { type: type }); + const link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = 'testCSV.csv'; + link.click(); + }; + + const onModalConfirm = async () => { + if (!csvFile) return; + await dispatch(uploadCsv(csvFile)); + dispatch(setRefetch(true)); + setCsvFile(null); + setIsModalActive(false); + }; + + const onModalCancel = () => { + setCsvFile(null); + setIsModalActive(false); + }; + + return ( + <> + + {getPageTitle('Test')} + + + + {''} + + + {hasCreatePermission && ( + + )} + + + + + {hasCreatePermission && ( + setIsModalActive(true)} + /> + )} + +
+
+
+
+ + + +
+ + + + + ); +}; + +TestTablesPage.getLayout = function getLayout(page: ReactElement) { + return ( + {page} + ); +}; + +export default TestTablesPage; diff --git a/frontend/src/pages/test/test-view.tsx b/frontend/src/pages/test/test-view.tsx new file mode 100644 index 0000000..8485684 --- /dev/null +++ b/frontend/src/pages/test/test-view.tsx @@ -0,0 +1,86 @@ +import React, { ReactElement, useEffect } from 'react'; +import Head from 'next/head'; +import DatePicker from 'react-datepicker'; +import 'react-datepicker/dist/react-datepicker.css'; +import dayjs from 'dayjs'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { useRouter } from 'next/router'; +import { fetch } from '../../stores/test/testSlice'; +import { saveFile } from '../../helpers/fileSaver'; +import dataFormatter from '../../helpers/dataFormatter'; +import ImageField from '../../components/ImageField'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { getPageTitle } from '../../config'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import SectionMain from '../../components/SectionMain'; +import CardBox from '../../components/CardBox'; +import BaseButton from '../../components/BaseButton'; +import BaseDivider from '../../components/BaseDivider'; +import { mdiChartTimelineVariant } from '@mdi/js'; +import { SwitchField } from '../../components/SwitchField'; +import FormField from '../../components/FormField'; + +const TestView = () => { + const router = useRouter(); + const dispatch = useAppDispatch(); + const { test } = useAppSelector((state) => state.test); + + const { id } = router.query; + + function removeLastCharacter(str) { + console.log(str, `str`); + return str.slice(0, -1); + } + + useEffect(() => { + dispatch(fetch({ id })); + }, [dispatch, id]); + + return ( + <> + + {getPageTitle('View test')} + + + + + + +
+

Name

+

{test?.name}

+
+ +
+

Amount

+

{test?.amount}

+
+ + + + router.push('/test/test-list')} + /> +
+
+ + ); +}; + +TestView.getLayout = function getLayout(page: ReactElement) { + return ( + {page} + ); +}; + +export default TestView; diff --git a/frontend/src/stores/store.ts b/frontend/src/stores/store.ts index d19b99c..75d5e47 100644 --- a/frontend/src/stores/store.ts +++ b/frontend/src/stores/store.ts @@ -4,15 +4,16 @@ import mainReducer from './mainSlice'; import authSlice from './authSlice'; import openAiSlice from './openAiSlice'; -import usersSlice from "./users/usersSlice"; -import rolesSlice from "./roles/rolesSlice"; -import permissionsSlice from "./permissions/permissionsSlice"; -import coursesSlice from "./courses/coursesSlice"; -import lessonsSlice from "./lessons/lessonsSlice"; -import enrollmentsSlice from "./enrollments/enrollmentsSlice"; -import lesson_progressSlice from "./lesson_progress/lesson_progressSlice"; -import announcementsSlice from "./announcements/announcementsSlice"; -import course_resourcesSlice from "./course_resources/course_resourcesSlice"; +import usersSlice from './users/usersSlice'; +import rolesSlice from './roles/rolesSlice'; +import permissionsSlice from './permissions/permissionsSlice'; +import coursesSlice from './courses/coursesSlice'; +import lessonsSlice from './lessons/lessonsSlice'; +import enrollmentsSlice from './enrollments/enrollmentsSlice'; +import lesson_progressSlice from './lesson_progress/lesson_progressSlice'; +import announcementsSlice from './announcements/announcementsSlice'; +import course_resourcesSlice from './course_resources/course_resourcesSlice'; +import testSlice from './test/testSlice'; export const store = configureStore({ reducer: { @@ -21,19 +22,20 @@ export const store = configureStore({ auth: authSlice, openAi: openAiSlice, -users: usersSlice, -roles: rolesSlice, -permissions: permissionsSlice, -courses: coursesSlice, -lessons: lessonsSlice, -enrollments: enrollmentsSlice, -lesson_progress: lesson_progressSlice, -announcements: announcementsSlice, -course_resources: course_resourcesSlice, + users: usersSlice, + roles: rolesSlice, + permissions: permissionsSlice, + courses: coursesSlice, + lessons: lessonsSlice, + enrollments: enrollmentsSlice, + lesson_progress: lesson_progressSlice, + announcements: announcementsSlice, + course_resources: course_resourcesSlice, + test: testSlice, }, -}) +}); // Infer the `RootState` and `AppDispatch` types from the store itself -export type RootState = ReturnType +export type RootState = ReturnType; // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} -export type AppDispatch = typeof store.dispatch +export type AppDispatch = typeof store.dispatch; diff --git a/frontend/src/stores/test/testSlice.ts b/frontend/src/stores/test/testSlice.ts new file mode 100644 index 0000000..409b828 --- /dev/null +++ b/frontend/src/stores/test/testSlice.ts @@ -0,0 +1,236 @@ +import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; +import axios from 'axios'; +import { + fulfilledNotify, + rejectNotify, + resetNotify, +} from '../../helpers/notifyStateHandler'; + +interface MainState { + test: any; + loading: boolean; + count: number; + refetch: boolean; + rolesWidgets: any[]; + notify: { + showNotification: boolean; + textNotification: string; + typeNotification: string; + }; +} + +const initialState: MainState = { + test: [], + loading: false, + count: 0, + refetch: false, + rolesWidgets: [], + notify: { + showNotification: false, + textNotification: '', + typeNotification: 'warn', + }, +}; + +export const fetch = createAsyncThunk('test/fetch', async (data: any) => { + const { id, query } = data; + const result = await axios.get(`test${query || (id ? `/${id}` : '')}`); + return id + ? result.data + : { rows: result.data.rows, count: result.data.count }; +}); + +export const deleteItemsByIds = createAsyncThunk( + 'test/deleteByIds', + async (data: any, { rejectWithValue }) => { + try { + await axios.post('test/deleteByIds', { data }); + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const deleteItem = createAsyncThunk( + 'test/deleteTest', + async (id: string, { rejectWithValue }) => { + try { + await axios.delete(`test/${id}`); + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const create = createAsyncThunk( + 'test/createTest', + async (data: any, { rejectWithValue }) => { + try { + const result = await axios.post('test', { data }); + return result.data; + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const uploadCsv = createAsyncThunk( + 'test/uploadCsv', + async (file: File, { rejectWithValue }) => { + try { + const data = new FormData(); + data.append('file', file); + data.append('filename', file.name); + + const result = await axios.post('test/bulk-import', data, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + return result.data; + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const update = createAsyncThunk( + 'test/updateTest', + async (payload: any, { rejectWithValue }) => { + try { + const result = await axios.put(`test/${payload.id}`, { + id: payload.id, + data: payload.data, + }); + return result.data; + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const testSlice = createSlice({ + name: 'test', + initialState, + reducers: { + setRefetch: (state, action: PayloadAction) => { + state.refetch = action.payload; + }, + }, + extraReducers: (builder) => { + builder.addCase(fetch.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + builder.addCase(fetch.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(fetch.fulfilled, (state, action) => { + if (action.payload.rows && action.payload.count >= 0) { + state.test = action.payload.rows; + state.count = action.payload.count; + } else { + state.test = action.payload; + } + state.loading = false; + }); + + builder.addCase(deleteItemsByIds.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + + builder.addCase(deleteItemsByIds.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, 'Test has been deleted'); + }); + + builder.addCase(deleteItemsByIds.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(deleteItem.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + + builder.addCase(deleteItem.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, `${'Test'.slice(0, -1)} has been deleted`); + }); + + builder.addCase(deleteItem.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(create.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + builder.addCase(create.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(create.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, `${'Test'.slice(0, -1)} has been created`); + }); + + builder.addCase(update.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + builder.addCase(update.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, `${'Test'.slice(0, -1)} has been updated`); + }); + builder.addCase(update.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(uploadCsv.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + builder.addCase(uploadCsv.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, 'Test has been uploaded'); + }); + builder.addCase(uploadCsv.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + }, +}); + +// Action creators are generated for each case reducer function +export const { setRefetch } = testSlice.actions; + +export default testSlice.reducer;