From 5adaa000b7f7ff4f262187ad3d420482ec8fbe13 Mon Sep 17 00:00:00 2001 From: Oscar Thomson Date: Mon, 13 Jul 2026 10:09:47 +0000 Subject: [PATCH 1/3] Fixed database issue where a teamId was needed --- backend/db/mongodb-schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/db/mongodb-schema.json b/backend/db/mongodb-schema.json index a2293f3..4e36cdf 100644 --- a/backend/db/mongodb-schema.json +++ b/backend/db/mongodb-schema.json @@ -157,7 +157,7 @@ "validator": { "$jsonSchema": { "bsonType": "object", - "required": ["_id", "name", "teamId"], + "required": ["_id", "name"], "properties": { "_id": { "bsonType": "objectId" From 7c66fb9ab8c0499ee1cddc3ffd907ecfca46238b Mon Sep 17 00:00:00 2001 From: Oscar Thomson Date: Mon, 13 Jul 2026 10:58:55 +0000 Subject: [PATCH 2/3] Added a method to the scheduler that will allow the schedule to be reset after any change that might alter how the schedule would be generated --- .../endpoints/hpc/controller/addHpcToPool.js | 5 +- .../hpc/controller/removeHpcFromPool.js | 5 +- .../people/controller/addPeopleToTeam.js | 9 ++- .../pool/controller/addPoolToTeam.js | 5 +- .../pool/controller/removePoolFromTeam.js | 5 +- backend/endpoints/rota/scheduleLogic.js | 8 +-- .../team/controller/updateTeamSettings.js | 5 +- backend/schedule/scheduler.js | 64 ++++++++++++++----- frontend/setup.sh | 6 +- 9 files changed, 80 insertions(+), 32 deletions(-) diff --git a/backend/endpoints/hpc/controller/addHpcToPool.js b/backend/endpoints/hpc/controller/addHpcToPool.js index 7983d84..7edbcd7 100644 --- a/backend/endpoints/hpc/controller/addHpcToPool.js +++ b/backend/endpoints/hpc/controller/addHpcToPool.js @@ -1,9 +1,10 @@ const { ObjectId } = require('mongodb'); +const { handleStateChange } = require('../../../schedule/scheduler'); /** * @param {import('mongodb').Db} db */ -module.exports = (db) => { +module.exports = db => { /** * @param {import('express').Request} req * @param {import('express').Response} res @@ -91,6 +92,8 @@ module.exports = (db) => { } ); + await handleStateChange(db); + return res.status(200).json({ success: true, body: { diff --git a/backend/endpoints/hpc/controller/removeHpcFromPool.js b/backend/endpoints/hpc/controller/removeHpcFromPool.js index f399609..86db4a1 100644 --- a/backend/endpoints/hpc/controller/removeHpcFromPool.js +++ b/backend/endpoints/hpc/controller/removeHpcFromPool.js @@ -1,9 +1,10 @@ const { ObjectId } = require('mongodb'); +const { handleStateChange } = require('../../../schedule/scheduler'); /** * @param {import('mongodb').Db} db */ -module.exports = (db) => { +module.exports = db => { /** * @param {import('express').Request} req * @param {import('express').Response} res @@ -90,6 +91,8 @@ module.exports = (db) => { } ); + await handleStateChange(db); + return res.status(200).json({ success: true, body: { diff --git a/backend/endpoints/people/controller/addPeopleToTeam.js b/backend/endpoints/people/controller/addPeopleToTeam.js index 8a3ed4c..418d747 100644 --- a/backend/endpoints/people/controller/addPeopleToTeam.js +++ b/backend/endpoints/people/controller/addPeopleToTeam.js @@ -1,9 +1,10 @@ const { ObjectId } = require('mongodb'); +const { handleStateChange } = require('../../../schedule/scheduler'); /** * @param {import('mongodb').Db} db */ -module.exports = (db) => { +module.exports = db => { /** * @param {import('express').Request} req * @param {import('express').Response} res @@ -69,9 +70,9 @@ module.exports = (db) => { return res.status(404).json({ success: false, error: "Person doesn't exist" }); } - // Check if clusters current team wont be left without clusters + // Check if team won't be left with less than one person if (person.teamId) { - const cluster_count = await db.collection('cluster').countDocuments({ + const cluster_count = await db.collection('person').countDocuments({ teamId: person.teamId }); @@ -98,6 +99,8 @@ module.exports = (db) => { { $set: { teamId: teamId } } ); + await handleStateChange(db); + return res.status(200).json({ success: true }); } catch (error) { return res.status(500).json({ success: false, error: error.message }); diff --git a/backend/endpoints/pool/controller/addPoolToTeam.js b/backend/endpoints/pool/controller/addPoolToTeam.js index 60230ab..1864fb5 100644 --- a/backend/endpoints/pool/controller/addPoolToTeam.js +++ b/backend/endpoints/pool/controller/addPoolToTeam.js @@ -1,9 +1,10 @@ const { ObjectId } = require('mongodb'); +const { handleStateChange } = require('../../../schedule/scheduler'); /** * @param {import('mongodb').Db} db */ -module.exports = (db) => { +module.exports = db => { /** * @param {import('express').Request} req * @param {import('express').Response} res @@ -83,6 +84,8 @@ module.exports = (db) => { const insertedId = (await db.collection('teampool').insertOne({ teamId, poolId })) .insertedId; + await handleStateChange(db); + return res.status(200).json({ success: true, body: { diff --git a/backend/endpoints/pool/controller/removePoolFromTeam.js b/backend/endpoints/pool/controller/removePoolFromTeam.js index b01eb50..6f49ce0 100644 --- a/backend/endpoints/pool/controller/removePoolFromTeam.js +++ b/backend/endpoints/pool/controller/removePoolFromTeam.js @@ -1,9 +1,10 @@ const { ObjectId } = require('mongodb'); +const { handleStateChange } = require('../../../schedule/scheduler'); /** * @param {import('mongodb').Db} db */ -module.exports = (db) => { +module.exports = db => { /** * @param {import('express').Request} req * @param {import('express').Response} res @@ -82,6 +83,8 @@ module.exports = (db) => { // Delete from database await db.collection('teampool').deleteOne({ teamId, poolId }); + await handleStateChange(db); + return res.status(200).json({ success: true }); } catch (error) { return res.status(500).json({ success: false, error: error.message }); diff --git a/backend/endpoints/rota/scheduleLogic.js b/backend/endpoints/rota/scheduleLogic.js index 162899e..d1428b2 100644 --- a/backend/endpoints/rota/scheduleLogic.js +++ b/backend/endpoints/rota/scheduleLogic.js @@ -1,6 +1,6 @@ -const getScheduleForDay = require('../../schedule/scheduler'); +const { getScheduleForDay } = require('../../schedule/scheduler'); -async function getDaily(db, day = new Date()) { +async function getDaily (db, day = new Date()) { const schedule = {}; const targetDate = new Date(day); @@ -29,7 +29,7 @@ async function getDaily(db, day = new Date()) { }) .toArray(); - const overrideMap = new Map(overrides.map((o) => [o.personId, o.newPersonId])); + const overrideMap = new Map(overrides.map(o => [o.personId, o.newPersonId])); const dailySchedule = await getScheduleForDay(db, new Date(day)); @@ -46,7 +46,7 @@ async function getDaily(db, day = new Date()) { return schedule; } -async function getWeekly(db, date = new Date()) { +async function getWeekly (db, date = new Date()) { const days = ['mon', 'tue', 'wed', 'thu', 'fri']; const weekly = {}; diff --git a/backend/endpoints/team/controller/updateTeamSettings.js b/backend/endpoints/team/controller/updateTeamSettings.js index a0feeea..78fa39e 100644 --- a/backend/endpoints/team/controller/updateTeamSettings.js +++ b/backend/endpoints/team/controller/updateTeamSettings.js @@ -1,9 +1,10 @@ const { ObjectId } = require('mongodb'); +const { handleStateChange } = require('../../../schedule/scheduler'); /** * @param {import('mongodb').Db} db */ -module.exports = (db) => { +module.exports = db => { /** * @param {import('express').Request} req * @param {import('express').Response} res @@ -59,6 +60,8 @@ module.exports = (db) => { // Updates values in the database await db.collection('team').updateOne({ _id: new ObjectId(teamId) }, { $set: updates }); + await handleStateChange(db); + return res.status(200).json({ success: true }); } catch (error) { return res.status(500).json({ success: false, error: error.message }); diff --git a/backend/schedule/scheduler.js b/backend/schedule/scheduler.js index cb480af..a1db7d5 100644 --- a/backend/schedule/scheduler.js +++ b/backend/schedule/scheduler.js @@ -6,13 +6,13 @@ require('dotenv').config; * @param {import('mongodb').Db} db - MongoDB database instance. * @returns {Promise} Team IDs */ -async function getTeams(db) { +async function getTeams (db) { const response = await db .collection('team') .find({}) .toArray() - .then((res) => - res.map((data) => ({ + .then(res => + res.map(data => ({ id: data._id.toString(), clustersPerDay: data.clusters_per_day })) @@ -20,15 +20,15 @@ async function getTeams(db) { return response; } -async function getTeamsOrderedByEffectiveCapacity(db) { +async function getTeamsOrderedByEffectiveCapacity (db) { const teams = await getTeams(db); const teamStats = await Promise.all( - teams.map(async (team) => { + teams.map(async team => { // Pools this team belongs to const pools = await db.collection('teampool').find({ teamId: team.id }).toArray(); - const poolIds = pools.map((p) => p.poolId); + const poolIds = pools.map(p => p.poolId); // All clusters available to this team const clusters = await db @@ -70,7 +70,7 @@ async function getTeamsOrderedByEffectiveCapacity(db) { return teamStats; } -async function isWorkingDay(db, date) { +async function isWorkingDay (db, date) { const day = new Date(date); day.setHours(0, 0, 0, 0); const isWeekend = day.getDay() === 0 || day.getDay() === 6; @@ -88,14 +88,14 @@ async function isWorkingDay(db, date) { * @param {import('mongodb').Db} db - MongoDB database instance. * @param {Date} date - MongoDB database instance. */ -async function generateScheduleForDay(db, date) { +async function generateScheduleForDay (db, date) { const teams = await getTeamsOrderedByEffectiveCapacity(db); for (let i = 0; i < teams.length; i++) { const team = teams[i]; // get pools for team const pools = await db.collection('teampool').find({ teamId: team.id }).toArray(); - const poolIds = pools.map((p) => p.poolId); + const poolIds = pools.map(p => p.poolId); // get the last team.clustersPerDay clusters that are in all the pools ordered by the last time they appeared in the schedule const clusters = await db @@ -108,7 +108,7 @@ async function generateScheduleForDay(db, date) { .aggregate([ { $match: { - clusterId: { $in: clusters.map((c) => c._id.toString()) } + clusterId: { $in: clusters.map(c => c._id.toString()) } } }, { @@ -120,7 +120,7 @@ async function generateScheduleForDay(db, date) { ]) .toArray(); - const lastMap = new Map(lastUsage.map((x) => [x._id, x.lastDay])); + const lastMap = new Map(lastUsage.map(x => [x._id, x.lastDay])); clusters.sort((a, b) => { const aTime = lastMap.get(a._id.toString())?.getTime() ?? -Infinity; @@ -149,7 +149,7 @@ async function generateScheduleForDay(db, date) { * @param {import('mongodb').Db} db - MongoDB database instance. * @param {Date|string|number} date - Date to build the schedule for. */ -async function getNextPerson(db, teamId) { +async function getNextPerson (db, teamId) { console.log(`Getting next person for team: ${teamId}`); let people = await db .collection('person') @@ -198,7 +198,7 @@ async function getNextPerson(db, teamId) { * @param {import('mongodb').Db} db - MongoDB database instance. * @param {Date|string|number} date - Date to build the schedule for. */ -async function generateUpToDay(db, date) { +async function generateUpToDay (db, date) { const targetDate = new Date(date); targetDate.setHours(0, 0, 0, 0); @@ -235,7 +235,7 @@ async function generateUpToDay(db, date) { const scheduleLocks = new Map(); -async function getScheduleForDay(db, date) { +async function getScheduleForDay (db, date) { // Normalise date const startOfDay = new Date(date); startOfDay.setHours(0, 0, 0, 0); @@ -279,11 +279,11 @@ async function getScheduleForDay(db, date) { * @param {Date|string|number} date - Date to build the schedule for. * @returns {Promise} Array of objects containing person IDs and cluster IDs. */ -async function formatScheduleForDay(db, date) { +async function formatScheduleForDay (db, date) { const scheduleForDay = await getScheduleForDay(db, date); // Format response - const response = scheduleForDay.map((entry) => ({ + const response = scheduleForDay.map(entry => ({ personId: entry.personId, clusterId: entry.clusterId })); @@ -300,4 +300,34 @@ async function formatScheduleForDay(db, date) { return scheduleDict; } -module.exports = formatScheduleForDay; +const tomorrow = () => { + const date = new Date(); + date.setDate(date.getDate() + 1); + date.setHours(0, 0, 0, 0); + return date; +}; + +/** + * Remove the data in the schedule following tommorrow after relevant data is changed + * + * @param {import('mongodb').Db} db - MongoDB database instance. + * @returns {Promise} Array of objects containing person IDs and cluster IDs. + */ +async function handleStateChange (db) { + const dateFrom = tomorrow(); + + const scheduleExists = await db.collection('schedule').findOne({ + day: { $gte: dateFrom } + }); + + if (!scheduleExists) return; + + await db.collection('schedule').deleteMany({ + day: { $gte: dateFrom } + }); +} + +module.exports = { + getScheduleForDay: formatScheduleForDay, + handleStateChange: handleStateChange +}; diff --git a/frontend/setup.sh b/frontend/setup.sh index 6627480..55cc8c7 100755 --- a/frontend/setup.sh +++ b/frontend/setup.sh @@ -2,7 +2,7 @@ npm i -#npm run build -#npm run start +npm run build +npm run start -npm run dev \ No newline at end of file +#npm run dev \ No newline at end of file From 5c3b10a3a78984cd03b956b5fa1286a869b18345 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 11:01:09 +0000 Subject: [PATCH 3/3] chore: apply formatting --- .../endpoints/hpc/controller/addHpcToPool.js | 2 +- .../hpc/controller/removeHpcFromPool.js | 2 +- .../people/controller/addPeopleToTeam.js | 2 +- .../pool/controller/addPoolToTeam.js | 2 +- .../pool/controller/removePoolFromTeam.js | 2 +- backend/endpoints/rota/scheduleLogic.js | 6 ++-- .../team/controller/updateTeamSettings.js | 2 +- backend/schedule/scheduler.js | 34 +++++++++---------- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/backend/endpoints/hpc/controller/addHpcToPool.js b/backend/endpoints/hpc/controller/addHpcToPool.js index 7edbcd7..33891fc 100644 --- a/backend/endpoints/hpc/controller/addHpcToPool.js +++ b/backend/endpoints/hpc/controller/addHpcToPool.js @@ -4,7 +4,7 @@ const { handleStateChange } = require('../../../schedule/scheduler'); /** * @param {import('mongodb').Db} db */ -module.exports = db => { +module.exports = (db) => { /** * @param {import('express').Request} req * @param {import('express').Response} res diff --git a/backend/endpoints/hpc/controller/removeHpcFromPool.js b/backend/endpoints/hpc/controller/removeHpcFromPool.js index 86db4a1..b2e1589 100644 --- a/backend/endpoints/hpc/controller/removeHpcFromPool.js +++ b/backend/endpoints/hpc/controller/removeHpcFromPool.js @@ -4,7 +4,7 @@ const { handleStateChange } = require('../../../schedule/scheduler'); /** * @param {import('mongodb').Db} db */ -module.exports = db => { +module.exports = (db) => { /** * @param {import('express').Request} req * @param {import('express').Response} res diff --git a/backend/endpoints/people/controller/addPeopleToTeam.js b/backend/endpoints/people/controller/addPeopleToTeam.js index 418d747..2a26380 100644 --- a/backend/endpoints/people/controller/addPeopleToTeam.js +++ b/backend/endpoints/people/controller/addPeopleToTeam.js @@ -4,7 +4,7 @@ const { handleStateChange } = require('../../../schedule/scheduler'); /** * @param {import('mongodb').Db} db */ -module.exports = db => { +module.exports = (db) => { /** * @param {import('express').Request} req * @param {import('express').Response} res diff --git a/backend/endpoints/pool/controller/addPoolToTeam.js b/backend/endpoints/pool/controller/addPoolToTeam.js index 1864fb5..c2914ee 100644 --- a/backend/endpoints/pool/controller/addPoolToTeam.js +++ b/backend/endpoints/pool/controller/addPoolToTeam.js @@ -4,7 +4,7 @@ const { handleStateChange } = require('../../../schedule/scheduler'); /** * @param {import('mongodb').Db} db */ -module.exports = db => { +module.exports = (db) => { /** * @param {import('express').Request} req * @param {import('express').Response} res diff --git a/backend/endpoints/pool/controller/removePoolFromTeam.js b/backend/endpoints/pool/controller/removePoolFromTeam.js index 6f49ce0..24cc6d7 100644 --- a/backend/endpoints/pool/controller/removePoolFromTeam.js +++ b/backend/endpoints/pool/controller/removePoolFromTeam.js @@ -4,7 +4,7 @@ const { handleStateChange } = require('../../../schedule/scheduler'); /** * @param {import('mongodb').Db} db */ -module.exports = db => { +module.exports = (db) => { /** * @param {import('express').Request} req * @param {import('express').Response} res diff --git a/backend/endpoints/rota/scheduleLogic.js b/backend/endpoints/rota/scheduleLogic.js index d1428b2..aef6ecb 100644 --- a/backend/endpoints/rota/scheduleLogic.js +++ b/backend/endpoints/rota/scheduleLogic.js @@ -1,6 +1,6 @@ const { getScheduleForDay } = require('../../schedule/scheduler'); -async function getDaily (db, day = new Date()) { +async function getDaily(db, day = new Date()) { const schedule = {}; const targetDate = new Date(day); @@ -29,7 +29,7 @@ async function getDaily (db, day = new Date()) { }) .toArray(); - const overrideMap = new Map(overrides.map(o => [o.personId, o.newPersonId])); + const overrideMap = new Map(overrides.map((o) => [o.personId, o.newPersonId])); const dailySchedule = await getScheduleForDay(db, new Date(day)); @@ -46,7 +46,7 @@ async function getDaily (db, day = new Date()) { return schedule; } -async function getWeekly (db, date = new Date()) { +async function getWeekly(db, date = new Date()) { const days = ['mon', 'tue', 'wed', 'thu', 'fri']; const weekly = {}; diff --git a/backend/endpoints/team/controller/updateTeamSettings.js b/backend/endpoints/team/controller/updateTeamSettings.js index 78fa39e..49b231d 100644 --- a/backend/endpoints/team/controller/updateTeamSettings.js +++ b/backend/endpoints/team/controller/updateTeamSettings.js @@ -4,7 +4,7 @@ const { handleStateChange } = require('../../../schedule/scheduler'); /** * @param {import('mongodb').Db} db */ -module.exports = db => { +module.exports = (db) => { /** * @param {import('express').Request} req * @param {import('express').Response} res diff --git a/backend/schedule/scheduler.js b/backend/schedule/scheduler.js index a1db7d5..0dbd78b 100644 --- a/backend/schedule/scheduler.js +++ b/backend/schedule/scheduler.js @@ -6,13 +6,13 @@ require('dotenv').config; * @param {import('mongodb').Db} db - MongoDB database instance. * @returns {Promise} Team IDs */ -async function getTeams (db) { +async function getTeams(db) { const response = await db .collection('team') .find({}) .toArray() - .then(res => - res.map(data => ({ + .then((res) => + res.map((data) => ({ id: data._id.toString(), clustersPerDay: data.clusters_per_day })) @@ -20,15 +20,15 @@ async function getTeams (db) { return response; } -async function getTeamsOrderedByEffectiveCapacity (db) { +async function getTeamsOrderedByEffectiveCapacity(db) { const teams = await getTeams(db); const teamStats = await Promise.all( - teams.map(async team => { + teams.map(async (team) => { // Pools this team belongs to const pools = await db.collection('teampool').find({ teamId: team.id }).toArray(); - const poolIds = pools.map(p => p.poolId); + const poolIds = pools.map((p) => p.poolId); // All clusters available to this team const clusters = await db @@ -70,7 +70,7 @@ async function getTeamsOrderedByEffectiveCapacity (db) { return teamStats; } -async function isWorkingDay (db, date) { +async function isWorkingDay(db, date) { const day = new Date(date); day.setHours(0, 0, 0, 0); const isWeekend = day.getDay() === 0 || day.getDay() === 6; @@ -88,14 +88,14 @@ async function isWorkingDay (db, date) { * @param {import('mongodb').Db} db - MongoDB database instance. * @param {Date} date - MongoDB database instance. */ -async function generateScheduleForDay (db, date) { +async function generateScheduleForDay(db, date) { const teams = await getTeamsOrderedByEffectiveCapacity(db); for (let i = 0; i < teams.length; i++) { const team = teams[i]; // get pools for team const pools = await db.collection('teampool').find({ teamId: team.id }).toArray(); - const poolIds = pools.map(p => p.poolId); + const poolIds = pools.map((p) => p.poolId); // get the last team.clustersPerDay clusters that are in all the pools ordered by the last time they appeared in the schedule const clusters = await db @@ -108,7 +108,7 @@ async function generateScheduleForDay (db, date) { .aggregate([ { $match: { - clusterId: { $in: clusters.map(c => c._id.toString()) } + clusterId: { $in: clusters.map((c) => c._id.toString()) } } }, { @@ -120,7 +120,7 @@ async function generateScheduleForDay (db, date) { ]) .toArray(); - const lastMap = new Map(lastUsage.map(x => [x._id, x.lastDay])); + const lastMap = new Map(lastUsage.map((x) => [x._id, x.lastDay])); clusters.sort((a, b) => { const aTime = lastMap.get(a._id.toString())?.getTime() ?? -Infinity; @@ -149,7 +149,7 @@ async function generateScheduleForDay (db, date) { * @param {import('mongodb').Db} db - MongoDB database instance. * @param {Date|string|number} date - Date to build the schedule for. */ -async function getNextPerson (db, teamId) { +async function getNextPerson(db, teamId) { console.log(`Getting next person for team: ${teamId}`); let people = await db .collection('person') @@ -198,7 +198,7 @@ async function getNextPerson (db, teamId) { * @param {import('mongodb').Db} db - MongoDB database instance. * @param {Date|string|number} date - Date to build the schedule for. */ -async function generateUpToDay (db, date) { +async function generateUpToDay(db, date) { const targetDate = new Date(date); targetDate.setHours(0, 0, 0, 0); @@ -235,7 +235,7 @@ async function generateUpToDay (db, date) { const scheduleLocks = new Map(); -async function getScheduleForDay (db, date) { +async function getScheduleForDay(db, date) { // Normalise date const startOfDay = new Date(date); startOfDay.setHours(0, 0, 0, 0); @@ -279,11 +279,11 @@ async function getScheduleForDay (db, date) { * @param {Date|string|number} date - Date to build the schedule for. * @returns {Promise} Array of objects containing person IDs and cluster IDs. */ -async function formatScheduleForDay (db, date) { +async function formatScheduleForDay(db, date) { const scheduleForDay = await getScheduleForDay(db, date); // Format response - const response = scheduleForDay.map(entry => ({ + const response = scheduleForDay.map((entry) => ({ personId: entry.personId, clusterId: entry.clusterId })); @@ -313,7 +313,7 @@ const tomorrow = () => { * @param {import('mongodb').Db} db - MongoDB database instance. * @returns {Promise} Array of objects containing person IDs and cluster IDs. */ -async function handleStateChange (db) { +async function handleStateChange(db) { const dateFrom = tomorrow(); const scheduleExists = await db.collection('schedule').findOne({