diff --git a/backend/src/components/institute/institute.js b/backend/src/components/institute/institute.js index 3252bc435..0b921bd6a 100644 --- a/backend/src/components/institute/institute.js +++ b/backend/src/components/institute/institute.js @@ -863,6 +863,105 @@ async function getSchoolByID(req, res) { } } +async function getSchoolByMincode(req, res) { + const token = getBackendToken(req); + try { + let school = cacheService.getSchoolJSONByMincode(req.params.mincode); + const url = `${config.get('server:institute:rootURL')}/school/${school.schoolID}`; + const data = await getData(token, url); + return res.status(200).json(data); + } catch (e) { + logApiError(e, 'getSchoolByMincode', 'Error occurred while attempting to GET school entity by mincode.'); + return errorResponse(res); + } +} + + +async function getStudentRegistrationContacts(req, res) { + const token = getBackendToken(req); + let contactsList = []; + try { + const schoolContactURL = `${config.get('server:institute:instituteSchoolURL')}/contact/paginated?pageNumber=0&pageSize=10000&searchCriteriaList=[{"searchCriteriaList":[{"key":"schoolContactTypeCode","operation":"eq","value":"STUDREGIS","valueType":"STRING","condition":"AND"}]}]`; + const districtContactURL = `${config.get('server:institute:instituteDistrictURL')}/contact/paginated?pageNumber=0&pageSize=10000&searchCriteriaList=[{"searchCriteriaList":[{"key":"districtContactTypeCode","operation":"eq","value":"STUDREGIS","valueType":"STRING","condition":"AND"}]}]`; + Promise.all([ + getData(token, schoolContactURL), + getData(token, districtContactURL), + ]) + .then(async ([schoolContactResponse, districtContactResponse]) => { + if (schoolContactResponse && districtContactResponse) { + schoolContactResponse.content.forEach((element) => { + let school = cacheService.getSchoolBySchoolID(element.schoolId); + let schoolRegistrationContact = {}; + schoolRegistrationContact.name = (element.firstName ? element.firstName + ' ' + element.lastName : element.lastName).trim(); + schoolRegistrationContact.email = element.email; + schoolRegistrationContact.instituteName = school.schoolName; + schoolRegistrationContact.instituteIdentifier = school.mincode; + schoolRegistrationContact.instituteGUID = school.schoolID; + schoolRegistrationContact.instituteType = 'SCHOOL'; + contactsList.push(schoolRegistrationContact); + }); + districtContactResponse.content.forEach((element) => { + let district = cacheService.getDistrictJSONByDistrictId(element.districtId); + let schoolRegistrationContact = {}; + schoolRegistrationContact.name = (element.firstName ? element.firstName + ' ' + element.lastName : element.lastName).trim(); + schoolRegistrationContact.email = element.email; + schoolRegistrationContact.instituteName = district.name; + schoolRegistrationContact.instituteGUID = district.districtId; + schoolRegistrationContact.instituteIdentifier = district.districtNumber; + schoolRegistrationContact.instituteType = 'DISTRICT'; + contactsList.push(schoolRegistrationContact); + }); + return res.status(200).json(contactsList); + } + }); + } catch (e) { + logApiError(e, 'getStudentRegistrationContacts', 'Error occurred while attempting to GET student registration contacts.'); + return errorResponse(res); + } +} + +async function getStudentRegistrationContactByMincode(req, res) { + const accessToken = getBackendToken(req); + try { + let school = cacheService.getSchoolJSONByMincode(req.params.mincode); + let searchCriteriaList = []; + searchCriteriaList.push({key: 'schoolContactTypeCode', operation: FILTER_OPERATION.EQUAL, value: 'STUDREGIS', valueType: VALUE_TYPE.STRING, condition: CONDITION.AND}); + searchCriteriaList.push({key: 'schoolID', operation: FILTER_OPERATION.EQUAL, value: school.schoolID, valueType: VALUE_TYPE.UUID, condition: CONDITION.AND}); + + const schoolSearchCriteria = [{ + condition: null, + searchCriteriaList: searchCriteriaList, + }]; + + const schoolSearchParam = { + params: { + pageNumber: req.query.pageNumber, + pageSize: req.query.pageSize, + sort: req.query.sort, + searchCriteriaList: JSON.stringify(schoolSearchCriteria) + } + }; + + let response = await getData(accessToken, config.get('server:institute:rootURL') + '/school/contact/paginated', schoolSearchParam); + let schoolRegistrationContact = {}; + if(response?.content && response.content[0]){ + let firstStudRegContact = response.content[0]; + schoolRegistrationContact.name = (firstStudRegContact.firstName ? firstStudRegContact.firstName + ' ' + firstStudRegContact.lastName : firstStudRegContact.lastName).trim(); + schoolRegistrationContact.email = firstStudRegContact.email; + schoolRegistrationContact.instituteName = school.schoolName; + schoolRegistrationContact.instituteIdentifier = school.mincode; + schoolRegistrationContact.instituteGUID = school.schoolID; + schoolRegistrationContact.instituteType = 'SCHOOL'; + } + + return res.status(200).json(schoolRegistrationContact); + } catch (e) { + logApiError(e, 'getStudentRegistrationContactByMincode', 'Error occurred while attempting to GET student registration contact entity.'); + return errorResponse(res); + } +} + + async function updateSchool(req, res) { try { const token = getBackendToken(req); @@ -1356,5 +1455,8 @@ module.exports = { addDistrictContact, deleteDistrictContact, getSchoolHistoryPaginated, - moveSchool + moveSchool, + getStudentRegistrationContacts, + getStudentRegistrationContactByMincode, + getSchoolByMincode }; diff --git a/backend/src/components/school-api-cache-service.js b/backend/src/components/school-api-cache-service.js deleted file mode 100644 index 3995c6212..000000000 --- a/backend/src/components/school-api-cache-service.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; -const config = require('../config/index'); -const log = require('../components/logger'); -const {getApiCredentials} = require('../components/auth'); -const {getData} = require('../components/utils'); -const retry = require('async-retry'); - -let mincodeSchoolMap = new Map(); -let mincodeSchools = []; - -const schoolApiCacheService = { - - async loadAllSchoolsToMap() { - log.debug('loading all schools during start up'); - await retry(async () => { - // if anything throws, we retry - const data = await getApiCredentials(); // get the tokens first to make api calls. - const schools = await getData(data.accessToken, `${config.get('server:schoolAPIURL')}/schools`); - mincodeSchools = []; // reset the value. - mincodeSchoolMap.clear();// reset the value. - if (schools && schools.length > 0) { - for (const school of schools) { - const mincodeSchool = { - mincode: `${school.distNo}${school.schlNo}`, - schoolName: school.schoolName, - effectiveDate: school.dateOpened, - expiryDate: school.dateClosed, - }; - mincodeSchoolMap.set(`${school.distNo}${school.schlNo}`, mincodeSchool); - mincodeSchools.push(mincodeSchool); - } - } - log.info(`loaded ${mincodeSchoolMap.size} schools to school-api-cache.`); - }, { - retries: 50 - }); - - }, - getAllSchoolsJSON() { - return mincodeSchools; - }, - getSchoolNameJSONByMincode(mincode) { - return mincodeSchoolMap.get(mincode); - }, - -}; - -module.exports = schoolApiCacheService; diff --git a/backend/src/components/school.js b/backend/src/components/school.js deleted file mode 100644 index dbd738936..000000000 --- a/backend/src/components/school.js +++ /dev/null @@ -1,107 +0,0 @@ -'use strict'; -const config = require('../config/index'); -const {getBackendToken, getData, putData, getUser, logApiError, errorResponse} = require('./utils'); -const HttpStatus = require('http-status-codes'); -const schoolApiCacheService = require('./school-api-cache-service'); -const lodash = require('lodash'); -const retry = require('async-retry'); -const {LocalDate, DateTimeFormatter} = require('@js-joda/core'); - -async function getSchoolByMincode(req, res) { - try { - if(req.query?.mincode){ - const data = schoolApiCacheService.getSchoolNameJSONByMincode(req.query.mincode); - if(data){ - return res.status(200).json(data); - } - }else { - let data = schoolApiCacheService.getAllSchoolsJSON(); - if(data){ - data = data.filter(school => { - return isSchoolExpired(school); - }); - return res.status(200).json(data); - }else { - return res.status(200).json([]); - } - } - return res.status(200).json(); - } catch (e) { - logApiError(e, 'getSchoolByMincode', 'Error occurred while attempting to GET school entity.'); - return errorResponse(res); - } -} - -async function getPenCoordinatorByMincode(req, res) { - const token = getBackendToken(req); - try { - const url = `${config.get('server:schoolAPIURL')}/schools/${req.params.mincode}/pen-coordinator`; - const data = await getData(token, url); - return res.status(200).json(data); - } catch (e) { - logApiError(e, 'getPenCoordinatorByMincode', 'Error occurred while attempting to GET pen coordinator entity.'); - return errorResponse(res); - } -} - -async function updatePenCoordinatorByMincode(req, res) { - try { - const token = getBackendToken(req); - const url = `${config.get('server:schoolAPIURL')}/schools/${req.params.mincode}/pen-coordinator`; - - await retry(async () => { - const result = await putData(token, url, req.body, getUser(req).idir_username); - return res.status(HttpStatus.OK).json(result); - }, - { - retries: 3 - }); - } catch (e) { - logApiError(e, 'updatePenCoordinatorByMincode', 'Error occurred while attempting to update a PEN Coordinator.'); - return errorResponse(res); - } - -} - -async function getPenCoordinators(req, res) { - const token = getBackendToken(req); - try { - const url = `${config.get('server:schoolAPIURL')}/schools/pen-coordinator`; - const data = await getData(token, url); - const coords = lodash.sortBy(data, ['mincode', 'penCoordinatorName']); - const filteredCords = coords.filter(coord=> { - const school = schoolApiCacheService.getSchoolNameJSONByMincode(coord.mincode); - if(!school){ - return true; - } - return isSchoolExpired(school); - }).map(coord=> { - coord.schoolName = schoolApiCacheService.getSchoolNameJSONByMincode(coord.mincode)?.schoolName; - if(!coord.schoolName){ - coord.schoolName = 'District'; - } - return coord; - }); - return res.status(200).json(filteredCords); - } catch (e) { - logApiError(e, 'getPenCoordinators', 'Error occurred while attempting to GET pen coordinator entities.'); - return errorResponse(res); - } -} - -function isSchoolExpired(school) { - if(school === null){ - return false; - } - - const openedDate = school?.effectiveDate; - const closedDate = school?.expiryDate; - return !(!school || !school.schoolName || !openedDate || LocalDate.parse(openedDate, DateTimeFormatter.ISO_LOCAL_DATE_TIME).isAfter(LocalDate.now()) || (closedDate && LocalDate.parse(closedDate, DateTimeFormatter.ISO_LOCAL_DATE_TIME).isBefore(LocalDate.now()))); -} - -module.exports = { - getSchoolByMincode, - getPenCoordinatorByMincode, - updatePenCoordinatorByMincode, - getPenCoordinators, -}; diff --git a/backend/src/routes/institute.js b/backend/src/routes/institute.js index 8aa97946e..25aae081a 100644 --- a/backend/src/routes/institute.js +++ b/backend/src/routes/institute.js @@ -4,7 +4,9 @@ const router = express.Router(); const { getDistricts, getSchools, getSchoolsPaginated, getAuthoritiesPaginated, getAuthorityByID, getSchoolByID, getDistrictByDistrictID, addNewSchoolNote, updateSchoolNote, deleteSchoolNote, updateSchoolContact, updateAuthority, addAuthorityContact, updateAuthorityContact, deleteAuthorityContact, addNewAuthorityNote, updateAuthorityNote, deleteAuthorityNote, updateSchool, addSchool, addSchoolContact, deleteSchoolContact, updateDistrict, updateDistrictContact, deleteDistrictContact, addAuthority, - addDistrictContact, addNewDistrictNote, updateDistrictNote, deleteDistrictNote, moveSchool, getSchoolHistoryPaginated + addDistrictContact, addNewDistrictNote, updateDistrictNote, deleteDistrictNote, moveSchool, getSchoolHistoryPaginated, + getStudentRegistrationContacts, + getStudentRegistrationContactByMincode, getSchoolByMincode } = require('../components/institute/institute'); const utils = require('../components/utils'); const auth = require('../components/auth'); @@ -12,12 +14,17 @@ const extendSession = utils.extendSession(); const {getCodes} = require('../components/utils'); const {CACHE_KEYS} = require('../util/constants'); + router.get('/district', passport.authenticate('jwt', {session: false}, undefined), auth.isLoggedInUser, extendSession, getDistricts); router.get('/district/:districtId', passport.authenticate('jwt', {session: false}, undefined), auth.isLoggedInUser, extendSession, getDistrictByDistrictID); router.put('/district/:districtId', passport.authenticate('jwt', {session: false}, undefined), auth.isValidDistrictAdmin, extendSession, updateDistrict); +router.get('/studentRegistrationContacts', passport.authenticate('jwt', {session: false}, undefined), auth.isLoggedInUser, extendSession, getStudentRegistrationContacts); + +router.get('/studentRegistrationContact/:mincode', passport.authenticate('jwt', {session: false}, undefined), extendSession, getStudentRegistrationContactByMincode); + router.put('/district/contact/:contactId', passport.authenticate('jwt', {session: false}, undefined), auth.isValidDistrictAdmin, extendSession, updateDistrictContact); router.delete('/district/contact/:districtId/:contactId', passport.authenticate('jwt', {session: false}, undefined), auth.isValidDistrictAdmin, extendSession, deleteDistrictContact); @@ -56,6 +63,8 @@ router.post('/school/contact', passport.authenticate('jwt', {session: false}, un router.get('/school/:id', passport.authenticate('jwt', {session: false}, undefined), auth.isLoggedInUser, extendSession, getSchoolByID); +router.get('/school/mincode/:mincode', passport.authenticate('jwt', {session: false}, undefined), auth.isLoggedInUser, extendSession, getSchoolByMincode); + router.put('/school/:id', passport.authenticate('jwt', {session: false}, undefined), auth.isLoggedInUser, extendSession, updateSchool); router.get('/schoolsPaginated', passport.authenticate('jwt', {session: false}, undefined), auth.isLoggedInUser, extendSession, getSchoolsPaginated); diff --git a/backend/src/routes/schools.js b/backend/src/routes/schools.js index 1daeb1737..adb8452ad 100644 --- a/backend/src/routes/schools.js +++ b/backend/src/routes/schools.js @@ -1,33 +1,8 @@ const passport = require('passport'); const express = require('express'); const router = express.Router(); -const { getSchoolByMincode, getPenCoordinatorByMincode, getPenCoordinators, updatePenCoordinatorByMincode } = require('../components/school'); const utils = require('../components/utils'); -const auth = require('../components/auth'); const extendSession = utils.extendSession(); -const roles = require('../components/roles'); - -const isValidUiTokenWithEditRoles = auth.isValidUiTokenWithRoles('StaffAdministration & NominalRoll', [roles.Admin.StaffAdministration, roles.Admin.NominalRoll, roles.Admin.EDX]); - -/* - * Get a school entity by mincode - */ -router.get('/', passport.authenticate('jwt', {session: false}, undefined), extendSession, getSchoolByMincode); - -/* - * Get a pen coordinator entity by mincode - */ -router.get('/:mincode/penCoordinator', passport.authenticate('jwt', {session: false}, undefined), extendSession, getPenCoordinatorByMincode); - -/* - * Update a pen coordinator entity by mincode - */ -router.put('/:mincode/penCoordinator', passport.authenticate('jwt', {session: false}, undefined), isValidUiTokenWithEditRoles, extendSession, updatePenCoordinatorByMincode); - -/* - * Get all pen coordinator entities - */ -router.get('/penCoordinators', passport.authenticate('jwt', {session: false}, undefined), extendSession, getPenCoordinators); router.get('/fedProvSchoolCodes', passport.authenticate('jwt', {session: false}, undefined), extendSession, utils.getCodes('server:schoolAPIURL', 'fedProvSchoolCodes', '/schools/federal-province-codes', false)); diff --git a/backend/src/schedulers/school-api-cache-service-scheduler.js b/backend/src/schedulers/school-api-cache-service-scheduler.js deleted file mode 100644 index af5ead6ea..000000000 --- a/backend/src/schedulers/school-api-cache-service-scheduler.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; -const CronJob = require('cron').CronJob; -const log = require('../components/logger'); -const schoolApiCacheService = require('../components/school-api-cache-service'); - -try { - // reload the cache every midnight at 12.15 AM as the api reloads cache at 12 AM. - const reloadCache = new CronJob('0 15 0 * * *', async () => { - log.debug('Starting reload cache'); - try { - await schoolApiCacheService.loadAllSchoolsToMap(); - } catch (e) { - log.error(e); - } - }); - reloadCache.start(); -} catch (e) { - log.error(e); -} - diff --git a/backend/src/server.js b/backend/src/server.js index e20a1e378..97ddf1ba4 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -29,7 +29,6 @@ const server = http.createServer(app); const WS = require('./socket/web-socket'); const NATS = require('./messaging/message-pub-sub'); const cacheService = require('./components/cache-service'); -const schoolApiCacheServicce = require('./components/school-api-cache-service'); const constants = require('./util/constants'); cacheService.loadAllSchoolsToMap().then(() => { @@ -125,11 +124,6 @@ cacheService.loadAllDocumentTypeCodesToMap().then(() => { }).catch((e) => { log.error('Error loading document type codes during boot .', e); }); -schoolApiCacheServicce.loadAllSchoolsToMap().then(() => { - log.info('Loaded school data to memory school-api-cache'); -}).catch((e) => { - log.error('Error loading schools during boot school-api-cache .', e); -}); WS.init(app, server); /** diff --git a/backend/tests/unit/components/school.spec.js b/backend/tests/unit/components/school.spec.js deleted file mode 100644 index 82142c079..000000000 --- a/backend/tests/unit/components/school.spec.js +++ /dev/null @@ -1,60 +0,0 @@ -const HttpStatus = require('http-status-codes'); -jest.mock('../../../src/components/utils', () => { - const originalModule = jest.requireActual('../../../src/components/utils'); - return { - __esModule: true, // Use it when dealing with esModules - ...originalModule, - getBackendToken: jest.fn(), - getData: jest.fn(), - putData: jest.fn(), - postData: jest.fn(), - getUser: jest.fn().mockReturnValue({idir_username: 'User'}) - }; -}); -jest.mock('../../../src/components/school-api-cache-service'); -const cacheService = require('../../../src/components/school-api-cache-service'); -const { mockRequest, mockResponse } = require('../helpers'); -const utils = require('../../../src/components/utils'); -const { ApiError } = require('../../../src/components/error'); -const school = require('../../../src/components/school'); -const {LocalDateTime} = require('@js-joda/core'); - - -describe('getPenCoordinators', () => { - let req; - let res; - const coords = [ - {mincode: '00200001', penCoordinatorName: 'coord A'}, - {mincode: '00100001', penCoordinatorName: 'coord B'}, - ]; - - const formattedResponse = [ - {mincode: '00100001', penCoordinatorName: 'coord B', schoolName: 'School A'}, - {mincode: '00200001', penCoordinatorName: 'coord A', schoolName: 'School A'}, - ]; - - - beforeEach(() => { - utils.getBackendToken.mockReturnValue('token'); - req = mockRequest(); - res = mockResponse(); - }); - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should return all coords correctly', async () => { - utils.getData.mockResolvedValue(coords); - cacheService.getSchoolNameJSONByMincode.mockReturnValue({schoolName: 'School A', effectiveDate: LocalDateTime.now().minusMinutes(2).toJSON()}); - await school.getPenCoordinators(req,res); - expect(res.status).toHaveBeenCalledWith(HttpStatus.OK); - expect(res.json).toHaveBeenCalledWith(formattedResponse); - }); - it('should return 500 if getData fails', async () => { - utils.getData.mockRejectedValue(new ApiError()); - - await school.getPenCoordinators(req,res); - expect(res.status).toHaveBeenCalledWith(HttpStatus.INTERNAL_SERVER_ERROR); - expect(res.json).toHaveBeenCalledWith({code: HttpStatus.INTERNAL_SERVER_ERROR, message: 'INTERNAL SERVER ERROR'}); - }); -}); diff --git a/frontend/src/common/apiService.js b/frontend/src/common/apiService.js index 8e86ce15e..70b1da2b8 100644 --- a/frontend/src/common/apiService.js +++ b/frontend/src/common/apiService.js @@ -82,7 +82,7 @@ export default { getPrbValidationFieldCodes: getCodes(Routes.penRequestBatch.PRB_VALIDATION_FIELD_CODE_URL), getPrbValidationIssueSeverityCodes: getCodes(Routes.penRequestBatch.PRB_VALIDATION_ISSUE_SEVERITY_CODE_URL), getPrbValidationIssueTypeCodes: getCodes(Routes.penRequestBatch.PRB_VALIDATION_ISSUE_TYPE_CODE_URL), - getMincodeSchoolNames: getCodes(Routes.cache.SCHOOL_DATA_URL), + getAllSchools: getCodes(Routes.cache.SCHOOL_DATA_URL), getActiveSchools: getCodes(`${Routes.cache.SCHOOL_DATA_URL}?active=true`), getActiveDistricts: getCodes(`${Routes.cache.DISTRICT_DATA_URL}?active=true`), getSchools:getCodes(Routes.cache.SCHOOL_DATA_URL), @@ -107,7 +107,6 @@ export default { getInstituteGradeCodes: getCodes(Routes.cache.GRADE_TYPES_URL), getInstituteProvinceCodes: getCodes(Routes.cache.PROVINCES_URL), getInstituteCountryCodes: getCodes(Routes.cache.COUNTRIES_URL), - getSchoolApiMincodeSchoolNames: getCodes(Routes.SCHOOL_DATA_URL), getSchoolCategoryFacilityTypes: getCodes(Routes.cache.SCHOOL_CATEGORY_FACILITY_TYPE_URL), getAllActiveFacilityTypeCodes: getCodes(`${Routes.cache.FACILITY_TYPES_URL}?active=true`), getAllActiveSchoolCategoryTypeCodes: getCodes(`${Routes.cache.SCHOOL_CATEGORY_TYPES_URL}?active=true`), diff --git a/frontend/src/components/Home.vue b/frontend/src/components/Home.vue index e71c649c8..2add190b7 100644 --- a/frontend/src/components/Home.vue +++ b/frontend/src/components/Home.vue @@ -408,7 +408,7 @@ export default { } }, computed: { - ...mapState(appStore, ['schoolApiMincodeSchoolNames', 'schoolApiDistrictCodes']), + ...mapState(appStore, ['mincodeSchoolNames', 'districtCodes']), ...mapState(authStore, ['VIEW_GMP_REQUESTS_ROLE', 'VIEW_UMP_REQUESTS_ROLE', 'ADVANCED_SEARCH_ROLE', 'VIEW_EDIT_PEN_REQUEST_BATCH_FILES_ROLE', 'HAS_STATS_ROLE', 'STUDENT_ANALYTICS_STUDENT_PROFILE', 'STUDENT_ANALYTICS_BATCH', 'EXCHANGE_ROLE', 'PEN_TEAM_ROLE']), requestTypes() { return REQUEST_TYPES; @@ -461,9 +461,9 @@ export default { isValidDistrictOrMincode(v) { if (isValidMincode(v) && (v.length === 3 || v.length === 8)) { if (v.length === 3) { - return this.schoolApiDistrictCodes.size === 0 || this.schoolApiDistrictCodes.has(v); + return this.districtCodes.size === 0 || this.districtCodes.has(v); } else { - return this.schoolApiMincodeSchoolNames.size === 0 || this.schoolApiMincodeSchoolNames.has(v); + return this.mincodeSchoolNames.size === 0 || this.mincodeSchoolNames.has(v); } } return false; diff --git a/frontend/src/components/institute/authority/AuthorityContact.vue b/frontend/src/components/institute/authority/AuthorityContact.vue index f0f5c3972..34ed86237 100644 --- a/frontend/src/components/institute/authority/AuthorityContact.vue +++ b/frontend/src/components/institute/authority/AuthorityContact.vue @@ -70,7 +70,7 @@ class="pt-1" >