From cbef768a4bfbb9e4bcf26dd74cdfc965e98c81dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikolas=20G=C3=B6rlitz?= Date: Fri, 10 Nov 2023 20:20:02 +0100 Subject: [PATCH] Solos, remove skillsets, and much more... :) --- .../20221115171243-create-user-solos.js | 60 ++++++++++++ ...246-create-course-skill-templates-table.js | 32 ------- .../20221115171247-create-courses-table.js | 10 -- ...endorsement-groups-belongto-users-table.js | 33 ++++--- ...221115171257-create-training-logs-table.js | 5 - ...259-create-user-belongsto-courses-table.js | 5 - ...261-create-course-skill-templates-table.js | 32 ------- src/Router.ts | 27 ++++-- .../_validators/_GenericValidator.ts | 12 +-- .../course/CourseAdministrationController.ts | 19 ++-- .../course/CourseInformationController.ts | 2 +- .../SkillTemplateAdminController.ts | 16 ---- src/controllers/solo/SoloAdminController.ts | 89 +++++++++++++++++ src/controllers/solo/_SoloAdmin.validator.ts | 60 ++++++++++++ .../training-log/TrainingLogController.ts | 2 +- .../TrainingRequestAdminController.ts | 4 +- .../TrainingSessionAdminController.ts | 96 +++++++++++++++---- .../_TrainingSessionAdminValidator.ts | 26 +++++ .../TrainingTypeAdminController.ts | 8 +- .../training-type/TrainingTypeController.ts | 8 +- ...rCourseProgressAdministrationController.ts | 46 +++++---- ...rCourseProgressAdministration.validator.ts | 10 +- src/controllers/user/UserCourseController.ts | 3 +- .../user/UserEndorsementAdminController.ts | 34 +++++++ .../user/UserInformationAdminController.ts | 17 ++-- .../user/UserNoteAdminController.ts | 6 +- src/controllers/user/_UserAdmin.validator.ts | 30 ++++++ src/models/Course.ts | 14 --- src/models/CourseSkillTemplate.ts | 42 -------- src/models/TrainingLog.ts | 5 - src/models/User.ts | 5 + src/models/UserSolo.ts | 87 +++++++++++++++++ src/models/associations/CourseAssociations.ts | 10 -- src/models/associations/UserAssociations.ts | 41 ++++++++ .../EndorsementGroupsBelongsToUsers.ts | 44 +++++---- src/models/through/UsersBelongsToCourses.ts | 5 - 36 files changed, 641 insertions(+), 304 deletions(-) create mode 100644 db/migrations/20221115171243-create-user-solos.js delete mode 100644 db/migrations/20221115171246-create-course-skill-templates-table.js delete mode 100644 db/migrations/20221115171261-create-course-skill-templates-table.js delete mode 100644 src/controllers/skill-template/SkillTemplateAdminController.ts create mode 100644 src/controllers/solo/SoloAdminController.ts create mode 100644 src/controllers/solo/_SoloAdmin.validator.ts create mode 100644 src/controllers/user/UserEndorsementAdminController.ts create mode 100644 src/controllers/user/_UserAdmin.validator.ts delete mode 100644 src/models/CourseSkillTemplate.ts create mode 100644 src/models/UserSolo.ts diff --git a/db/migrations/20221115171243-create-user-solos.js b/db/migrations/20221115171243-create-user-solos.js new file mode 100644 index 0000000..5582437 --- /dev/null +++ b/db/migrations/20221115171243-create-user-solos.js @@ -0,0 +1,60 @@ +const { DataType } = require("sequelize-typescript"); + +const UserSolosModelAttributes = { + id: { + type: DataType.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + user_id: { + unique: true, + type: DataType.INTEGER, + allowNull: false, + references: { + model: "users", + key: "id", + }, + onUpdate: "cascade", + onDelete: "cascade", + }, + created_by: { + type: DataType.INTEGER, + allowNull: true, + references: { + model: "users", + key: "id", + }, + onUpdate: "cascade", + onDelete: "set null", + }, + solo_used: { + type: DataType.INTEGER, + default: 0, + allowNull: false, + }, + extension_count: { + type: DataType.INTEGER, + default: 0, + allowNull: false, + }, + current_solo_start: { + type: DataType.DATE, + allowNull: true, + }, + current_solo_end: { + type: DataType.DATE, + allowNull: true, + }, + createdAt: DataType.DATE, + updatedAt: DataType.DATE, +}; + +module.exports = { + async up(queryInterface) { + await queryInterface.createTable("user_solos", UserSolosModelAttributes); + }, + + async down(queryInterface) { + await queryInterface.dropTable("user_solos"); + }, +}; diff --git a/db/migrations/20221115171246-create-course-skill-templates-table.js b/db/migrations/20221115171246-create-course-skill-templates-table.js deleted file mode 100644 index 1217879..0000000 --- a/db/migrations/20221115171246-create-course-skill-templates-table.js +++ /dev/null @@ -1,32 +0,0 @@ -const { DataType } = require("sequelize-typescript"); - -const DataModelAttributes = { - id: { - type: DataType.INTEGER, - primaryKey: true, - autoIncrement: true, - }, - name: { - type: DataType.STRING, - allowNull: false, - }, - content: { - type: DataType.JSON, - allowNull: false, - defaultValue: [], - }, - createdAt: DataType.DATE, - updatedAt: DataType.DATE, -}; - -module.exports = { - async up(queryInterface) { - await queryInterface.createTable("course_skill_templates", DataModelAttributes); - }, - - async down(queryInterface) { - await queryInterface.dropTable("course_skill_templates"); - }, - - DataModelAttributes, -}; diff --git a/db/migrations/20221115171247-create-courses-table.js b/db/migrations/20221115171247-create-courses-table.js index 9ad74bc..442c152 100644 --- a/db/migrations/20221115171247-create-courses-table.js +++ b/db/migrations/20221115171247-create-courses-table.js @@ -52,16 +52,6 @@ const DataModelAttributes = { onUpdate: "cascade", onDelete: "cascade", }, - skill_template_id: { - type: DataType.INTEGER, - allowNull: true, - references: { - model: "course_skill_templates", - key: "id", - }, - onUpdate: "cascade", - onDelete: "set null", - }, createdAt: DataType.DATE, updatedAt: DataType.DATE, deletedAt: { diff --git a/db/migrations/20221115171255-create-endorsement-groups-belongto-users-table.js b/db/migrations/20221115171255-create-endorsement-groups-belongto-users-table.js index d3c31bf..fd06ffc 100644 --- a/db/migrations/20221115171255-create-endorsement-groups-belongto-users-table.js +++ b/db/migrations/20221115171255-create-endorsement-groups-belongto-users-table.js @@ -1,7 +1,5 @@ const { DataType } = require("sequelize-typescript"); -const ratingEnum = ["s1", "s2", "s3", "c1", "c3"]; - const DataModelAttributes = { id: { type: DataType.INTEGER, @@ -28,22 +26,27 @@ const DataModelAttributes = { onUpdate: "cascade", onDelete: "cascade", }, - solo: { - type: DataType.BOOLEAN, - defaultValue: false, - allowNull: false, - }, - solo_rating: { - type: DataType.ENUM(...ratingEnum), + created_by: { + type: DataType.INTEGER, allowNull: true, - defaultValue: "s1", - }, - solo_expires: { - type: DataType.DATE, + references: { + model: "users", + key: "id", + }, + onUpdate: "cascade", + onDelete: "set null", }, - solo_extension_count: { + solo_id: { type: DataType.INTEGER, - defaultValue: 0, + allowNull: true, + references: { + model: "user_solos", + key: "id", + }, + onUpdate: "cascade", + onDelete: "set null", + // The solo is only ever deleted IFF a rating change has taken place. + // Therefore, we can just set it null to indicate that the solo is over. }, createdAt: DataType.DATE, updatedAt: DataType.DATE, diff --git a/db/migrations/20221115171257-create-training-logs-table.js b/db/migrations/20221115171257-create-training-logs-table.js index 9e17196..b35e3a5 100644 --- a/db/migrations/20221115171257-create-training-logs-table.js +++ b/db/migrations/20221115171257-create-training-logs-table.js @@ -15,11 +15,6 @@ const DataModelAttributes = { allowNull: false, default: [], }, - log_public: { - type: DataType.BOOLEAN, - allowNull: false, - defaultValue: true, - }, author_id: { type: DataType.INTEGER, allowNull: false, diff --git a/db/migrations/20221115171259-create-user-belongsto-courses-table.js b/db/migrations/20221115171259-create-user-belongsto-courses-table.js index 70a5592..1d82f48 100644 --- a/db/migrations/20221115171259-create-user-belongsto-courses-table.js +++ b/db/migrations/20221115171259-create-user-belongsto-courses-table.js @@ -37,11 +37,6 @@ const DataModelAttributes = { onUpdate: "cascade", onDelete: "set null", }, - skill_set: { - type: DataType.JSON, - allowNull: true, - defaultValue: null, - }, completed: { type: DataType.BOOLEAN, allowNull: false, diff --git a/db/migrations/20221115171261-create-course-skill-templates-table.js b/db/migrations/20221115171261-create-course-skill-templates-table.js deleted file mode 100644 index c0fcd0e..0000000 --- a/db/migrations/20221115171261-create-course-skill-templates-table.js +++ /dev/null @@ -1,32 +0,0 @@ -const { DataType } = require("sequelize-typescript"); - -const DataModelAttributes = { - id: { - type: DataType.INTEGER, - primaryKey: true, - autoIncrement: true, - }, - name: { - type: DataType.STRING, - allowNull: false, - }, - content: { - type: DataType.JSON, - allowNull: false, - default: [], - }, - createdAt: DataType.DATE, - updatedAt: DataType.DATE, -}; - -module.exports = { - async up(queryInterface) { - await queryInterface.createTable("course_skill_templates", DataModelAttributes); - }, - - async down(queryInterface) { - await queryInterface.dropTable("course_skill_templates"); - }, - - DataModelAttributes, -}; diff --git a/src/Router.ts b/src/Router.ts index 5afa871..a595528 100644 --- a/src/Router.ts +++ b/src/Router.ts @@ -13,7 +13,6 @@ import UserInformationAdminController from "./controllers/user/UserInformationAd import UserNoteAdminController from "./controllers/user/UserNoteAdminController"; import UserController from "./controllers/user/UserAdminController"; import TrainingRequestAdminController from "./controllers/training-request/TrainingRequestAdminController"; -import SkillTemplateAdministrationController from "./controllers/skill-template/SkillTemplateAdminController"; import TrainingTypeAdministrationController from "./controllers/training-type/TrainingTypeAdminController"; import FastTrackAdministrationController from "./controllers/fast-track/FastTrackAdminController"; import LogTemplateAdministrationController from "./controllers/log-template/LogTemplateAdminController"; @@ -33,6 +32,8 @@ import TrainingLogController from "./controllers/training-log/TrainingLogControl import ActionRequirementAdministrationController from "./controllers/action-requirement/ActionRequirementAdministrationController"; import EndorsementGroupAdminController from "./controllers/endorsement-group/EndorsementGroupAdminController"; import UserCourseProgressAdministrationController from "./controllers/user-course-progress/UserCourseProgressAdministrationController"; +import SoloAdminController from "./controllers/solo/SoloAdminController"; +import UserEndorsementAdminController from "./controllers/user/UserEndorsementAdminController"; const routerGroup = (callback: (router: Router) => void) => { const router = Router(); @@ -188,6 +189,7 @@ router.use( r.delete("/training", TrainingSessionAdminController.deleteTrainingSession); r.get("/:uuid", TrainingSessionAdminController.getByUUID); r.patch("/:uuid", TrainingSessionAdminController.updateByUUID); + r.get("/:uuid/mentors", TrainingSessionAdminController.getAvailableMentorsByUUID); r.get("/training-types/:uuid", TrainingSessionAdminController.getCourseTrainingTypes); r.get("/log-template/:uuid", TrainingSessionAdminController.getLogTemplate); @@ -220,7 +222,6 @@ router.use( r.use( "/course", routerGroup((r: Router) => { - // TODO: CLEANUP THE TOP 2 r.get("/mentorable", CourseAdministrationController.getMentorable); r.get("/editable", CourseAdministrationController.getEditable); // ----------------------- @@ -251,13 +252,6 @@ router.use( }) ); - r.use( - "/skill-template", - routerGroup((r: Router) => { - r.get("/", SkillTemplateAdministrationController.getAll); - }) - ); - r.use( "/training-type", routerGroup((r: Router) => { @@ -293,6 +287,21 @@ router.use( }) ); + r.use( + "/endorsement", + routerGroup((r: Router) => { + r.post("/", UserEndorsementAdminController.addEndorsement); + }) + ); + + r.use( + "/solo", + routerGroup((r: Router) => { + r.post("/", SoloAdminController.createSolo); + r.patch("/", SoloAdminController.updateSolo); + }) + ); + r.use( "/mentor-group", routerGroup((r: Router) => { diff --git a/src/controllers/_validators/_GenericValidator.ts b/src/controllers/_validators/_GenericValidator.ts index 1d9cbd2..afa69b4 100644 --- a/src/controllers/_validators/_GenericValidator.ts +++ b/src/controllers/_validators/_GenericValidator.ts @@ -1,13 +1,13 @@ -import ValidationHelper, {ValidationOptions} from "../../utility/helper/ValidationHelper"; -import {ValidationException} from "../../exceptions/ValidationException"; +import ValidationHelper, { ValidationOptions } from "../../utility/helper/ValidationHelper"; +import { ValidationException } from "../../exceptions/ValidationException"; function validateCID(data: any, key: string = "cid") { const validation = ValidationHelper.validate([ { name: key, validationObject: data[key], - toValidate: [{ val: ValidationOptions.NON_NULL }, {val: ValidationOptions.NUMBER}], - } + toValidate: [{ val: ValidationOptions.NON_NULL }, { val: ValidationOptions.NUMBER }], + }, ]); if (validation.invalid) { @@ -16,5 +16,5 @@ function validateCID(data: any, key: string = "cid") { } export default { - validateCID -} \ No newline at end of file + validateCID, +}; diff --git a/src/controllers/course/CourseAdministrationController.ts b/src/controllers/course/CourseAdministrationController.ts index 22fc584..ed13c10 100644 --- a/src/controllers/course/CourseAdministrationController.ts +++ b/src/controllers/course/CourseAdministrationController.ts @@ -27,7 +27,6 @@ interface ICourseBody { self_enrol_enabled: boolean; training_type_id: string; mentor_group_id: string; - skill_template_id?: string; } /** @@ -48,7 +47,6 @@ async function createCourse(request: Request, response: Response, next: NextFunc return; } - const skillTemplateID = isNaN(Number(body.skill_template_id)) || body.skill_template_id == "-1" ? null : Number(body.skill_template_id); const t = await sequelize.transaction(); try { @@ -62,7 +60,6 @@ async function createCourse(request: Request, response: Response, next: NextFunc is_active: body.active, self_enrollment_enabled: body.self_enrol_enabled, initial_training_type: Number(body.training_type_id), - skill_template_id: skillTemplateID, }, { transaction: t, @@ -80,6 +77,16 @@ async function createCourse(request: Request, response: Response, next: NextFunc } ); + await TrainingTypesBelongsToCourses.create( + { + course_id: course.id, + training_type_id: Number(body.training_type_id), + }, + { + transaction: t, + } + ); + await t.commit(); response.status(HttpStatusCode.Created).send({ uuid: course.uuid }); } catch (e) { @@ -108,7 +115,6 @@ async function updateCourse(request: Request, response: Response, next: NextFunc return; } - const skillTemplateID = isNaN(Number(body.skill_template_id)) || body.skill_template_id == "-1" ? null : Number(body.skill_template_id); await Course.update( { name: body.name_de, @@ -118,7 +124,6 @@ async function updateCourse(request: Request, response: Response, next: NextFunc is_active: body.active, self_enrollment_enabled: body.self_enrol_enabled, initial_training_type: Number(body.training_type_id), - skill_template_id: skillTemplateID, }, { where: { @@ -145,7 +150,7 @@ async function getAllCourses(request: Request, response: Response) { /** * Gets basic course information based on the provided course UUID. - * This includes the initial training type alongside the skill template used for this course (or null, if this is not set) + * This includes the initial training type used for this course * @param request * @param response */ @@ -156,7 +161,7 @@ async function getCourse(request: Request, response: Response) { where: { uuid: params.course_uuid, }, - include: [Course.associations.training_type, Course.associations.skill_template], + include: [Course.associations.training_type], }); if (course == null) { diff --git a/src/controllers/course/CourseInformationController.ts b/src/controllers/course/CourseInformationController.ts index 495cd52..116754b 100644 --- a/src/controllers/course/CourseInformationController.ts +++ b/src/controllers/course/CourseInformationController.ts @@ -108,7 +108,7 @@ async function getCourseTrainingInformationByUUID(request: Request, response: Re include: [ { association: TrainingSession.associations.training_logs, - attributes: ["uuid", "log_public", "id"], + attributes: ["uuid", "id"], through: { attributes: [] }, }, { diff --git a/src/controllers/skill-template/SkillTemplateAdminController.ts b/src/controllers/skill-template/SkillTemplateAdminController.ts deleted file mode 100644 index 4fabe7e..0000000 --- a/src/controllers/skill-template/SkillTemplateAdminController.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Request, Response } from "express"; -import { CourseSkillTemplate } from "../../models/CourseSkillTemplate"; - -/** - * Gets all skill templates - * @param request - * @param response - */ -async function getAll(request: Request, response: Response) { - const trainingTypes = await CourseSkillTemplate.findAll(); - response.send(trainingTypes); -} - -export default { - getAll, -}; diff --git a/src/controllers/solo/SoloAdminController.ts b/src/controllers/solo/SoloAdminController.ts new file mode 100644 index 0000000..189bfc2 --- /dev/null +++ b/src/controllers/solo/SoloAdminController.ts @@ -0,0 +1,89 @@ +import { NextFunction, Request, Response } from "express"; +import _SoloAdminValidator from "./_SoloAdmin.validator"; +import { UserSolo } from "../../models/UserSolo"; +import dayjs from "dayjs"; +import { HttpStatusCode } from "axios"; +import { User } from "../../models/User"; +import { EndorsementGroupsBelongsToUsers } from "../../models/through/EndorsementGroupsBelongsToUsers"; + +type CreateSoloRequestBody = { + solo_duration: string; + solo_start: string; + trainee_id: number; + endorsement_group_id: string; +}; + +type UpdateSoloRequestBody = Omit; + +async function createSolo(request: Request, response: Response, next: NextFunction) { + try { + const user: User = request.body.user; + const body = request.body as CreateSoloRequestBody; + _SoloAdminValidator.validateCreateRequest(body); + + const startDate = dayjs.utc(body.solo_start); + const endDate = startDate.add(Number(body.solo_duration), "days"); + + const solo = await UserSolo.create({ + user_id: body.trainee_id, + created_by: user.id, + // @ts-ignore | We checked whether the string is contained in the array of ratings, so all fine. + solo_rating: body.solo_rating, + solo_used: Number(body.solo_duration), + extension_count: 0, + current_solo_start: startDate.toDate(), + current_solo_end: endDate.toDate(), + }); + + await EndorsementGroupsBelongsToUsers.create({ + user_id: body.trainee_id, + endorsement_group_id: Number(body.endorsement_group_id), + solo_id: solo.id, + }); + + const returnUser = await User.findOne({ + where: { + id: body.trainee_id, + }, + include: [User.associations.user_solo, User.associations.endorsement_groups], + }); + + response.status(HttpStatusCode.Created).send(returnUser); + } catch (e) { + next(e); + } +} + +async function updateSolo(request: Request, response: Response, next: NextFunction) { + try { + const user: User = request.body.user; + const body = request.body as UpdateSoloRequestBody; + _SoloAdminValidator.validateUpdateRequest(body); + + const currentSolo = await UserSolo.findOne({ + where: { + user_id: body.trainee_id, + }, + }); + + if (currentSolo == null) { + response.sendStatus(HttpStatusCode.NotFound); + return; + } + + const newDuration = currentSolo.solo_used + Number(body.solo_duration); + await currentSolo.update({ + solo_used: newDuration, + current_solo_end: dayjs.utc(currentSolo.current_solo_start).add(newDuration, "days").toDate(), + }); + + response.sendStatus(HttpStatusCode.Ok); + } catch (e) { + next(e); + } +} + +export default { + createSolo, + updateSolo, +}; diff --git a/src/controllers/solo/_SoloAdmin.validator.ts b/src/controllers/solo/_SoloAdmin.validator.ts new file mode 100644 index 0000000..30e57c1 --- /dev/null +++ b/src/controllers/solo/_SoloAdmin.validator.ts @@ -0,0 +1,60 @@ +import ValidationHelper, { ValidationOptions } from "../../utility/helper/ValidationHelper"; +import { ValidationException } from "../../exceptions/ValidationException"; + +function validateCreateRequest(data: any) { + const validation = ValidationHelper.validate([ + { + name: "solo_duration", + validationObject: data.solo_duration, + toValidate: [{ val: ValidationOptions.NON_NULL }, { val: ValidationOptions.NUMBER }], + }, + { + name: "endorsement_group_id", + validationObject: data.endorsement_group_id, + toValidate: [{ val: ValidationOptions.NON_NULL }, { val: ValidationOptions.NUMBER }], + }, + { + name: "solo_start", + validationObject: data.solo_start, + toValidate: [{ val: ValidationOptions.VALID_DATE }], + }, + { + name: "trainee_id", + validationObject: data.trainee_id, + toValidate: [{ val: ValidationOptions.NON_NULL }, { val: ValidationOptions.NUMBER }], + }, + ]); + + if (validation.invalid) { + throw new ValidationException(validation); + } +} + +function validateUpdateRequest(data: any) { + const validation = ValidationHelper.validate([ + { + name: "solo_duration", + validationObject: data.solo_duration, + toValidate: [{ val: ValidationOptions.NON_NULL }, { val: ValidationOptions.NUMBER }], + }, + { + name: "solo_start", + validationObject: data.solo_start, + toValidate: [{ val: ValidationOptions.VALID_DATE }], + }, + { + name: "trainee_id", + validationObject: data.trainee_id, + toValidate: [{ val: ValidationOptions.NON_NULL }, { val: ValidationOptions.NUMBER }], + }, + ]); + + if (validation.invalid) { + throw new ValidationException(validation); + } +} + +export default { + validateCreateRequest, + validateUpdateRequest, +}; diff --git a/src/controllers/training-log/TrainingLogController.ts b/src/controllers/training-log/TrainingLogController.ts index 0ca4ede..d4ca8f6 100644 --- a/src/controllers/training-log/TrainingLogController.ts +++ b/src/controllers/training-log/TrainingLogController.ts @@ -10,7 +10,7 @@ async function getByUUID(request: Request, response: Response, next: NextFunctio where: { uuid: params.uuid, }, - include: [TrainingLog.associations.author] + include: [TrainingLog.associations.author], }); if (trainingLog == null) { diff --git a/src/controllers/training-request/TrainingRequestAdminController.ts b/src/controllers/training-request/TrainingRequestAdminController.ts index 2d384d6..92bda53 100644 --- a/src/controllers/training-request/TrainingRequestAdminController.ts +++ b/src/controllers/training-request/TrainingRequestAdminController.ts @@ -1,4 +1,4 @@ -import { Request, Response } from "express"; +import { NextFunction, Request, Response } from "express"; import { User } from "../../models/User"; import { MentorGroup } from "../../models/MentorGroup"; import { TrainingRequest } from "../../models/TrainingRequest"; @@ -6,6 +6,8 @@ import { Op } from "sequelize"; import NotificationLibrary from "../../libraries/notification/NotificationLibrary"; import { TrainingType } from "../../models/TrainingType"; import { TrainingSession } from "../../models/TrainingSession"; +import { Course } from "../../models/Course"; +import { HttpStatusCode } from "axios"; /** * Returns all currently open training requests diff --git a/src/controllers/training-session/TrainingSessionAdminController.ts b/src/controllers/training-session/TrainingSessionAdminController.ts index 47f8116..220015b 100644 --- a/src/controllers/training-session/TrainingSessionAdminController.ts +++ b/src/controllers/training-session/TrainingSessionAdminController.ts @@ -15,6 +15,7 @@ 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"; /** * Creates a new training session with one user and one mentor @@ -115,29 +116,34 @@ async function createTrainingSession(request: Request, response: Response) { response.send(trainingSession); } -async function updateByUUID(request: Request, response: Response) { - const user = request.body.user; - const sessionUUID = request.params.uuid; - const data = request.body as { date: string; training_station?: string }; +async function updateByUUID(request: Request, response: Response, next: NextFunction) { + try { + const params = request.params as { uuid: string }; + const body = request.body as { date: string; mentor_id: string; training_station_id: string }; + _TrainingSessionAdminValidator.validateUpdateRequest(body); - const session = await TrainingSession.findOne({ - where: { - uuid: sessionUUID, - }, - }); + const session = await TrainingSession.findOne({ + where: { + uuid: params.uuid, + }, + }); - if (session == null) { - response.sendStatus(HttpStatusCode.BadRequest); - return; - } + if (session == null) { + response.sendStatus(HttpStatusCode.NotFound); + return; + } - const training_station_id = Number(data.training_station); - await session.update({ - date: dayjs.utc(data.date).toDate(), - training_station_id: isNaN(training_station_id) || training_station_id == -1 ? null : training_station_id, - }); + const trainingStationIDNum = Number(body.training_station_id); + await session.update({ + mentor_id: Number(body.mentor_id), + date: dayjs.utc(body.date).toDate(), + training_station_id: trainingStationIDNum == -1 ? null : trainingStationIDNum, + }); - response.sendStatus(HttpStatusCode.Ok); + response.sendStatus(HttpStatusCode.Ok); + } catch (e) { + next(e); + } } /** @@ -369,7 +375,6 @@ async function createTrainingLogs(request: Request, response: Response, next: Ne user_id: number; next_training_id: number; course_completed: boolean; - log_public: boolean; passed: boolean; user_log: any[]; }[]; @@ -398,7 +403,6 @@ async function createTrainingLogs(request: Request, response: Response, next: Ne { uuid: generateUUID(), content: body[i].user_log, - log_public: body[i].log_public, author_id: user.id, }, { @@ -496,6 +500,55 @@ async function createTrainingLogs(request: Request, response: Response, next: Ne } } +/** + * Returns all the available + * @param request + * @param response + * @param next + */ +async function getAvailableMentorsByUUID(request: Request, response: Response, next: NextFunction) { + try { + const params = request.params as { uuid: string }; + + // Find the training request, so we can find the course! + const trainingSession = await TrainingSession.findOne({ + where: { + uuid: params.uuid, + }, + include: [ + { + association: TrainingSession.associations.course, + include: [ + { + association: Course.associations.mentor_groups, + include: [MentorGroup.associations.users], + }, + ], + }, + ], + }); + + if (trainingSession == null || trainingSession.course == null) { + response.sendStatus(HttpStatusCode.NotFound); + return; + } + + let mentors: any[] = []; + + for (const mentorGroup of trainingSession.course.mentor_groups ?? []) { + for (const user of mentorGroup.users ?? []) { + if (mentors.find(u => u.id == user.id) == null) { + mentors.push(user); + } + } + } + + response.send(mentors); + } catch (e) { + next(e); + } +} + export default { getByUUID, createTrainingSession, @@ -506,4 +559,5 @@ export default { createTrainingLogs, getCourseTrainingTypes, getPlanned, + getAvailableMentorsByUUID, }; diff --git a/src/controllers/training-session/_TrainingSessionAdminValidator.ts b/src/controllers/training-session/_TrainingSessionAdminValidator.ts index f9692f4..feacc33 100644 --- a/src/controllers/training-session/_TrainingSessionAdminValidator.ts +++ b/src/controllers/training-session/_TrainingSessionAdminValidator.ts @@ -1,5 +1,6 @@ import { ValidatorType } from "../_validators/ValidatorType"; import ValidationHelper, { ValidationOptions } from "../../utility/helper/ValidationHelper"; +import { ValidationException } from "../../exceptions/ValidationException"; function validateCreateSessionRequest(data: any): ValidatorType { return ValidationHelper.validate([ @@ -26,6 +27,31 @@ function validateCreateSessionRequest(data: any): ValidatorType { ]); } +function validateUpdateRequest(data: any) { + const validation = ValidationHelper.validate([ + { + name: "date", + validationObject: data.date, + toValidate: [{ val: ValidationOptions.NON_NULL }, { val: ValidationOptions.VALID_DATE }], + }, + { + name: "mentor_id", + validationObject: data.mentor_id, + toValidate: [{ val: ValidationOptions.NON_NULL }, { val: ValidationOptions.NUMBER }], + }, + { + name: "training_station_id", + validationObject: data.training_station_id, + toValidate: [{ val: ValidationOptions.NON_NULL }, { val: ValidationOptions.NUMBER }], + }, + ]); + + if (validation.invalid) { + throw new ValidationException(validation); + } +} + export default { validateCreateSessionRequest, + validateUpdateRequest, }; diff --git a/src/controllers/training-type/TrainingTypeAdminController.ts b/src/controllers/training-type/TrainingTypeAdminController.ts index 3a067b4..deaf641 100644 --- a/src/controllers/training-type/TrainingTypeAdminController.ts +++ b/src/controllers/training-type/TrainingTypeAdminController.ts @@ -1,4 +1,4 @@ -import {NextFunction, Request, Response} from "express"; +import { NextFunction, Request, Response } from "express"; import { TrainingType } from "../../models/TrainingType"; import ValidationHelper, { ValidationOptions } from "../../utility/helper/ValidationHelper"; import { TrainingStation } from "../../models/TrainingStation"; @@ -23,18 +23,18 @@ async function getAll(request: Request, response: Response) { */ async function getByID(request: Request, response: Response, next: NextFunction) { try { - const params = request.params as {id: string}; + const params = request.params as { id: string }; const validation = ValidationHelper.validate([ { name: "id", validationObject: params.id, - toValidate: [{val: ValidationOptions.NON_NULL}], + toValidate: [{ val: ValidationOptions.NON_NULL }], }, ]); if (validation.invalid) { - response.status(400).send({validation: validation.message, validation_failed: validation.invalid}); + response.status(400).send({ validation: validation.message, validation_failed: validation.invalid }); return; } diff --git a/src/controllers/training-type/TrainingTypeController.ts b/src/controllers/training-type/TrainingTypeController.ts index df7b269..66153e9 100644 --- a/src/controllers/training-type/TrainingTypeController.ts +++ b/src/controllers/training-type/TrainingTypeController.ts @@ -1,21 +1,21 @@ -import {NextFunction, Request, Response} from "express"; +import { NextFunction, Request, Response } from "express"; import ValidationHelper, { ValidationOptions } from "../../utility/helper/ValidationHelper"; import { TrainingType } from "../../models/TrainingType"; async function getByID(request: Request, response: Response, next: NextFunction) { try { - const params = request.params as {id: string}; + const params = request.params as { id: string }; const validation = ValidationHelper.validate([ { name: "id", validationObject: params.id, - toValidate: [{val: ValidationOptions.NON_NULL}], + toValidate: [{ val: ValidationOptions.NON_NULL }], }, ]); if (validation.invalid) { - response.status(400).send({validation: validation.message, validation_failed: validation.invalid}); + response.status(400).send({ validation: validation.message, validation_failed: validation.invalid }); return; } diff --git a/src/controllers/user-course-progress/UserCourseProgressAdministrationController.ts b/src/controllers/user-course-progress/UserCourseProgressAdministrationController.ts index 59bd37c..072fd10 100644 --- a/src/controllers/user-course-progress/UserCourseProgressAdministrationController.ts +++ b/src/controllers/user-course-progress/UserCourseProgressAdministrationController.ts @@ -3,10 +3,10 @@ import _UserCourseProgressAdministrationValidator from "./_UserCourseProgressAdm import { User } from "../../models/User"; import { HttpStatusCode } from "axios"; import { Course } from "../../models/Course"; -import {TrainingRequest} from "../../models/TrainingRequest"; -import {TrainingSession} from "../../models/TrainingSession"; +import { TrainingRequest } from "../../models/TrainingRequest"; +import { TrainingSession } from "../../models/TrainingSession"; import _GenericValidator from "../_validators/_GenericValidator"; -import {UsersBelongsToCourses} from "../../models/through/UsersBelongsToCourses"; +import { UsersBelongsToCourses } from "../../models/through/UsersBelongsToCourses"; async function getInformation(request: Request, response: Response, next: NextFunction) { try { @@ -35,22 +35,25 @@ async function getInformation(request: Request, response: Response, next: NextFu }, { association: User.associations.training_requests, - include: [TrainingRequest.associations.training_type, { - association: TrainingRequest.associations.training_session, - attributes: ["id", "mentor_id"], - include: [TrainingSession.associations.mentor] - }] + include: [ + TrainingRequest.associations.training_type, + { + association: TrainingRequest.associations.training_session, + attributes: ["id", "mentor_id"], + include: [TrainingSession.associations.mentor], + }, + ], }, { association: User.associations.training_sessions, through: { as: "training_session_belongs_to_users", }, - include: [TrainingSession.associations.training_type, TrainingSession.associations.mentor] + include: [TrainingSession.associations.training_type, TrainingSession.associations.mentor], }, { - association: User.associations.training_logs - } + association: User.associations.training_logs, + }, ], }); @@ -73,22 +76,25 @@ async function getInformation(request: Request, response: Response, next: NextFu */ async function updateInformation(request: Request, response: Response, next: NextFunction) { try { - const body = request.body as {course_completed: "0" | "1", user_id: string, course_uuid: string, next_training_type_id?: string,}; + const body = request.body as { course_completed: "0" | "1"; user_id: string; course_uuid: string; next_training_type_id?: string }; _UserCourseProgressAdministrationValidator.validateUpdateRequest(body); - const course = await Course.findOne({where: {uuid: body.course_uuid}}); + const course = await Course.findOne({ where: { uuid: body.course_uuid } }); if (course == null) { response.sendStatus(HttpStatusCode.NotFound); return; } - await UsersBelongsToCourses.update({ - next_training_type: body.course_completed == "1" ? null : Number(body.next_training_type_id), - completed: body.course_completed == "1" - }, { - where: {course_id: course.id, user_id: body.user_id} - }); + await UsersBelongsToCourses.update( + { + next_training_type: body.course_completed == "1" ? null : Number(body.next_training_type_id), + completed: body.course_completed == "1", + }, + { + where: { course_id: course.id, user_id: body.user_id }, + } + ); response.sendStatus(HttpStatusCode.NoContent); } catch (e) { @@ -98,5 +104,5 @@ async function updateInformation(request: Request, response: Response, next: Nex export default { getInformation, - updateInformation + updateInformation, }; diff --git a/src/controllers/user-course-progress/_UserCourseProgressAdministration.validator.ts b/src/controllers/user-course-progress/_UserCourseProgressAdministration.validator.ts index 28b067d..b296e30 100644 --- a/src/controllers/user-course-progress/_UserCourseProgressAdministration.validator.ts +++ b/src/controllers/user-course-progress/_UserCourseProgressAdministration.validator.ts @@ -1,5 +1,5 @@ -import ValidationHelper, {ValidationOptions} from "../../utility/helper/ValidationHelper"; -import {ValidationException} from "../../exceptions/ValidationException"; +import ValidationHelper, { ValidationOptions } from "../../utility/helper/ValidationHelper"; +import { ValidationException } from "../../exceptions/ValidationException"; function validateGetAllRequest(data: any) { const validation = ValidationHelper.validate([ @@ -30,13 +30,13 @@ function validateUpdateRequest(data: any) { { name: "user_id", validationObject: data.user_id, - toValidate: [{ val: ValidationOptions.NON_NULL }, {val: ValidationOptions.NUMBER}], + toValidate: [{ val: ValidationOptions.NON_NULL }, { val: ValidationOptions.NUMBER }], }, { name: "course_uuid", validationObject: data.course_uuid, toValidate: [{ val: ValidationOptions.NON_NULL }], - } + }, ]); if (validation.invalid) { @@ -46,5 +46,5 @@ function validateUpdateRequest(data: any) { export default { validateGetAllRequest, - validateUpdateRequest + validateUpdateRequest, }; diff --git a/src/controllers/user/UserCourseController.ts b/src/controllers/user/UserCourseController.ts index 296a27f..3211076 100644 --- a/src/controllers/user/UserCourseController.ts +++ b/src/controllers/user/UserCourseController.ts @@ -101,7 +101,7 @@ async function enrolInCourse(request: Request, response: Response) { where: { uuid: query.course_uuid, }, - include: [Course.associations.training_type, Course.associations.skill_template], + include: [Course.associations.training_type], }); // If Course-Instance couldn't be found, throw an error (caught locally) @@ -120,7 +120,6 @@ async function enrolInCourse(request: Request, response: Response) { course_id: course.id, completed: false, next_training_type: course?.training_type?.id ?? null, - skill_set: course?.skill_template?.content ?? null, }, }); diff --git a/src/controllers/user/UserEndorsementAdminController.ts b/src/controllers/user/UserEndorsementAdminController.ts new file mode 100644 index 0000000..1140ed6 --- /dev/null +++ b/src/controllers/user/UserEndorsementAdminController.ts @@ -0,0 +1,34 @@ +import { NextFunction, Request, Response } from "express"; +import { User } from "../../models/User"; +import _UserAdminValidator from "./_UserAdmin.validator"; +import { EndorsementGroupsBelongsToUsers } from "../../models/through/EndorsementGroupsBelongsToUsers"; +import { HttpStatusCode } from "axios"; + +async function addEndorsement(request: Request, response: Response, next: NextFunction) { + try { + const requestingUser: User = request.body.user; + const body = request.body as { user_id: string; endorsement_group_id: string }; + _UserAdminValidator.validateCreateRequest(body); + + await EndorsementGroupsBelongsToUsers.create({ + user_id: Number(body.user_id), + endorsement_group_id: Number(body.endorsement_group_id), + created_by: requestingUser.id, + }); + + const user = await User.findOne({ + where: { + id: body.user_id, + }, + include: [User.associations.endorsement_groups], + }); + + response.status(HttpStatusCode.Created).send(user?.endorsement_groups ?? []); + } catch (e) { + next(e); + } +} + +export default { + addEndorsement, +}; diff --git a/src/controllers/user/UserInformationAdminController.ts b/src/controllers/user/UserInformationAdminController.ts index b27871d..2453428 100644 --- a/src/controllers/user/UserInformationAdminController.ts +++ b/src/controllers/user/UserInformationAdminController.ts @@ -1,6 +1,7 @@ import { Request, Response } from "express"; import { User } from "../../models/User"; import PermissionHelper from "../../utility/helper/PermissionHelper"; +import { UserSolo } from "../../models/UserSolo"; /** * Returns the user data for a user with id request.query.user_id @@ -23,17 +24,13 @@ async function getUserDataByID(request: Request, response: Response) { }, include: [ { - association: User.associations.user_data, - }, - { - association: User.associations.mentor_groups, - }, - { - association: User.associations.courses, - }, - { - association: User.associations.endorsement_groups, + association: User.associations.user_solo, + include: [UserSolo.associations.solo_creator], }, + User.associations.user_data, + User.associations.mentor_groups, + User.associations.courses, + User.associations.endorsement_groups, ], }); diff --git a/src/controllers/user/UserNoteAdminController.ts b/src/controllers/user/UserNoteAdminController.ts index 9cd236f..f49e56e 100644 --- a/src/controllers/user/UserNoteAdminController.ts +++ b/src/controllers/user/UserNoteAdminController.ts @@ -10,12 +10,12 @@ import { generateUUID } from "../../utility/UUID"; * @param response */ async function getGeneralUserNotes(request: Request, response: Response) { - const user_id = request.query.user_id?.toString(); + const query = request.query as { user_id: string }; const validation = ValidationHelper.validate([ { name: "user_id", - validationObject: user_id, + validationObject: query.user_id, toValidate: [{ val: ValidationOptions.NON_NULL }], }, ]); @@ -27,7 +27,7 @@ async function getGeneralUserNotes(request: Request, response: Response) { const notes: UserNote[] = await UserNote.findAll({ where: { - user_id: user_id, + user_id: query.user_id, course_id: null, }, include: { diff --git a/src/controllers/user/_UserAdmin.validator.ts b/src/controllers/user/_UserAdmin.validator.ts new file mode 100644 index 0000000..9ee4e41 --- /dev/null +++ b/src/controllers/user/_UserAdmin.validator.ts @@ -0,0 +1,30 @@ +import ValidationHelper, { ValidationOptions } from "../../utility/helper/ValidationHelper"; +import { ValidationException } from "../../exceptions/ValidationException"; + +function validateCreateRequest(data: any) { + const validation = ValidationHelper.validate([ + { + name: "user_id", + validationObject: data.user_id, + toValidate: [{ val: ValidationOptions.NON_NULL }, { val: ValidationOptions.NUMBER }], + }, + { + name: "endorsement_group_id", + validationObject: data.endorsement_group_id, + toValidate: [{ val: ValidationOptions.NON_NULL }, { val: ValidationOptions.NUMBER }], + }, + { + name: "solo", + validationObject: data.solo, + toValidate: [{ val: ValidationOptions.NON_NULL }], + }, + ]); + + if (validation.invalid) { + throw new ValidationException(validation); + } +} + +export default { + validateCreateRequest, +}; diff --git a/src/models/Course.ts b/src/models/Course.ts index 391bc1e..f315f2a 100644 --- a/src/models/Course.ts +++ b/src/models/Course.ts @@ -4,7 +4,6 @@ import { DataType } from "sequelize-typescript"; import { sequelize } from "../core/Sequelize"; import { ActionRequirement } from "./ActionRequirement"; import { MentorGroup } from "./MentorGroup"; -import { CourseSkillTemplate } from "./CourseSkillTemplate"; import { User } from "./User"; import { CourseInformation } from "./CourseInformation"; @@ -25,7 +24,6 @@ export class Course extends Model, InferCreationAttribut // Optional Attributes // declare id: CreationOptional; - declare skill_template_id: CreationOptional> | null; declare createdAt: CreationOptional | null; declare updatedAt: CreationOptional | null; declare deletedAt: CreationOptional | null; @@ -37,7 +35,6 @@ export class Course extends Model, InferCreationAttribut declare training_types?: NonAttribute; // List of all training types assigned to this course declare action_requirements?: NonAttribute; declare mentor_groups?: NonAttribute; - declare skill_template?: NonAttribute; declare users?: NonAttribute; declare information?: NonAttribute; @@ -46,7 +43,6 @@ export class Course extends Model, InferCreationAttribut training_types: Association; action_requirements: Association; mentor_groups: Association; - skill_template: Association; users: Association; information: Association; }; @@ -125,16 +121,6 @@ Course.init( onUpdate: "cascade", onDelete: "cascade", }, - skill_template_id: { - type: DataType.INTEGER, - allowNull: true, - references: { - model: "course_skill_templates", - key: "id", - }, - onUpdate: "cascade", - onDelete: "cascade", - }, createdAt: DataType.DATE, updatedAt: DataType.DATE, deletedAt: { diff --git a/src/models/CourseSkillTemplate.ts b/src/models/CourseSkillTemplate.ts deleted file mode 100644 index ccb36d0..0000000 --- a/src/models/CourseSkillTemplate.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { CreationOptional, InferAttributes, InferCreationAttributes, Model } from "sequelize"; -import { DataType } from "sequelize-typescript"; -import { sequelize } from "../core/Sequelize"; - -export class CourseSkillTemplate extends Model, InferCreationAttributes> { - // - // Attributes - // - declare name: string; - declare content: string; - - // - // Optional Attributes - // - declare id: CreationOptional; - declare createdAt: CreationOptional | null; - declare updatedAt: CreationOptional | null; -} - -CourseSkillTemplate.init( - { - id: { - type: DataType.INTEGER, - primaryKey: true, - autoIncrement: true, - }, - name: { - type: DataType.STRING, - allowNull: false, - }, - content: { - type: DataType.JSON, - allowNull: false, - }, - createdAt: DataType.DATE, - updatedAt: DataType.DATE, - }, - { - tableName: "course_skill_templates", - sequelize: sequelize, - } -); diff --git a/src/models/TrainingLog.ts b/src/models/TrainingLog.ts index b3d6e48..df908d7 100644 --- a/src/models/TrainingLog.ts +++ b/src/models/TrainingLog.ts @@ -8,7 +8,6 @@ export class TrainingLog extends Model, InferCreati // Attributes // declare uuid: string; - declare log_public: boolean; declare content: any; declare author_id: ForeignKey; @@ -44,10 +43,6 @@ TrainingLog.init( type: DataType.JSON, allowNull: false, }, - log_public: { - type: DataType.BOOLEAN, - allowNull: false, - }, author_id: { type: DataType.INTEGER, allowNull: false, diff --git a/src/models/User.ts b/src/models/User.ts index a3f4396..fbd7884 100644 --- a/src/models/User.ts +++ b/src/models/User.ts @@ -15,6 +15,7 @@ import { TrainingRequest } from "./TrainingRequest"; import { UserBelongToMentorGroups } from "./through/UserBelongToMentorGroups"; import UserExtensions from "./extensions/UserExtensions"; import { EndorsementGroupsBelongsToUsers } from "./through/EndorsementGroupsBelongsToUsers"; +import { UserSolo } from "./UserSolo"; export class User extends Model, InferCreationAttributes> { // @@ -38,6 +39,8 @@ export class User extends Model, InferCreationAttributes; declare user_settings?: NonAttribute; + declare user_solo?: NonAttribute; + declare solos_created?: NonAttribute; declare mentor_groups?: NonAttribute; declare mentor_sessions?: NonAttribute; declare cpt_examiner_sessions?: NonAttribute; @@ -58,6 +61,8 @@ export class User extends Model, InferCreationAttributes; user_settings: Association; + user_solo: Association; + solos_created: Association; mentor_groups: Association; mentor_sessions: Association; cpt_examiner_sessions: Association; diff --git a/src/models/UserSolo.ts b/src/models/UserSolo.ts new file mode 100644 index 0000000..b164bca --- /dev/null +++ b/src/models/UserSolo.ts @@ -0,0 +1,87 @@ +import { Model, InferAttributes, CreationOptional, InferCreationAttributes, ForeignKey, Association, NonAttribute } from "sequelize"; +import { User } from "./User"; +import { DataType } from "sequelize-typescript"; +import { sequelize } from "../core/Sequelize"; + +export class UserSolo extends Model, InferCreationAttributes> { + // + // Attributes + // + declare user_id: ForeignKey; + declare solo_used: number; + declare extension_count: number; + + // + // Optional Attributes + // + declare id: CreationOptional; + declare created_by: CreationOptional>; + declare current_solo_start: CreationOptional | null; + declare current_solo_end: CreationOptional | null; + declare createdAt: CreationOptional | null; + declare updatedAt: CreationOptional | null; + + // + // Association Placeholders + // + declare user?: NonAttribute>; + declare solo_creator?: NonAttribute>; + + declare static associations: { + user: Association; + solo_creator: Association; + }; +} + +UserSolo.init( + { + id: { + type: DataType.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + user_id: { + unique: true, + type: DataType.INTEGER, + allowNull: false, + references: { + model: "users", + key: "id", + }, + onUpdate: "cascade", + onDelete: "cascade", + }, + created_by: { + type: DataType.INTEGER, + allowNull: true, + references: { + model: "users", + key: "id", + }, + onUpdate: "cascade", + onDelete: "set null", + }, + solo_used: { + type: DataType.INTEGER, + allowNull: false, + }, + extension_count: { + type: DataType.INTEGER, + allowNull: false, + }, + current_solo_start: { + type: DataType.DATE, + allowNull: true, + }, + current_solo_end: { + type: DataType.DATE, + allowNull: true, + }, + createdAt: DataType.DATE, + updatedAt: DataType.DATE, + }, + { + tableName: "user_solos", + sequelize: sequelize, + } +); diff --git a/src/models/associations/CourseAssociations.ts b/src/models/associations/CourseAssociations.ts index 813f14d..c237a94 100644 --- a/src/models/associations/CourseAssociations.ts +++ b/src/models/associations/CourseAssociations.ts @@ -5,7 +5,6 @@ import { MentorGroup } from "../MentorGroup"; import { MentorGroupsBelongsToCourses } from "../through/MentorGroupsBelongsToCourses"; import { User } from "../User"; import { UsersBelongsToCourses } from "../through/UsersBelongsToCourses"; -import { CourseSkillTemplate } from "../CourseSkillTemplate"; import { CourseInformation } from "../CourseInformation"; export function registerCourseAssociations() { @@ -28,15 +27,6 @@ export function registerCourseAssociations() { otherKey: "mentor_group_id", }); - // - // Course -> CourseSkills - // - Course.hasOne(CourseSkillTemplate, { - as: "skill_template", - foreignKey: "id", - sourceKey: "skill_template_id", - }); - // // MentorGroups <- Course // diff --git a/src/models/associations/UserAssociations.ts b/src/models/associations/UserAssociations.ts index 8e3f4a1..dd26b6e 100644 --- a/src/models/associations/UserAssociations.ts +++ b/src/models/associations/UserAssociations.ts @@ -11,6 +11,7 @@ import { RoleBelongsToUsers } from "../through/RoleBelongsToUsers"; import { TrainingSession } from "../TrainingSession"; import { TrainingLog } from "../TrainingLog"; import { TrainingSessionBelongsToUsers } from "../through/TrainingSessionBelongsToUsers"; +import { UserSolo } from "../UserSolo"; export function registerUserAssociations() { // @@ -33,6 +34,46 @@ export function registerUserAssociations() { as: "user", }); + // + // User -> UserSolo + // 1 : 1 + // + User.hasOne(UserSolo, { + sourceKey: "id", + foreignKey: "user_id", + as: "user_solo", + }); + + // + // User <- UserSolo + // 1 : 1 + // + UserSolo.belongsTo(User, { + foreignKey: "user_id", + targetKey: "id", + as: "user", + }); + + // + // User -> UserSolo + // 1 : 1 + // + User.hasMany(UserSolo, { + sourceKey: "id", + foreignKey: "created_by", + as: "solos_created", + }); + + // + // User <- UserSolo + // 1 : 1 + // + UserSolo.belongsTo(User, { + foreignKey: "created_by", + targetKey: "id", + as: "solo_creator", + }); + // // User -> UserSettings // 1 : 1 diff --git a/src/models/through/EndorsementGroupsBelongsToUsers.ts b/src/models/through/EndorsementGroupsBelongsToUsers.ts index ee82202..2fda74e 100644 --- a/src/models/through/EndorsementGroupsBelongsToUsers.ts +++ b/src/models/through/EndorsementGroupsBelongsToUsers.ts @@ -1,8 +1,8 @@ -import { CreationOptional, InferAttributes, InferCreationAttributes, Model } from "sequelize"; +import { CreationOptional, ForeignKey, InferAttributes, InferCreationAttributes, Model } from "sequelize"; import { DataType } from "sequelize-typescript"; import { sequelize } from "../../core/Sequelize"; - -const ratingEnum = ["s1", "s2", "s3", "c1", "c3"]; +import { UserSolo } from "../UserSolo"; +import { User } from "../User"; export class EndorsementGroupsBelongsToUsers extends Model< InferAttributes, @@ -11,17 +11,15 @@ export class EndorsementGroupsBelongsToUsers extends Model< // // Attributes // - declare id: number; declare endorsement_group_id: number; declare user_id: number; - declare solo: boolean; // // Optional Attributes // - declare solo_rating: CreationOptional<"s1" | "s2" | "s3" | "c1" | "c3"> | null; - declare solo_expires: CreationOptional | null; - declare solo_extension_count: CreationOptional | null; + declare id: CreationOptional; + declare solo_id: CreationOptional> | null; + declare created_by: CreationOptional>; declare createdAt: CreationOptional | null; declare updatedAt: CreationOptional | null; } @@ -53,19 +51,27 @@ EndorsementGroupsBelongsToUsers.init( onUpdate: "cascade", onDelete: "cascade", }, - solo: { - type: DataType.BOOLEAN, - allowNull: false, - }, - solo_rating: { - type: DataType.ENUM(...ratingEnum), - allowNull: false, - }, - solo_expires: { - type: DataType.DATE, + created_by: { + type: DataType.INTEGER, + allowNull: true, + references: { + model: "users", + key: "id", + }, + onUpdate: "cascade", + onDelete: "set null", }, - solo_extension_count: { + solo_id: { type: DataType.INTEGER, + allowNull: true, + references: { + model: "user_solos", + key: "id", + }, + onUpdate: "cascade", + onDelete: "set null", + // The solo is only ever deleted IFF a rating change has taken place. + // Therefore, we can just set it null to indicate that the solo is over. }, createdAt: DataType.DATE, updatedAt: DataType.DATE, diff --git a/src/models/through/UsersBelongsToCourses.ts b/src/models/through/UsersBelongsToCourses.ts index 1f894f6..8321a12 100644 --- a/src/models/through/UsersBelongsToCourses.ts +++ b/src/models/through/UsersBelongsToCourses.ts @@ -18,7 +18,6 @@ export class UsersBelongsToCourses extends Model | null; declare next_training_type: CreationOptional> | number | null; // Required for type inference - don't know why - declare skill_set: CreationOptional | null; declare createdAt: CreationOptional | null; declare updatedAt: CreationOptional | null; @@ -71,10 +70,6 @@ UsersBelongsToCourses.init( onUpdate: "cascade", onDelete: "set null", }, - skill_set: { - type: DataType.JSON, - allowNull: true, - }, completed: { type: DataType.BOOLEAN, allowNull: false,