From 8569767c0cdee64937e91b0b7d6517fe61a1e858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikolas=20G=C3=B6rlitz?= Date: Fri, 15 Mar 2024 21:41:58 +0100 Subject: [PATCH] Small changes to GDPR Downloader --- .../20221115171243-create-user-data-table.ts | 6 +- db/seeders/20221121101837-PermissionSeeder.ts | 2 +- .../TrainingSessionAdminController.ts | 38 ++++---- src/controllers/user/GDPRController.ts | 86 ++++++++++++++++++- .../user/UserStatisticsController.ts | 4 +- src/core/tasks/SendEmailTask.ts | 2 +- src/libraries/EmailLibrary.ts | 4 +- src/libraries/JobLibrary.ts | 6 +- src/libraries/vateud/VateudCoreLibrary.ts | 53 ++++++------ .../vateud/VateudCoreLibraryTypes.ts | 22 ++--- src/models/User.ts | 39 +++++---- src/models/UserData.ts | 15 ++-- src/models/associations/UserAssociations.ts | 2 +- src/utility/Validator.ts | 13 ++- 14 files changed, 190 insertions(+), 102 deletions(-) diff --git a/db/migrations/20221115171243-create-user-data-table.ts b/db/migrations/20221115171243-create-user-data-table.ts index f016732..9f5b3db 100644 --- a/db/migrations/20221115171243-create-user-data-table.ts +++ b/db/migrations/20221115171243-create-user-data-table.ts @@ -1,7 +1,7 @@ -import {DataType} from "sequelize-typescript"; -import {QueryInterface} from "sequelize"; +import { DataType } from "sequelize-typescript"; +import { QueryInterface } from "sequelize"; -export const USER_DATA_TABLE_NAME = "user_data" +export const USER_DATA_TABLE_NAME = "user_data"; export const USER_DATA_TABLE_ATTRIBUTES = { user_id: { diff --git a/db/seeders/20221121101837-PermissionSeeder.ts b/db/seeders/20221121101837-PermissionSeeder.ts index 07ca42f..45a8f9f 100644 --- a/db/seeders/20221121101837-PermissionSeeder.ts +++ b/db/seeders/20221121101837-PermissionSeeder.ts @@ -1,4 +1,4 @@ -import {QueryInterface} from "sequelize"; +import { QueryInterface } from "sequelize"; const now = new Date(); diff --git a/src/controllers/training-session/TrainingSessionAdminController.ts b/src/controllers/training-session/TrainingSessionAdminController.ts index 093b38d..c32ce85 100644 --- a/src/controllers/training-session/TrainingSessionAdminController.ts +++ b/src/controllers/training-session/TrainingSessionAdminController.ts @@ -1,24 +1,24 @@ -import {NextFunction, Request, Response} from "express"; -import {User} from "../../models/User"; +import { NextFunction, Request, Response } from "express"; +import { User } from "../../models/User"; import _TrainingSessionAdminValidator from "./_TrainingSessionAdminValidator"; -import {Course} from "../../models/Course"; -import {TrainingSession} from "../../models/TrainingSession"; -import {generateUUID} from "../../utility/UUID"; +import { Course } from "../../models/Course"; +import { TrainingSession } from "../../models/TrainingSession"; +import { generateUUID } from "../../utility/UUID"; import dayjs from "dayjs"; -import {TrainingRequest} from "../../models/TrainingRequest"; -import {TrainingSessionBelongsToUsers} from "../../models/through/TrainingSessionBelongsToUsers"; -import {HttpStatusCode} from "axios"; +import { TrainingRequest } from "../../models/TrainingRequest"; +import { TrainingSessionBelongsToUsers } from "../../models/through/TrainingSessionBelongsToUsers"; +import { HttpStatusCode } from "axios"; import NotificationLibrary from "../../libraries/notification/NotificationLibrary"; -import {Config} from "../../core/Config"; -import {TrainingType} from "../../models/TrainingType"; -import {Op} from "sequelize"; -import {TrainingLog} from "../../models/TrainingLog"; -import {UsersBelongsToCourses} from "../../models/through/UsersBelongsToCourses"; -import {sequelize} from "../../core/Sequelize"; -import {MentorGroup} from "../../models/MentorGroup"; -import JobLibrary, {JobTypeEnum} from "../../libraries/JobLibrary"; -import Validator, {ValidationTypeEnum} from "../../utility/Validator"; -import Logger, {LogLevels} from "../../utility/Logger"; +import { Config } from "../../core/Config"; +import { TrainingType } from "../../models/TrainingType"; +import { Op } from "sequelize"; +import { TrainingLog } from "../../models/TrainingLog"; +import { UsersBelongsToCourses } from "../../models/through/UsersBelongsToCourses"; +import { sequelize } from "../../core/Sequelize"; +import { MentorGroup } from "../../models/MentorGroup"; +import JobLibrary, { JobTypeEnum } from "../../libraries/JobLibrary"; +import Validator, { ValidationTypeEnum } from "../../utility/Validator"; +import Logger, { LogLevels } from "../../utility/Logger"; /** * Creates a new training session with one user and one mentor @@ -38,7 +38,7 @@ async function createTrainingSession(request: Request, response: Response) { Validator.validate(body, { training_type_id: [ValidationTypeEnum.NON_NULL, ValidationTypeEnum.NUMBER], - user_ids: [ValidationTypeEnum.VALID_JSON] // Parses to number[] + user_ids: [ValidationTypeEnum.VALID_JSON], // Parses to number[] }); // 1. Find out which of these users is actually enrolled in the course. To do this, query the course and it's members, and check against the array of user_ids. Create a new actual array with only those people diff --git a/src/controllers/user/GDPRController.ts b/src/controllers/user/GDPRController.ts index 1800b83..913fce2 100644 --- a/src/controllers/user/GDPRController.ts +++ b/src/controllers/user/GDPRController.ts @@ -1,5 +1,6 @@ import { NextFunction, Request, Response } from "express"; import { User } from "../../models/User"; +import { EndorsementGroup } from "../../models/EndorsementGroup"; async function getData(request: Request, response: Response, next: NextFunction) { try { @@ -10,10 +11,91 @@ async function getData(request: Request, response: Response, next: NextFunction) where: { id: user.id, }, + include: [ + User.associations.user_data, + User.associations.user_settings, + { + association: User.associations.user_solo, + attributes: { + exclude: ["id", "created_by", "solo_used", "vateud_solo_id", "createdAt", "updatedAt"], + }, + }, + { + association: User.associations.mentor_groups, + attributes: { + exclude: ["id", "createdAt", "updatedAt"], + }, + through: { + attributes: ["group_admin", "can_manage_course"], + }, + }, + { + association: User.associations.mentor_sessions, + attributes: { + exclude: ["id", "uuid", "cpt_examiner_id", "cpt_atsim_passed", "training_station_id", "training_type_id", "createdAt", "updatedAt"], + }, + }, + { + association: User.associations.endorsement_groups, + attributes: { + exclude: ["id", "createdAt", "updatedAt"], + }, + through: { + attributes: [], + }, + include: [ + { + association: EndorsementGroup.associations.stations, + attributes: ["callsign"], + through: { + attributes: [], + }, + }, + ], + }, + { + association: User.associations.training_sessions, + attributes: ["completed", "date", "mentor_id"], + through: { + attributes: [], + }, + }, + { + association: User.associations.training_requests, + attributes: ["comment", "status", "expires", "createdAt"], + }, + { + association: User.associations.training_logs, + attributes: ["content", "author_id", "createdAt"], + through: { + attributes: [], + }, + }, + { + association: User.associations.courses, + attributes: ["name"], + through: { + attributes: ["completed", "createdAt"], + }, + }, + { + association: User.associations.fast_track_requests, + attributes: ["rating", "status", "createdAt"], + }, + { + association: User.associations.roles, + attributes: ["id"], + through: { + attributes: [], + }, + }, + { + association: User.associations.user_notes, + attributes: ["author_id", "content", "createdAt"], + }, + ], }); - console.log("USER: ", foundUser); - response.send(foundUser); } catch (e) { next(e); diff --git a/src/controllers/user/UserStatisticsController.ts b/src/controllers/user/UserStatisticsController.ts index e7644b1..9a72293 100644 --- a/src/controllers/user/UserStatisticsController.ts +++ b/src/controllers/user/UserStatisticsController.ts @@ -23,7 +23,7 @@ async function getUserTrainingSessionCount(request: Request, response: Response, const sessions = await user.getTrainingSessions(); const completedSession = sessions.filter(s => s.completed); - response.send({count: sessions.length, completedCount: completedSession.length}); + response.send({ count: sessions.length, completedCount: completedSession.length }); } catch (e) { next(e); } @@ -31,5 +31,5 @@ async function getUserTrainingSessionCount(request: Request, response: Response, export default { getUserRatingTimes, - getUserTrainingSessionCount + getUserTrainingSessionCount, }; diff --git a/src/core/tasks/SendEmailTask.ts b/src/core/tasks/SendEmailTask.ts index 94a035e..4108784 100644 --- a/src/core/tasks/SendEmailTask.ts +++ b/src/core/tasks/SendEmailTask.ts @@ -1,5 +1,5 @@ import JobLibrary from "../../libraries/JobLibrary"; -import EmailLibrary, {EMailPayload, EMailTypes} from "../../libraries/EmailLibrary"; +import EmailLibrary, { EMailPayload, EMailTypes } from "../../libraries/EmailLibrary"; import dayjs from "dayjs"; // This file maps between the type of the email and the corresponding html file used to actually render the template diff --git a/src/libraries/EmailLibrary.ts b/src/libraries/EmailLibrary.ts index 564fda3..33779fb 100644 --- a/src/libraries/EmailLibrary.ts +++ b/src/libraries/EmailLibrary.ts @@ -20,8 +20,8 @@ export type EMailPayload = { recipient: string; subject: string; replacements: - Record<"message", { message_de: string; message_en: string; name: string }> | - Record<"reminder", { name: string; expiry_date: string; link: string }>; + | Record<"message", { message_de: string; message_en: string; name: string }> + | Record<"reminder", { name: string; expiry_date: string; link: string }>; }; async function sendMail(options: SendMailOptions, nonPooled = true) { diff --git a/src/libraries/JobLibrary.ts b/src/libraries/JobLibrary.ts index a0c9bbc..60c33e7 100644 --- a/src/libraries/JobLibrary.ts +++ b/src/libraries/JobLibrary.ts @@ -1,11 +1,11 @@ import { Job } from "../models/Job"; import { generateUUID } from "../utility/UUID"; -import {EMailPayload} from "./EmailLibrary"; -import {VateudCorePayload} from "./vateud/VateudCoreLibraryTypes"; +import { EMailPayload } from "./EmailLibrary"; +import { VateudCorePayload } from "./vateud/VateudCoreLibraryTypes"; export enum JobTypeEnum { EMAIL = "email", - VATEUD_CORE = "vateud_core" + VATEUD_CORE = "vateud_core", } async function scheduleJob(type: JobTypeEnum, payload: EMailPayload | VateudCorePayload) { diff --git a/src/libraries/vateud/VateudCoreLibrary.ts b/src/libraries/vateud/VateudCoreLibrary.ts index 84406ad..f0d459e 100644 --- a/src/libraries/vateud/VateudCoreLibrary.ts +++ b/src/libraries/vateud/VateudCoreLibrary.ts @@ -1,21 +1,21 @@ -import axios, {Method} from "axios"; -import JobLibrary, {JobTypeEnum} from "../JobLibrary"; +import axios, { Method } from "axios"; +import JobLibrary, { JobTypeEnum } from "../JobLibrary"; import { VateudCoreSoloCreateResponseT, VateudCoreSoloCreateT, VateudCoreSoloRemoveResponseT, VateudCoreSoloRemoveT, - VateudCoreTypeEnum + VateudCoreTypeEnum, } from "./VateudCoreLibraryTypes"; -import {Config} from "../../core/Config"; -import {UserSolo} from "../../models/UserSolo"; -import Logger, {LogLevels} from "../../utility/Logger"; +import { Config } from "../../core/Config"; +import { UserSolo } from "../../models/UserSolo"; +import Logger, { LogLevels } from "../../utility/Logger"; type SendT = { method: Method; endpoint: string; data?: any; -} +}; /** * Sends the actual request to VATEUD Core @@ -30,11 +30,11 @@ async function _send(props: SendT): Promise { try { const res = await axios({ headers: { - 'x-api-key': Config.VATEUD_CORE_CONFIG.API_KEY, + "x-api-key": Config.VATEUD_CORE_CONFIG.API_KEY, }, url: `${Config.URI_CONFIG.VATEUD_API_BASE}/${props.endpoint}`, method: props.method, - data: props.data + data: props.data, }); return res.data as T; @@ -54,7 +54,7 @@ async function createSolo(soloInfo: VateudCoreSoloCreateT) { const res = await _send({ endpoint: "/solo", method: "post", - data: soloInfo.post_data + data: soloInfo.post_data, }); if (!res) { // If the request fails, we schedule a job to attempt it additional times. @@ -67,20 +67,23 @@ async function createSolo(soloInfo: VateudCoreSoloCreateT) { user_id: soloInfo.post_data.user_id, position: soloInfo.post_data.position, instructor_cid: soloInfo.post_data.instructor_cid, - expire_at: soloInfo.post_data.expires_at - } - } + expire_at: soloInfo.post_data.expires_at, + }, + }, }); return undefined; } - await UserSolo.update({ - vateud_solo_id: res.data.id - }, { - where: { - id: soloInfo.local_solo_id + await UserSolo.update( + { + vateud_solo_id: res.data.id, + }, + { + where: { + id: soloInfo.local_solo_id, + }, } - }); + ); return res; } @@ -93,7 +96,7 @@ async function createSolo(soloInfo: VateudCoreSoloCreateT) { async function removeSolo(soloInfo: VateudCoreSoloRemoveT) { const res = await _send({ endpoint: `/solo/${soloInfo.vateud_solo_id}`, - method: "delete" + method: "delete", }); if (!res) { await JobLibrary.scheduleJob(JobTypeEnum.VATEUD_CORE, { @@ -102,14 +105,14 @@ async function removeSolo(soloInfo: VateudCoreSoloRemoveT) { data: { solo_remove: { local_solo_id: soloInfo.local_solo_id, - vateud_solo_id: soloInfo.vateud_solo_id - } - } + vateud_solo_id: soloInfo.vateud_solo_id, + }, + }, }); } } export default { createSolo, - removeSolo -} \ No newline at end of file + removeSolo, +}; diff --git a/src/libraries/vateud/VateudCoreLibraryTypes.ts b/src/libraries/vateud/VateudCoreLibraryTypes.ts index 7845a33..fba5806 100644 --- a/src/libraries/vateud/VateudCoreLibraryTypes.ts +++ b/src/libraries/vateud/VateudCoreLibraryTypes.ts @@ -1,17 +1,17 @@ -import {Method} from "axios"; +import { Method } from "axios"; export enum VateudCoreTypeEnum { SOLO_CREATE = "solo_create", - SOLO_REMOVE = "solo_remove" + SOLO_REMOVE = "solo_remove", } export type VateudCorePayload = { type: VateudCoreTypeEnum; method: Method; data: - Record<"solo_create", { local_solo_id: number; user_id: number; position: string; instructor_cid: number; expire_at: string }> | - Record<"solo_remove", { local_solo_id: number; vateud_solo_id: number }> -} + | Record<"solo_create", { local_solo_id: number; user_id: number; position: string; instructor_cid: number; expire_at: string }> + | Record<"solo_remove", { local_solo_id: number; vateud_solo_id: number }>; +}; export type VateudCoreSoloCreateT = { local_solo_id: number; @@ -21,8 +21,8 @@ export type VateudCoreSoloCreateT = { instructor_cid: number; starts_at: string; expires_at: string; - } -} + }; +}; export type VateudCoreSoloCreateResponseT = { success: boolean; @@ -35,15 +35,15 @@ export type VateudCoreSoloCreateResponseT = { facility: number; created_at: string; updated_at: string; - } -} + }; +}; export type VateudCoreSoloRemoveT = { local_solo_id: number; vateud_solo_id: number; -} +}; export type VateudCoreSoloRemoveResponseT = { success: boolean; message: string; -} \ No newline at end of file +}; diff --git a/src/models/User.ts b/src/models/User.ts index 726563e..70ed685 100644 --- a/src/models/User.ts +++ b/src/models/User.ts @@ -15,7 +15,8 @@ import { UserBelongToMentorGroups } from "./through/UserBelongToMentorGroups"; import UserExtensions from "./extensions/UserExtensions"; import { EndorsementGroupsBelongsToUsers } from "./through/EndorsementGroupsBelongsToUsers"; import { UserSolo } from "./UserSolo"; -import {USER_TABLE_ATTRIBUTES, USER_TABLE_NAME} from "../../db/migrations/20221115171242-create-user-table"; +import { USER_TABLE_ATTRIBUTES, USER_TABLE_NAME } from "../../db/migrations/20221115171242-create-user-table"; +import { UserNote } from "./UserNote"; export class User extends Model, InferCreationAttributes> { // @@ -51,6 +52,7 @@ export class User extends Model, InferCreationAttributes; declare fast_track_requests?: NonAttribute; declare roles?: NonAttribute; + declare user_notes?: NonAttribute; // // Through Association Placeholders @@ -73,6 +75,7 @@ export class User extends Model, InferCreationAttributes; fast_track_requests: Association; roles: Association; + user_notes: Association; }; hasRole = UserExtensions.hasRole.bind(this); @@ -164,27 +167,25 @@ export class User extends Model, InferCreationAttributes, InferCreationAttributes> { // @@ -29,9 +26,7 @@ export class UserData extends Model, InferCreationAttr declare updatedAt: CreationOptional | null; } -UserData.init(USER_DATA_TABLE_ATTRIBUTES, - { - tableName: USER_DATA_TABLE_NAME, - sequelize: sequelize, - } -); +UserData.init(USER_DATA_TABLE_ATTRIBUTES, { + tableName: USER_DATA_TABLE_NAME, + sequelize: sequelize, +}); diff --git a/src/models/associations/UserAssociations.ts b/src/models/associations/UserAssociations.ts index dd26b6e..18f482f 100644 --- a/src/models/associations/UserAssociations.ts +++ b/src/models/associations/UserAssociations.ts @@ -99,7 +99,7 @@ export function registerUserAssociations() { // 1 : n // User.hasMany(UserNote, { - as: "notes", + as: "user_notes", foreignKey: "user_id", sourceKey: "id", }); diff --git a/src/utility/Validator.ts b/src/utility/Validator.ts index d10e31f..2e1ec40 100644 --- a/src/utility/Validator.ts +++ b/src/utility/Validator.ts @@ -25,7 +25,7 @@ enum ValidationTypeEnum { NUMBER, NOT_EQUAL, IN_ARRAY, - IS_ARRAY, + IS_ARRAY, // This will convert strings to json and check if it is a valid array. The parsed value is stored in the original key! VALID_DATE, ARRAY_LENGTH_GT, VALID_JSON, // This checks if the data is valid json and assigns the parsed value to the key! @@ -164,8 +164,15 @@ function _validateEntry( break; case ValidationTypeEnum.IS_ARRAY: - if (!Array.isArray(data)) { - addErrorEntry("Parameter is not an array"); + try { + if (typeof data == "string") { + rawData[key] = JSON.parse(data); + } + if (!Array.isArray(rawData[key])) { + addErrorEntry("Parameter is not an array"); + } + } catch (e) { + addErrorEntry("Paramter is not valid JSON"); } break;