Compare commits

...
58 Commits
Author SHA1 Message Date
Riku Rauhala 83ef6f5760 [Faculties] Graduation times: Include median study times in Excel exports 2024-05-28 18:08:44 +03:00
Valtteri Kantanen 59de371d9a [Guidance groups] Add info about the Close to graduation feature 2024-05-28 17:42:37 +03:00
Valtteri Kantanen 362250868d [Class statistics] Remove code that is no longer used 2024-05-28 17:41:40 +03:00
Valtteri Kantanen 846d4b9ec7 [Sortable table] Remove check for headerRowData type: it's no longer a function 2024-05-28 17:39:34 +03:00
Valtteri Kantanen 6d8f4e0992 Remove unused CSS files 2024-05-28 17:38:03 +03:00
Valtteri Kantanen b41bfbd47e [Close to graduation] Allow user see students in their study guidance groups (#4503) 2024-05-28 17:22:32 +03:00
Riku Rauhala d774d5f073 [Faculties] Graduation times: set the year label based on grouping 2024-05-28 15:28:56 +03:00
Valtteri Kantanen 6259cfface [Class statistics] Fix automatic curriculum selection 2024-05-28 14:17:17 +03:00
Riku Rauhala 7cd101bb57 [Faculties] Add support for exporting in the Graduation Times tab 2024-05-28 14:01:59 +03:00
Riku Rauhala 329bd4696a [Updater] Sort model imports 2024-05-28 14:01:19 +03:00
Valtteri Kantanen 750a12e14b Remove unused backend route 2024-05-28 13:26:51 +03:00
Valtteri Kantanen f46a8fee5b [Teachers] Add provider code as a description 2024-05-28 13:25:55 +03:00
Valtteri Kantanen 0d8f51bc4f [Course statistics] Fix redux error and simplify the component 2024-05-28 13:25:34 +03:00
Valtteri Kantanen d3c01133b7 [Class statistics] Obfuscate some new fields 2024-05-28 13:24:41 +03:00
Valtteri Kantanen abb7d2211f [Class statistics] Allow to get course information if the student numbers are encrypted 2024-05-28 13:23:52 +03:00
Valtteri Kantanen dd7088c332 Add userId back 2024-05-27 14:44:44 +03:00
Riku RauhalaandGitHub 27bbfc6519 Merge pull request #4501 from UniversityOfHelsinkiCS/backend-imports
[Backend, Updater] Update and enforce a consistent import order
2024-05-27 11:00:45 +03:00
Riku Rauhala c1433392d4 [Backend, Updater] Update and enforce a consistent import order 2024-05-27 10:29:54 +03:00
Riku Rauhala f7131d361c [Class statistics] Fix broken access rights 2024-05-24 15:39:37 +03:00
Riku Rauhala 5840889fa9 Rename functions for consistency 2024-05-24 15:39:37 +03:00
Valtteri Kantanen 887b57fca7 Fix setting language 2024-05-24 15:21:43 +03:00
Valtteri Kantanen c496912d88 [Users] Remove sessions table from user-db 2024-05-24 14:53:17 +03:00
Valtteri Kantanen 442a58c74a [Users] Remove unnecessary tables and columns from user-db 2024-05-24 14:13:13 +03:00
Valtteri Kantanen ac1632c1e1 [Close to graduation] Add master's students 2024-05-24 11:49:07 +03:00
Valtteri Kantanen 25dc072b2e [Users] Take userService changes into account in the frontend 2024-05-24 10:01:04 +03:00
Valtteri Kantanen 46b575ae6d Refactor userService completely 2024-05-24 09:59:16 +03:00
Valtteri Kantanen 24fafbe4ff Upgrade lru-cache version 2024-05-24 09:58:13 +03:00
Valtteri Kantanen 18f6d6157c Add roles and programmeRights to users table 2024-05-24 09:57:54 +03:00
Riku Rauhala 3c27f19ea1 Format curriculum periods in the frontend 2024-05-23 17:04:34 +03:00
Riku Rauhala ad7c36f235 [Users] Update email button text 2024-05-23 14:51:39 +03:00
Riku Rauhala 6caebdab25 [Class statistics] Fix curriculum year ranges 2024-05-22 20:23:16 +03:00
Riku Rauhala 58423df517 Refactor CourseTab
Rename variables and improve readability
2024-05-22 15:52:29 +03:00
Riku Rauhala a547382f1c Use consistent spelling for email
All other instances use the spelling email instead of e-mail
2024-05-22 09:40:03 +03:00
Valtteri Kantanen bf178fb086 [Class statistics] Fix small percentages not showing in credit gain stats 2024-05-21 18:33:57 +03:00
Valtteri Kantanen fc023f09cb [Class statistics] Add median to credit gain stats 2024-05-21 18:32:28 +03:00
Valtteri Kantanen 3fe9683c98 [Class statistics] Fix filtering by admission type 2024-05-21 18:31:28 +03:00
Riku Rauhala 8d20225a57 [Class statistics] Update pagination icons 2024-05-21 17:27:14 +03:00
Riku Rauhala b83c90dca1 [Class statistics] Fix ghost border 2024-05-21 11:49:39 +03:00
Riku Rauhala 47de8e89c3 Add a default value for the search history header 2024-05-21 09:14:05 +03:00
Riku Rauhala 64606c2a98 Refactor search history 2024-05-20 20:00:51 +03:00
Valtteri Kantanen b5e913d85e Fix tests 2024-05-20 19:50:43 +03:00
Valtteri Kantanen 8e5dcb3e78 Add retries to flaky filters tests 2024-05-20 19:39:26 +03:00
Valtteri Kantanen ebd03acff0 [Courses tab] Fix table flowing over its borders 2024-05-20 19:35:56 +03:00
Valtteri Kantanen 0fe06bfd12 [Course statistics] Exclude selected primary groups from comparison group options 2024-05-20 19:15:49 +03:00
Valtteri Kantanen 3f564ab66a [Course statistics] Fix filtering statistics by study programme 2024-05-20 19:15:49 +03:00
Valtteri Kantanen 9e6349739a [Course population] Fix visibility for users with full Sisu access 2024-05-20 19:15:49 +03:00
Valtteri Kantanen d1936c6358 [Close to graduation] Only show current study tracks 2024-05-20 19:15:49 +03:00
Riku Rauhala a075df412c Update some titles for consistency 2024-05-20 17:51:59 +03:00
Valtteri Kantanen b53d84f706 [Updater] Increase Sequelize acquire timeout closer to the one in the backend 2024-05-20 10:52:12 +03:00
Valtteri Kantanen 0312174e40 Add a button for refreshing the close to graduation data 2024-05-20 10:51:14 +03:00
Valtteri Kantanen 442ced9124 Add logging to see how long a big db query takes to finish 2024-05-17 15:06:20 +03:00
Valtteri Kantanen ea9345d82c [Students] Fix tests 2024-05-17 10:15:11 +03:00
Valtteri Kantanen 132bf930fb Upgrade React 16 to React 18 2024-05-17 10:04:12 +03:00
Valtteri Kantanen 980fbef339 [Students] Fix calculation of average grades 2024-05-16 23:15:56 +03:00
Valtteri Kantanen c68e9abfd4 [General tab] Fix page crashing if no correct studyright is found 2024-05-16 17:58:50 +03:00
Valtteri Kantanen 530435a069 [Class statistics] Fix filtering with credit distribution development 2024-05-16 17:09:32 +03:00
Valtteri Kantanen 8cbd4fd957 [Study guidance groups] Fix tests 2024-05-16 17:01:50 +03:00
Valtteri Kantanen 3152e5a76d [Close to graduation] Fix column resizing over and over again 2024-05-16 16:39:58 +03:00
447 changed files with 2441 additions and 2917 deletions
+11 -1
View File
@@ -21,7 +21,17 @@
"cypress/no-assigning-return-values": "off",
"cypress/unsafe-to-chain-command": "off",
"default-param-last": "off",
"import/order": "off",
"import/order": [
"error",
{
"alphabetize": { "order": "asc", "caseInsensitive": true },
"groups": [
["builtin", "external"],
["internal", "parent", "sibling", "index"]
],
"newlines-between": "always"
}
],
"no-async-promise-executor": "off",
"no-await-in-loop": "off",
"no-console": "error",
+2 -2
View File
@@ -162,13 +162,13 @@ describe('Population Statistics', () => {
})
})
it('Credit filter works', () => {
it('Credit filter works', { retries: 2 }, () => {
runTestStepWithPreAndPostParts('CreditsEarned', () => {
testRangeFilter('CreditsEarned-filter-card', 50, 150, 118)
})
})
it('Age filter works', () => {
it('Age filter works', { retries: 2 }, () => {
cy.clock(MOCKED_DATE, ['Date'])
runTestStepWithPreAndPostParts('Age', () => {
testRangeFilter('Age-filter-card', 20, 40, 47)
+1 -1
View File
@@ -57,7 +57,7 @@ describe('When language center is opened', () => {
it('"Access denied" is shown', () => {
cy.init('/languagecenterview', 'norights')
cy.contains('Access denied')
cy.contains("You're currently not allowed to see this page.")
cy.contains("You don't currently have permission to view this page.")
cy.contains('Special populations').click()
cy.contains('Language center view').should('not.exist')
})
+4 -4
View File
@@ -165,7 +165,7 @@ describe('Population Statistics tests', () => {
}
cy.get("[data-cy='credits-gained-table-body'] td:nth-child(3)").then($els => {
const sum = [...$els].map($el => parseInt($el.innerText)).reduce((a, b) => a + b, 0)
const sum = [...$els].map($el => parseInt($el.innerText, 10)).reduce((a, b) => a + b, 0)
expect(sum).to.equal(170)
})
@@ -182,7 +182,6 @@ describe('Population Statistics tests', () => {
if (!$parentDiv.hasClass('active')) cy.get("[data-cy='Credit statistics']").click()
})
cy.contains('Credits gained').click()
cy.get('.credits-gained-divider').click()
cy.get("[data-cy='credits-gained-table-Avoin väylä']").should('exist')
cy.get("[data-cy='credits-gained-table-Yhteispisteet']").should('exist')
cy.get("[data-cy='credits-gained-table-Muu']").should('exist')
@@ -198,8 +197,9 @@ describe('Population Statistics tests', () => {
})
cy.get("[data-cy='credit-stats-tab'] > .menu > :nth-child(2)").click()
cy.get("[data-cy='credit-stats-table-name-header']").should('contain', 'Statistic for n = 170 Students')
cy.get("[data-cy='credit-stats-mean']").should('contain', '114.74')
cy.get("[data-cy='credit-stats-population-size']").should('contain', 'n = 170')
cy.get("[data-cy='credit-stats-average']").should('contain', '114.74')
cy.get("[data-cy='credit-stats-median']").should('contain', '109.00')
cy.get("[data-cy='credit-stats-stdev']").should('contain', '65.53')
cy.get("[data-cy='credit-stats-min']").should('contain', '5')
cy.get("[data-cy='credit-stats-max']").should('contain', '314')
+1
View File
@@ -64,6 +64,7 @@ describe('Students tests', () => {
cy.get('.prompt').type(student.studentnumber)
cy.contains('td a', student.studentnumber).click()
cy.contains('Introduction to Machine Learning (DATA11002)')
.parent()
.siblings()
.last()
.within(() => {
+2 -12
View File
@@ -15,7 +15,7 @@ describe('Study guidance group tests', () => {
cy.init('/studyguidancegroups')
cy.contains('Access denied')
cy.contains(
"You're currently not allowed to see this page. Please contant oodikone@helsinki.fi, if this is a mistake."
"You don't currently have permission to view this page. If you believe this is a mistake, please contact oodikone@helsinki.fi."
)
})
})
@@ -79,14 +79,7 @@ describe('Study guidance group tests', () => {
})
it('general tab of the students table has the correct columns', () => {
const firstRowHeadings = [
'Phone number',
'Student number',
'Credits',
'Start year at uni',
'Study programmes',
'Tags',
]
const firstRowHeadings = ['Student number', 'Credits', 'Start year at uni', 'Study programmes', 'Tags']
const secondRowHeadings = ['All', 'Since 1.1.1970']
cy.cs('Students (2)').click()
cy.get('[data-cy="student-table-tabs"] table thead tr').then($tr => {
@@ -137,17 +130,14 @@ describe('Study guidance group tests', () => {
it('general tab of the students table has the correct columns', () => {
const firstRowHeadings = [
'Phone number',
'Student number',
'Credits',
'Start of studyright',
'Started in programme',
'Semesters present',
'Enrollment status',
'Graduation date',
'Start year at uni',
'Other programmes',
'Semesters present amount',
'Transferred from',
'Gender',
'Citizenship',
Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

+6 -11
View File
@@ -16,7 +16,7 @@
"express": "^4.19.2",
"express-async-errors": "^3.1.1",
"lodash": "^4.17.21",
"lru-cache": "^5.1.1",
"lru-cache": "^10.2.2",
"moment": "^2.30.1",
"moment-timezone": "^0.5.37",
"morgan": "^1.10.0",
@@ -1317,11 +1317,11 @@
"integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ=="
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dependencies": {
"yallist": "^3.0.2"
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
"integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
"engines": {
"node": "14 || >=16.14"
}
},
"node_modules/luxon": {
@@ -2494,11 +2494,6 @@
"engines": {
"node": ">=0.4"
}
},
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
}
}
}
+1 -1
View File
@@ -17,7 +17,7 @@
"express": "^4.19.2",
"express-async-errors": "^3.1.1",
"lodash": "^4.17.21",
"lru-cache": "^5.1.1",
"lru-cache": "^10.2.2",
"moment": "^2.30.1",
"moment-timezone": "^0.5.37",
"morgan": "^1.10.0",
+3 -2
View File
@@ -1,9 +1,10 @@
require('express-async-errors')
const express = require('express')
const { baseUrl, backendPort } = require('./conf-backend')
const routes = require('./routes')
const { startCron } = require('./events')
const { initializeDatabaseConnection, dbConnections } = require('./database/connection')
const { startCron } = require('./events')
const routes = require('./routes')
const logger = require('./util/logger')
require('./worker/worker')
+2 -1
View File
@@ -1,6 +1,7 @@
const Sequelize = require('sequelize')
const EventEmitter = require('events')
const Sequelize = require('sequelize')
const Umzug = require('umzug')
const conf = require('../conf-backend')
const logger = require('../util/logger')
@@ -0,0 +1,41 @@
const { ARRAY, STRING } = require('sequelize')
module.exports = {
up: async queryInterface => {
await queryInterface.addColumn('users', 'roles', {
type: ARRAY(STRING),
allowNull: false,
defaultValue: [],
})
await queryInterface.sequelize.query(`
UPDATE users
SET roles = COALESCE(subquery.roles, '{}')
FROM (
SELECT uag."userId" AS "userId", ARRAY_AGG(ag.group_code) AS roles
FROM user_accessgroup uag
JOIN access_groups ag ON uag."accessGroupId" = ag.id
GROUP BY uag."userId"
) AS subquery
WHERE users.id = subquery."userId"
`)
await queryInterface.addColumn('users', 'programme_rights', {
type: ARRAY(STRING),
allowNull: false,
defaultValue: [],
})
await queryInterface.sequelize.query(`
UPDATE users
SET programme_rights = subquery.programme_rights
FROM (
SELECT "userId", ARRAY_AGG("elementDetailCode") AS programme_rights
FROM "user_elementdetails"
GROUP BY "userId"
) AS subquery
WHERE users.id = subquery."userId"
`)
},
down: async queryInterface => {
await queryInterface.removeColumn('users', 'roles')
await queryInterface.removeColumn('users', 'programme_rights')
},
}
@@ -0,0 +1,6 @@
module.exports = {
up: async queryInterface => {
await queryInterface.dropTable('user_faculties')
},
down: async () => {},
}
@@ -0,0 +1,6 @@
module.exports = {
up: async queryInterface => {
await queryInterface.dropTable('user_elementdetails')
},
down: async () => {},
}
@@ -0,0 +1,6 @@
module.exports = {
up: async queryInterface => {
await queryInterface.dropTable('user_accessgroup')
},
down: async () => {},
}
@@ -0,0 +1,6 @@
module.exports = {
up: async queryInterface => {
await queryInterface.dropTable('access_groups')
},
down: async () => {},
}
@@ -0,0 +1,6 @@
module.exports = {
up: async queryInterface => {
await queryInterface.removeColumn('users', 'iam_groups')
},
down: async () => {},
}
@@ -0,0 +1,6 @@
module.exports = {
up: async queryInterface => {
await queryInterface.dropTable('"Sessions"')
},
down: async () => {},
}
+18 -16
View File
@@ -1,23 +1,24 @@
const { CronJob } = require('cron')
const { refreshAssociationsInRedis } = require('./services/studyrights')
const { getAllProgrammes } = require('./services/studyrights')
const { updateBasicView, updateStudytrackView } = require('./services/studyprogramme/studyprogrammeUpdates')
const { findAndSaveTeachers } = require('./services/topteachers')
const { faculties } = require('./services/organisations')
const { combinedStudyprogrammes } = require('./services/studyprogramme/studyprogrammeHelpers')
const { updateFacultyOverview, updateFacultyProgressOverview } = require('./services/faculty/facultyUpdates')
const { isProduction, runningInCI } = require('./conf-backend')
const { getCurrentSemester } = require('./services/semesters')
const logger = require('./util/logger')
const { getAssociations } = require('./services/studyrights')
const { redisClient } = require('./services/redis')
const { updateFacultyOverview, updateFacultyProgressOverview } = require('./services/faculty/facultyUpdates')
const { computeLanguageCenterData, LANGUAGE_CENTER_REDIS_KEY } = require('./services/languageCenterData')
const { jobMaker } = require('./worker/queue')
const { deleteOutdatedUsers } = require('./services/userService')
const { faculties } = require('./services/organisations')
const {
findStudentsCloseToGraduation,
CLOSE_TO_GRADUATION_REDIS_KEY,
} = require('./services/populations/closeToGraduation')
const { redisClient } = require('./services/redis')
const { getCurrentSemester } = require('./services/semesters')
const { combinedStudyprogrammes } = require('./services/studyprogramme/studyprogrammeHelpers')
const { updateBasicView, updateStudytrackView } = require('./services/studyprogramme/studyprogrammeUpdates')
const { refreshAssociationsInRedis } = require('./services/studyrights')
const { getAllProgrammes } = require('./services/studyrights')
const { getAssociations } = require('./services/studyrights')
const { findAndSaveTeachers } = require('./services/topteachers')
const { deleteOutdatedUsers } = require('./services/userService')
const logger = require('./util/logger')
const { jobMaker } = require('./worker/queue')
const schedule = (cronTime, func) => new CronJob({ cronTime, onTick: func, start: true, timeZone: 'Europe/Helsinki' })
@@ -112,7 +113,7 @@ const dailyJobs = () => {
refreshProgrammes()
jobMaker.languagecenter()
jobMaker.statistics()
refreshCloseToGraduating()
jobMaker.closeToGraduation()
}
const startCron = () => {
@@ -124,8 +125,8 @@ const startCron = () => {
})
schedule('0 4 * * 3', async () => {
logger.info("Deleting users who haven't logged in for 18 months")
const numberOfDeletedUsers = await deleteOutdatedUsers()
logger.info(`Deleted ${numberOfDeletedUsers} users.`)
const [, result] = await deleteOutdatedUsers()
logger.info(`Deleted ${result.rowCount} users.`)
})
schedule('0 19 * * 1', async () => {
logger.info('Updating students whose studyplans have not been updated recently')
@@ -146,4 +147,5 @@ module.exports = {
refreshProgramme,
refreshProgrammes,
refreshLanguageCenterData,
refreshCloseToGraduating,
}
@@ -1,6 +1,7 @@
const morgan = require('morgan')
const logger = require('../util/logger')
const _ = require('lodash')
const morgan = require('morgan')
const logger = require('../util/logger')
const { getFullStudyProgrammeRights } = require('../util/utils')
// So this appears to be a hack to get neatly formatted stats like response-time etc. from morgan
@@ -28,8 +29,9 @@ const accessLogger = morgan((tokens, req, res) => {
].join(' ')
const fullStudyProgrammeRights = getFullStudyProgrammeRights(user.programmeRights)
const iamBasedRights = user.programmeRights.filter(right => right.isIamBased)
const usingIamRights = user.iamRights.some(programmeCode => tokens.url(req, res).includes(programmeCode))
const usingIamRights = iamBasedRights.some(right => tokens.url(req, res).includes(right.code))
const onlyIamRights = !user.isAdmin && fullStudyProgrammeRights.length === 0
logger.info(message, {
@@ -1,11 +1,12 @@
const Sentry = require('@sentry/node')
const { ApplicationError } = require('../util/customErrors')
const { getUser, getMockedUser, getOrganizationAccess } = require('../services/userService')
const { requiredGroup } = require('../conf-backend')
const _ = require('lodash')
const { requiredGroup } = require('../conf-backend')
const { getUser, getMockedUser, getOrganizationAccess } = require('../services/userService')
const { ApplicationError } = require('../util/customErrors')
const logger = require('../util/logger')
const parseIamGroups = iamGroups => iamGroups?.split(';').filter(Boolean) ?? []
const parseIamGroups = iamGroups => iamGroups?.split(';') ?? []
const hasRequiredIamGroup = (iamGroups, iamRights) =>
_.intersection(iamGroups, requiredGroup).length > 0 || iamRights.length > 0
@@ -36,7 +37,7 @@ const currentUserMiddleware = async (req, _res, next) => {
}
const iamGroups = parseIamGroups(hygroupcn)
const { access = {}, specialGroup = {} } = await getOrganizationAccess({ id: sisId, iamGroups })
const { access = {}, specialGroup = {} } = await getOrganizationAccess(sisId, iamGroups)
const iamRights = Object.keys(access)
if (!hasRequiredIamGroup(iamGroups, iamRights)) {
@@ -44,19 +45,12 @@ const currentUserMiddleware = async (req, _res, next) => {
throw new ApplicationError(`User '${username}' does not have required iam group`, 403, { logoutUrl })
}
let user = await getUser({
username,
name,
email,
iamGroups,
iamRights,
specialGroup,
sisId,
access,
})
let user
if (showAsUser && user.isAdmin) {
if (showAsUser && specialGroup?.superAdmin) {
user = await getMockedUser({ userToMock: showAsUser, mockedBy: username })
} else {
user = await getUser({ username, name, email, iamGroups, specialGroup, sisId, access })
}
Sentry.setUser({ username: user.mockedBy ?? username })
@@ -1,4 +1,5 @@
const Sentry = require('@sentry/node')
const { ApplicationError } = require('../util/customErrors')
const logger = require('../util/logger')
+2 -1
View File
@@ -1,4 +1,5 @@
const { Model, STRING, DATE, JSONB, BOOLEAN } = require('sequelize')
const { Model, BOOLEAN, DATE, JSONB, STRING } = require('sequelize')
const { dbConnections } = require('../database/connection')
class Course extends Model {}
@@ -1,4 +1,5 @@
const { Model, STRING, DATE, JSONB } = require('sequelize')
const { Model, DATE, JSONB, STRING } = require('sequelize')
const { dbConnections } = require('../database/connection')
class CourseProvider extends Model {}
+2 -1
View File
@@ -1,4 +1,5 @@
const { Model, STRING, DATE, JSONB } = require('sequelize')
const { Model, DATE, JSONB, STRING } = require('sequelize')
const { dbConnections } = require('../database/connection')
class CourseType extends Model {}
+2 -1
View File
@@ -1,4 +1,5 @@
const { Model, DATE, INTEGER, STRING, DOUBLE, BOOLEAN } = require('sequelize')
const { Model, BOOLEAN, DATE, DOUBLE, INTEGER, STRING } = require('sequelize')
const { dbConnections } = require('../database/connection')
class Credit extends Model {}
+2 -1
View File
@@ -1,4 +1,5 @@
const { Model, STRING, DATE } = require('sequelize')
const { Model, DATE, STRING } = require('sequelize')
const { dbConnections } = require('../database/connection')
class CreditTeacher extends Model {}
+2 -1
View File
@@ -1,4 +1,5 @@
const { Model, INTEGER, DATE, JSONB } = require('sequelize')
const { Model, DATE, INTEGER, JSONB } = require('sequelize')
const { dbConnections } = require('../database/connection')
class CreditType extends Model {}
+2 -1
View File
@@ -1,4 +1,5 @@
const { Model, INTEGER, DATE, JSONB, STRING } = require('sequelize')
const { Model, DATE, INTEGER, JSONB, STRING } = require('sequelize')
const { dbConnections } = require('../database/connection')
class ElementDetail extends Model {}
+2 -1
View File
@@ -1,4 +1,5 @@
const { Model, DATE, INTEGER, STRING, BOOLEAN } = require('sequelize')
const { Model, BOOLEAN, DATE, INTEGER, STRING } = require('sequelize')
const { dbConnections } = require('../database/connection')
class Enrollment extends Model {}
@@ -1,4 +1,5 @@
const { Model, INTEGER, STRING, DATE } = require('sequelize')
const { Model, DATE, INTEGER, STRING } = require('sequelize')
const { dbConnections } = require('../database/connection')
class ExcludedCourse extends Model {}
+25 -25
View File
@@ -1,24 +1,24 @@
const Organization = require('./organization')
const CourseType = require('./courseType')
const Course = require('./course')
const Student = require('./student')
const CourseProvider = require('./courseProvider')
const Semester = require('./semester')
const SemesterEnrollment = require('./semesterEnrollment')
const Teacher = require('./teacher')
const CreditType = require('./creditType')
const CourseType = require('./courseType')
const Credit = require('./credit')
const CreditTeacher = require('./creditTeacher')
const CreditType = require('./creditType')
const ElementDetail = require('./elementDetail')
const Studyplan = require('./studyplan')
const StudyrightExtent = require('./studyrightExtent')
const Studyright = require('./studyright')
const StudyrightElement = require('./studyrightElement')
const Transfer = require('./transfer')
const Enrollment = require('./enrollment')
const ExcludedCourse = require('./excludedCourse')
const Organization = require('./organization')
const ProgrammeModule = require('./programmeModule')
const ProgrammeModuleChild = require('./programmeModuleChild')
const ExcludedCourse = require('./excludedCourse')
const Enrollment = require('./enrollment')
const Semester = require('./semester')
const SemesterEnrollment = require('./semesterEnrollment')
const Student = require('./student')
const Studyplan = require('./studyplan')
const Studyright = require('./studyright')
const StudyrightElement = require('./studyrightElement')
const StudyrightExtent = require('./studyrightExtent')
const Teacher = require('./teacher')
const Transfer = require('./transfer')
CourseType.hasMany(Course, { foreignKey: 'coursetypecode', sourceKey: 'coursetypecode' })
Course.belongsTo(CourseType, { foreignKey: 'coursetypecode', targetKey: 'coursetypecode' })
@@ -121,25 +121,25 @@ ProgrammeModule.belongsToMany(ProgrammeModule, {
Organization.hasMany(Organization, { foreignKey: 'parent_id', as: 'children' })
module.exports = {
Organization,
Course,
CourseType,
Student,
CourseProvider,
Semester,
SemesterEnrollment,
Teacher,
CreditType,
CourseType,
Credit,
CreditTeacher,
CreditType,
ElementDetail,
Enrollment,
ExcludedCourse,
Organization,
ProgrammeModule,
ProgrammeModuleChild,
Semester,
SemesterEnrollment,
Student,
Studyplan,
StudyrightExtent,
Studyright,
StudyrightElement,
Teacher,
Transfer,
ProgrammeModule,
ProgrammeModuleChild,
ExcludedCourse,
Enrollment,
}
+44 -44
View File
@@ -1,5 +1,5 @@
const { STRING } = require('sequelize')
const Sequelize = require('sequelize')
const { ARRAY, BIGINT, DATE, INTEGER, STRING } = require('sequelize')
const { sequelizeKone } = require('../database/connection')
const ExcludedCourse = sequelizeKone.define(
@@ -7,23 +7,23 @@ const ExcludedCourse = sequelizeKone.define(
{
id: {
primaryKey: true,
type: Sequelize.INTEGER,
type: INTEGER,
autoIncrement: true,
},
programme_code: {
type: Sequelize.STRING,
type: STRING,
},
curriculum_version: {
type: STRING,
},
course_code: {
type: Sequelize.STRING,
type: STRING,
},
created_at: {
type: Sequelize.DATE,
type: DATE,
},
updated_at: {
type: Sequelize.DATE,
type: DATE,
},
},
{
@@ -36,11 +36,11 @@ const TagStudent = sequelizeKone.define(
{
studentnumber: {
primaryKey: true,
type: Sequelize.STRING,
type: STRING,
},
tag_id: {
primaryKey: true,
type: Sequelize.BIGINT,
type: BIGINT,
},
},
{
@@ -52,21 +52,21 @@ const Tag = sequelizeKone.define(
'tag',
{
tagname: {
type: Sequelize.STRING,
type: STRING,
},
tag_id: {
primaryKey: true,
type: Sequelize.BIGINT,
type: BIGINT,
autoIncrement: true,
},
studytrack: {
type: Sequelize.STRING,
type: STRING,
},
year: {
type: Sequelize.STRING,
type: STRING,
},
personal_user_id: {
type: Sequelize.BIGINT,
type: BIGINT,
},
},
{
@@ -77,17 +77,17 @@ const Tag = sequelizeKone.define(
const CustomPopulationSearch = sequelizeKone.define('custom_population_searches', {
id: {
primaryKey: true,
type: Sequelize.BIGINT,
type: BIGINT,
autoIncrement: true,
},
userId: {
type: Sequelize.BIGINT,
type: BIGINT,
},
name: {
type: Sequelize.STRING,
type: STRING,
},
students: {
type: Sequelize.ARRAY(Sequelize.STRING),
type: ARRAY(STRING),
},
})
@@ -97,18 +97,18 @@ const OpenUniPopulationSearch = sequelizeKone.define(
{
id: {
primaryKey: true,
type: Sequelize.BIGINT,
type: BIGINT,
autoIncrement: true,
},
userId: {
type: Sequelize.BIGINT,
type: BIGINT,
},
name: {
type: Sequelize.STRING,
type: STRING,
allowNull: false,
},
courseCodes: {
type: Sequelize.ARRAY(Sequelize.STRING),
type: ARRAY(STRING),
},
},
{ underscored: true, timestamps: true }
@@ -125,40 +125,40 @@ const ProgressCriteria = sequelizeKone.define(
type: STRING,
},
coursesYearOne: {
type: Sequelize.ARRAY(Sequelize.STRING),
type: ARRAY(STRING),
},
coursesYearTwo: {
type: Sequelize.ARRAY(Sequelize.STRING),
type: ARRAY(STRING),
},
coursesYearThree: {
type: Sequelize.ARRAY(Sequelize.STRING),
type: ARRAY(STRING),
},
coursesYearFour: {
type: Sequelize.ARRAY(Sequelize.STRING),
type: ARRAY(STRING),
},
coursesYearFive: {
type: Sequelize.ARRAY(Sequelize.STRING),
type: ARRAY(STRING),
},
coursesYearSix: {
type: Sequelize.ARRAY(Sequelize.STRING),
type: ARRAY(STRING),
},
creditsYearOne: {
type: Sequelize.INTEGER,
type: INTEGER,
},
creditsYearTwo: {
type: Sequelize.INTEGER,
type: INTEGER,
},
creditsYearThree: {
type: Sequelize.INTEGER,
type: INTEGER,
},
creditsYearFour: {
type: Sequelize.INTEGER,
type: INTEGER,
},
creditsYearFive: {
type: Sequelize.INTEGER,
type: INTEGER,
},
creditsYearSix: {
type: Sequelize.INTEGER,
type: INTEGER,
},
},
{ underscored: true, timestamps: true }
@@ -169,18 +169,18 @@ const StudyGuidanceGroupTag = sequelizeKone.define(
{
id: {
primaryKey: true,
type: Sequelize.BIGINT,
type: BIGINT,
autoIncrement: true,
},
studyGuidanceGroupId: {
type: Sequelize.STRING,
type: STRING,
unique: true,
},
studyProgramme: {
type: Sequelize.STRING,
type: STRING,
},
year: {
type: Sequelize.STRING,
type: STRING,
},
},
{ underscored: true, timestamps: true }
@@ -191,10 +191,10 @@ const StudyProgrammePin = sequelizeKone.define(
{
userId: {
primaryKey: true,
type: Sequelize.INTEGER,
type: INTEGER,
},
studyProgrammes: {
type: Sequelize.ARRAY(Sequelize.STRING),
type: ARRAY(STRING),
},
},
{ underscored: true, timestamps: false }
@@ -204,12 +204,12 @@ TagStudent.belongsTo(Tag, { foreignKey: 'tag_id', sourceKey: 'tag_id' })
Tag.hasMany(TagStudent, { foreignKey: 'tag_id', sourceKey: 'tag_id' })
module.exports = {
TagStudent,
Tag,
CustomPopulationSearch,
OpenUniPopulationSearch,
ExcludedCourse,
StudyGuidanceGroupTag,
OpenUniPopulationSearch,
ProgressCriteria,
StudyGuidanceGroupTag,
StudyProgrammePin,
Tag,
TagStudent,
}
+17 -60
View File
@@ -1,85 +1,42 @@
const Sequelize = require('sequelize')
const { ARRAY, BIGINT, DATE, STRING } = require('sequelize')
const { sequelizeUser } = require('../database/connection')
const UserElementDetails = sequelizeUser.define(
'user_elementdetails',
{
userId: {
primaryKey: true,
type: Sequelize.BIGINT,
},
elementDetailCode: {
primaryKey: true,
type: Sequelize.STRING,
},
},
{
tablename: 'user_elementdetails',
}
)
const AccessGroup = sequelizeUser.define('access_group', {
id: {
primaryKey: true,
type: Sequelize.BIGINT,
autoIncrement: true,
},
group_code: {
type: Sequelize.STRING,
unique: true,
},
group_info: {
type: Sequelize.STRING,
},
createdAt: {
type: Sequelize.DATE,
},
updatedAt: {
type: Sequelize.DATE,
},
})
const User = sequelizeUser.define(
'users',
{
id: {
primaryKey: true,
type: Sequelize.BIGINT,
type: BIGINT,
autoIncrement: true,
},
full_name: { type: Sequelize.STRING },
fullName: STRING,
username: {
type: Sequelize.STRING,
type: STRING,
unique: true,
},
email: { type: Sequelize.STRING },
language: { type: Sequelize.STRING },
sisu_person_id: { type: Sequelize.STRING },
iam_groups: {
type: Sequelize.ARRAY(Sequelize.STRING),
email: STRING,
language: STRING,
sisuPersonId: STRING,
lastLogin: DATE,
roles: {
type: ARRAY(STRING),
allowNull: false,
defaultValue: [],
},
programmeRights: {
type: ARRAY(STRING),
allowNull: false,
defaultValue: [],
},
last_login: { type: Sequelize.DATE },
},
{
tableName: 'users',
timestamps: false,
underscored: true,
}
)
User.hasMany(UserElementDetails, { as: 'programme' })
UserElementDetails.belongsTo(User)
User.belongsToMany(AccessGroup, {
through: 'user_accessgroup',
as: 'accessgroup',
})
AccessGroup.belongsToMany(User, { through: 'user_accessgroup' })
module.exports = {
User,
UserElementDetails,
AccessGroup,
sequelizeUser,
}
+2 -1
View File
@@ -1,4 +1,5 @@
const { Model, STRING, DATE, JSONB } = require('sequelize')
const { Model, DATE, JSONB, STRING } = require('sequelize')
const { dbConnections } = require('../database/connection')
class Organization extends Model {}
@@ -1,4 +1,5 @@
const { Model, STRING, DATE, INTEGER, JSONB, ARRAY, TEXT } = require('sequelize')
const { Model, ARRAY, DATE, INTEGER, JSONB, STRING, TEXT } = require('sequelize')
const { dbConnections } = require('../database/connection')
class ProgrammeModule extends Model {}
@@ -1,4 +1,5 @@
const { Model, STRING, DATE } = require('sequelize')
const { Model, DATE, STRING } = require('sequelize')
const { dbConnections } = require('../database/connection')
class ProgrammeModuleChild extends Model {}
+2 -1
View File
@@ -1,4 +1,5 @@
const { Model, STRING, DATE, JSONB, INTEGER } = require('sequelize')
const { Model, DATE, INTEGER, JSONB, STRING } = require('sequelize')
const { dbConnections } = require('../database/connection')
class Semester extends Model {}
@@ -1,4 +1,5 @@
const { Model, STRING, DATE, INTEGER, BOOLEAN } = require('sequelize')
const { Model, BOOLEAN, DATE, INTEGER, STRING } = require('sequelize')
const { dbConnections } = require('../database/connection')
class SemesterEnrollment extends Model {}
+2 -1
View File
@@ -1,4 +1,5 @@
const { Model, STRING, DATE, INTEGER } = require('sequelize')
const { Model, DATE, INTEGER, STRING } = require('sequelize')
const { dbConnections } = require('../database/connection')
class Student extends Model {}
+2 -1
View File
@@ -1,4 +1,5 @@
const { Model, DATE, ARRAY, STRING, INTEGER } = require('sequelize')
const { Model, ARRAY, DATE, INTEGER, STRING } = require('sequelize')
const { dbConnections } = require('../database/connection')
class Studyplan extends Model {}
+2 -1
View File
@@ -1,4 +1,5 @@
const { Model, STRING, DATE, INTEGER, BOOLEAN, JSONB } = require('sequelize')
const { Model, BOOLEAN, DATE, INTEGER, JSONB, STRING } = require('sequelize')
const { dbConnections } = require('../database/connection')
class Studyright extends Model {}
@@ -1,4 +1,5 @@
const { Model, STRING, DATE } = require('sequelize')
const { Model, DATE, STRING } = require('sequelize')
const { dbConnections } = require('../database/connection')
class StudyrightElement extends Model {}
@@ -1,4 +1,5 @@
const { Model, INTEGER, DATE, JSONB } = require('sequelize')
const { Model, DATE, INTEGER, JSONB } = require('sequelize')
const { dbConnections } = require('../database/connection')
class StudyrightExtent extends Model {}
+2 -1
View File
@@ -1,4 +1,5 @@
const { Model, STRING, DATE } = require('sequelize')
const { Model, DATE, STRING } = require('sequelize')
const { dbConnections } = require('../database/connection')
class Teacher extends Model {}
+2 -1
View File
@@ -1,4 +1,5 @@
const { Model, STRING, DATE } = require('sequelize')
const { Model, DATE, STRING } = require('sequelize')
const { dbConnections } = require('../database/connection')
class Transfer extends Model {}
+3 -2
View File
@@ -1,8 +1,9 @@
/* eslint-disable no-console */
const { computeCreditsProduced } = require('../services/providerCredits')
const { parseCsv } = require('./helpers')
const _ = require('lodash')
const { parseCsv } = require('./helpers')
const { computeCreditsProduced } = require('../services/providerCredits')
const diffs = []
const noDiffCounter = 0
+1 -1
View File
@@ -1,6 +1,6 @@
/* eslint-disable no-console */
const Papa = require('papaparse')
const fs = require('fs/promises')
const Papa = require('papaparse')
const parseCsv = async (fileName, fun) => {
try {
@@ -1,12 +1,11 @@
/* eslint-disable no-console */
const axios = require('axios').default
const _ = require('lodash')
const { optimizedStatisticsOf } = require('../services/populations')
const { Student, Transfer, StudyrightElement, Studyright } = require('../models')
const { rapoToken, tokenImporterApi, importerDbApiUrl, nodeproxyUrl } = require('./conf')
const { Student, Transfer, StudyrightElement, Studyright } = require('../models')
const { optimizedStatisticsOf } = require('../services/populations')
const getFromRapo = async (urlStart, urlEnd) => {
console.log({ urlStart, urlEnd })
+22 -24
View File
@@ -1,40 +1,39 @@
const Sentry = require('@sentry/node')
const express = require('express')
const cors = require('cors')
const express = require('express')
const { frontUrl } = require('./conf-backend')
const shibbolethCharsetMiddleware = require('./middleware/shibbolethCharsetMiddleware')
const currentUserMiddleware = require('./middleware/currentUserMiddleware')
const accessLogger = require('./middleware/accessLogger')
const auth = require('./middleware/auth')
const currentUserMiddleware = require('./middleware/currentUserMiddleware')
const errorMiddleware = require('./middleware/errorMiddleware')
const shibbolethCharsetMiddleware = require('./middleware/shibbolethCharsetMiddleware')
const changelog = require('./routes/changelog')
const courses = require('./routes/courses')
const students = require('./routes/students')
const population = require('./routes/population')
const login = require('./routes/login')
const language = require('./routes/language')
const users = require('./routes/users')
const elementdetails = require('./routes/elementdetails')
const teachers = require('./routes/teachers')
const providers = require('./routes/providers')
const faculties = require('./routes/faculties')
const university = require('./routes/university')
const closeToGraduation = require('./routes/closeToGraduation')
const completedCoursesSearch = require('./routes/completedCoursesSearch')
const courseExclusions = require('./routes/courseExclusions')
const semesters = require('./routes/semesters')
const feedback = require('./routes/feedback')
const tags = require('./routes/tags')
const updater = require('./routes/updater')
const courses = require('./routes/courses')
const customOpenUniSearch = require('./routes/customOpenUniSearch')
const customPopulationSearch = require('./routes/customPopulationSearch')
const elementdetails = require('./routes/elementdetails')
const faculties = require('./routes/faculties')
const feedback = require('./routes/feedback')
const languageCenterData = require('./routes/languageCenterData')
const login = require('./routes/login')
const population = require('./routes/population')
const programmeModules = require('./routes/programmeModules')
const providers = require('./routes/providers')
const semesters = require('./routes/semesters')
const students = require('./routes/students')
const studyGuidanceGroups = require('./routes/studyGuidanceGroups')
const studyProgramme = require('./routes/studyProgramme')
const customOpenUniSearch = require('./routes/customOpenUniSearch')
const studyProgrammeCriteria = require('./routes/studyProgrammeCriteria')
const completedCoursesSearch = require('./routes/completedCoursesSearch')
const closeToGraduation = require('./routes/closeToGraduation')
const studyProgrammePins = require('./routes/studyProgrammePins')
const tags = require('./routes/tags')
const teachers = require('./routes/teachers')
const university = require('./routes/university')
const updater = require('./routes/updater')
const users = require('./routes/users')
const initializeSentry = require('./util/sentry')
module.exports = (app, url) => {
@@ -53,7 +52,6 @@ module.exports = (app, url) => {
app.use(url, courses)
app.use(url, students)
app.use(url, population)
app.use(url, language)
app.use(url, providers)
app.use(url, semesters)
app.use(url, tags)
@@ -69,11 +67,11 @@ module.exports = (app, url) => {
app.use(`${url}/university`, university)
app.use(`${url}/updater`, auth.roles(['admin']), updater)
app.use(`${url}/teachers`, auth.roles(['teachers']), teachers)
app.use(`${url}/users`, auth.roles(['admin']), users)
app.use(`${url}/users`, users)
app.use(`${url}/feedback`, feedback)
app.use(`${url}/custom-population-search`, customPopulationSearch)
app.use(`${url}/studyguidancegroups`, auth.roles(['studyGuidanceGroups']), studyGuidanceGroups)
app.use(`${url}/close-to-graduation`, auth.roles(['fullSisuAccess']), closeToGraduation)
app.use(`${url}/close-to-graduation`, auth.roles(['fullSisuAccess', 'studyGuidanceGroups']), closeToGraduation)
app.use(`${url}/study-programme-pins`, studyProgrammePins)
app.get('*', async (_, res) => {
const results = { error: 'unknown endpoint' }
+1 -1
View File
@@ -1,5 +1,5 @@
const router = require('express').Router()
const axios = require('axios')
const router = require('express').Router()
const { isDev } = require('../conf-backend')
@@ -1,9 +1,16 @@
const { getCloseToGraduationData } = require('../services/populations/closeToGraduation')
const router = require('express').Router()
router.get('/', async (_req, res) => {
const result = await getCloseToGraduationData()
const { getCloseToGraduationData } = require('../services/populations/closeToGraduation')
const { getAllStudentsUserHasInGroups } = require('../services/studyGuidanceGroups')
const { hasFullAccessToStudentData } = require('../util/utils')
router.get('/', async (req, res) => {
if (hasFullAccessToStudentData(req.user.roles)) {
const result = await getCloseToGraduationData()
return res.json(result)
}
const studentsInUsersGuidanceGroups = await getAllStudentsUserHasInGroups(req.user.sisPersonId)
const result = await getCloseToGraduationData(studentsInUsersGuidanceGroups)
res.json(result)
})
@@ -1,18 +1,19 @@
const router = require('express').Router()
const { getCompletedCourses } = require('../services/completedCoursesSearch')
const { getImporterClient } = require('../util/importerClient')
const importerClient = getImporterClient()
const _ = require('lodash')
const { getCompletedCourses } = require('../services/completedCoursesSearch')
const {
getOpenUniSearches,
createNewSearch,
deleteSearch,
updateSearch,
} = require('../services/openUni/openUniManageSearches')
const { getImporterClient } = require('../util/importerClient')
const logger = require('../util/logger')
const { hasFullAccessToStudentData } = require('../util/utils')
const importerClient = getImporterClient()
router.get('/', async (req, res) => {
const studentNumbers = JSON.parse(req.query?.studentlist) || []
const courseCodes = JSON.parse(req.query?.courselist) || []
@@ -1,4 +1,5 @@
const router = require('express').Router()
const { addExcludedCourses, removeExcludedCourses } = require('../services/excludedCourses')
router.delete('/v3/course_exclusions/:code', async (req, res) => {
+10 -15
View File
@@ -1,9 +1,9 @@
const router = require('express').Router()
const crypto = require('crypto')
const router = require('express').Router()
const courseService = require('../services/courses')
const { faculties } = require('../services/organisations')
const { validateParamLength } = require('../util')
const { getFullStudyProgrammeRights } = require('../util/utils')
const { getFullStudyProgrammeRights, hasFullAccessToStudentData } = require('../util/utils')
router.get('/v2/coursesmulti', async (req, res) => {
let results = { courses: [] }
@@ -38,12 +38,12 @@ router.get('/v3/courseyearlystats', async (req, res) => {
user: { roles, programmeRights },
} = req
const allowedRoles = roles && ['admin', 'courseStatistics'].find(role => roles.includes(role))
const userHasFullAccessToStudentData = hasFullAccessToStudentData(roles)
const userHasCorrectRole = userHasFullAccessToStudentData || roles.includes('courseStatistics')
const fullStudyProgrammeRights = getFullStudyProgrammeRights(programmeRights)
// If user has rights to see at least one programme, then they are allowed
// to see all of them
if (!allowedRoles && fullStudyProgrammeRights.length === 0) {
// If user has rights to see at least one programme, then they are allowed to see all of them
if (!userHasCorrectRole && fullStudyProgrammeRights.length === 0) {
return res.status(403).json({ error: 'No programmes so no access to course stats' })
}
@@ -54,18 +54,13 @@ router.get('/v3/courseyearlystats', async (req, res) => {
if (!codes) {
res.status(422).send('Missing required query parameters')
} else {
// Studentnumbers should be obfuscated to all other users except admins
// and users with rights to any specific study programmes
const anonymize = allowedRoles !== 'admin' && fullStudyProgrammeRights.length === 0
// Student numbers should be obfuscated to all other users except admins,
// fullSisuAccess users, and users with rights to any specific study programmes
const anonymize = !userHasFullAccessToStudentData && fullStudyProgrammeRights.length === 0
const anonymizationSalt = anonymize ? crypto.randomBytes(12).toString('hex') : null
const results = await courseService.courseYearlyStats(codes, separate, anonymizationSalt, combineSubstitutions)
res.json(results)
}
})
router.get('/faculties-only', async (_req, res) => {
const facultyList = await faculties()
res.json(facultyList)
})
module.exports = router
@@ -1,12 +1,13 @@
const router = require('express').Router()
const moment = require('moment-timezone')
const { getCustomOpenUniCourses } = require('../services/openUni/openUniStats')
const {
getOpenUniSearches,
createNewSearch,
deleteSearch,
updateSearch,
} = require('../services/openUni/openUniManageSearches')
const { getCustomOpenUniCourses } = require('../services/openUni/openUniStats')
router.get('/', async (req, res) => {
const courseCodes = JSON.parse(req.query?.courselist) || []
@@ -1,4 +1,5 @@
const router = require('express').Router()
const {
getCustomPopulationSearchesByUser,
createCustomPopulationSearch,
@@ -1,4 +1,5 @@
const router = require('express').Router()
const { getAllProgrammes, getAllElementDetails } = require('../services/studyrights')
router.get('/elementdetails/all', async (_req, res) => {
+7 -6
View File
@@ -1,11 +1,10 @@
const router = require('express').Router()
const auth = require('../middleware/auth')
const { combineFacultyBasics } = require('../services/faculty/facultyBasics')
const { getFacultyCredits } = require('../services/faculty/facultyCredits')
const { combineFacultyThesisWriters } = require('../services/faculty/facultyThesisWriters')
const { countGraduationTimes } = require('../services/faculty/facultyGraduationTimes')
const { updateFacultyOverview, updateFacultyProgressOverview } = require('../services/faculty/facultyUpdates')
const { combineFacultyStudentProgress } = require('../services/faculty/facultyStudentProgress')
const { combineFacultyStudents } = require('../services/faculty/facultyStudents')
const { getFacultyList } = require('../services/faculty/facultyHelpers')
const {
getProgrammes,
getBasicStats,
@@ -19,9 +18,11 @@ const {
getFacultyProgressStats,
setFacultyProgressStats,
} = require('../services/faculty/facultyService')
const { combineFacultyStudentProgress } = require('../services/faculty/facultyStudentProgress')
const { combineFacultyStudents } = require('../services/faculty/facultyStudents')
const { combineFacultyThesisWriters } = require('../services/faculty/facultyThesisWriters')
const { updateFacultyOverview, updateFacultyProgressOverview } = require('../services/faculty/facultyUpdates')
const logger = require('../util/logger')
const { getFacultyList } = require('../services/faculty/facultyHelpers')
const auth = require('../middleware/auth')
// Faculty uses a lot of tools designed for Study programme.
// Some of them have been copied here and slightly edited for faculty purpose.
+1
View File
@@ -1,4 +1,5 @@
const router = require('express').Router()
const { sendFeedbackToToska } = require('../services/mailservice')
const logger = require('../util/logger')
-20
View File
@@ -1,20 +0,0 @@
const router = require('express').Router()
const userService = require('../services/userService')
router.post('/language', async (req, res) => {
const {
body: { language },
user: { userId },
} = req
if (!['fi', 'sv', 'en'].includes(language)) {
return res.status(400).json('invalid language')
}
try {
const result = await userService.updateUser(userId, { language })
return res.status(200).json(result)
} catch (e) {
return res.status(e.response.status).json(e.response.data)
}
})
module.exports = router
@@ -1,4 +1,5 @@
const router = require('express').Router()
const { getLanguageCenterData } = require('../services/languageCenterData')
router.get('/', async (req, res) => {
+2 -1
View File
@@ -1,7 +1,8 @@
const router = require('express').Router()
const { ApplicationError } = require('../util/customErrors')
const _ = require('lodash')
const { ApplicationError } = require('../util/customErrors')
router.get('/login', async (req, res) => {
const { user, logoutUrl } = req
+61 -41
View File
@@ -1,17 +1,17 @@
const crypto = require('crypto')
const Sentry = require('@sentry/node')
const crypto = require('crypto')
const router = require('express').Router()
const _ = require('lodash')
const populationService = require('../services/populations')
const studentService = require('../services/students')
const StudyrightService = require('../services/studyrights')
const CourseService = require('../services/courses')
const StatMergeService = require('../services/statMerger')
const { mapToProviders, getFullStudyProgrammeRights, hasFullAccessToStudentData } = require('../util/utils')
const { encrypt, decrypt } = require('../services/encrypt')
const { mapCodesToIds } = require('../services/studyprogramme/studyprogrammeHelpers')
const courseService = require('../services/courses')
const { encrypt, decrypt } = require('../services/encrypt')
const populationService = require('../services/populations')
const statMergeService = require('../services/statMerger')
const studentService = require('../services/students')
const { mapCodesToIds } = require('../services/studyprogramme/studyprogrammeHelpers')
const studyrightService = require('../services/studyrights')
const { ApplicationError } = require('../util/customErrors')
const { getFullStudyProgrammeRights, hasFullAccessToStudentData, mapToProviders } = require('../util/utils')
const filterPersonalTags = (population, userId) => {
return {
@@ -27,9 +27,9 @@ const filterPersonalTags = (population, userId) => {
router.post('/v2/populationstatistics/courses', async (req, res) => {
if (!req.body.year || !req.body.semesters || !req.body.studyRights) {
Sentry.captureException(new Error('The body should have a year, semester and study rights defined'))
res.status(400).json({ error: 'The body should have a year, semester and study rights defined' })
return
return res.status(400).json({ error: 'The body should have a year, semester and study rights defined' })
}
if (!Array.isArray(req.body.studyRights)) {
// studyRights should always be an array
req.body.studyRights = [req.body.studyRights]
@@ -41,6 +41,27 @@ router.post('/v2/populationstatistics/courses', async (req, res) => {
const encrypted = req.body.selectedStudents[0]?.encryptedData
if (
!encrypted &&
!hasFullAccessToStudentData(req.user.roles) &&
req.body.selectedStudents.some(student => !req.user.studentsUserCanAccess.includes(student))
) {
return res.status(403).json({ error: 'Trying to request unauthorized students data' })
}
if (!req.body.semesters.every(semester => semester === 'FALL' || semester === 'SPRING')) {
return res.status(400).json({ error: 'Semester should be either SPRING OR FALL' })
}
if (
req.body.studentStatuses &&
!req.body.studentStatuses.every(
status => status === 'EXCHANGE' || status === 'NONDEGREE' || status === 'TRANSFERRED'
)
) {
return res.status(400).json({ error: 'Student status should be either EXCHANGE or NONDEGREE or TRANSFERRED' })
}
if (req.body.years) {
const upperYearBound = new Date().getFullYear() + 1
const multicoursestatPromises = Promise.all(
@@ -63,26 +84,23 @@ router.post('/v2/populationstatistics/courses', async (req, res) => {
})
)
const multicoursestats = await multicoursestatPromises
const result = StatMergeService.populationCourseStatsMerger(multicoursestats)
const result = statMergeService.populationCourseStatsMerger(multicoursestats)
if (result.error) {
res.status(400).json(result)
return
return res.status(400).json(result)
}
res.json(result)
} else {
if (encrypted) req.body.selectedStudents = req.body.selectedStudents.map(decrypt)
const result = await populationService.bottlenecksOf(req.body, null, encrypted)
if (result.error) {
Sentry.captureException(new Error(result.error))
res.status(400).json(result)
return
}
res.json(result)
return res.json(result)
}
if (encrypted) req.body.selectedStudents = req.body.selectedStudents.map(decrypt)
const result = await populationService.bottlenecksOf(req.body, null, encrypted)
if (result.error) {
Sentry.captureException(new Error(result.error))
return res.status(400).json(result)
}
return res.json(result)
})
router.post('/v2/populationstatistics/coursesbytag', async (req, res) => {
@@ -203,7 +221,7 @@ router.get('/v3/populationstatistics', async (req, res) => {
)
const multipopulationstudents = await multipopulationstudentPromises
const result = StatMergeService.populationStudentsMerger(multipopulationstudents)
const result = statMergeService.populationStudentsMerger(multipopulationstudents)
if (result.error) {
Sentry.captureException(new Error(result.error))
@@ -231,18 +249,20 @@ router.get('/v3/populationstatistics', async (req, res) => {
!fullProgrammeRights.includes(studyRights.combinedProgramme)
) {
result.students = result.students.map(student => {
const { iv, encryptedData } = encrypt(student.studentNumber)
const obfuscatedBirthDate = new Date(new Date(student.birthdate).setMonth(0, 0)) // only birth year for age distribution
const { iv, encryptedData: studentNumber } = encrypt(student.studentNumber)
const obfuscatedBirthDate = new Date(Date.UTC(new Date(student.birthdate).getUTCFullYear(), 0, 1)) // correct year for age distribution calculation but the date is always January 1st
return {
...student,
firstnames: '',
lastname: '',
phone_number: '',
started: null,
phoneNumber: '',
iv,
studentNumber: encryptedData,
studentNumber,
name: '',
email: '',
secondaryEmail: '',
sis_person_id: '',
enrollments: student.enrollments?.map(enrollment => ({ ...enrollment, studentnumber: studentNumber })),
tags: [],
birthdate: obfuscatedBirthDate,
obfuscated: true,
@@ -265,7 +285,7 @@ router.get('/v3/populationstatisticsbycourse', async (req, res) => {
return res.status(400).json({ error: 'The body should have a yearcode and coursecode defined' })
}
const maxYearsToCreatePopulationFrom = await CourseService.maxYearsToCreatePopulationFrom(JSON.parse(coursecodes))
const maxYearsToCreatePopulationFrom = await courseService.maxYearsToCreatePopulationFrom(JSON.parse(coursecodes))
const toFromDiff = Math.abs(to - from + 1)
// 2 semesters = 1 year
const requestedYearsToCreatePopulationFrom = Math.ceil(separate ? toFromDiff / 2 : toFromDiff)
@@ -294,7 +314,7 @@ router.get('/v3/populationstatisticsbycourse', async (req, res) => {
)
let courseproviders = []
if (!hasFullAccessToStudentData(roles)) {
courseproviders = await CourseService.getCourseProvidersForCourses(JSON.parse(coursecodes))
courseproviders = await courseService.getCourseProvidersForCourses(JSON.parse(coursecodes))
}
const fullStudyProgrammeRights = getFullStudyProgrammeRights(programmeRights)
const rightsMappedToProviders = mapToProviders(fullStudyProgrammeRights)
@@ -372,7 +392,7 @@ router.get('/v3/populationstatistics/studyprogrammes', async (req, res) => {
user: { roles, programmeRights },
} = req
if (hasFullAccessToStudentData(roles)) {
const studyrights = await StudyrightService.getAssociations()
const studyrights = await studyrightService.getAssociations()
mapCodesToIds(studyrights.programmes)
res.json(studyrights)
} else {
@@ -380,28 +400,28 @@ router.get('/v3/populationstatistics/studyprogrammes', async (req, res) => {
// For combined programme
// If more programmes are combined, then a function might be a better idea to add moar rights
if (allRights.includes('KH90_001') || allRights.includes('MH90_001')) allRights.push('KH90_001', 'MH90_001')
const studyrights = await StudyrightService.getFilteredAssociations(allRights)
const studyrights = await studyrightService.getFilteredAssociations(allRights)
mapCodesToIds(studyrights.programmes)
res.json(studyrights)
}
})
router.get('/v3/populationstatistics/studyprogrammes/unfiltered', async (req, res) => {
const studyrights = await StudyrightService.getAssociations()
const studyrights = await studyrightService.getAssociations()
res.json(studyrights)
})
router.get('/v3/populationstatistics/maxYearsToCreatePopulationFrom', async (req, res) => {
const { courseCodes } = req.query
const maxYearsToCreatePopulationFromOpen = await CourseService.maxYearsToCreatePopulationFrom(
const maxYearsToCreatePopulationFromOpen = await courseService.maxYearsToCreatePopulationFrom(
JSON.parse(courseCodes),
'openStats'
)
const maxYearsToCreatePopulationFromUni = await CourseService.maxYearsToCreatePopulationFrom(
const maxYearsToCreatePopulationFromUni = await courseService.maxYearsToCreatePopulationFrom(
JSON.parse(courseCodes),
'regularStats'
)
const maxYearsToCreatePopulationFromBoth = await CourseService.maxYearsToCreatePopulationFrom(
const maxYearsToCreatePopulationFromBoth = await courseService.maxYearsToCreatePopulationFrom(
JSON.parse(courseCodes),
'unifyStats'
)
@@ -1,4 +1,5 @@
const router = require('express').Router()
const { getCoursesAndModules, getCurriculumVersions } = require('../services/programmeModules')
router.get('/v3/curriculum-options/:code', async (req, res) => {
@@ -9,7 +10,7 @@ router.get('/v3/curriculum-options/:code', async (req, res) => {
router.get('/v3/curriculum/:code/:periodIds', async (req, res) => {
const { code, periodIds } = req.params
const result = await getCoursesAndModules(code, periodIds.replace(' ', '').split(','))
const result = await getCoursesAndModules(code, periodIds)
res.json({
defaultProgrammeCourses: result.defaultProgrammeCourses.courses,
defaultProgrammeModules: result.defaultProgrammeCourses.modules,
+1
View File
@@ -1,4 +1,5 @@
const router = require('express').Router()
const { getAllProviders } = require('../services/providers')
router.get('/providers', async (_req, res) => {
+1
View File
@@ -1,4 +1,5 @@
const router = require('express').Router()
const { getSemestersAndYears } = require('../services/semesters')
router.get('/semesters/codes', async (req, res) => {
@@ -1,6 +1,7 @@
const router = require('express').Router()
const { changeGroupTags, getAllGroupsAndStudents } = require('../services/studyGuidanceGroups')
const { logger } = require('../util/logger')
const { getAllGroupsAndStudents, changeGroupTags } = require('../services/studyGuidanceGroups')
router.get('/', async (req, res) => {
const {
+12 -11
View File
@@ -1,12 +1,6 @@
const router = require('express').Router()
const { getBasicStatsForStudytrack } = require('../services/studyprogramme/studyprogrammeBasics')
const { getCreditsProduced } = require('../services/providerCredits')
const { getGraduationStatsForStudytrack } = require('../services/studyprogramme/studyprogrammeGraduations')
const {
getStudyprogrammeCoursesForStudytrack,
getStudyprogrammeStatsForColorizedCoursesTable,
} = require('../services/studyprogramme/studyprogrammeCourses')
const { getStudytrackStatsForStudyprogramme } = require('../services/studyprogramme/studytrackStats')
const { ElementDetail } = require('../models')
const {
getBasicStats,
setBasicStats,
@@ -15,11 +9,18 @@ const {
getStudytrackStats,
setStudytrackStats,
} = require('../services/analyticsService')
const { updateBasicView, updateStudytrackView } = require('../services/studyprogramme/studyprogrammeUpdates')
const { getCreditsProduced } = require('../services/providerCredits')
const { getProgrammeName } = require('../services/studyprogramme')
const logger = require('../util/logger')
const { getBasicStatsForStudytrack } = require('../services/studyprogramme/studyprogrammeBasics')
const {
getStudyprogrammeCoursesForStudytrack,
getStudyprogrammeStatsForColorizedCoursesTable,
} = require('../services/studyprogramme/studyprogrammeCourses')
const { getGraduationStatsForStudytrack } = require('../services/studyprogramme/studyprogrammeGraduations')
const { updateBasicView, updateStudytrackView } = require('../services/studyprogramme/studyprogrammeUpdates')
const { getStudytrackStatsForStudyprogramme } = require('../services/studyprogramme/studytrackStats')
const { getAssociations } = require('../services/studyrights')
const { ElementDetail } = require('../models')
const logger = require('../util/logger')
// For grafana statistics (idea stolen from Norppa)
const logInfoForGrafana = async (code, combinedProgramme) => {
@@ -1,11 +1,11 @@
const router = require('express').Router()
const {
getCriteria,
saveYearlyCourseCriteria,
saveYearlyCreditCriteria,
} = require('../services/studyprogramme/studyProgrammeCriteria')
const router = require('express').Router()
router.get('/', async (req, res) => {
const studyProgramme = req.query?.programmecode
if (studyProgramme !== '' && !studyProgramme) return res.status(422).end()
@@ -1,11 +1,11 @@
const router = require('express').Router()
const {
createStudyProgrammePin,
getStudyProgrammePins,
removeStudyProgrammePin,
} = require('../services/studyProgrammePins')
const router = require('express').Router()
router.get('/', async (req, res) => {
const userId = req.user.id
const result = await getStudyProgrammePins(userId)
+1 -1
View File
@@ -1,8 +1,8 @@
const router = require('express').Router()
const { difference } = require('lodash')
const Tags = require('../services/tags')
const Students = require('../services/students')
const Tags = require('../services/tags')
const { getFullStudyProgrammeRights } = require('../util/utils')
const filterRelevantTags = (tags, userId) => {
+4 -3
View File
@@ -1,8 +1,9 @@
const router = require('express').Router()
const { isFaculty, providersOfFaculty } = require('../services/organisations')
const teachers = require('../services/teachers')
const topteachers = require('../services/topteachers')
const { isFaculty, providersOfFaculty } = require('../services/organisations')
const router = require('express').Router()
const { mapToProviders, getFullStudyProgrammeRights } = require('../util/utils')
const { getFullStudyProgrammeRights, mapToProviders } = require('../util/utils')
router.get('/', async (req, res) => {
const { searchTerm } = req.query
+2 -2
View File
@@ -1,7 +1,7 @@
const router = require('express').Router()
const { getFacultyList } = require('../services/faculty/facultyHelpers')
const { getGraduationStats, getFacultyProgressStats } = require('../services/faculty/facultyService')
const { getFacultyList } = require('../services/faculty/facultyHelpers')
const { getFacultyProgressStats, getGraduationStats } = require('../services/faculty/facultyService')
const { getMedian } = require('../services/studyprogramme/studyprogrammeHelpers')
const degreeNames = ['bachelor', 'bachelorMaster', 'master', 'licentiate', 'doctor']
+17 -11
View File
@@ -1,16 +1,16 @@
const router = require('express').Router()
const { refreshFaculties, refreshProgrammes } = require('../events')
const {
updateSISMetadata,
updateSISStudents,
updateSISProgrammes,
updateSISRedisCache,
updateStudentsByStudentNumber,
abort,
updateCoursesByCourseCode,
updateSISMetadata,
updateSISProgrammes,
updateSISRedisCache,
updateSISStudents,
updateStudentsByStudentNumber,
updateStudentsIndividually,
} = require('../services/sisUpdaterService')
const logger = require('../util/logger')
const { jobMaker, getJobs } = require('../worker/queue')
@@ -75,7 +75,7 @@ router.get('/update/v2/programmes', async (_req, res) => {
})
router.get('/refresh_redis_cache', async (req, res) => {
logger.info(`${req.user.userId} requested refresh of redis cache`)
logger.info(`${req.user.username} requested refresh of redis cache`)
const response = await updateSISRedisCache()
if (response) {
res.status(200).json('Refreshing SIS redis cache scheduled')
@@ -83,29 +83,35 @@ router.get('/refresh_redis_cache', async (req, res) => {
})
router.post('/refresh_statistic_v2', async (req, res) => {
logger.info(`${req.user.userId} requested refresh of statistics`)
logger.info(`${req.user.username} requested refresh of statistics`)
jobMaker.statistics()
res.status(200).json('Teacher and study programme statistics refreshed')
})
router.post('/refresh_study_programmes_v2', async (req, res) => {
logger.info(`${req.user.userId} requested refresh of study programmes`)
logger.info(`${req.user.username} requested refresh of study programmes`)
refreshProgrammes()
res.status(200).json('Added job for refreshing study programme overviews')
})
router.post('/refresh_faculties_v2', async (req, res) => {
logger.info(`${req.user.userId} requested refresh of faculties`)
logger.info(`${req.user.username} requested refresh of faculties`)
refreshFaculties()
res.status(200).json('Added job for refreshing faculties')
})
router.post('/refresh_language_center_data', async (req, res) => {
logger.info(`${req.user.userId} requested refresh of language center data`)
logger.info(`${req.user.username} requested refresh of language center data`)
jobMaker.languagecenter()
res.status(200).json('Added job for refreshing language center data')
})
router.post('/refresh-close-to-graduation', async (req, res) => {
logger.info(`${req.user.username} requested refresh of close to graduation data`)
jobMaker.closeToGraduation()
res.status(200).json('Added job for refreshing close to graduation data')
})
router.get('/abort', async (_req, res) => {
await abort()
res.status(200).json()
+35 -17
View File
@@ -1,19 +1,20 @@
const router = require('express').Router()
const userService = require('../services/userService')
const auth = require('../middleware/auth')
const { sendNotificationAboutAccessToUser, previewNotificationAboutAccessToUser } = require('../services/mailservice')
const userService = require('../services/userService')
const logger = require('../util/logger')
router.get('/', async (_req, res) => {
router.get('/', auth.roles(['admin']), async (_req, res) => {
const results = await userService.findAll()
res.json(results)
})
router.get('/access_groups', async (_req, res) => {
const result = await userService.getAccessGroups()
res.json(result)
router.get('/access_groups', auth.roles(['admin']), async (_req, res) => {
res.json(userService.roles)
})
router.get('/:uid', async (req, res) => {
router.get('/:uid', auth.roles(['admin']), async (req, res) => {
try {
const { uid } = req.params
const user = await userService.findOne(uid)
@@ -23,21 +24,22 @@ router.get('/:uid', async (req, res) => {
}
})
router.post('/modifyaccess', async (req, res) => {
router.post('/modifyaccess', auth.roles(['admin']), async (req, res) => {
const { username, accessgroups } = req.body
try {
const result = await userService.modifyAccess(req.body)
res.status(200).json(result)
await userService.modifyAccess(username, accessgroups)
res.status(204).end()
} catch (e) {
res.status(400).json(e)
}
})
router.get('/email/preview', (_req, res) => {
router.get('/email/preview', auth.roles(['admin']), (_req, res) => {
const { accessMessageSubject, accessMessageText } = previewNotificationAboutAccessToUser()
res.json({ subject: accessMessageSubject, html: accessMessageText })
})
router.post('/email', async (req, res) => {
router.post('/email', auth.roles(['admin']), async (req, res) => {
const userEmail = req.body.email
if (!userEmail) {
return res.status(400).json({ error: 'email is missing' })
@@ -51,18 +53,34 @@ router.post('/email', async (req, res) => {
res.status(200).end()
})
router.post('/:uid/elements', async (req, res) => {
router.post('/:uid/elements', auth.roles(['admin']), async (req, res) => {
const { uid } = req.params
const { codes } = req.body
const user = await userService.enableElementDetails(uid, codes)
res.json(user)
await userService.modifyElementDetails(uid, codes, true)
res.status(204).end()
})
router.delete('/:uid/elements', async (req, res) => {
router.delete('/:uid/elements', auth.roles(['admin']), async (req, res) => {
const { uid } = req.params
const { codes } = req.body
const user = await userService.removeElementDetails(uid, codes)
res.json(user)
await userService.modifyElementDetails(uid, codes, false)
res.status(204).end()
})
router.post('/language', async (req, res) => {
const {
body: { language },
user: { username },
} = req
if (!['fi', 'sv', 'en'].includes(language)) {
return res.status(400).json('invalid language')
}
try {
await userService.updateUser(username, { language })
return res.status(204).end()
} catch (e) {
return res.status(500).json({ error: e.message })
}
})
module.exports = router
@@ -1,6 +1,7 @@
const { redisClient } = require('./redis')
const moment = require('moment')
const { redisClient } = require('./redis')
// Only new bachelor, masters and doctoral programmes get their data updated in redis every night, use redis for them
const isUpdatedNewProgramme = code => code.includes('KH') || code.includes('MH') || /^(T)[0-9]{6}$/.test(code)
const isFaculty = code =>
@@ -1,6 +1,7 @@
const { Credit, Enrollment, Student, Course } = require('../models')
const { Op, where, fn, col } = require('sequelize')
const { Credit, Enrollment, Student, Course } = require('../models')
const getCompletedCourses = async (studentNumbers, courseCodes) => {
let courses = await Course.findAll({
attributes: ['code', 'name', 'substitutions'],
@@ -1,4 +1,5 @@
const { Op } = require('sequelize')
const { Course } = require('../../models')
const { getSortRank } = require('../../util/utils')
@@ -1,4 +1,5 @@
const { Op } = require('sequelize')
const {
Credit,
Student,
@@ -1,16 +1,16 @@
const { Op } = require('sequelize')
const { Credit, Course, Organization, Enrollment } = require('../../models')
const { parseCredit } = require('./parseCredits')
const { parseEnrollment } = require('./parseEnrollments')
const { CourseYearlyStatsCounter } = require('./course_yearly_stats_counter')
const { sortMainCode } = require('../../util/utils')
const { byNameAndOrCodeLike, byCodes } = require('./courseFinders')
const {
enrollmentsForCourses,
creditsForCourses,
getStudentNumberToSrElementsMap,
} = require('./creditsAndEnrollmentsOfCourse')
const { byNameAndOrCodeLike, byCodes } = require('./courseFinders')
const { parseCredit } = require('./parseCredits')
const { parseEnrollment } = require('./parseEnrollments')
const { Credit, Course, Organization, Enrollment } = require('../../models')
const { sortMainCode } = require('../../util/utils')
const isOpenUniCourseCode = code => code.match(/^AY?(.+?)(?:en|fi|sv)?$/)
@@ -1,4 +1,5 @@
const crypto = require('crypto')
const { Credit } = require('../../models')
const formatStudyrightElement = ({ code, element_detail, startdate, studyright: sr }) => {
@@ -1,4 +1,5 @@
const crypto = require('crypto')
const { formatStudyrightElement } = require('./parseCredits')
const parseEnrollment = (enrollment, anonymizationSalt, studentNumberToSrElementsMap) => {
+1
View File
@@ -1,4 +1,5 @@
const { createCipheriv, createDecipheriv, randomBytes } = require('crypto')
const { CRYPT_KEY } = require('../conf-backend')
const algorithm = 'aes-256-cbc'
@@ -1,4 +1,5 @@
const { Op } = require('sequelize')
const { ExcludedCourse } = require('../models/models_kone')
const addExcludedCourses = async (programmecode, coursecodes, curriculum) => {
@@ -1,4 +1,19 @@
const { uniqBy } = require('lodash')
const { Op } = require('sequelize')
const {
facultyFormatStudyright,
facultyFormatProgramme,
formatFacultyTransfer,
formatFacultyThesisWriter,
formatOrganization,
isNewProgramme,
mapCodesToIds,
facultyProgrammeStudents,
} = require('./facultyHelpers')
const {
dbConnections: { sequelize },
} = require('../../database/connection')
const {
ElementDetail,
Organization,
@@ -11,20 +26,6 @@ const {
Credit,
Course,
} = require('../../models')
const {
facultyFormatStudyright,
facultyFormatProgramme,
formatFacultyTransfer,
formatFacultyThesisWriter,
formatOrganization,
isNewProgramme,
mapCodesToIds,
facultyProgrammeStudents,
} = require('./facultyHelpers')
const { uniqBy } = require('lodash')
const {
dbConnections: { sequelize },
} = require('../../database/connection')
const transferredFaculty = async (programmeCodeIn, programmeCodeOut, start, end) =>
(
@@ -1,5 +1,6 @@
const { indexOf, isArray } = require('lodash')
const moment = require('moment')
const {
startedStudyrights,
graduatedStudyrights,
@@ -11,9 +12,9 @@ const {
studyrightsByRightStartYear,
getTransfersIn,
} = require('./faculty')
const { getStatsBasis, getYearsArray, defineYear } = require('../studyprogramme/studyprogrammeHelpers')
const { findRightProgramme, isNewProgramme, checkTransfers, getExtentFilter } = require('./facultyHelpers')
const { codes } = require('../../../config/programmeCodes')
const { getStatsBasis, getYearsArray, defineYear } = require('../studyprogramme/studyprogrammeHelpers')
const filterDuplicateStudyrights = studyrights => {
// bachelor+master students have two studyrights (separated by two last digits in studyrightid)
@@ -1,5 +1,5 @@
const { getCreditsProduced } = require('../providerCredits')
const { getProgrammes } = require('./facultyService')
const { getCreditsProduced } = require('../providerCredits')
/*
Returns credits produced by the programmes of the faculty but also the credits where the provider is the faculty itself.
@@ -1,10 +1,11 @@
const moment = require('moment')
const { graduatedStudyrights, studyrightsByRightStartYear, hasMasterRight } = require('./faculty')
const { findRightProgramme, isNewProgramme } = require('./facultyHelpers')
const { getYearsArray, getYearsObject, getMedian, defineYear } = require('../studyprogramme/studyprogrammeHelpers')
const { getProgrammes } = require('./facultyService')
const { codes } = require('../../../config/programmeCodes')
const { bachelorStudyright, countTimeCategories, getStatutoryAbsences } = require('../graduationHelpers')
const { getProgrammes } = require('./facultyService')
const { getYearsArray, getYearsObject, getMedian, defineYear } = require('../studyprogramme/studyprogrammeHelpers')
const sortProgrammes = data => {
const check = name => {
@@ -1,4 +1,5 @@
const { Op } = require('sequelize')
const { codes } = require('../../../config/programmeCodes')
const { faculties } = require('../organisations')
@@ -1,6 +1,7 @@
const { redisClient } = require('../redis')
const moment = require('moment')
const { findFacultyProgrammeCodes } = require('./faculty')
const { redisClient } = require('../redis')
const createRedisKeyForFacultyProgrammes = (id, programmeFilter) => `FACULTY_PROGRAMMES_${id}_${programmeFilter}`
const createRedisKeyForBasicStats = (id, yearType, programmeFilter, specialGroups) =>
@@ -1,10 +1,11 @@
const { indexOf, inRange } = require('lodash')
const moment = require('moment')
const { getAcademicYearDates } = require('../../util/semester')
const { getCreditThresholds, getYearsArray } = require('../studyprogramme/studyprogrammeHelpers')
const { studytrackStudents } = require('../studyprogramme/studentGetters')
const { getStudyRightsByExtent, getStudyRightsByBachelorStart, getTransfersIn, getTransfersOut } = require('./faculty')
const { checkTransfers } = require('./facultyHelpers')
const { getAcademicYearDates } = require('../../util/semester')
const { studytrackStudents } = require('../studyprogramme/studentGetters')
const { getCreditThresholds, getYearsArray } = require('../studyprogramme/studyprogrammeHelpers')
const getStudentData = (
startDate,
@@ -1,3 +1,7 @@
const { getStudyRightsByExtent, getStudentsByStudentnumbers, getTransfersIn } = require('./faculty')
const { checkTransfers } = require('./facultyHelpers')
const { getAcademicYearDates } = require('../../util/semester')
const { enrolledStudents, absentStudents } = require('../studyprogramme/studentGetters')
const {
getStartDate,
getYearsArray,
@@ -5,11 +9,7 @@ const {
getYearsObject,
tableTitles,
} = require('../studyprogramme/studyprogrammeHelpers')
const { getAcademicYearDates } = require('../../util/semester')
const { getStudyRightsByExtent, getStudentsByStudentnumbers, getTransfersIn } = require('./faculty')
const { checkTransfers } = require('./facultyHelpers')
const { inactiveStudyrights, graduatedStudyRights } = require('../studyprogramme/studyrightFinders')
const { enrolledStudents, absentStudents } = require('../studyprogramme/studentGetters')
const emptyTotals = () => {
return {
@@ -1,6 +1,7 @@
const { indexOf } = require('lodash')
const { mapToProviders } = require('../../util/utils')
const { thesisWriters } = require('./faculty')
const { mapToProviders } = require('../../util/utils')
const {
getStatsBasis,
getYearsArray,
@@ -1,7 +1,5 @@
const logger = require('../../util/logger')
const { findFacultyProgrammeCodes } = require('./faculty')
const { combineFacultyBasics } = require('./facultyBasics')
const { combineFacultyThesisWriters } = require('./facultyThesisWriters')
const { countGraduationTimes } = require('./facultyGraduationTimes')
const {
setFacultyProgrammes,
@@ -13,8 +11,10 @@ const {
} = require('./facultyService')
const { combineFacultyStudentProgress } = require('./facultyStudentProgress')
const { combineFacultyStudents } = require('./facultyStudents')
const { computeCreditsProduced } = require('../providerCredits')
const { combineFacultyThesisWriters } = require('./facultyThesisWriters')
const logger = require('../../util/logger')
const { setCreditStats } = require('../analyticsService')
const { computeCreditsProduced } = require('../providerCredits')
const updateFacultyOverview = async (faculty, statsType) => {
const calendarNewSpecial = {
@@ -1,5 +1,6 @@
const { Op } = require('sequelize')
const moment = require('moment')
const { Op } = require('sequelize')
const { Semester, SemesterEnrollment, Studyright } = require('../models')
// categorize graduation times
@@ -1,7 +1,8 @@
const { Course, Credit, Enrollment, StudyrightElement, Studyright } = require('../models')
const { Op } = require('sequelize')
const { redisClient } = require('./redis')
const { orderBy } = require('lodash')
const { Op } = require('sequelize')
const { redisClient } = require('./redis')
const { Course, Credit, Enrollment, StudyrightElement, Studyright } = require('../models')
const LANGUAGE_CENTER_REDIS_KEY = 'LANGUAGE_CENTER_DATA'
@@ -1,4 +1,5 @@
const axios = require('axios')
const { isProduction, pateToken } = require('../conf-backend')
const { ApplicationError } = require('../util/customErrors')
@@ -1,10 +1,10 @@
const logger = require('../../util/logger')
const {
getOpenUniSearchesByUser,
createOpenUniPopulationSearch,
updateOpenUniPopulationSearch,
deleteOpenUniSearch,
} = require('./openUniSearches')
const logger = require('../../util/logger')
const getOpenUniSearches = async userId => {
const savedSearches = await getOpenUniSearchesByUser(userId)
@@ -1,5 +1,5 @@
const { Credit, Enrollment, Studyright, Student, Course } = require('../../models')
const { OpenUniPopulationSearch } = require('../../models/models_kone')
const { Op } = require('sequelize')
const {
mapOpenCredits,
mapOpenEnrollments,
@@ -7,7 +7,8 @@ const {
mapStudyRights,
mapCourseInfo,
} = require('./openUniHelpers')
const { Op } = require('sequelize')
const { Credit, Enrollment, Studyright, Student, Course } = require('../../models')
const { OpenUniPopulationSearch } = require('../../models/models_kone')
const getCredits = async (courseCodes, startdate) =>
(

Some files were not shown because too many files have changed in this diff Show More