UNPKG

@universis/candidates

Version:

Universis api server plugin for study program candidates, internship selection etc

258 lines (241 loc) 7.89 kB
import { ApplicationService, DataError, DataNotFoundError, } from '@themost/common'; import { ModelClassLoaderStrategy, SchemaLoaderStrategy } from '@themost/data'; import { EdmMapping, DataObject, EdmType, DataObjectJunction } from '@themost/data'; import path from 'path'; import fs from 'fs'; const EventStatusTypes = { EventOpened: 'EventOpened', }; const ActionStatusTypes = { CancelledActionStatus: 'CancelledActionStatus', }; class StudyProgram extends DataObject { @EdmMapping.func( 'availableForEnrollment', EdmType.CollectionOf('StudyProgram') ) static async getAvailableProgramsForEnrollment(context) { if (context == null) { throw new DataError( 'E_CONTEXT', 'An error occured while getting the application context.' ); } const availableStudyProgramsQuery = context .model('StudyProgramEnrollmentEvent') // open enrollment events .where('eventStatus/alternateName') .equal(EventStatusTypes.EventOpened) // that accept self registration .and('isAccessibleForFree') .equal(true) // of active study programs .and('studyProgram/isActive') .equal(true); // get user from context const contextUser = context.user; if (contextUser && contextUser.name) { // get user's id (candidate user may have been renamed) const user = await context .model('User') .where('name') .equal(contextUser.name) .select('id') .silent() .getItem(); // fetch all events that the candidate has already tried to enroll in const alreadyEnrolledInEvents = ( await context .model('StudyProgramRegisterAction') .where('owner') .equal(user && user.id) .and('actionStatus/alternateName') .notEqual(ActionStatusTypes.CancelledActionStatus) .and('studyProgram/specialtySelectionSemester') .notEqual(1) .select('studyProgramEnrollmentEvent') .silent() .getAllItems() )?.map((studyProgramRegisterAction) => { return studyProgramRegisterAction.studyProgramEnrollmentEvent; }); // and exclude them from available events if ( Array.isArray(alreadyEnrolledInEvents) && alreadyEnrolledInEvents.length ) { availableStudyProgramsQuery.and('id').notIn(alreadyEnrolledInEvents); } } const availableStudyPrograms = ( await availableStudyProgramsQuery.select('studyProgram').getAllItems() )?.map((enrollmentEvent) => { return enrollmentEvent.studyProgram; }); // return a prepared queryable of active StudyPrograms return context .model('StudyProgram') .where('id') .in(availableStudyPrograms || []) .prepare(); /* Note: Maybe investigate how to achieve this using QueryExpression, QueryEntity etc. */ } @EdmMapping.func( 'availableEnrollmentSpecialties', EdmType.CollectionOf('StudyProgramSpecialty') ) async getAvailableEnrollmentSpecialties() { const context = this.context; const self = await ( await StudyProgram.getAvailableProgramsForEnrollment(context) ) .where('id') .equal(this.getId()) .select('id') .getItem(); if (self == null) { throw new DataNotFoundError( 'The specified study program is not available for enrollment, or cannot be found, or is inaccessible.' ); } // get context user const user = await context .model('User') .find(context.user) .select('id') .value(); if (user == null) { throw new DataNotFoundError( 'The user cannot be found or is inaccessible.' ); } const availableSpecialties = context .model('StudyProgramSpecialty') .where('studyProgram') .equal(self.id); // find if user has already enrolled in any specialty const alreadyEnrolledInSpecialties = ( await context .model('StudyProgramRegisterAction') .where('owner') .equal(user) .and('studyProgram') .equal(self.id) .and('actionStatus/alternateName') .notEqual(ActionStatusTypes.CancelledActionStatus) .select('specialization') .flatten() .getAllItems() )?.map((action) => { return action.specialization; }); if ( Array.isArray(alreadyEnrolledInSpecialties) && alreadyEnrolledInSpecialties.length ) { availableSpecialties.and('id').notIn(alreadyEnrolledInSpecialties); } return availableSpecialties.prepare(); } } export class StudyProgramReplacer extends ApplicationService { constructor(app) { super(app); } apply() { // get schema loader const schemaLoader = this.getApplication() .getConfiguration() .getStrategy(SchemaLoaderStrategy); if (schemaLoader == null) { return; } // get model definition const model = schemaLoader.getModelDefinition('StudyProgram'); if (model == null) { return; } // get model class const loader = this.getApplication() .getConfiguration() .getStrategy(ModelClassLoaderStrategy); if (loader == null) { return; } const StudyProgramBase = loader.resolve(model); // extend class StudyProgramBase.getAvailableProgramsForEnrollment = StudyProgram.getAvailableProgramsForEnrollment; StudyProgramBase.prototype.getAvailableEnrollmentSpecialties = StudyProgram.prototype.getAvailableEnrollmentSpecialties; const listeners = [ { model: 'StudyProgramRegisterActionAttachments', listenerPath: 'listeners/request-attachment-event-listener' }, { model: 'StudyProgramRegisterAction', listenerPath: 'listeners/student-request-action-attachments-listener', insertBefore: 'listeners/send-mail-after-status-change' } ]; const context = this.getApplication().createContext(); const StudyProgramRegisterActions = context.model('StudyProgramRegisterAction'); const association = new DataObjectJunction(StudyProgramRegisterActions.convert({}), StudyProgramRegisterActions.inferMapping('attachments')); association.getBaseModel(); context.finalize(); for (const listener of listeners) { const modelDefinition = schemaLoader.getModelDefinition(listener.model); if (modelDefinition == null) { continue; } // ensure listeners array modelDefinition.eventListeners = modelDefinition.eventListeners || []; const listenerDefinition = path.resolve( this.getApplication().getConfiguration().getExecutionPath(), listener.listenerPath ); const stat = fs.statSync(`${listenerDefinition}.js`); if (!stat.isFile()) { return; } const findListener = modelDefinition.eventListeners.find( (listener) => { return listener.type === listenerDefinition; } ); if (findListener) { return; } // Handle optional `insertBefore` if (listener.insertBefore) { const insertBeforeIndex = modelDefinition.eventListeners.findIndex( (existingListener) => existingListener.type.replace(/\\/g, '/').endsWith(listener.insertBefore) ); if (insertBeforeIndex >= 0) { modelDefinition.eventListeners.splice(insertBeforeIndex, 0, { type: listenerDefinition, }); } else { modelDefinition.eventListeners.push({ type: listenerDefinition, }); } } else { modelDefinition.eventListeners.push({ type: listenerDefinition, }); } // and set model definition schemaLoader.setModelDefinition(modelDefinition); } } }