@universis/candidates
Version:
Universis api server plugin for study program candidates, internship selection etc
258 lines (241 loc) • 7.89 kB
JavaScript
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 {
.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.
*/
}
.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);
}
}
}