Skip to content

Commit

Permalink
Solo handling
Browse files Browse the repository at this point in the history
  • Loading branch information
ngoerlitz committed Dec 8, 2023
1 parent d22cd8c commit 5cacd5d
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 39 deletions.
53 changes: 32 additions & 21 deletions misc/CheckEndorsements.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,43 @@
const Config = require("../dist/core/Config");
const Sequelize = require("sequelize")
const Sequelize = require("sequelize");
const dayjs = require("dayjs");

/**
* This script periodically checks, if a user has an endorsement bound to a solo, whilst the solo has already passed
* Once every 24 Hours.
*/
const newConf = {...Config.SequelizeConfig, logging: (message) => {console.log(message)}}
const newConf = {
...Config.SequelizeConfig,
logging: message => {
console.log(message);
},
};
const seq = new Sequelize(newConf);
seq.authenticate()
.catch(() => {
console.log("[SEQ] Failed to authenticate...");
});
seq.authenticate().catch(() => {
console.log("[SEQ] Failed to authenticate...");
});

seq.query("SELECT endorsement_groups_belong_to_users.id, endorsement_groups_belong_to_users.user_id, endorsement_groups_belong_to_users.solo_id, user_solos.current_solo_start, user_solos.current_solo_end FROM endorsement_groups_belong_to_users JOIN user_solos ON user_solos.id = endorsement_groups_belong_to_users.solo_id", {
type: Sequelize.QueryTypes.SELECT
})
.then((res) => {
res.forEach(async (solo) => {
if (dayjs.utc(solo.current_solo_end).isBefore(dayjs.utc())) {
console.log(`Solo ID ${solo.solo_id} has expired. Removing Endorsement Group...`);
} else {
console.log(`Solo ID ${solo.solo_id} is expiring on ${dayjs.utc(solo.current_solo_end)} (${Math.abs(dayjs.utc(solo.current_solo_end).diff(dayjs.utc()))} Day(s) remaining).`)
}
seq.query(
"SELECT endorsement_groups_belong_to_users.id, endorsement_groups_belong_to_users.user_id, endorsement_groups_belong_to_users.solo_id, user_solos.current_solo_start, user_solos.current_solo_end FROM endorsement_groups_belong_to_users JOIN user_solos ON user_solos.id = endorsement_groups_belong_to_users.solo_id",
{
type: Sequelize.QueryTypes.SELECT,
}
)
.then(res => {
res.forEach(async solo => {
if (dayjs.utc(solo.current_solo_end).isBefore(dayjs.utc())) {
console.log(`Solo ID ${solo.solo_id} has expired. Removing Endorsement Group...`);
} else {
console.log(
`Solo ID ${solo.solo_id} is expiring on ${dayjs.utc(solo.current_solo_end)} (${Math.abs(
dayjs.utc(solo.current_solo_end).diff(dayjs.utc(), "day")
)} Day(s) remaining).`
);
}

await seq.query("DELETE FROM endorsement_groups_belong_to_users WHERE ID = ?", {replacements: [solo.id], type: Sequelize.QueryTypes.DELETE})
await seq.query("DELETE FROM endorsement_groups_belong_to_users WHERE ID = ?", { replacements: [solo.id], type: Sequelize.QueryTypes.DELETE });
});
})
.finally(async () => {
await seq.close();
});
})
.finally(async() => {
await seq.close()
});
2 changes: 1 addition & 1 deletion misc/crontab.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* * * * * cd /opt/trainingcenter_backend && node /opt/trainingcenter_backend/misc/CheckEndorsements.js >> /var/log/check_endorsement.log
0 1 * * * cd /opt/trainingcenter_backend && node /opt/trainingcenter_backend/misc/CheckEndorsements.js >> /var/log/check_endorsement.log
2 changes: 2 additions & 0 deletions src/Router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ router.use(
r.use(
"/endorsement-group",
routerGroup((r: Router) => {
r.get("/mentorable", EndorsementGroupAdminController.getMentorable);

r.get("/", EndorsementGroupAdminController.getAll);
r.get("/with-stations", EndorsementGroupAdminController.getAllWithStations);
r.post("/", EndorsementGroupAdminController.createEndorsementGroup);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,34 @@ import { TrainingStation } from "../../models/TrainingStation";
import { EndorsementGroupsBelongsToUsers } from "../../models/through/EndorsementGroupsBelongsToUsers";
import { User } from "../../models/User";

/**
* Returns all Endorsement groups that are mentorable by the current user
* @param request
* @param response
* @param next
*/
async function getMentorable(request: Request, response: Response, next: NextFunction) {
try {
const user: User = request.body.user;
const userMentorGroups = await user.getMentorGroups();

let endorsementGroups: EndorsementGroup[] = [];
for (const m of userMentorGroups) {
const egs = await m.getEndorsementGroups();

for (const eg of egs) {
if (endorsementGroups.find(e => e.id === eg.id) == null) {
endorsementGroups.push(eg);
}
}
}

response.send(endorsementGroups);
} catch (e) {
next(e);
}
}

/**
* Gets a collection of all endorsement groups
* @param request
Expand Down Expand Up @@ -310,6 +338,7 @@ async function createEndorsementGroup(request: Request, response: Response, next
}

export default {
getMentorable,
getAll,
getAllWithStations,
getByID,
Expand Down
44 changes: 40 additions & 4 deletions src/controllers/solo/SoloAdminController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ type CreateSoloRequestBody = {
endorsement_group_id: string;
};

type UpdateSoloRequestBody = Omit<CreateSoloRequestBody, "endorsement_group_id">;
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
type UpdateSoloRequestBody = Optional<CreateSoloRequestBody, "endorsement_group_id">;

/**
* Create a new Solo
Expand Down Expand Up @@ -44,6 +45,7 @@ async function createSolo(request: Request, response: Response, next: NextFuncti

await EndorsementGroupsBelongsToUsers.create({
user_id: body.trainee_id,
created_by: user.id,
endorsement_group_id: Number(body.endorsement_group_id),
solo_id: solo.id,
});
Expand Down Expand Up @@ -75,7 +77,7 @@ async function createSolo(request: Request, response: Response, next: NextFuncti
*/
async function updateSolo(request: Request, response: Response, next: NextFunction) {
try {
const body = request.body as UpdateSoloRequestBody;
const body = request.body as UpdateSoloRequestBody & { endorsement_group_id?: string };
_SoloAdminValidator.validateUpdateRequest(body);

const currentSolo = await UserSolo.findOne({
Expand All @@ -89,6 +91,23 @@ async function updateSolo(request: Request, response: Response, next: NextFuncti
return;
}

if (body.endorsement_group_id != null) {
// We are trying to assign a new endorsement group, so remove all old ones first
await EndorsementGroupsBelongsToUsers.destroy({
where: {
user_id: body.trainee_id,
solo_id: currentSolo.id,
},
});

await EndorsementGroupsBelongsToUsers.create({
user_id: body.trainee_id,
endorsement_group_id: Number(body.endorsement_group_id),
solo_id: currentSolo.id,
created_by: request.body.user.id,
});
}

const newDuration = currentSolo.solo_used + Number(body.solo_duration);

// If solo_start == NULL, then the solo is still active
Expand All @@ -109,7 +128,20 @@ async function updateSolo(request: Request, response: Response, next: NextFuncti
});
}

response.sendStatus(HttpStatusCode.Ok);
const returnUser = await User.findOne({
where: {
id: body.trainee_id,
},
include: [
{
association: User.associations.user_solo,
include: [UserSolo.associations.solo_creator],
},
User.associations.endorsement_groups,
],
});

response.send(returnUser);
} catch (e) {
next(e);
}
Expand Down Expand Up @@ -177,10 +209,14 @@ async function extendSolo(request: Request, response: Response, next: NextFuncti
});

if (solo == null) {
response.sendStatus(HttpStatusCode.InternalServerError);
response.sendStatus(HttpStatusCode.NotFound);
return;
}

await solo.update({
extension_count: solo.extension_count + 1,
});

response.sendStatus(HttpStatusCode.Ok);
} catch (e) {
next(e);
Expand Down
11 changes: 11 additions & 0 deletions src/controllers/solo/_SoloAdmin.validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ function validateUpdateRequest(data: any) {
validationObject: data.trainee_id,
toValidate: [{ val: ValidationOptions.NON_NULL }, { val: ValidationOptions.NUMBER }],
},
{
name: "endorsement_group_id_present",
validationObject: data,
toValidate: arg0 => {
if (arg0.solo_start != null) {
return arg0.endorsement_group_id != null;
}

return true;
},
},
]);

if (validation.invalid) {
Expand Down
3 changes: 3 additions & 0 deletions src/models/MentorGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { sequelize } from "../core/Sequelize";
import { Course } from "./Course";
import { UserBelongToMentorGroups } from "./through/UserBelongToMentorGroups";
import { EndorsementGroup } from "./EndorsementGroup";
import MentorGroupExtensions from "./extensions/MentorGroupExtensions";

export class MentorGroup extends Model<InferAttributes<MentorGroup>, InferCreationAttributes<MentorGroup>> {
//
Expand Down Expand Up @@ -38,6 +39,8 @@ export class MentorGroup extends Model<InferAttributes<MentorGroup>, InferCreati
courses: Association<MentorGroup, Course>;
endorsement_groups: Association<MentorGroup, EndorsementGroup>;
};

getEndorsementGroups = MentorGroupExtensions.getEndorsementGroups.bind(this);
}

MentorGroup.init(
Expand Down
27 changes: 27 additions & 0 deletions src/models/extensions/MentorGroupExtensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { MentorGroup } from "../MentorGroup";
import { EndorsementGroup } from "../EndorsementGroup";

/**
* Gets all the endorsement groups associated to this
*/
async function getEndorsementGroups(this: MentorGroup): Promise<EndorsementGroup[]> {
const m = await MentorGroup.findOne({
where: {
id: this.id,
},
include: [
{
association: MentorGroup.associations.endorsement_groups,
through: {
attributes: [],
},
},
],
});

return m?.endorsement_groups ?? [];
}

export default {
getEndorsementGroups,
};
28 changes: 15 additions & 13 deletions src/utility/helper/ValidationHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,10 @@ import { Response } from "express";
import { ValidatorType } from "../../controllers/_validators/ValidatorType";
import { HttpStatusCode } from "axios";

export function validateObject(object: any, keys: string[], checkStringNull = false) {
let malformedKeys: any[] = [];

keys.forEach(value => {
if (object[value] == null || (checkStringNull && object[value] == "")) {
malformedKeys.push(value);
}
});

return malformedKeys;
}

type ValidationType = {
name: string;
validationObject: any;
toValidate: Array<{ val: ValidationOptions; value?: any }>;
toValidate: Array<{ val: ValidationOptions; value?: any }> | ((arg0: any) => boolean);
};

export enum ValidationOptions {
Expand Down Expand Up @@ -50,6 +38,20 @@ function validate(options: ValidationType[]): { invalid: boolean; message: any[]
// Replace spaces with nothing!
if (typeof opt.validationObject == "string") toCheck = toCheck.replace(/\s/g, "");

if (typeof opt.toValidate == "function") {
if (!opt.toValidate(toCheck)) {
return {
invalid: true,
message: ["Unknown Validation Error (custom validation func)"],
};
}

return {
invalid: false,
message: [],
};
}

opt.toValidate.forEach(val => {
switch (val.val) {
case ValidationOptions.NON_NULL:
Expand Down

0 comments on commit 5cacd5d

Please sign in to comment.