[META] Remove apparent service provider specific code

This commit is contained in:
Santeri Rajamäki
2026-02-27 16:36:24 +02:00
committed by Aaro Koivukangas
parent 4ed8b3d2e9
commit c2684a4e8e
49 changed files with 155 additions and 724 deletions
-3
View File
@@ -65,9 +65,6 @@ instrumented/
.vscode/launch.json
.vscode/terminals.json
# Funidata specific
environments/
# Target of shared symlink
services/**/src/shared
-47
View File
@@ -1,47 +0,0 @@
services:
oodikone-backend:
build:
context: ${OODIKONE_REPO:-.}/services
dockerfile: backend/Dockerfile
args:
SENTRY_ENVIRONMENT: ''
SENTRY_RELEASE: ''
SENTRY_DSN: ''
SERVICE_PROVIDER: fd
NODE_ENV: production
image: ${DOCKER_OODIKONE_NAMESPACE:-637423527834.dkr.ecr.eu-north-1.amazonaws.com/oodikone}/oodikone-backend:${DOCKER_IMAGE_TAG:-dev}
oodikone-frontend:
build:
context: ${OODIKONE_REPO:-.}/services
dockerfile: frontend/Dockerfile
args:
SENTRY_ENVIRONMENT: ''
SENTRY_RELEASE: ''
SENTRY_DSN: ''
SERVICE_PROVIDER: fd
LANGUAGE_CENTER_VIEW_ENABLED: 'false' # currently no intention to be enabled
SIS_URL: 'https://sis-qa.funidata.fi'
NODE_ENV: production
image: ${DOCKER_OODIKONE_NAMESPACE:-637423527834.dkr.ecr.eu-north-1.amazonaws.com/oodikone}/oodikone-frontend:${DOCKER_IMAGE_TAG:-dev}
updater-scheduler:
build:
context: ${OODIKONE_REPO:-.}/updater/sis-updater-scheduler
args:
SENTRY_ENVIRONMENT: ''
SENTRY_RELEASE: ''
SENTRY_DSN: ''
NODE_ENV: production
image: ${DOCKER_OODIKONE_NAMESPACE:-637423527834.dkr.ecr.eu-north-1.amazonaws.com/oodikone}/updater-scheduler:${DOCKER_IMAGE_TAG:-dev}
updater-worker:
build:
context: ${OODIKONE_REPO:-.}/updater/sis-updater-worker
args:
SENTRY_ENVIRONMENT: ''
SENTRY_RELEASE: ''
SENTRY_DSN: ''
SERVICE_PROVIDER: fd
NODE_ENV: production
image: ${DOCKER_OODIKONE_NAMESPACE:-637423527834.dkr.ecr.eu-north-1.amazonaws.com/oodikone}/updater-worker:${DOCKER_IMAGE_TAG:-dev}
-190
View File
@@ -1,190 +0,0 @@
x-defaults: &defaults
restart: unless-stopped
x-build_defaults: &build_defaults
dockerfile: dev.Dockerfile
services:
adminer:
<<: *defaults
container_name: adminer
environment:
ADMINER_DESIGN: pepa-linha
image: adminer:4.8.1
ports:
- 5050:8080
volumes:
- ./adminer-plugins:/var/www/html/plugins-enabled
backend:
<<: *defaults
build:
<<: *build_defaults
context: services/backend
depends_on:
- kone-db
- redis
- sis-db
- user-db
env_file:
- .env
environment:
DB_URL_KONE: postgres://postgres@kone-db:5432/kone-db
DB_URL_USER: postgres://postgres@user-db:5432/user-db
FRONT_URL: http://localhost:3000
REDIS: redis
SECRET_TOKEN: dev
SIS_DB_URL: postgres://postgres@sis-db:5432/sis-db
SIS_UPDATER_URL: http://sis-updater-scheduler:8082
IMPORTER_DB_API_URL: 'https://localhost:3005'
JAMI_URL: 'http://jami:3003/'
SERVICE_PROVIDER: fd
SIS_HOST: ${SIS_HOST}
SIS_GRAPHQL_APPLICATION_ACCOUNT: ${SIS_GRAPHQL_APPLICATION_ACCOUNT}
SIS_GRAPHQL_APPLICATION_KEY: ${SIS_GRAPHQL_APPLICATION_KEY}
IMPORTER_DB_API_USER: dev_user
IMPORTER_DB_API_PASSWORD: dev_password
ROOT_ORG_ID: ${ROOT_ORG_ID}
LANGUAGE_CENTER_VIEW_ENABLED: 'false'
ports:
- 8080:8080
volumes:
- backend-nodemod:/opt/app-root/backend/node_modules
- ./environments/testu:/opt/app-root/backend/src/environment
- ./services/backend/:/opt/app-root/backend
- ./services/shared/:/opt/app-root/shared
frontend:
<<: *defaults
build:
<<: *build_defaults
context: services/frontend
container_name: frontend
ports:
- 3000:3000
environment:
REACT_APP_SERVICE_PROVIDER: fd
REACT_APP_LANGUAGE_CENTER_VIEW_ENABLED: 'false'
REACT_APP_SIS_URL: 'https://sis-qa.funidata.fi'
volumes:
- frontend-nodemod:/opt/app-root/frontend/node_modules
- ./services/frontend/:/opt/app-root/frontend
- ./services/shared/:/opt/app-root/shared
kone-db:
<<: *defaults
container_name: kone-db
environment:
PGDATA: /data
POSTGRES_DB: kone-db
POSTGRES_HOST_AUTH_METHOD: trust
image: postgres:15.10
ports:
- 12349:5432
volumes:
- kone-db-data:/data
redis:
<<: *defaults
command: 'redis-server --appendonly yes'
container_name: redis
image: redis:6.2.16
ports:
- '6379:6379'
volumes:
- redis-data:/data
sis-db:
<<: *defaults
container_name: sis-db
environment:
PGDATA: /data
POSTGRES_DB: sis-db
POSTGRES_HOST_AUTH_METHOD: trust
image: postgres:15.10
volumes:
- sis-db-data:/data
shm_size: 2gb
ports:
- 12345:5432
sis-updater-scheduler:
<<: *defaults
build:
<<: *build_defaults
context: updater/sis-updater-scheduler
container_name: sis-updater-scheduler
extra_hosts:
- 'host.docker.internal:host-gateway'
depends_on:
- redis
environment:
REDIS_HOST: redis
REDIS_PORT: 6379
SECRET_TOKEN: dev
SIS_IMPORTER_HOST: ${SIS_IMPORTER_DB_HOST:-host.docker.internal}
SIS_IMPORTER_PORT: ${SIS_IMPORTER_DB_PORT:-15234}
SIS_IMPORTER_DATABASE: importer-db
SIS_IMPORTER_PASSWORD: dev
SIS_IMPORTER_USER: dev
SERVICE_PROVIDER: fd
ports:
- 8082:8082
volumes:
- ./updater/sis-updater-scheduler/:/opt/app-root/src
sis-updater-worker:
<<: *defaults
deploy:
replicas: 2
mode: replicated
build:
<<: *build_defaults
context: updater/sis-updater-worker
depends_on:
- redis
- sis-db
extra_hosts:
- 'host.docker.internal:host-gateway'
environment:
DB_URL: postgres://postgres@sis-db:5432/sis-db
REDIS_HOST: redis
REDIS_PORT: 6379
SIS_IMPORTER_HOST: ${SIS_IMPORTER_DB_HOST:-host.docker.internal}
SIS_IMPORTER_PORT: ${SIS_IMPORTER_DB_PORT:-15234}
SIS_IMPORTER_DATABASE: importer-db
SIS_IMPORTER_PASSWORD: dev
SIS_IMPORTER_USER: dev
SIS_PASSWORD: postgres
ROOT_ORG_ID: ${ROOT_ORG_ID}
SERVICE_PROVIDER: fd
volumes:
- ./updater/sis-updater-worker/:/opt/app-root/src
user-db:
<<: *defaults
container_name: user-db
environment:
PGDATA: /data
POSTGRES_DB: user-db
POSTGRES_HOST_AUTH_METHOD: trust
image: postgres:15.10
volumes:
- user-db-data:/data
ports:
- 12347:5432
volumes:
backend-nodemod:
frontend-nodemod:
kone-db-data:
redis-data:
sis-db-data:
user-db-data:
networks:
default:
name: oodikone
importer:
name: importer_network
external: true
-6
View File
@@ -33,12 +33,6 @@ export const baseUrl = isDev ? '/api' : ''
export const frontUrl = process.env.FRONT_URL
export const backendPort = 8080
// System run in whose environment
export const serviceProvider = process.env.SERVICE_PROVIDER?.toLowerCase() ?? 'toska'
// Optional logout-url configuration
export const configLogoutUrl = serviceProvider === 'toska' ? undefined : process.env.LOGOUT_URL
// Other stuff
export const { DB_URL_KONE, DB_URL_USER, SECRET_TOKEN, SIS_DB_URL, SIS_UPDATER_URL, CRYPT_KEY } = process.env
+3 -33
View File
@@ -2,9 +2,8 @@ import * as Sentry from '@sentry/node'
import { NextFunction, Request, Response } from 'express'
import { intersection } from 'lodash-es'
import { configLogoutUrl, isDev, requiredGroup, serviceProvider } from '../config'
import { getMockedUser, getMockedUserFd, getOrganizationAccess, getUserFd, getUserToska } from '../services/userService'
import { FormattedUser } from '../types'
import { requiredGroup } from '../config'
import { getMockedUser, getOrganizationAccess, getUserToska } from '../services/userService'
import { ApplicationError } from '../util/customErrors'
import logger from '../util/logger'
@@ -81,33 +80,4 @@ const toskaUserMiddleware = async (req: Request, _: Response, next: NextFunction
next()
}
const fdUserMiddleware = async (req: Request, _res: Response, next: NextFunction) => {
const { remote_user: remoteUser, 'x-show-as-user': showAsUser } = req.headers
if (!remoteUser) {
throw new ApplicationError('Not enough data in request headers, remote_user was missing', 403, { configLogoutUrl })
}
let user: FormattedUser | null
// getMockedUser in production requires the superAdmin role, which is only available
// via iamGroups, so it's now only implemented for the dev environment
if (showAsUser && isDev) {
user = await getMockedUserFd({ userToMock: showAsUser as string, mockedBy: remoteUser as string })
} else {
user = await getUserFd({ username: remoteUser as string })
}
if (!user) {
throw new ApplicationError(`Could not grant access with the eppn ${remoteUser}.`, 403, { configLogoutUrl })
}
req.user = user
req.logoutUrl = configLogoutUrl
next()
}
const currentUserMiddleware = serviceProvider === 'toska' ? toskaUserMiddleware : fdUserMiddleware
export default currentUserMiddleware
export default toskaUserMiddleware
+1 -4
View File
@@ -1,10 +1,7 @@
import headersMiddleware from 'unfuck-utf8-headers-middleware'
import { serviceProvider } from '../config'
const headersToska = ['uid', 'displayName', 'shib-session-id', 'hyGroupCn', 'mail', 'hyPersonSisuId']
const headersOther = ['remote_user']
const headers = serviceProvider === 'toska' ? headersToska : headersOther
const headers = headersToska
export default headersMiddleware(headers)
+3 -9
View File
@@ -3,7 +3,7 @@ import compression from 'compression'
import cors from 'cors'
import { Express, json as jsonExpress } from 'express'
import { frontUrl, languageCenterViewEnabled, serviceProvider } from './config'
import { frontUrl, languageCenterViewEnabled } from './config'
import accessLogger from './middleware/accessLogger'
import * as auth from './middleware/auth'
import currentUserMiddleware from './middleware/currentUser'
@@ -34,8 +34,7 @@ import tags from './routes/tags'
import teachers from './routes/teachers'
import university from './routes/university'
import updater from './routes/updater'
import usersToska from './routes/users'
import usersFd from './routes/usersFd'
import users from './routes/users'
const routes = (app: Express, url: string) => {
app.use(cors({ credentials: true, origin: frontUrl }))
@@ -66,12 +65,7 @@ const routes = (app: Express, url: string) => {
app.use(`${url}/university`, university)
app.use(`${url}/updater`, auth.roles(['admin']), updater)
app.use(`${url}/teachers`, teachers)
if (serviceProvider === 'toska') {
app.use(`${url}/users`, usersToska)
} else {
app.use(`${url}/users`, usersToska)
app.use(`${url}/users`, usersFd)
}
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)
+1 -3
View File
@@ -1,7 +1,6 @@
import { Request, Response, Router } from 'express'
import { mapToProviders, splitByEmptySpace } from '@oodikone/shared/util'
import { serviceProvider } from '../config'
import * as auth from '../middleware/auth'
import { getProvidersOfFaculty, isFaculty } from '../services/organizations'
import { getTeachersBySearchTerm, getTeacherStatistics, getYearlyStatistics } from '../services/teachers'
@@ -12,8 +11,7 @@ const router = Router()
const iamGroupsGivingFullAccess = ['hy-dekaanit', 'hy-varadekaanit-opetus']
const fullAccessAuth = () =>
serviceProvider === 'toska' ? auth.roles([], iamGroupsGivingFullAccess) : auth.roles(['teachers'])
const fullAccessAuth = () => auth.roles([], iamGroupsGivingFullAccess)
interface GetTeachersRequest extends Request {
query: {
+1 -2
View File
@@ -2,7 +2,6 @@ import { Request, Response, Router } from 'express'
import { cloneDeep } from 'lodash-es'
import { Graduated, NameWithCode } from '@oodikone/shared/types'
import { serviceProvider } from '../config'
import { magicFacultyCode } from '../config/organizationConstants'
import { OrganizationModel } from '../models'
import { getDegreeProgrammesOfFaculty } from '../services/faculty/faculty'
@@ -125,7 +124,7 @@ router.get('/allgraduationstats', async (_req: Request, res: Response) => {
const allFaculties = await getSortedFaculties()
const facultyCodes = allFaculties.map(faculty => faculty.code)
const facultyData: Record<string, Awaited<ReturnType<typeof countGraduationTimes>>> = {}
const programmeFilter = serviceProvider === 'toska' ? 'NEW_DEGREE_PROGRAMMES' : 'ALL_PROGRAMMES'
const programmeFilter = 'NEW_DEGREE_PROGRAMMES'
for (const facultyCode of facultyCodes) {
let data: any = await getGraduationStats(facultyCode, programmeFilter, true)
if (!data) {
-68
View File
@@ -1,68 +0,0 @@
import { Request, Response, Router } from 'express'
import { isDev } from '../config'
import * as auth from '../middleware/auth'
import * as userService from '../services/userService'
const router = Router()
interface NewUserEppnRequest extends Request {
params: {
newUserEppn: string
}
}
router.get('/from-sisu-by-eppn/:newUserEppn', auth.roles(['admin']), async (req: NewUserEppnRequest, res: Response) => {
let { username: requesterEppn } = req.user
const { newUserEppn } = req.params
// In order to test this feature in the dev environment we need to set an eppn that demo sisu will recognize
if (isDev && requesterEppn === 'mluukkai') {
requesterEppn = newUserEppn
}
const person = await userService.getUserFromSisuByEppn(requesterEppn, newUserEppn)
res.json(person)
})
interface User {
id: string
first_name: string
last_name: string
call_name: string
eppn: string
email_address: string
student_number: string
phone_number: string
document_state: string
}
interface AddUserRequest extends Request {
body: {
user: User
}
}
interface DeleteUserRequest extends Request {
body: {
userId: string
}
}
router.post('/add', auth.roles(['admin']), async (req: AddUserRequest, res: Response) => {
const { user } = req.body
const oldUser = await userService.findByUsername(user.eppn)
if (oldUser) return res.status(400).json({ error: 'Trying to add a user already present in the user database.' })
const success = await userService.addNewUser(user)
res.json(success)
})
router.delete('/delete', auth.roles(['admin']), async (req: DeleteUserRequest, res: Response) => {
const { userId } = req.body
try {
await userService.deleteUserById(userId)
res.json({ deleted: userId })
} catch (error) {
return res.status(400).json({ error: 'Deletion was unsuccessful with the user id included in the request' })
}
})
export default router
@@ -3,7 +3,6 @@ import { QueryTypes } from 'sequelize'
import { Organization } from '@oodikone/shared/models'
import { ProgrammeModuleWithRelevantAttributes } from '@oodikone/shared/types'
import { serviceProvider } from '../../config'
import { programmeCodes } from '../../config/programmeCodes'
import { dbConnections } from '../../database/connection'
import { OrganizationModel } from '../../models'
@@ -57,9 +56,7 @@ export const getDegreeProgrammesOfOrganization = async (organizationId: string,
const programmesWithProgIds = programmesOfOrganization.map(programme => ({
...programme,
progId:
programme.code in programmeCodes && serviceProvider !== 'fd'
? programmeCodes[programme.code as keyof typeof programmeCodes]
: programme.code,
programme.code in programmeCodes ? programmeCodes[programme.code as keyof typeof programmeCodes] : programme.code,
}))
const programmesGroupedByCode = groupBy(orderBy(programmesWithProgIds, ['valid_from'], ['desc']), prog => prog.code)
const curriculumPeriods = await getCurriculumPeriods()
@@ -3,7 +3,6 @@ import { Op } from 'sequelize'
import { SISStudyRight, SISStudyRightElement } from '@oodikone/shared/models'
import { DegreeProgrammeType, Phase } from '@oodikone/shared/types'
import { serviceProvider } from '../../config'
import { programmeCodes } from '../../config/programmeCodes'
import { ProgrammeModuleModel } from '../../models'
import { getDegreeProgrammeType } from '../../util'
@@ -97,17 +96,11 @@ export const getThesisType = async (studyProgramme: string) => {
const degreeProgrammeType = await getDegreeProgrammeType(studyProgramme)
if (degreeProgrammeType === DegreeProgrammeType.MASTER) {
const mastersThesisTypes = ['urn:code:course-unit-type:masters-thesis']
if (serviceProvider !== 'toska') {
mastersThesisTypes.push('urn:code:course-unit-type:amk-masters-thesis')
}
return mastersThesisTypes
}
if (degreeProgrammeType === DegreeProgrammeType.BACHELOR) {
const bachelorsThesisTypes = ['urn:code:course-unit-type:bachelors-thesis']
if (serviceProvider !== 'toska') {
bachelorsThesisTypes.push('urn:code:course-unit-type:amk-bachelors-thesis')
}
return bachelorsThesisTypes
}
@@ -167,8 +160,7 @@ export const tableTitles = {
} as const
export const getId = (code: string) => {
if (serviceProvider !== 'fd') return code in programmeCodes ? programmeCodes[code as keyof typeof programmeCodes] : ''
return code
return code in programmeCodes ? programmeCodes[code as keyof typeof programmeCodes] : ''
}
export const getGoal = async (programme?: string) => {
+4 -36
View File
@@ -3,7 +3,6 @@ import { LRUCache } from 'lru-cache'
import { User } from '@oodikone/shared/models/user'
import { DetailedProgrammeRights, Role } from '@oodikone/shared/types'
import { serviceProvider } from '../config'
import { roles } from '../config/roles'
import { sequelizeUser } from '../database/connection'
import { UserModel } from '../models/user'
@@ -11,13 +10,12 @@ import { ExpandedUser, FormattedUser, IamAccess } from '../types'
import { createLocaleComparator, getFullStudyProgrammeRights, hasFullAccessToStudentData } from '../util'
import * as jami from '../util/jami'
import mami from '../util/mami'
import { sendNotificationAboutNewUser } from './mailService'
import { getSisuAuthData, personSearchQuery, getGraphqlData } from './oriProvider'
import { getStudentnumbersByElementdetails } from './students'
import { checkStudyGuidanceGroupsAccess, getAllStudentsUserHasInGroups } from './studyGuidanceGroups'
const userAccessUtils = serviceProvider === 'toska' ? jami : mami
const userAccessUtils = jami
const { getAllUserAccess, getUserIams, getUserIamAccess } = userAccessUtils
const courseStatisticsGroup = 'grp-oodikone-basic-users'
@@ -68,7 +66,7 @@ export const updateUser = async (username: string, fields: Partial<User>) => {
userDataCache.delete(username)
}
const getIamBasedRights = (iamAccess: IamAccess): DetailedProgrammeRights[] => {
const getIamBasedRights = (iamAccess: IamAccess | undefined): DetailedProgrammeRights[] => {
if (!iamAccess) {
return []
}
@@ -89,8 +87,8 @@ const getUserProgrammesRights = (userProgrammes: string[]): DetailedProgrammeRig
}
const getStudyProgrammeRights = (
iamAccess: IamAccess,
specialGroup: Record<string, boolean>,
iamAccess: IamAccess | undefined,
specialGroup: Record<string, boolean> | undefined,
userProgrammes: string[]
): DetailedProgrammeRights[] => {
const hasFullSisuAccess = specialGroup?.fullSisuAccess
@@ -233,14 +231,6 @@ export const getMockedUser = async ({ userToMock, mockedBy }: { userToMock: stri
return mockedUser
}
export const getMockedUserFd = async ({ userToMock, mockedBy }: { userToMock: string; mockedBy: string }) => {
const mockedByFromDb = (await UserModel.findOne({ where: { username: mockedBy } }))?.toJSON()
if (!mockedByFromDb) {
return null
}
return await getMockedUser({ userToMock, mockedBy })
}
export const getUserToska = async ({
username,
name,
@@ -283,28 +273,6 @@ export const getUserToska = async ({
return user
}
export const getUserFd = async ({ username }: { username: string }) => {
if (userDataCache.has(username)) {
return userDataCache.get(username) as FormattedUser
}
const userFromDbOrm = await UserModel.findOne({ where: { username } })
if (!userFromDbOrm) {
return null
}
userFromDbOrm.lastLogin = new Date()
await userFromDbOrm.save()
const userFromDb = userFromDbOrm.toJSON()
const detailedProgrammeRights = getStudyProgrammeRights({}, {}, userFromDb.programmeRights)
const user = await formatUser({ ...userFromDb, iamGroups: [], detailedProgrammeRights })
userDataCache.set(username, user)
return user
}
export const addNewUser = async user => {
const name = user.first_name.concat(' ', user.last_name)
try {
+2 -25
View File
@@ -1,37 +1,14 @@
import axios from 'axios'
import { importerDbApiPassword, importerDbApiUser, importerToken, importerUrl, serviceProvider } from '../config'
import { importerToken, importerUrl } from '../config'
import logger from './logger'
const createBasicAuthHeader = () => {
const auth = `${importerDbApiUser}:${importerDbApiPassword}`
const token = Buffer.from(auth).toString('base64')
return {
Authorization: `Basic ${token}`,
}
}
const toskaImporterClient = axios.create({
headers: { token: importerToken },
baseURL: importerUrl,
timeout: 10000,
})
const fdImporterClient = axios.create({
headers: createBasicAuthHeader(),
baseURL: importerUrl,
})
const missingBasicAuthCredentials = !importerDbApiUser || !importerDbApiPassword
const getFdImporterClient = () => {
if (missingBasicAuthCredentials) {
logger.error("Basic auth credentials not set, can't return client!")
return null
}
return fdImporterClient
}
const getToskaImporterClient = () => {
if (!importerToken) {
logger.error("Importer token not set, can't return client!")
@@ -40,4 +17,4 @@ const getToskaImporterClient = () => {
return toskaImporterClient
}
export const getImporterClient = serviceProvider === 'toska' ? getToskaImporterClient : getFdImporterClient
export const getImporterClient = getToskaImporterClient
+10 -12
View File
@@ -1,7 +1,7 @@
import * as Sentry from '@sentry/node'
import axios from 'axios'
import { importerToken, isStaging, jamiUrl, serviceProvider } from '../config'
import { importerToken, isStaging, jamiUrl } from '../config'
import { IamAccess } from '../types'
import logger from './logger'
@@ -69,14 +69,12 @@ export const getAllUserAccess = async (userIds: string[]) => {
return data as Access[]
}
if (serviceProvider === 'toska') {
jamiClient
.get('/ping', { timeout: 4000 })
.then(() => {
logger.info('JAMI connected')
})
.catch(error => {
logger.error('JAMI not responding :(', { error })
logger.info('Are you sure you are using the latest JAMI image?')
})
}
jamiClient
.get('/ping', { timeout: 4000 })
.then(() => {
logger.info('JAMI connected')
})
.catch(error => {
logger.error('JAMI not responding :(', { error })
logger.info('Are you sure you are using the latest JAMI image?')
})
+18 -20
View File
@@ -3,29 +3,27 @@ import * as winston from 'winston'
import { WinstonGelfTransporter } from 'winston-gelf-transporter'
import LokiTransport from 'winston-loki'
import { isProduction, serviceProvider } from '../config'
import { isProduction } from '../config'
const transports: winston.transport[] = [new winston.transports.Console()]
if (serviceProvider !== 'fd') {
transports.push(
new LokiTransport({
host: 'http://loki-svc.toska-lokki.svc.cluster.local:3100',
labels: { app: 'oodikone', environment: process.env.NODE_ENV ?? 'production' },
}),
new WinstonGelfTransporter({
handleExceptions: true,
host: 'svm-116.cs.helsinki.fi',
port: 9503,
protocol: 'udp',
hostName: os.hostname(),
additional: {
app: 'oodikone',
environment: 'production',
},
}) as winston.transport
)
}
transports.push(
new LokiTransport({
host: 'http://loki-svc.toska-lokki.svc.cluster.local:3100',
labels: { app: 'oodikone', environment: process.env.NODE_ENV ?? 'production' },
}),
new WinstonGelfTransporter({
handleExceptions: true,
host: 'svm-116.cs.helsinki.fi',
port: 9503,
protocol: 'udp',
hostName: os.hostname(),
additional: {
app: 'oodikone',
environment: 'production',
},
}) as winston.transport
)
const { colorize, combine, timestamp, printf } = winston.format
const logger = winston.createLogger({
-17
View File
@@ -1,17 +0,0 @@
const getUserIamAccess = async (_, __, ___) => {
return {}
}
const getUserIams = async _ => {
return []
}
const getAllUserAccess = async _ => {
return []
}
module.exports = {
getUserIamAccess,
getUserIams,
getAllUserAccess,
}
@@ -1 +0,0 @@
{ "sisUrl": "https://sis-qa.funidata.fi" }
-8
View File
@@ -4,7 +4,6 @@ import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
import { maxBy, orderBy, range } from 'lodash-es'
import { serviceProvider } from '@/conf'
import { SemestersData } from '@/redux/semesters'
import type { Absence } from '@/types/students'
import type { SISStudyRightElement } from '@oodikone/shared/models'
@@ -437,13 +436,6 @@ export const getEnrollmentTypeTextForExcel = (type: number, statutoryAbsence?: b
return 'No study right'
}
export const isDefaultServiceProvider = () => {
if (!serviceProvider) {
return false
}
return serviceProvider === 'toska'
}
export const formatContent = (content: string) => content.replace(/\n +/g, '\n')
export const filterInternalReleases = (release: Release) => !release.title.startsWith('Internal:')
@@ -3,7 +3,6 @@ import Divider from '@mui/material/Divider'
import Stack from '@mui/material/Stack'
import Typography from '@mui/material/Typography'
import { isDefaultServiceProvider } from '@/common'
import { ExternalLink } from '@/components/common/ExternalLink'
import { ToskaLogo } from '@/components/Footer/ToskaLogo'
import { builtAt, dataProtectionUrl, licenseUrl, sentryRelease, sourceCodeUrl } from '@/conf'
@@ -36,7 +35,7 @@ export const Footer = () => {
<Stack direction="row" gap={3}>
<Stack>
<Typography variant="subtitle1">Oodikone</Typography>
{isDefaultServiceProvider() && <ExternalLink href={dataProtectionUrl} text="Data protection" />}
<ExternalLink href={dataProtectionUrl} text="Data protection" />
<ExternalLink href={sourceCodeUrl} text="Source code" />
<ExternalLink href={licenseUrl} text="License" />
</Stack>
@@ -5,7 +5,6 @@ import Typography from '@mui/material/Typography'
import { useState } from 'react'
import { useLocation } from 'react-router'
import { isDefaultServiceProvider } from '@/common'
import { Link } from '@/components/common/Link'
import { languageCenterViewEnabled } from '@/conf'
import { useGetAuthorizedUserQuery } from '@/redux/auth'
@@ -26,11 +25,7 @@ export const NavigationButton = ({ item }: { item: NavigationItem }) => {
return true
}
if (
checkUserAccess(['admin', 'openUniSearch'], roles) &&
subItemKey === 'openUniSearch' &&
isDefaultServiceProvider()
) {
if (checkUserAccess(['admin', 'openUniSearch'], roles) && subItemKey === 'openUniSearch') {
return true
}
@@ -6,7 +6,6 @@ import Toolbar from '@mui/material/Toolbar'
import { useState, useEffect, useMemo } from 'react'
import { useLocation } from 'react-router'
import { isDefaultServiceProvider } from '@/common'
import { Link } from '@/components/common/Link'
import { useGetAuthorizedUserQuery } from '@/redux/auth'
import { checkUserAccess, getFullStudyProgrammeRights, hasFullAccessToTeacherData } from '@/util/access'
@@ -48,8 +47,6 @@ export const NavigationBar = () => {
if (key === 'faculty' && !checkUserAccess(['admin', 'fullSisuAccess', 'facultyStatistics'], roles))
return false
if (key === 'feedback' && !isDefaultServiceProvider()) return false
if (key === 'admin' && !isAdmin) return false
if (
@@ -1,7 +1,6 @@
import { Suspense } from 'react'
import { Navigate, Route, Routes as RouterRoutes } from 'react-router'
import { isDefaultServiceProvider } from '@/common'
import { CoursePopulation } from '@/components/CoursePopulation'
import { CustomOpenUniPopulation } from '@/components/CustomOpenUniPopulation'
import { CustomPopulation } from '@/components/CustomPopulation'
@@ -36,12 +35,12 @@ export const Routes = () => (
<Route element={<Changelog />} path="/changelog" />
<Route element={<CompletedCourses />} path="/completedcoursessearch" />
<Route element={<University />} path="/university" />
{isDefaultServiceProvider() && <Route element={<Feedback />} path="/feedback" />}
<Route element={<Feedback />} path="/feedback" />
<Route element={<ProtectedRoute requireUserHasRights requiredRoles={['fullSisuAccess']} />}>
<Route element={<PopulationStatistics />} path="/populations" />
<Route element={<StudyProgramme />} path="/study-programme/:studyProgrammeId?" />
{isDefaultServiceProvider() && <Route element={<CoursePopulation />} path="/coursepopulation" />}
<Route element={<CoursePopulation />} path="/coursepopulation" />
</Route>
<Route element={<ProtectedRoute requiredRoles={['admin']} />}>
@@ -79,11 +78,9 @@ export const Routes = () => (
<Route element={<CloseToGraduation />} path="/close-to-graduation" />
</Route>
{isDefaultServiceProvider() && (
<Route element={<ProtectedRoute requiredRoles={['openUniSearch']} />}>
<Route element={<CustomOpenUniPopulation />} path="/openunipopulation" />
</Route>
)}
<Route element={<ProtectedRoute requiredRoles={['openUniSearch']} />}>
<Route element={<CustomOpenUniPopulation />} path="/openunipopulation" />
</Route>
{/* Access control inside ProtectedRoute */}
<Route element={<ProtectedRoute />}>
@@ -2,7 +2,7 @@ import Typography from '@mui/material/Typography'
import { createColumnHelper } from '@tanstack/react-table'
import { useMemo } from 'react'
import { calculatePercentage, isDefaultServiceProvider } from '@/common'
import { calculatePercentage } from '@/common'
import { Link } from '@/components/common/Link'
import { OodiTable } from '@/components/OodiTable'
import { OodiTableExcelExport } from '@/components/OodiTable/excelExport'
@@ -42,11 +42,9 @@ export const TeacherStatisticsTable = ({ statistics, variant }) => {
cell: ({ row }) => (
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '0.5em' }}>
{row.original.name}
{isDefaultServiceProvider() && (
<Link target="_blank" to={`/teachers/${row.original.id}`}>
<NorthEastIcon />
</Link>
)}
<Link target="_blank" to={`/teachers/${row.original.id}`}>
<NorthEastIcon />
</Link>
</div>
),
})
@@ -3,13 +3,11 @@ import Stack from '@mui/material/Stack'
import Typography from '@mui/material/Typography'
import { Link } from '@/components/common/Link'
import { useSisUrl } from '@/hooks/useSisUrl'
import { sisUrl } from '@/conf'
import { PersonIcon } from '@/theme'
import { ExternalLink } from './ExternalLink'
export const StudentInfoItem = ({ sisPersonId, studentNumber }: { sisPersonId: string; studentNumber: string }) => {
const usableSisUrl = useSisUrl()
return (
<Stack direction="row" justifyContent="space-between">
<Typography component="span" sx={{ alignContent: 'center', padding: '0 0.25em' }} variant="body2">
@@ -18,7 +16,7 @@ export const StudentInfoItem = ({ sisPersonId, studentNumber }: { sisPersonId: s
<IconButton component={Link} target="_blank" to={`/students/${studentNumber}`}>
<PersonIcon color="primary" fontSize="small" />
</IconButton>
<ExternalLink href={`${usableSisUrl}/tutor/role/staff/student/${sisPersonId}/basic/basic-info`} text="Sisu" />
<ExternalLink href={`${sisUrl}/tutor/role/staff/student/${sisPersonId}/basic/basic-info`} text="Sisu" />
</Stack>
)
}
-3
View File
@@ -22,9 +22,6 @@ export const sourceCodeUrl = 'https://github.com/UniversityOfHelsinkiCS/oodikone
export const licenseUrl = `${sourceCodeUrl}/blob/master/LICENSE`
export const dataProtectionUrl = `${sourceCodeUrl}/blob/master/documentation/tietosuoja.md`
// Service provider depending this hiding some not needed features default value toska
export const serviceProvider = process.env.REACT_APP_SERVICE_PROVIDER?.toLowerCase() ?? 'toska'
// Variable that can be used to disable the language center view, by default the view is enabled
export const languageCenterViewEnabled = !process.env.REACT_APP_LANGUAGE_CENTER_VIEW_ENABLED
? true
-25
View File
@@ -1,25 +0,0 @@
import { useState, useEffect } from 'react'
import { sisUrl, serviceProvider } from '@/conf'
export const useSisUrl = () => {
const [runtimeConfiguredSisUrl, setRuntimeConfiguredSisUrl] = useState(sisUrl)
useEffect(() => {
if (serviceProvider === 'fd')
fetch('/frontend-config.json')
.then(response => {
if (!response.ok) {
throw new Error('Json response was not ok')
}
return response.json()
})
.then(data => {
if (data.sisUrl) setRuntimeConfiguredSisUrl(data.sisUrl)
})
.catch(error => {
throw new Error(error)
})
}, [])
return runtimeConfiguredSisUrl
}
@@ -2,7 +2,6 @@ import IconButton from '@mui/material/IconButton'
import Stack from '@mui/material/Stack'
import Typography from '@mui/material/Typography'
import { isDefaultServiceProvider } from '@/common'
import { Link } from '@/components/common/Link'
import { NorthEastIcon } from '@/theme'
@@ -23,7 +22,7 @@ export const TimeCell = ({
{name}
{name === 'Total' && !userHasAccessToAllStats && <strong> *</strong>}
</Typography>
{name !== 'Total' && userHasAccessToAllStats && isDefaultServiceProvider() ? (
{name !== 'Total' && userHasAccessToAllStats ? (
<IconButton
color="primary"
component={Link}
@@ -7,7 +7,6 @@ import { difference, flatten, max, min, pickBy, uniq } from 'lodash-es'
import { useEffect, useState } from 'react'
import { useLocation, useNavigate } from 'react-router'
import { isDefaultServiceProvider } from '@/common'
import { ProgrammeDropdown } from '@/components/CourseStatistics/ProgrammeDropdown'
import { useLanguage } from '@/components/LanguagePicker/useLanguage'
import { Section } from '@/components/Section'
@@ -435,9 +434,6 @@ export const SingleCourseStats = ({
if (!userHasAccessToAllStats) {
return null
}
if (!isDefaultServiceProvider()) {
return null
}
return (
<Button disabled={disabled} onClick={showPopulation} variant="contained">
Show population
@@ -4,7 +4,7 @@ import Divider from '@mui/material/Divider'
import Stack from '@mui/material/Stack'
import { useEffect, useState } from 'react'
import { filterInternalReleases, isDefaultServiceProvider } from '@/common'
import { filterInternalReleases } from '@/common'
import { Link } from '@/components/common/Link'
import { PageLayout } from '@/components/common/PageLayout'
import { PageTitle } from '@/components/common/PageTitle'
@@ -53,7 +53,7 @@ export const FrontPage = () => {
content: 'View detailed information for a given student',
},
{
show: isDefaultServiceProvider(),
show: true,
title: 'Feedback',
content: (
<p>
@@ -8,8 +8,8 @@ import Typography from '@mui/material/Typography'
import { callApi } from '@/apiConnection'
import { ExternalLink } from '@/components/common/ExternalLink'
import { useStudentNameVisibility } from '@/components/common/StudentNameVisibilityToggle'
import { sisUrl } from '@/conf'
import { DateFormat } from '@/constants/date'
import { useSisUrl } from '@/hooks/useSisUrl'
import { useGetAuthorizedUserQuery } from '@/redux/auth'
import { RefreshIcon } from '@/theme'
import { reformatDate } from '@/util/timeAndDate'
@@ -17,7 +17,6 @@ import { StudentPageStudent } from '@oodikone/shared/types/studentData'
import { EnrollmentAccordion } from './EnrollmentAccordion'
export const StudentInfoCard = ({ student }: { student: StudentPageStudent }) => {
const usableSisUrl = useSisUrl()
const { visible: showName } = useStudentNameVisibility()
const { isAdmin } = useGetAuthorizedUserQuery()
const name = showName ? `${student.name}, ` : ''
@@ -42,7 +41,7 @@ export const StudentInfoCard = ({ student }: { student: StudentPageStudent }) =>
</Typography>
<ExternalLink
cypress="sisu-link"
href={`${usableSisUrl}/tutor/role/staff/student/${student.sisPersonId}/basic/basic-info`}
href={`${sisUrl}/tutor/role/staff/student/${student.sisPersonId}/basic/basic-info`}
text="Sisu"
variant="h6"
/>
@@ -4,7 +4,6 @@ import Typography from '@mui/material/Typography'
import { useState } from 'react'
import { isDefaultServiceProvider } from '@/common'
import { studyProgrammeToolTips } from '@/common/InfoToolTips'
import { LineGraph } from '@/components/common/LineGraph'
import { MedianTimeBarChart } from '@/components/common/MedianTimeBarChart'
@@ -149,7 +148,7 @@ export const BasicInformationTab = ({
isLoading={creditsIsLoading}
title="Credits produced by the degree programme"
>
{creditGraphStats && tableStats && isDefaultServiceProvider() ? (
{creditGraphStats && tableStats ? (
<Stack gap={2}>
<StackedBarChart
cypress="credits-produced-by-the-study-programme"
@@ -5,7 +5,6 @@ import Stack from '@mui/material/Stack'
import { debounce } from 'lodash-es'
import { useState } from 'react'
import { isDefaultServiceProvider } from '@/common'
import { FilterOldProgrammesToggle } from '@/components/common/FilterOldProgrammesToggle'
import { PageLayout } from '@/components/common/PageLayout'
import { PageTitle } from '@/components/common/PageTitle'
@@ -94,7 +93,7 @@ export const StudyProgrammeSelector = () => {
progId: `${programme.progId} - ${secondProgramme[0]?.progId}`,
})
}
if (isDefaultServiceProvider() && programme.code.startsWith('2_')) {
if (programme.code.startsWith('2_')) {
continue
}
@@ -4,7 +4,6 @@ import Tabs from '@mui/material/Tabs'
import { useState } from 'react'
import { useParams } from 'react-router'
import { isDefaultServiceProvider } from '@/common'
import { PageLayout } from '@/components/common/PageLayout'
import { PageTitle } from '@/components/common/PageTitle'
import { GetTextIn, useLanguage } from '@/components/LanguagePicker/useLanguage'
@@ -114,7 +113,7 @@ export const StudyProgramme = () => {
},
]
if (otherTabsVisible && isDefaultServiceProvider()) {
if (otherTabsVisible) {
tabs.push({
key: 'ProgrammeCoursesTab',
label: 'Programme courses',
@@ -10,7 +10,6 @@ import Typography from '@mui/material/Typography'
import { useEffect, useState } from 'react'
import { callApi } from '@/apiConnection'
import { isDefaultServiceProvider } from '@/common'
import { updaterToolTips } from '@/common/InfoToolTips'
import { ExternalLink } from '@/components/common/ExternalLink'
import { PageLayout } from '@/components/common/PageLayout'
@@ -70,11 +69,9 @@ export const Updater = () => {
<PageLayout maxWidth="lg">
<PageTitle title="Updater" />
<Stack spacing={2}>
{isDefaultServiceProvider() && (
<Button color="error" onClick={() => setError(true)} variant="contained">
Cause frontend crash
</Button>
)}
<Button color="error" onClick={() => setError(true)} variant="contained">
Cause frontend crash
</Button>
<Section
infoBoxContent={updaterToolTips.updaterSection}
title="Updater (data pulled from sis-importer-db and brought to sis-db)"
@@ -7,7 +7,6 @@ import TableContainer from '@mui/material/TableContainer'
import TableHead from '@mui/material/TableHead'
import TableRow from '@mui/material/TableRow'
import { isDefaultServiceProvider } from '@/common'
import { Link } from '@/components/common/Link'
import { MockButton } from '@/components/Users/MockButton'
import { DateFormat } from '@/constants/date'
@@ -27,7 +26,7 @@ export const InfoCard = ({ user }: { user: User }) => {
buttons={
<>
{user.username !== currentUserName && <MockButton username={user.username} />}
{isDefaultServiceProvider() && <NotifyButton userEmail={user.email} />}
{<NotifyButton userEmail={user.email} />}
</>
}
title={user.name}
@@ -7,7 +7,6 @@ import Stack from '@mui/material/Stack'
import { useEffect, useState } from 'react'
import { isDefaultServiceProvider } from '@/common'
import { useStatusNotification } from '@/components/StatusNotification/Context'
import { RoleChip } from '@/components/Users/RoleChip'
import { useGetRolesQuery, useModifyRolesMutation } from '@/redux/users'
@@ -71,7 +70,7 @@ export const RolesCard = ({ user }: { user: User }) => {
<Checkbox
checked={selected.includes(role)}
color="success"
disabled={!editing || (!['admin', 'teachers'].includes(role) && isDefaultServiceProvider())}
disabled={!editing || !['admin', 'teachers'].includes(role)}
onChange={() => toggleRole(role)}
/>
</Stack>
@@ -14,7 +14,6 @@ import Typography from '@mui/material/Typography'
import { useEffect, useState } from 'react'
import { isDefaultServiceProvider } from '@/common'
import { userToolTips } from '@/common/InfoToolTips'
import { FilterOldProgrammesToggle } from '@/components/common/FilterOldProgrammesToggle'
import { InfoBox } from '@/components/InfoBox/InfoBoxWithTooltip'
@@ -209,28 +208,26 @@ export const StudyProgrammeRightsCard = ({ user }: { user: User }) => {
</Stack>
</CardContent>
{isDefaultServiceProvider() && (
<CardContent>
<Stack alignItems="center" direction="row" gap={1}>
<Typography component="h3" fontWeight="bold">
Current IAM group based degree programme access rights
</Typography>
<InfoBox content={userToolTips.iamGroupBasedAccess} mini />
</Stack>
{currentIamAccessRights.length > 0 ? (
currentIamAccessRights.map(({ code, name, limited }) => (
<Stack direction="row" justifyContent="space-between" key={code}>
<Typography>{`${name} ${code}`}</Typography>
<Tooltip arrow placement="left" title={limited ? 'Limited rights' : 'Full rights'}>
{limited ? <WarningIcon color="warning" /> : <CheckCircleIcon color="success" />}
</Tooltip>
</Stack>
))
) : (
<Typography color="text.secondary">No IAM based access rights</Typography>
)}
</CardContent>
)}
<CardContent>
<Stack alignItems="center" direction="row" gap={1}>
<Typography component="h3" fontWeight="bold">
Current IAM group based degree programme access rights
</Typography>
<InfoBox content={userToolTips.iamGroupBasedAccess} mini />
</Stack>
{currentIamAccessRights.length > 0 ? (
currentIamAccessRights.map(({ code, name, limited }) => (
<Stack direction="row" justifyContent="space-between" key={code}>
<Typography>{`${name} ${code}`}</Typography>
<Tooltip arrow placement="left" title={limited ? 'Limited rights' : 'Full rights'}>
{limited ? <WarningIcon color="warning" /> : <CheckCircleIcon color="success" />}
</Tooltip>
</Stack>
))
) : (
<Typography color="text.secondary">No IAM based access rights</Typography>
)}
</CardContent>
</>
)}
</Card>
@@ -2,7 +2,6 @@ import Alert from '@mui/material/Alert'
import Box from '@mui/material/Box'
import Stack from '@mui/material/Stack'
import { isDefaultServiceProvider } from '@/common'
import { PageLayout } from '@/components/common/PageLayout'
import { Loading } from '@/components/Loading'
import { useTitle } from '@/hooks/title'
@@ -38,11 +37,9 @@ export const UserPage = ({ userId }: { userId: string }) => {
<MissingIdAlert visible={!user.sisPersonId} />
<InfoCard user={user} />
<Box gap={2} sx={{ display: 'flex', flexDirection: { xs: 'column', md: 'row' } }}>
{isDefaultServiceProvider() && (
<Box flex={1}>
<IamGroupsCard user={user} />
</Box>
)}
<Box flex={1}>
<IamGroupsCard user={user} />
</Box>
<Box flex={2}>
<RolesCard user={user} />
</Box>
@@ -5,7 +5,6 @@ import Stack from '@mui/material/Stack'
import { MaterialReactTable, MRT_ColumnDef, useMaterialReactTable } from 'material-react-table'
import { useCallback, useMemo } from 'react'
import { isDefaultServiceProvider } from '@/common'
import { Link } from '@/components/common/Link'
import { useLanguage } from '@/components/LanguagePicker/useLanguage'
import { MockButton } from '@/components/Users/MockButton'
@@ -17,7 +16,6 @@ import { User } from '@/types/api/users'
import { reformatDate } from '@/util/timeAndDate'
import { DetailedProgrammeRights, Role } from '@oodikone/shared/types'
import { CopyEmailAddressesButton } from './CopyEmailAddressesButton'
import { DeleteButton } from './DeleteButton'
import { StatusMessage } from './StatusMessage'
export const UsersTable = ({
@@ -128,9 +126,6 @@ export const UsersTable = ({
Cell: ({ row }) => (
<Stack direction="row" gap={1}>
<MockButton username={row.original.username} />
{!isDefaultServiceProvider() && (
<DeleteButton getAllUsersQuery={getAllUsersQuery} userId={row.original.id} />
)}
</Stack>
),
enableSorting: false,
@@ -158,7 +153,7 @@ export const UsersTable = ({
username: true,
roles: true,
programmeRights: true,
iamGroups: isDefaultServiceProvider(),
iamGroups: true,
lastLogin: true,
},
},
@@ -2,12 +2,10 @@ import Stack from '@mui/material/Stack'
import { useEffect } from 'react'
import { useParams } from 'react-router'
import { isDefaultServiceProvider } from '@/common'
import { PageLayout } from '@/components/common/PageLayout'
import { PageTitle } from '@/components/common/PageTitle'
import { useTitle } from '@/hooks/title'
import { useLazyGetAllUsersQuery } from '@/redux/users'
import { NewUserSection } from './NewUserSection'
import { UserPage } from './UserPage'
import { UsersTable } from './UsersTable'
@@ -16,10 +14,6 @@ export const Users = () => {
const { userid } = useParams()
const [getAllUsersQuery, { data: users = [], isLoading, isError }] = useLazyGetAllUsersQuery()
const onAddUser = () => {
void getAllUsersQuery()
}
useEffect(() => {
if (!userid) {
void getAllUsersQuery()
@@ -30,7 +24,6 @@ export const Users = () => {
<PageLayout maxWidth="lg">
<PageTitle title="Users" />
<Stack gap={2}>
{!userid && !isDefaultServiceProvider() && <NewUserSection onAddUser={onAddUser} />}
{userid ? (
<UserPage userId={userid} />
) : (
@@ -1,7 +1,4 @@
import { isDefaultServiceProvider } from '@/common'
export const getGraduationGraphTitle = (programmeCode: string, doCombo = false) => {
if (!isDefaultServiceProvider()) return ''
if (!programmeCode) return ''
if (['MH30_001', 'MH30_003'].includes(programmeCode)) return 'Licenciate study right'
if (doCombo && programmeCode === 'MH90_001') return 'Bachelor + licentiate study right'
@@ -21,6 +21,5 @@ module.exports.SIS_IMPORTER_DATABASE = process.env.SIS_IMPORTER_DATABASE
module.exports.EXIT_AFTER_IMMEDIATES = process.env.EXIT_AFTER_IMMEDIATES === 'yes'
module.exports.SCHEDULE_IMMEDIATE = process.env.SCHEDULE_IMMEDIATE ? process.env.SCHEDULE_IMMEDIATE.split(',') : []
module.exports.SLACK_WEBHOOK = process.env.SLACK_WEBHOOK
module.exports.serviceProvider = process.env.SERVICE_PROVIDER ? process.env.SERVICE_PROVIDER.toLowerCase() : ''
module.exports.runningInCI = process.env.CI === 'true'
module.exports.SENTRY_DSN = process.env.SENTRY_DSN
@@ -4,7 +4,7 @@ const { WinstonGelfTransporter } = require('winston-gelf-transporter')
const LokiTransport = require('winston-loki')
const Sentry = require('winston-transport-sentry-node').default
const { isDev, isStaging, isProduction, runningInCI, serviceProvider, SENTRY_DSN } = require('../config')
const { isDev, isStaging, isProduction, runningInCI, SENTRY_DSN } = require('../config')
const { combine, timestamp, printf, colorize, uncolorize } = winston.format
@@ -38,16 +38,14 @@ transports.push(
})
)
if (serviceProvider !== 'fd') {
transports.push(
new LokiTransport({
host: 'http://loki-svc.toska-lokki.svc.cluster.local:3100',
labels: { app: 'updater-scheduler', environment: process.env.NODE_ENV ?? 'production' },
})
)
}
transports.push(
new LokiTransport({
host: 'http://loki-svc.toska-lokki.svc.cluster.local:3100',
labels: { app: 'updater-scheduler', environment: process.env.NODE_ENV ?? 'production' },
})
)
if (isProduction && !isStaging && serviceProvider !== 'fd') {
if (isProduction && !isStaging) {
transports.push(
new WinstonGelfTransporter({
handleExceptions: true,
-1
View File
@@ -6,7 +6,6 @@ export const PURGE_LOCK = 'PURGE_LOCK'
export const REDIS_HOST = process.env.REDIS_HOST || 'redis-updater'
export const REDIS_PORT = process.env.REDIS_PORT || 6379
export const rootOrgId = process.env.ROOT_ORG_ID || 'hy-university-root-id'
export const serviceProvider = process.env.SERVICE_PROVIDER ? process.env.SERVICE_PROVIDER.toLowerCase() : ''
export const runningInCI = process.env.CI === 'true'
export const { SENTRY_DSN } = process.env
@@ -1,6 +1,5 @@
import { sortBy, flatten, uniqBy } from 'lodash-es'
import { serviceProvider } from '../config.js'
import logger from '../utils/logger.js'
import {
educationTypeToExtentcode,
@@ -177,24 +176,21 @@ export const creditMapper =
let is_open = false
// check if ay code or ay study right or ay responsible organisation
// if fd is not service provider
if (serviceProvider !== 'fd') {
if (course_code && !isModule(type)) {
if (course_code.match(/^AY?(.+?)(?:en|fi|sv)?$/)) {
if (course_code && !isModule(type)) {
if (course_code.match(/^AY?(.+?)(?:en|fi|sv)?$/)) {
is_open = true
} else if (study_right_id !== null) {
if (
studyRightIdToEducationType[study_right_id] ===
'urn:code:education-type:non-degree-education:open-university-studies'
)
is_open = true
} else if (study_right_id !== null) {
if (
studyRightIdToEducationType[study_right_id] ===
'urn:code:education-type:non-degree-education:open-university-studies'
)
is_open = true
} else if (
organisations
.filter(({ roleUrn }) => roleUrn === 'urn:code:organisation-role:responsible-organisation')
.some(org => org.organisationid === 'hy-org-48645785')
) {
is_open = true
}
} else if (
organisations
.filter(({ roleUrn }) => roleUrn === 'urn:code:organisation-role:responsible-organisation')
.some(org => org.organisationid === 'hy-org-48645785')
) {
is_open = true
}
}
@@ -1,5 +1,3 @@
import { serviceProvider } from '../../config.js'
const anyModule = rule => ({ id: rule.localId, name: 'Any module' })
const anyCourse = rule => ({ id: rule.localId, name: 'Any course' })
const unknownRule = rule => ({ type: rule.type, fact: 'Unhandled rule' })
@@ -75,7 +73,7 @@ export class ModuleResolver {
const children = []
for (const mod of modules) {
if (serviceProvider !== 'fd' && mod.code?.startsWith('KK-')) continue
if (mod.code?.startsWith('KK-')) continue
let result = this.moduleCache[mod.id]
if (!result) {
result = await this.resolveSingleModule(mod)
@@ -1,6 +1,6 @@
import { flatten, sortBy, uniqBy } from 'lodash-es'
import { Op } from 'sequelize'
import { rootOrgId, serviceProvider } from '../../config.js'
import { rootOrgId } from '../../config.js'
import { dbConnections } from '../../db/connection.js'
import { selectFromByIds, bulkCreate, selectOneById } from '../../db/index.js'
import { Course, Teacher, Credit, CreditTeacher, CourseProvider } from '../../db/models/index.js'
@@ -133,28 +133,24 @@ export const updateAttainments = async (
if (!customAttainmentTypes.includes(att.type)) return att
let courseUnit
if (serviceProvider === 'fd') {
courseUnit = courses.find(course => course.id === att.course_unit_id)
} else {
const courseUnits = courses.filter(course => course.code === attIdToCourseCode[att.id])
const courseUnits = courses.filter(course => course.code === attIdToCourseCode[att.id])
const isAfterStartAndBeforeEnd = (startDate, endDate, date) => {
const dateToCompare = new Date(date)
return new Date(startDate) <= dateToCompare && (!endDate || dateToCompare < new Date(endDate))
}
const isAfterStartAndBeforeEnd = (startDate, endDate, date) => {
const dateToCompare = new Date(date)
return new Date(startDate) <= dateToCompare && (!endDate || dateToCompare < new Date(endDate))
}
courseUnit = courseUnits.find(({ validity_period }) =>
isAfterStartAndBeforeEnd(validity_period.startDate, validity_period.endDate, att.attainment_date)
)
// Sometimes registrations are fakd, see attainment hy-opinto-141561630.
// The attainmentdate is outside of all courses, yet should be mapped.
// Try to catch suitable courseUnit for this purpose
if (!courseUnit) {
courseUnit = courseUnits.find(({ validity_period }) =>
isAfterStartAndBeforeEnd(validity_period.startDate, validity_period.endDate, att.attainment_date)
isAfterStartAndBeforeEnd(validity_period.startDate, validity_period.endDate, att.registration_date)
)
// Sometimes registrations are fakd, see attainment hy-opinto-141561630.
// The attainmentdate is outside of all courses, yet should be mapped.
// Try to catch suitable courseUnit for this purpose
if (!courseUnit) {
courseUnit = courseUnits.find(({ validity_period }) =>
isAfterStartAndBeforeEnd(validity_period.startDate, validity_period.endDate, att.registration_date)
)
}
}
let courseProvider
@@ -163,11 +159,17 @@ export const updateAttainments = async (
// If there's no suitable courseunit, there isn't courseunit available at all.
// --> Course should be created, if it doesn't exist in sis db
if (!courseUnit) {
if (serviceProvider === 'fd') {
const parsedCourseCode = attIdToCourseCode[att.id]
// see if course exists
course = await Course.findOne({ where: { code: parsedCourseCode, id: parsedCourseCode } })
// If course doesn't exist, create it
if (!course) {
courseUnit = {
id: att.id,
id: parsedCourseCode,
name: att.name,
code: att.code,
code: parsedCourseCode,
is_study_module: isModule(att.type),
coursetypecode: att.study_level_urn,
max_attainment_date: att.attainment_date,
@@ -175,42 +177,16 @@ export const updateAttainments = async (
substitutions: [],
course_unit_type: att.course_unit_type_urn,
}
coursesToBeCreated.set(att.id, courseUnit)
coursesToBeCreated.set(parsedCourseCode, courseUnit)
courseProvider = await CourseProvider.findOne({
where: {
coursecode: att.id,
coursecode: parsedCourseCode,
},
})
} else {
const parsedCourseCode = attIdToCourseCode[att.id]
// see if course exists
course = await Course.findOne({ where: { code: parsedCourseCode, id: parsedCourseCode } })
// If course doesn't exist, create it
if (!course) {
courseUnit = {
id: parsedCourseCode,
name: att.name,
code: parsedCourseCode,
is_study_module: isModule(att.type),
coursetypecode: att.study_level_urn,
max_attainment_date: att.attainment_date,
min_attainment_date: att.attainment_date,
substitutions: [],
course_unit_type: att.course_unit_type_urn,
}
coursesToBeCreated.set(parsedCourseCode, courseUnit)
courseProvider = await CourseProvider.findOne({
where: {
coursecode: parsedCourseCode,
},
})
}
}
const courseIdToUse = serviceProvider === 'fd' ? att.id : attIdToCourseCode[att.id]
const courseIdToUse = attIdToCourseCode[att.id]
// If there's no courseprovider, try to create course provider
if (!courseProvider) {
@@ -227,9 +203,7 @@ export const updateAttainments = async (
}
}
if (serviceProvider !== 'fd') {
courseUnit = course || { id: courseIdToUse, code: courseIdToUse }
}
courseUnit = course || { id: courseIdToUse, code: courseIdToUse }
courseUnit.group_id = courseUnit.id
}
+8 -10
View File
@@ -4,7 +4,7 @@ import { WinstonGelfTransporter } from 'winston-gelf-transporter'
import LokiTransport from 'winston-loki'
import WinstonSentry from 'winston-transport-sentry-node'
import { isDev, isStaging, isProduction, runningInCI, serviceProvider, SENTRY_DSN } from '../config.js'
import { isDev, isStaging, isProduction, runningInCI, SENTRY_DSN } from '../config.js'
const Sentry = WinstonSentry.default
@@ -40,16 +40,14 @@ transports.push(
})
)
if (serviceProvider !== 'fd') {
transports.push(
new LokiTransport({
host: 'http://loki-svc.toska-lokki.svc.cluster.local:3100',
labels: { app: 'updater-worker', environment: process.env.NODE_ENV ?? 'production' },
})
)
}
transports.push(
new LokiTransport({
host: 'http://loki-svc.toska-lokki.svc.cluster.local:3100',
labels: { app: 'updater-worker', environment: process.env.NODE_ENV ?? 'production' },
})
)
if (isProduction && !isStaging && serviceProvider !== 'fd') {
if (isProduction && !isStaging) {
transports.push(
new WinstonGelfTransporter({
handleExceptions: true,