Skip to content

Commit

Permalink
Merge pull request #72 from bcgov/schoolextract
Browse files Browse the repository at this point in the history
Schoolextract
  • Loading branch information
suzalflueck authored Oct 31, 2023
2 parents d3dd321 + 3259fdf commit 8c9ada6
Show file tree
Hide file tree
Showing 8 changed files with 250 additions and 22 deletions.
2 changes: 2 additions & 0 deletions backend/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const districtRouter = require("./routes/district-router");
const downloadRouter = require("./routes/download-router");
const authorityRouter = require("./routes/authority-router");
const offshoreRouter = require("./routes/offshore-router");
const schoolRouter = require("./routes/school-router");
const app = express();
const publicPath = path.join(__dirname, "public");

Expand Down Expand Up @@ -76,6 +77,7 @@ apiRouter.use("/v1/institute", instituteRouter);
apiRouter.use("/v1/district", districtRouter);
apiRouter.use("/v1/authority", authorityRouter);
apiRouter.use("/v1/offshore", offshoreRouter);
apiRouter.use("/v1/school", schoolRouter);

//Handle 500 error
app.use((err, _req, res, next) => {
Expand Down
4 changes: 3 additions & 1 deletion backend/src/components/cache.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const NodeCache = require("node-cache");
const listCache = new NodeCache({ stdTTL: 21600 });
const schoolCache = new NodeCache({ stdTTL: 21600 });
const codeCache = new NodeCache({ stdTTL: 21600 });

module.exports = {
listCache
listCache,schoolCache
};
150 changes: 147 additions & 3 deletions backend/src/components/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,20 @@ const ALLOWED_FILENAMES = new Set([
'chairperson',
'secretary-treasurer',
'executive-admin-assistant',
'exceldistrictcontacts'
'exceldistrictcontacts',
'publicschoolcontacts',
'independschoolcontacts',
'allschoolcontacts'
// Add more allowed filepaths as needed
]);
const ALLOWED_SCHOOLCATEGORYCODES = new Set([
'PUBLIC',
'INDEPEND'
// Add more allowed filepaths as needed
]);
function isAllowedSchoolCategory(category) {
return ALLOWED_SCHOOLCATEGORYCODES.has(category);
}
function isSafeFilePath(filepath) {
return ALLOWED_FILENAMES.has(filepath);
}
Expand Down Expand Up @@ -90,5 +101,138 @@ function addDistrictLabels(jsonData, districtList) {
}
return 0;
}

module.exports = { createList, isSafeFilePath, addDistrictLabels, districtNumberSort };

function rearrangeAndRelabelObjectProperties(object, propertyList) {
const reorderedObject = {};
propertyList.forEach((propertyInfo) => {
const prop = propertyInfo.property;
const label = propertyInfo.label;
reorderedObject[label] = object.hasOwnProperty(prop) ? object[prop] : "";
});
return reorderedObject;
}
function createSchoolCache(schoolData, schoolGrades) {
// Preload convertedGrades with schoolGrades.schoolGradeCode and set the value to "N"


// Map over each school object
return schoolData.map((school) => {
const convertedGrades = {};
schoolGrades.forEach((grade) => {
convertedGrades[grade.schoolGradeCode] = "N";
});

const addressFields = {
mailing: {},
physical: {},
};

// Loop through the grades and set the value to "Y" for each grade
school.grades.forEach((grade) => {
convertedGrades[grade.schoolGradeCode] = "Y";
});

// Extract and format principal contact information if it exists
const principalContact = school.contacts.find((contact) => contact.schoolContactTypeCode === "PRINCIPAL");
if (principalContact) {
school.firstName = principalContact.firstName;
school.lastName = principalContact.lastName;
school.email = principalContact.email;
school.phoneNumber = principalContact.phoneNumber;
}

// Loop through addresses and update the fields based on addressTypeCode
school.addresses.forEach((address) => {
if (address.addressTypeCode === "MAILING") {
Object.keys(address).forEach((field) => {
// Exclude the specified fields
if (![
"createUser",
"updateUser",
"createDate",
"updateDate",
"schoolAddressId",
"schoolId",
"addressTypeCode"
].includes(field)) {
addressFields.mailing[`mailing_${field}`] = address[field];
}
});
} else if (address.addressTypeCode === "PHYSICAL") {
Object.keys(address).forEach((field) => {
if (![
"createUser",
"updateUser",
"createDate",
"updateDate",
"schoolAddressId",
"schoolId",
"addressTypeCode"
].includes(field)) {
addressFields.mailing[`physical_${field}`] = address[field];
}
});
}
});

// Concatenate neighborhoodLearningTypeCode into a single string
const nlc = school.neighborhoodLearning.map(learning => learning.neighborhoodLearningTypeCode).join(' | ');

// Merge the address fields and nlc into the school object
Object.assign(school, convertedGrades, addressFields.mailing, addressFields.physical, { nlc });

// Remove the original grades property and the updated address object
delete school.grades;
delete school.addresses;
delete school.neighborhoodLearning;
delete school.createUser;
delete school.updateUser;
delete school.updateDate;
delete school.createDate;
delete school.schoolId;
delete school.openedDate;
delete school.closedDate;
delete school.notes;
delete school.schoolMove.createUser;
delete school.schoolMove;

// Remove the contacts property
delete school.contacts;
const propertyOrder = [
{ property: "districtNumber", label: "District Number" },
{ property: "mincode", label: "School Code" },
{ property: "displayName", label: "School Name" },
{ property: "mailing_addressLine1", label: "Address" },
{ property: "mailing_city", label: "City" },
{ property: "mailing_provinceCode", label: "Province" },
{ property: "mailing_postal", label: "Postal Code" },
// { property: "principalTitle", label: "Principal Title" },
{ property: "firstName", label: "Principal First Name" },
{ property: "lastName", label: "Principal Last Name" },
{ property: "schoolCategoryCode", label: "Type" },
// { property: "gradeRange", label: "Grade Range" },
// { property: "schoolCategory", label: "School Category" },
// { property: "fundingGroups", label: "Funding Group(s)" },
{ property: "phoneNumber", label: "Phone" },
// { property: "fax", label: "Fax" },
{ property: "email", label: "Email" },
{ property: "GRADE01", label: "Grade 1 Enrollment" },
{ property: "GRADE02", label: "Grade 2 Enrollment" },
{ property: "GRADE03", label: "Grade 3 Enrollment" },
{ property: "GRADE04", label: "Grade 4 Enrollment" },
{ property: "GRADE05", label: "Grade 5 Enrollment" },
{ property: "GRADE06", label: "Grade 6 Enrollment" },
{ property: "GRADE07", label: "Grade 7 Enrollment" },
{ property: "GRADE08", label: "Grade 8 Enrollment" },
{ property: "GRADE09", label: "Grade 9 Enrollment" },
{ property: "GRADE10", label: "Grade 10 Enrollment" },
{ property: "GRADE11", label: "Grade 11 Enrollment" },
{ property: "GRADE12", label: "Grade 12 Enrollment" }


];
const schools = rearrangeAndRelabelObjectProperties(school,propertyOrder)
return schools;
});
}
module.exports = { createList, isSafeFilePath,isAllowedSchoolCategory, addDistrictLabels, districtNumberSort, createSchoolCache };
22 changes: 16 additions & 6 deletions backend/src/routes/download-router.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,16 @@ async function addDistrictLabels(req, res, next) {
let districtList = [];


if (listCache.has("districtList")) {
districtList= listCache.get("districtList");
if (listCache.has("districtlist")) {
districtList= listCache.get("districtlist");
} else {
try {
const response = await axios.get('http://localhost:8080/api/v1/institute/district/list', { headers: { Authorization: `Bearer ${req.accessToken}` } });
const path = "/api/v1/institute/district/list"
const url = `${req.protocol}://${req.hostname}:8080${path}`;

const response = await axios.get(url, { headers: { Authorization: `Bearer ${req.accessToken}` } });
const districts = response.data;
listCache.set("districtList", districts);
listCache.set("districtlist", districts);
districtList = districts

} catch (error) {
Expand Down Expand Up @@ -99,10 +102,17 @@ async function getDownload(req, res,next){
return res.sendFile(filePath);
}else{
try {
const url = `${config.get('server:instituteAPIURL')}` + req.url.replace('/csv', '');
const path = req.url.replace('/csv', ''); // Modify the URL path as needed
const url = `${req.protocol}://${req.hostname}:8080/api/v1${path}`;
console.log(url)
const response = await axios.get(url, { headers: { Authorization: `Bearer ${req.accessToken}` } });
// Attach the fetched data to the request object
req.jsonData = response.data.content;
if (response.data?.content) {
req.jsonData = response.data.content;
}else{
req.jsonData = response.data;
}

next(); // Call the next middleware
} catch (error) {
console.error("Error:", error);
Expand Down
68 changes: 67 additions & 1 deletion backend/src/routes/institute-router.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,34 @@ const config = require("../config/index");

const axios = require("axios");
const { checkToken } = require("../components/auth");
const { createList, addDistrictLabels, districtNumberSort } = require("../components/utils");
const { createList, addDistrictLabels, districtNumberSort, isAllowedSchoolCategory } = require("../components/utils");
const { listCache } = require("../components/cache");

const schoolListOptions = { fields: ["mincode", "displayName", "schoolId"], fieldToInclude: "closedDate", valueToInclude: null, sortField: "mincode" };
const districtListOptions = { fields: ["displayName", "districtId","districtNumber"] ,fieldToInclude: "districtStatusCode", valueToInclude: "ACTIVE", sortField: "districtNumber"};
const authorityListOptions = { fields: ["displayName", "authorityNumber","independentAuthorityId"], fieldToInclude: "closedDate", valueToInclude: null, sortField: "authorityNumber" };
const openSchoolListOptions = { fields: [
"schoolId",
"districtId",
"mincode",
"schoolNumber",
"faxNumber",
"phoneNumber",
"email",
"website",
"displayName",
"schoolCategoryCode",
"facilityTypeCode",
"openedDate",
"closedDate",
"districtNumber"
],fieldToInclude: "closedDate", valueToInclude: null, sortField: "mincode" };

//Batch Routes
router.get("/contact-type-codes", checkToken, getContactTypeCodes);
router.get("/grade-codes", checkToken, getGradeCodes);
router.get("/offshore-school/list", checkToken, getOffshoreSchoolList);
// router.get("/school", checkToken, getOpenSchools);
router.get("/school/list", checkToken, getSchoolList);
router.get("/authority/list", checkToken, getAuthorityList);
router.get("/district/list", checkToken, getDistrictList);
Expand Down Expand Up @@ -123,6 +141,30 @@ async function getAuthorityList(req, res) {
res.json(authorityList);
}
}
async function getOpenSchools(req, res) {


if (await !listCache.has("openschoollist")) {
const url = `${config.get("server:instituteAPIURL")}/institute/school`; // Update the URL according to your API endpoint
axios
.get(url, { headers: { Authorization: `Bearer ${req.accessToken}` } })
.then((response) => {
const openSchoolList = createList(response.data, openSchoolListOptions);
res.json(openSchoolList);
listCache.set("openschoollist", openSchoolList);
log.info(req.url);
})
.catch((e) => {
log.error(
"getSchoolsList Error",
e.response ? e.response.status : e.message
);
});
} else {
const openSchoolList = await listCache.get("openschoollist");
res.json(openSchoolList);
}
}
async function getSchoolList(req, res) {
if (await !listCache.has("schoollist")) {
const url = `${config.get("server:instituteAPIURL")}/institute/school`; // Update the URL according to your API endpoint
Expand Down Expand Up @@ -201,4 +243,28 @@ async function getDistrictContactsAPI(req, res) {
log.error("getData Error", e.response ? e.response.status : e.message);
});
}

async function getGradeCodes(req, res) {
if (await !codeCache.has("gradelist")) {
const url = `${config.get("server:instituteAPIURL")}/institute/grade-codes`; // Update the URL according to your API endpoint
axios
.get(url, { headers: { Authorization: `Bearer ${req.accessToken}` } })
.then((response) => {
//const districtList = response.data;
const gradeCodes = response.data;
codeCache.set("gradelist", gradeList);
res.json(gradeCodes);
log.info(req.url);
})
.catch((e) => {
log.error(
"getDistrictList Error",
e.response ? e.response.status : e.message
);
});
} else {
const gradeList = await codeCache.get("gradelist");
res.json(gradeList);
}
}
module.exports = router;
8 changes: 8 additions & 0 deletions frontend/src/components/AuthoritySelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ function downloadAuthorityContacts() {
/>
</v-row>
<v-row>
<v-btn
href="/api/v1/download/csv/school/INDEPEND?filepath=independschoolcontacts"
block
class="text-none text-subtitle-1 my-1"
><template v-slot:prepend> <v-icon icon="mdi-download" /> </template>

All Independent Schools</v-btn
>
<v-btn block class="text-none text-subtitle-1 my-1" @click="downloadAuthorityContacts"
><template v-slot:prepend> <v-icon icon="mdi-download" /></template> Contacts for All
Authorities</v-btn
Expand Down
4 changes: 0 additions & 4 deletions frontend/src/components/DistrictSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,6 @@ function downloadDistrictsMailing() {

Contacts for All Districts</v-btn
>
<!-- <v-btn class="text-none text-subtitle-1 ma-1" variant="flat"
><template v-slot:prepend> <v-icon icon="mdi-download" /> </template>Contacts by
Type</v-btn
> -->
<ContactTypeModal></ContactTypeModal>
</v-row>
</v-col>
Expand Down
14 changes: 7 additions & 7 deletions frontend/src/components/SchoolSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ function goToSchoolSearch() {
})
}
function downloadAllSchoolsInfo() {
alert('TODO - Implement all schools info extract download')
}
function downloadAllSchoolsMailing() {
alert('TODO - Implement all schools mailing extract download')
}
Expand Down Expand Up @@ -93,9 +89,13 @@ function downloadAllSchoolsMailing() {
</v-col>
<v-spacer />
<v-col>
<v-btn block class="text-none text-subtitle-1 ma-1" @click="downloadAllSchoolsInfo"
><template v-slot:prepend> <v-icon icon="mdi-download" /></template>All Schools
Info</v-btn
<v-btn
href="/api/v1/download/csv/school/ALL?filepath=allschoolcontacts"
block
class="text-none text-subtitle-1 my-1"
><template v-slot:prepend> <v-icon icon="mdi-download" /> </template>

All Schools Info</v-btn
>
</v-col>
<v-col>
Expand Down

0 comments on commit 8c9ada6

Please sign in to comment.