From b2c337ab5692efa4d218d3bbcbe5aab167f26b21 Mon Sep 17 00:00:00 2001 From: hwilks <hazel.wilks@gmail.com> Date: Sat, 29 Mar 2025 00:37:35 +0000 Subject: [PATCH 001/124] event_name is required (still need to add tests) --- frontend_microservice/static/script.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frontend_microservice/static/script.js b/frontend_microservice/static/script.js index b6dfe98..484e12c 100644 --- a/frontend_microservice/static/script.js +++ b/frontend_microservice/static/script.js @@ -179,6 +179,13 @@ function sendDates(){ console.log("button pressed") console.log(possible_dates) let name = document.getElementById('event_name').value; + + //check that event name is not empty + if (name == "") { + alert("Event name is required."); + return; + } + $.ajax({ url: `/create_event`, type: 'POST', -- GitLab From 30043c2ccff4352ad152022b3f6818ee94a7018c Mon Sep 17 00:00:00 2001 From: hwilks <hazel.wilks@gmail.com> Date: Sat, 29 Mar 2025 02:42:51 +0000 Subject: [PATCH 002/124] added jest tests --- frontend_microservice/static/script.js | 372 +- package-lock.json | 4476 ++++++++++++++++++++++++ package.json | 28 + tests/sendDates.test.js | 44 + 4 files changed, 4735 insertions(+), 185 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 tests/sendDates.test.js diff --git a/frontend_microservice/static/script.js b/frontend_microservice/static/script.js index 484e12c..48752fc 100644 --- a/frontend_microservice/static/script.js +++ b/frontend_microservice/static/script.js @@ -1,226 +1,228 @@ -let date = new Date(); -let year = date.getFullYear(); -let month = date.getMonth(); - -const day = document.querySelector(".calendar-dates"); -const currdate = document.querySelector(".calendar-current-date"); -const prenexIcons = document.querySelectorAll(".calendar-navigation span"); - -// Array of month names -const months = [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December" -]; - let possible_dates = []; -//eventdates = [datetime.datetime(2025, 3, 27, 0, 0, 0), datetime.datetime(2025, 3, 28,0,0,0), datetime.datetime(2025,3,31,0,0,0)] -const title = document.getElementById("title").textContent; -console.log("Title: ",title); - -//Convert dates to string -function convertDatesToString(dates){ - //depending on data from database, can convert to date - dates_str = [] - for (i=0; i<dates.length;i++){ - let date_ = new Date(dates[i]); - let day_ = date_.getDate(); - let month_ = date_.getMonth() +1; - let year_ = date_.getFullYear(); - let fullDate_ = `${year_}-${month_}-${day_}` - dates_str.push(fullDate_) +document.addEventListener('DOMContentLoaded', function() { + + let date = new Date(); + let year = date.getFullYear(); + let month = date.getMonth(); + + const day = document.querySelector(".calendar-dates"); + const currdate = document.querySelector(".calendar-current-date"); + const prenexIcons = document.querySelectorAll(".calendar-navigation span"); + + // Array of month names + const months = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December" + ]; + + + //eventdates = [datetime.datetime(2025, 3, 27, 0, 0, 0), datetime.datetime(2025, 3, 28,0,0,0), datetime.datetime(2025,3,31,0,0,0)] + const title = document.getElementById("title").textContent; + console.log("Title: ",title); + + //Convert dates to string + function convertDatesToString(dates){ + //depending on data from database, can convert to date + dates_str = [] + for (i=0; i<dates.length;i++){ + let date_ = new Date(dates[i]); + let day_ = date_.getDate(); + let month_ = date_.getMonth() +1; + let year_ = date_.getFullYear(); + let fullDate_ = `${year_}-${month_}-${day_}` + dates_str.push(fullDate_) + } + console.log(dates_str) + return dates_str } - console.log(dates_str) - return dates_str -} -let event_dates_str = []; -if (title=='Availability'){ - event_dates_str = convertDatesToString(eventdates); -} + let event_dates_str = []; + if (title=='Availability'){ + event_dates_str = convertDatesToString(eventdates); + } -// Function to generate the calendar -const manipulate = () => { - //console.log(possible_dates) - // Get the first day of the month - let dayone = new Date(year, month, 1).getDay(); + // Function to generate the calendar + const manipulate = () => { + //console.log(possible_dates) + // Get the first day of the month + let dayone = new Date(year, month, 1).getDay(); - // Get the last date of the month - let lastdate = new Date(year, month + 1, 0).getDate(); + // Get the last date of the month + let lastdate = new Date(year, month + 1, 0).getDate(); - // Get the day of the last date of the month - let dayend = new Date(year, month, lastdate).getDay(); + // Get the day of the last date of the month + let dayend = new Date(year, month, lastdate).getDay(); - // Get the last date of the previous month - let monthlastdate = new Date(year, month, 0).getDate(); + // Get the last date of the previous month + let monthlastdate = new Date(year, month, 0).getDate(); - // Variable to store the generated calendar HTML - let lit = ""; + // Variable to store the generated calendar HTML + let lit = ""; - // Loop to add the last dates of the previous month - for (let i = dayone; i > 0; i--) { - lit += - `<li class="not_this_month not_selectable">${monthlastdate - i + 1}</li>`; - } + // Loop to add the last dates of the previous month + for (let i = dayone; i > 0; i--) { + lit += + `<li class="not_this_month not_selectable">${monthlastdate - i + 1}</li>`; + } - // Loop to add the dates of the current month - for (let i = 1; i <= lastdate; i++) { + // Loop to add the dates of the current month + for (let i = 1; i <= lastdate; i++) { - let fullDate = `${year}-${month+1}-${i}`; + let fullDate = `${year}-${month+1}-${i}`; - // Check if the current date is today - let isToday = i === date.getDate() - && month === new Date().getMonth() - && year === new Date().getFullYear() - ? "today" - : ""; + // Check if the current date is today + let isToday = i === date.getDate() + && month === new Date().getMonth() + && year === new Date().getFullYear() + ? "today" + : ""; - let isSelectable = "" - if (title=="Availability"){ - isSelectable = event_dates_str.includes(fullDate) ? "" : "not_selectable"; + let isSelectable = "" + if (title=="Availability"){ + isSelectable = event_dates_str.includes(fullDate) ? "" : "not_selectable"; + } + let isSelected = possible_dates.includes(fullDate) ? "selected" : ""; // Check if the date is selected + lit += `<li class="${isToday} ${isSelected} ${isSelectable}"data-date="${fullDate}">${i}</li>`; } - let isSelected = possible_dates.includes(fullDate) ? "selected" : ""; // Check if the date is selected - lit += `<li class="${isToday} ${isSelected} ${isSelectable}"data-date="${fullDate}">${i}</li>`; - } - // Loop to add the first dates of the next month - for (let i = dayend; i < 6; i++) { - lit += `<li class="not_this_month not_selectable">${i - dayend + 1}</li>` - } + // Loop to add the first dates of the next month + for (let i = dayend; i < 6; i++) { + lit += `<li class="not_this_month not_selectable">${i - dayend + 1}</li>` + } - // Update the text of the current date element - // with the formatted current month and year - currdate.innerText = `${months[month]} ${year}`; + // Update the text of the current date element + // with the formatted current month and year + currdate.innerText = `${months[month]} ${year}`; - // update the HTML of the dates element - // with the generated calendar - day.innerHTML = lit; + // update the HTML of the dates element + // with the generated calendar + day.innerHTML = lit; - // Add event listeners for the new dates - document.querySelectorAll('.calendar-dates li').forEach((dateItem) => { - if (!dateItem.classList.contains('not_selectable')) { - dateItem.addEventListener('click', () => toggleDateSelection(dateItem)); - } - }); -} - -// Function to toggle date selection -const toggleDateSelection = (dateItem) => { - const dateStr = dateItem.getAttribute("data-date"); - const dateIndex = possible_dates.indexOf(dateStr); - - if (dateIndex === -1) { - // If not already selected, add to the selected dates list - possible_dates.push(dateStr); - dateItem.classList.add("selected"); // Add selected class to change color - } else { - // If already selected, remove from the selected dates list - possible_dates.splice(dateIndex, 1); - dateItem.classList.remove("selected"); // Remove selected class to reset color + // Add event listeners for the new dates + document.querySelectorAll('.calendar-dates li').forEach((dateItem) => { + if (!dateItem.classList.contains('not_selectable')) { + dateItem.addEventListener('click', () => toggleDateSelection(dateItem)); + } + }); } -}; - -// Function to toggle the selection state of buttons -function toggleDaySelection(button) { - button.classList.toggle('selected'); -} + // Function to toggle date selection + const toggleDateSelection = (dateItem) => { + const dateStr = dateItem.getAttribute("data-date"); + const dateIndex = possible_dates.indexOf(dateStr); + + if (dateIndex === -1) { + // If not already selected, add to the selected dates list + possible_dates.push(dateStr); + dateItem.classList.add("selected"); // Add selected class to change color + } else { + // If already selected, remove from the selected dates list + possible_dates.splice(dateIndex, 1); + dateItem.classList.remove("selected"); // Remove selected class to reset color + } + }; -manipulate(); + // Function to toggle the selection state of buttons + function toggleDaySelection(button) { + button.classList.toggle('selected'); -// Attach a click event listener to each icon -prenexIcons.forEach(icon => { + } - // When an icon is clicked - icon.addEventListener("click", () => { + manipulate(); - // Check if the icon is "calendar-prev" - // or "calendar-next" - month = icon.id === "calendar-prev" ? month - 1 : month + 1; + // Attach a click event listener to each icon + prenexIcons.forEach(icon => { - // Check if the month is out of range - if (month < 0 || month > 11) { + // When an icon is clicked + icon.addEventListener("click", () => { - // Set the date to the first day of the - // month with the new year - date = new Date(year, month, new Date().getDate()); + // Check if the icon is "calendar-prev" + // or "calendar-next" + month = icon.id === "calendar-prev" ? month - 1 : month + 1; - // Set the year to the new year - year = date.getFullYear(); + // Check if the month is out of range + if (month < 0 || month > 11) { - // Set the month to the new month - month = date.getMonth(); - } + // Set the date to the first day of the + // month with the new year + date = new Date(year, month, new Date().getDate()); - else { + // Set the year to the new year + year = date.getFullYear(); - // Set the date to the current date - date = new Date(); - } + // Set the month to the new month + month = date.getMonth(); + } - // Call the manipulate function to - // update the calendar display - manipulate(); - }); -}); + else { + // Set the date to the current date + date = new Date(); + } -function sendDates(){ - console.log("button pressed") - console.log(possible_dates) - let name = document.getElementById('event_name').value; + // Call the manipulate function to + // update the calendar display + manipulate(); + }); + }); +}) - //check that event name is not empty - if (name == "") { - alert("Event name is required."); - return; - } + function sendDates(){ + console.log("button pressed") + console.log(possible_dates) + let name = document.getElementById('event_name').value; - $.ajax({ - url: `/create_event`, - type: 'POST', - contentType: 'application/json', - data: JSON.stringify({ 'possible_dates': possible_dates, - 'name':name }), - success: function(response) { - /*TODO: Create page to go to*/ - window.location.href = "/index"; - }, - error: function(xhr, status, error) { - alert("Error: " + error); + //check that event name is not empty + if (name == "") { + alert("Event name cannot be empty."); + return; } - }); -} - -function sendAvailabilities(event_id, user_id){ - console.log("button pressed") - $.ajax({ - url: `/availability/${event_id}/${user_id}`, - type: 'POST', - contentType: 'application/json', - data: JSON.stringify({ 'availability': possible_dates, 'event_id':event_id, 'user_id':user_id}), - success: function(response) { - /*TODO: Create page to go to*/ - window.location.href = "/index"; - }, - error: function(xhr, status, error) { - alert("Error: " + error); - } - }); -} - + $.ajax({ + url: `/create_event`, + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({ 'possible_dates': possible_dates, + 'name':name }), + success: function(response) { + /*TODO: Create page to go to*/ + window.location.href = "/index"; + }, + error: function(xhr, status, error) { + alert("Error: " + error); + } + }); + } + + + function sendAvailabilities(event_id, user_id){ + console.log("button pressed") + $.ajax({ + url: `/availability/${event_id}/${user_id}`, + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({ 'availability': possible_dates, 'event_id':event_id, 'user_id':user_id}), + success: function(response) { + /*TODO: Create page to go to*/ + window.location.href = "/index"; + }, + error: function(xhr, status, error) { + alert("Error: " + error); + } + }); + } + module.exports = { sendDates, sendAvailabilities }; - diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..598a1dc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4476 @@ +{ + "name": "coursework", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "coursework", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jquery": "^3.7.1" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", + "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", + "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.8", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/node": { + "version": "22.13.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz", + "integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001707", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz", + "integrity": "sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", + "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "license": "MIT", + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.128", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.128.tgz", + "integrity": "sha512-bo1A4HH/NS522Ws0QNFIzyPcyUUNV/yyy70Ho1xqfGYzPUme2F/xr4tlEOuM6/A538U1vDA7a4XfCd1CKRegKQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", + "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0", + "jsdom": "^20.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nwsapi": { + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", + "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", + "dev": true, + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9d654b1 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "coursework", + "version": "1.0.0", + "description": "Change the name of the repo when we get a better group name.", + "main": "index.js", + "directories": { + "test": "tests" + }, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "https://gitlab.surrey.ac.uk/web-tech/coursework" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jquery": "^3.7.1" + }, + "jest": { + "testEnvironment": "jsdom" + }, + "type": "module" +} diff --git a/tests/sendDates.test.js b/tests/sendDates.test.js new file mode 100644 index 0000000..bf7a548 --- /dev/null +++ b/tests/sendDates.test.js @@ -0,0 +1,44 @@ +//const $ = require('jquery'); + + +// Mock jQuery AJAX request +//global.$ = jest.fn(() => ({ + // ajax: jest.fn().mockResolvedValue({}) +//})); + +// Mock alert function to prevent actual alerts in tests +global.alert = jest.fn(); + +// Mock jQuery AJAX request +global.$ = { + ajax: jest.fn().mockResolvedValue({}) +}; + +const { sendDates } = require('../frontend_microservice/static/script') + + +describe("sendDates function", () => { + beforeEach(() => { + document.body.innerHTML = ` + <input type="text" id="event_name"> + <button id="create_event_button" onclick="sendDates()">Create Event</button> + <div id="title">Create Event</div> + `; + + }); + + test('should alert if event name is empty', () => { + document.getElementById('event_name').value = ''; + sendDates(); + + expect(alert).toHaveBeenCalledWith("Event name cannot be empty."); + }); + + test('should proceed with a valid event name', () => { + document.getElementById('event_name').value = 'Sample Event'; + sendDates(); + + expect($.ajax).toHaveBeenCalled(); + }); +}); + -- GitLab From 93c85316df0537fd7afd1aa7fd02ca8710bfe4b9 Mon Sep 17 00:00:00 2001 From: hwilks <hazel.wilks@gmail.com> Date: Fri, 4 Apr 2025 19:53:11 +0100 Subject: [PATCH 003/124] add account events page --- frontend_microservice/static/script.js | 13 ++++++++++ .../templates/accountEvents.html | 25 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 frontend_microservice/templates/accountEvents.html diff --git a/frontend_microservice/static/script.js b/frontend_microservice/static/script.js index 41c9ec4..139ea4d 100644 --- a/frontend_microservice/static/script.js +++ b/frontend_microservice/static/script.js @@ -351,4 +351,17 @@ function new_list_element(availabilities, dates){ }; } +function show_account_events(events, url){ + event_block = document.getElementById('event_block') + for (const event in events){ + var button = document.createElement("button"); + button.classList.add('show_event') + button.textContent = event['name']; + //TODO: get url, probably able to get it from GET + button.onclick = "window.location.href = {{url}}/{{event['id']}}" + event_block.append(button) + } +} + + diff --git a/frontend_microservice/templates/accountEvents.html b/frontend_microservice/templates/accountEvents.html new file mode 100644 index 0000000..87fdc55 --- /dev/null +++ b/frontend_microservice/templates/accountEvents.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> + <link rel="icon" href="{{ url_for('static', filename='favicon.PNG') }}" type="image/png"> + <script> + const events = {{event | tojson}}; + const url = {{summary_url | tojson}}; + </script> + </head> + <body> + <div class="centered"> + <div class="card"> + <h1>These are your events</h1> + <h3>Click to go see the details</h3> + <div class="card internal"> + <div id="event_block"> + </div> + </div> + </div> + </div> + </body> + <script src="{{ url_for('static', filename='script.js') }}"></script> + <script>show_account_events(events,url)</script> +</html> \ No newline at end of file -- GitLab From b52ea8c177465a2f9a969041f4008427e0bda00a Mon Sep 17 00:00:00 2001 From: hwilks <hazel.wilks@gmail.com> Date: Fri, 4 Apr 2025 20:54:37 +0100 Subject: [PATCH 004/124] add backend and new table --- account_database/schema.sql | 9 +++++++- account_microservice/user.py | 18 ++++++++++++++++ frontend_microservice/frontend.py | 21 +++++++++++++++++++ frontend_microservice/static/script.js | 12 ++++++----- .../templates/accountEvents.html | 3 +-- 5 files changed, 55 insertions(+), 8 deletions(-) diff --git a/account_database/schema.sql b/account_database/schema.sql index 44f6ef1..577b49b 100644 --- a/account_database/schema.sql +++ b/account_database/schema.sql @@ -3,4 +3,11 @@ CREATE TABLE IF NOT EXISTS users ( email VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); \ No newline at end of file +); + +CREATE TABLE IF NOT EXISTS account_events{ + user_id INT NOT NULL, + event_id INT, + PRIMARY KEY (user_id, event_id), + FOREIGN KEY (user_id) REFERENCES users(user_id) +}; \ No newline at end of file diff --git a/account_microservice/user.py b/account_microservice/user.py index 5b49c91..15c86d5 100644 --- a/account_microservice/user.py +++ b/account_microservice/user.py @@ -124,6 +124,24 @@ def login(): logging.error(f"Login error: {str(e)}", exc_info=True) return jsonify({"error": "Login failed"}), 500 +@app.route('/get_events', methods=['GET']) +def get_events(): + data = request.get_json() + + account_id = data['account_id'] + + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute('SELECT event_id FROM account_events WHERE user_id = %s', (account_id,)) + ids = cursor.fetchall() + + event_ids = [event_id[0] for event_id in ids] + + cursor.close() + conn.close() + + return jsonify({'event_ids':event_ids}), 201 + @app.errorhandler(429) def ratelimit_handler(e): return jsonify({"error": "Too many requests"}), 429 diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index 55d2462..7ae2f69 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -135,6 +135,27 @@ def getDatesp(): return data_output return render_template('eventSummary.html') +@app.route('/account/<int: account_id>', methods=['GET']) +def get_events(account_id): + if request.method == 'GET': + data = {'account_id':account_id} + account_response = requests.get(f"{USER_API_URL}/get_events", json = data) + event_ids = account_response.json() if account_response.status_code == 200 else [] + + events = [] + + for event_id in event_ids: + event ={} + data_input = {"id":event_id, "to_return": ['event_name']} + response = requests.get(f"{EVENT_API_URL}/get_event_info", json = data_input) + data_output = response.json() if response.status_code == 200 else [] + event['name'] = data_output['name'] + event['url'] = url_for('availability', event_id=event_id) + events.append(event) + + return render_template('accountEvents.html', event=events) + return render_template('accountEvents.html') + if __name__ == "__main__": diff --git a/frontend_microservice/static/script.js b/frontend_microservice/static/script.js index 139ea4d..036749c 100644 --- a/frontend_microservice/static/script.js +++ b/frontend_microservice/static/script.js @@ -351,15 +351,17 @@ function new_list_element(availabilities, dates){ }; } -function show_account_events(events, url){ +function show_account_events(events){ event_block = document.getElementById('event_block') + for (const event in events){ var button = document.createElement("button"); - button.classList.add('show_event') + + button.classList.add('show_event'); button.textContent = event['name']; - //TODO: get url, probably able to get it from GET - button.onclick = "window.location.href = {{url}}/{{event['id']}}" - event_block.append(button) + button.onclick = "window.location.href = {{event[url]}}"; + + event_block.append(button); } } diff --git a/frontend_microservice/templates/accountEvents.html b/frontend_microservice/templates/accountEvents.html index 87fdc55..805b163 100644 --- a/frontend_microservice/templates/accountEvents.html +++ b/frontend_microservice/templates/accountEvents.html @@ -5,7 +5,6 @@ <link rel="icon" href="{{ url_for('static', filename='favicon.PNG') }}" type="image/png"> <script> const events = {{event | tojson}}; - const url = {{summary_url | tojson}}; </script> </head> <body> @@ -21,5 +20,5 @@ </div> </body> <script src="{{ url_for('static', filename='script.js') }}"></script> - <script>show_account_events(events,url)</script> + <script>show_account_events(events)</script> </html> \ No newline at end of file -- GitLab From 46c1282247c029b071c35ed345fc83a57bbf54a2 Mon Sep 17 00:00:00 2001 From: hwilks <hazel.wilks@gmail.com> Date: Fri, 4 Apr 2025 22:29:10 +0100 Subject: [PATCH 005/124] fix frontend of account events --- account_database/schema.sql | 2 +- frontend_microservice/frontend.py | 10 +++++++--- frontend_microservice/static/script.js | 15 +++++++++++---- .../templates/accountEvents.html | 2 +- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/account_database/schema.sql b/account_database/schema.sql index 577b49b..c961c3f 100644 --- a/account_database/schema.sql +++ b/account_database/schema.sql @@ -5,7 +5,7 @@ CREATE TABLE IF NOT EXISTS users ( created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -CREATE TABLE IF NOT EXISTS account_events{ +CREATE TABLE IF NOT EXISTS account_events { user_id INT NOT NULL, event_id INT, PRIMARY KEY (user_id, event_id), diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index 7ae2f69..ce0305f 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -135,7 +135,7 @@ def getDatesp(): return data_output return render_template('eventSummary.html') -@app.route('/account/<int: account_id>', methods=['GET']) +@app.route('/account/<int:account_id>', methods=['GET']) def get_events(account_id): if request.method == 'GET': data = {'account_id':account_id} @@ -150,9 +150,13 @@ def get_events(account_id): response = requests.get(f"{EVENT_API_URL}/get_event_info", json = data_input) data_output = response.json() if response.status_code == 200 else [] event['name'] = data_output['name'] - event['url'] = url_for('availability', event_id=event_id) + event['url'] = url_for('eventSummary', event_id=event_id) events.append(event) - + + #Test data + event1= {'name':'An event','url':url_for('eventSummary', event_id=1)} + event2= {'name':'An event 2','url':'a url'} + events = [event1, event2] return render_template('accountEvents.html', event=events) return render_template('accountEvents.html') diff --git a/frontend_microservice/static/script.js b/frontend_microservice/static/script.js index 036749c..290c82f 100644 --- a/frontend_microservice/static/script.js +++ b/frontend_microservice/static/script.js @@ -353,13 +353,20 @@ function new_list_element(availabilities, dates){ function show_account_events(events){ event_block = document.getElementById('event_block') - - for (const event in events){ + console.log(events) + for (i=0;i<events.length;i++){ + event_i=events[i] var button = document.createElement("button"); button.classList.add('show_event'); - button.textContent = event['name']; - button.onclick = "window.location.href = {{event[url]}}"; + button.textContent = event_i['name']; + console.log(event_i['url']) + button.onclick = button.onclick = function(event_i) { + return function() { + console.log('button pressed') + window.location.href = event_i['url']; + }; + }(event_i); event_block.append(button); } diff --git a/frontend_microservice/templates/accountEvents.html b/frontend_microservice/templates/accountEvents.html index 805b163..6e1202f 100644 --- a/frontend_microservice/templates/accountEvents.html +++ b/frontend_microservice/templates/accountEvents.html @@ -11,7 +11,7 @@ <div class="centered"> <div class="card"> <h1>These are your events</h1> - <h3>Click to go see the details</h3> + <h3>Click to see the details</h3> <div class="card internal"> <div id="event_block"> </div> -- GitLab From 7d884a0e0017d941d3a736af307bb7dd38be8310 Mon Sep 17 00:00:00 2001 From: hwilks <hazel.wilks@gmail.com> Date: Fri, 4 Apr 2025 23:54:40 +0100 Subject: [PATCH 006/124] account events page works --- account_database/schema.sql | 4 ++-- frontend_microservice/frontend.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/account_database/schema.sql b/account_database/schema.sql index c961c3f..2b71ebc 100644 --- a/account_database/schema.sql +++ b/account_database/schema.sql @@ -5,9 +5,9 @@ CREATE TABLE IF NOT EXISTS users ( created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -CREATE TABLE IF NOT EXISTS account_events { +CREATE TABLE IF NOT EXISTS account_events ( user_id INT NOT NULL, event_id INT, PRIMARY KEY (user_id, event_id), FOREIGN KEY (user_id) REFERENCES users(user_id) -}; \ No newline at end of file +); \ No newline at end of file diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index ce0305f..886aa30 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -152,11 +152,12 @@ def get_events(account_id): event['name'] = data_output['name'] event['url'] = url_for('eventSummary', event_id=event_id) events.append(event) - + ''' #Test data event1= {'name':'An event','url':url_for('eventSummary', event_id=1)} event2= {'name':'An event 2','url':'a url'} events = [event1, event2] + ''' return render_template('accountEvents.html', event=events) return render_template('accountEvents.html') -- GitLab From 5b3f1dce14de50ab0ac9ee2e3a1766477706896b Mon Sep 17 00:00:00 2001 From: sid <sk03140@surrey.ac.uk> Date: Wed, 9 Apr 2025 23:34:01 +0100 Subject: [PATCH 007/124] changed password lenght to 8 char --- account_microservice/user.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/account_microservice/user.py b/account_microservice/user.py index 15c86d5..3d454cf 100644 --- a/account_microservice/user.py +++ b/account_microservice/user.py @@ -46,8 +46,8 @@ def get_db_connection(): return conn def validate_password(password): - if len(password) < 12: - return "Password must be at least 12 characters long" + if len(password) < 8: + return "Password must be at least 8 characters long" if not re.search(r"[A-Z]", password): return "Password must contain at least one uppercase letter" if not re.search(r"[a-z]", password): -- GitLab From 40f673e4d9db8ba9489af6bfeb0089f6647cef34 Mon Sep 17 00:00:00 2001 From: sid <sk03140@surrey.ac.uk> Date: Wed, 9 Apr 2025 23:34:52 +0100 Subject: [PATCH 008/124] added account page function --- frontend_microservice/frontend.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index 886aa30..92352d1 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -161,7 +161,10 @@ def get_events(account_id): return render_template('accountEvents.html', event=events) return render_template('accountEvents.html') - +@app.route('/account', methods=['GET']) +def account(): + """User account page where they can see their profile and logout""" + return render_template('account.html') if __name__ == "__main__": # checks for hostname to see if running in containers then sets host appropriately -- GitLab From 4cb8c2f3bbcd894b208807f45f04ba4ef31ff252 Mon Sep 17 00:00:00 2001 From: sid <sk03140@surrey.ac.uk> Date: Wed, 9 Apr 2025 23:35:49 +0100 Subject: [PATCH 009/124] added login status nav --- frontend_microservice/static/auth.js | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/frontend_microservice/static/auth.js b/frontend_microservice/static/auth.js index df88016..4fe3816 100644 --- a/frontend_microservice/static/auth.js +++ b/frontend_microservice/static/auth.js @@ -1,4 +1,24 @@ document.addEventListener('DOMContentLoaded', () => { + // Function to update navigation based on login status + function updateNavigation() { + const userId = localStorage.getItem('user_id'); + const loginLink = document.getElementById('login-link'); + const accountLink = document.getElementById('account-link'); + + if (userId && loginLink && accountLink) { + // User is logged in + loginLink.style.display = 'none'; + accountLink.style.display = 'inline'; + } else if (loginLink && accountLink) { + // User is not logged in + loginLink.style.display = 'inline'; + accountLink.style.display = 'none'; + } + } + + // Run navigation update on every page load + updateNavigation(); + // Registration form handler if (document.getElementById('registerForm')) { document.getElementById('registerForm').addEventListener('submit', async (e) => { @@ -63,4 +83,14 @@ document.addEventListener('DOMContentLoaded', () => { } }); } + + // Logout button handler + const logoutButton = document.getElementById('logout-button'); + if (logoutButton) { + logoutButton.addEventListener('click', () => { + localStorage.removeItem('user_id'); + alert('Logged out successfully!'); + window.location.href = '/'; + }); + } }); \ No newline at end of file -- GitLab From ce4688f068aca1898592b58f4abd876cfd1f2592 Mon Sep 17 00:00:00 2001 From: sid <sk03140@surrey.ac.uk> Date: Wed, 9 Apr 2025 23:36:50 +0100 Subject: [PATCH 010/124] added user account page --- frontend_microservice/static/account.js | 30 +++++++++++++++++++ frontend_microservice/templates/account.html | 30 +++++++++++++++++++ frontend_microservice/templates/index.html | 4 ++- frontend_microservice/templates/register.html | 2 +- 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 frontend_microservice/static/account.js create mode 100644 frontend_microservice/templates/account.html diff --git a/frontend_microservice/static/account.js b/frontend_microservice/static/account.js new file mode 100644 index 0000000..bb665fb --- /dev/null +++ b/frontend_microservice/static/account.js @@ -0,0 +1,30 @@ +document.addEventListener('DOMContentLoaded', () => { + const userStatusDiv = document.getElementById('user-status'); + const logoutButton = document.getElementById('logout-button'); + const accountEventsLink = document.getElementById('account-events-link'); + + // Check if user is logged in + const userId = localStorage.getItem('user_id'); + + if (userId) { + userStatusDiv.textContent = `Logged in (User ID: ${userId})`; + + accountEventsLink.href = `/account/${userId}`; + + if (logoutButton) { + logoutButton.style.display = 'block'; + } + } else { + userStatusDiv.textContent = 'You are not logged in'; + + if (logoutButton) { + logoutButton.style.display = 'none'; + } + + accountEventsLink.href = '/login'; + accountEventsLink.textContent = 'Login to see your events'; + + // Redirect to login page + window.location.href = '/login'; + } +}); \ No newline at end of file diff --git a/frontend_microservice/templates/account.html b/frontend_microservice/templates/account.html new file mode 100644 index 0000000..8a8a10c --- /dev/null +++ b/frontend_microservice/templates/account.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> + <link rel="icon" href="{{ url_for('static', filename='favicon.PNG') }}" type="image/png"> + <script src="{{ url_for('static', filename='auth.js') }}"></script> + </head> + <body> + <nav class="top-nav"> + <div class="auth-links"> + <a href="/login" id="login-link">Login/Signup</a> + <a href="/account" id="account-link" style="display: none;">My Account</a> + </div> + </nav> + <div class="centered"> + <div class="card"> + <h1>My Account</h1> + <div id="user-status"> + </div> + <div class="card internal"> + <a id="account-events-link" href="/accountEvents">My Events</a> + </div> + <div class="card internal"> + <button id="logout-button">Logout</button> + </div> + </div> + </div> + <script src="{{ url_for('static', filename='account.js') }}"></script> + </body> +</html> \ No newline at end of file diff --git a/frontend_microservice/templates/index.html b/frontend_microservice/templates/index.html index 67b7106..695e900 100644 --- a/frontend_microservice/templates/index.html +++ b/frontend_microservice/templates/index.html @@ -3,11 +3,13 @@ <head> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> <link rel="icon" href="{{ url_for('static', filename='favicon.PNG') }}" type="image/png"> + <script src="{{ url_for('static', filename='auth.js') }}"></script> </head> <body> <nav class="top-nav"> <div class="auth-links"> - <a href="/login"> Login/Signup </a> + <a href="/login" id="login-link">Login/Signup</a> + <a href="/account" id="account-link" style="display: none;">My Account</a> </div> </nav> <div class="centered"> diff --git a/frontend_microservice/templates/register.html b/frontend_microservice/templates/register.html index 06a532f..ef1d239 100644 --- a/frontend_microservice/templates/register.html +++ b/frontend_microservice/templates/register.html @@ -16,7 +16,7 @@ <div class="form-group"> <label>Password:</label> <input type="password" id="password" required> - <small>Must be at least 12 characters with uppercase, lowercase, and numbers</small> + <small>Must be at least 8 characters with uppercase, lowercase, and numbers</small> </div> <button type="submit">Register</button> </form> -- GitLab From 8560fc6f6a8f1a4e1b57e5c37507852076f9fa4c Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Thu, 10 Apr 2025 09:45:21 +0100 Subject: [PATCH 011/124] add get_best_dates to nginx --- nginx/nginx.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 2d93af0..ced0533 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -32,4 +32,8 @@ server { location /login { proxy_pass http://account_service/login; } + + location /get_best_dates { + proxy_pass http://event_service/get_best_dates; + } } \ No newline at end of file -- GitLab From 32085265adfdc64a4f3cddf778106ab13d5ebd64 Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Thu, 10 Apr 2025 09:46:00 +0100 Subject: [PATCH 012/124] add correct route between pages --- frontend_microservice/frontend.py | 8 ++++---- frontend_microservice/templates/eventSummary.html | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index 55d2462..42692b9 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -24,7 +24,7 @@ def create_event(): data_output = response.json() if response.status_code == 201 else [] event_id=data_output['event_id'] if response.status_code == 201: # Check if event was successfully createds - redirect_url = url_for('share_link', event_id=event_id) + redirect_url = url_for('eventSummary', event_id=event_id) #print(redirect_url) return jsonify({'url': redirect_url}) else: @@ -81,7 +81,7 @@ def availability(event_id): response = requests.post(f"{EVENT_API_URL}/add_availabilities", json=data) if response.status_code == 201: # Check if was added successfully - return jsonify({'url': url_for('index')}) + return jsonify({'url': url_for('eventSummary', event_id=event_id)}) else: return f"Error: {response.text}", response.status_code @@ -114,8 +114,8 @@ def share_link(event_id): response = requests.get(f"{EVENT_API_URL}/get_event_info", json = data_input) data_output = response.json() if response.status_code == 200 else [] event_name = data_output['name'] - link = f"http://127.0.0.1:8000/availability/{event_id}" - redirect_url = url_for('availability', event_id=event_id) + link = f"http://127.0.0.1:8000/eventSummary/{event_id}" + redirect_url = url_for('eventSummary', event_id=event_id) return render_template('share_link.html', event=event_name, link=link,url=redirect_url) return render_template('share_link.html') diff --git a/frontend_microservice/templates/eventSummary.html b/frontend_microservice/templates/eventSummary.html index 531cf5a..ea8a641 100644 --- a/frontend_microservice/templates/eventSummary.html +++ b/frontend_microservice/templates/eventSummary.html @@ -15,6 +15,7 @@ </script> <h1>'{{ event }}' Summary</h1> + <a href="/share_link/{{ id }}"> Copy the event link </a> <button id="best_time_button" onclick="getDates(availabilities)">PRESS HERE TO FIND YOUR BEST DATE</button> <div id="best_dates"> @@ -25,6 +26,7 @@ <div id="summary_block" class="card"> <h2>Availabilities</h2> + <a href="/availability/{{ id }}"> Enter your availabilities </a> </div> -- GitLab From e81aeada9c3a70e8e74c9aec04f5d4f83771a603 Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Thu, 10 Apr 2025 10:33:59 +0100 Subject: [PATCH 013/124] fix repeating best dates --- frontend_microservice/static/script.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/frontend_microservice/static/script.js b/frontend_microservice/static/script.js index 41c9ec4..0cbe55a 100644 --- a/frontend_microservice/static/script.js +++ b/frontend_microservice/static/script.js @@ -244,13 +244,19 @@ function getDates(availabilities){ console.log("button pressed"); // Create card for best date section var date_area = document.getElementById("best_dates"); - date_area.classList.add("card") + date_area.classList.add("card"); + + var container = document.getElementById("date_container"); + container.innerHTML = ""; + all_old_div = container.querySelectorAll('div'); + for (let i = 0; index < all_old_div.length; index++) { + const div_element = all_old_div[i]; + div_element.remove(); + }; // Create a heading for that section - var date_heading = document.getElementById("date_heading") - var new_heading = document.createElement("h2"); - new_heading.textContent = "CALCULATING BEST DATES" - date_heading.appendChild(new_heading); + var date_heading = document.getElementById("date_heading"); + date_heading.textContent = "CALCULATING BEST DATES"; // Start request for information $.ajax({ @@ -260,10 +266,9 @@ function getDates(availabilities){ data: JSON.stringify({'availabilities': availabilities}), success: function(response) { // Format the section in which the dates will be listed - var container = document.getElementById("date_container"); // https://www.w3schools.com/jsref/prop_element_classlist.asp container.classList.add("columns"); - new_heading.textContent = "POSSIBLE DATES" + date_heading.textContent = "POSSIBLE DATES" // Loop through the response and add new div elements for each date for (const date in response){ -- GitLab From b2edd5c2d10dac762e3ea5cad1680ac96ba8b075 Mon Sep 17 00:00:00 2001 From: "Trieu, Hoang Hiep (PG/T - Comp Sci & Elec Eng)" <ht00865@surrey.ac.uk> Date: Fri, 11 Apr 2025 10:48:53 +0000 Subject: [PATCH 014/124] add load balancer on datefinder --- docker-compose.yml | 17 +++++++++++++++-- event_microservice/backend.py | 2 +- nginx/nginx.conf | 10 ++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index ca47ec1..e1cb407 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,6 +53,7 @@ services: depends_on: - event_service - account_service + - datefinder frontend: build: @@ -70,10 +71,22 @@ services: build: context: datefinder_microservice dockerfile: Dockerfile-datefinder - ports: - - "6000:6000" + #ports: + # - "6000:6000" depends_on: - postgres + deploy: + replicas: 2 + restart_policy: + condition: on-failure + delay: 1s + max_attempts: 3 + window: 120s + networks: + - backend + - loadbalancing + environment: + - PORT=6000 postgres: build: diff --git a/event_microservice/backend.py b/event_microservice/backend.py index 6c377f6..f744405 100644 --- a/event_microservice/backend.py +++ b/event_microservice/backend.py @@ -6,7 +6,7 @@ import os #TODO make sure the port is correct #Comment -ALGORITHM_PORT = 6000 +ALGORITHM_PORT = 80 EVENT_DATABASE_PORT = 5436 PORT = 5002 diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 2d93af0..3a5638c 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -6,6 +6,10 @@ upstream account_service { server account_service:5003 ; } +upstream datefinder { + server datefinder:6000 ; +} + server { listen 80; @@ -32,4 +36,10 @@ server { location /login { proxy_pass http://account_service/login; } + location /get_best_dates { + proxy_pass http://event_service/get_best_dates; + } + location /calculate { + proxy_pass http://datefinder/calculate; + } } \ No newline at end of file -- GitLab From ed91587b5a4352ed8f6f3bd5ed1cae425147dd7d Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Fri, 11 Apr 2025 12:14:01 +0100 Subject: [PATCH 015/124] fix duplicating dates --- frontend_microservice/static/script.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend_microservice/static/script.js b/frontend_microservice/static/script.js index 0cbe55a..01d25c4 100644 --- a/frontend_microservice/static/script.js +++ b/frontend_microservice/static/script.js @@ -247,11 +247,11 @@ function getDates(availabilities){ date_area.classList.add("card"); var container = document.getElementById("date_container"); - container.innerHTML = ""; + //container.innerHTML = ""; all_old_div = container.querySelectorAll('div'); - for (let i = 0; index < all_old_div.length; index++) { - const div_element = all_old_div[i]; - div_element.remove(); + for (let i = 0; i < all_old_div.length; i++) { + const div_element = all_old_div[i]; + div_element.remove(); }; // Create a heading for that section @@ -268,7 +268,7 @@ function getDates(availabilities){ // Format the section in which the dates will be listed // https://www.w3schools.com/jsref/prop_element_classlist.asp container.classList.add("columns"); - date_heading.textContent = "POSSIBLE DATES" + date_heading.textContent = "POSSIBLE DATES"; // Loop through the response and add new div elements for each date for (const date in response){ -- GitLab From 95d7c2d1a6113d9fccde9f5b444a362511f3ecec Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Fri, 11 Apr 2025 12:49:11 +0100 Subject: [PATCH 016/124] move copy link to event summary --- frontend_microservice/frontend.py | 4 ++- .../templates/eventSummary.html | 30 +++++++++++++++++++ .../templates/share_link.html | 2 ++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index 55d2462..07434d2 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -103,8 +103,10 @@ def eventSummary(event_id): dates = data_output['event_possible_dates'] availabilities = data_output['availabilities'] + link = f"http://127.0.0.1:8000/eventSummary/{event_id}" + # TODO: fix how this is being returned - return render_template('eventSummary.html', id=event_id, event=event_name, dates=dates, availabilities=availabilities) + return render_template('eventSummary.html', id=event_id, link=link, event=event_name, dates=dates, availabilities=availabilities) return render_template('eventSummary.html') @app.route('/share_link/<int:event_id>', methods =['GET']) diff --git a/frontend_microservice/templates/eventSummary.html b/frontend_microservice/templates/eventSummary.html index 531cf5a..668001b 100644 --- a/frontend_microservice/templates/eventSummary.html +++ b/frontend_microservice/templates/eventSummary.html @@ -15,6 +15,36 @@ </script> <h1>'{{ event }}' Summary</h1> + + <div class="centered"> + <div class="card"> + <h2>Here's the link to share for {{event}}</h2> + <div class="link-input-button"> + <input type="text" class="link-input" id="link" value="{{link}}" readonly> + <button class="copy" onclick="copyToClipboard()">Copy</button> + </div> + </div> + </div> + + + <script> + function copyToClipboard() { + var copyText = document.getElementById("link"); + + copyText.select(); + copyText.setSelectionRange(0, 99999); // For mobile devices + + // Use the Clipboard API + navigator.clipboard.writeText(copyText.value) + .then(function() { + alert("Text copied to clipboard!"); + }) + .catch(function(error) { + alert("Failed to copy text: " + error); + }); + } + </script> + <button id="best_time_button" onclick="getDates(availabilities)">PRESS HERE TO FIND YOUR BEST DATE</button> <div id="best_dates"> diff --git a/frontend_microservice/templates/share_link.html b/frontend_microservice/templates/share_link.html index ef7cf17..a340718 100644 --- a/frontend_microservice/templates/share_link.html +++ b/frontend_microservice/templates/share_link.html @@ -7,11 +7,13 @@ </head> <body> <div class="centered"> + <div> <h2>Here's the link to share for {{event}}</h2> <div class="link-input-button"> <input type="text" class="link-input" id="link" value="{{link}}" readonly> <button class="copy" onclick="copyToClipboard()">Copy</button> </div> + </div> <a href="{{url}}">Go to link</a> </div> -- GitLab From d9756ac627c950ce50c7ad3c36dc613c1beadc3b Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Fri, 11 Apr 2025 12:49:18 +0100 Subject: [PATCH 017/124] style copy link --- frontend_microservice/static/styles.css | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend_microservice/static/styles.css b/frontend_microservice/static/styles.css index 6e88f0a..b651266 100644 --- a/frontend_microservice/static/styles.css +++ b/frontend_microservice/static/styles.css @@ -37,6 +37,7 @@ button:active{ } .copy{ + width: 20%; background-color: lightblue; color: black } @@ -57,11 +58,13 @@ input{ } .link-input { - width: 50%; - max-width: 600px; + width: 100%; + font-size: small; } .link-input-button { + display: flex; + flex-direction: row; width: 100%; max-width: 600px; } -- GitLab From e6e22a2220be4b303508a0286ecd565d48d607fa Mon Sep 17 00:00:00 2001 From: hwilks <hazel.wilks@gmail.com> Date: Fri, 11 Apr 2025 16:22:33 +0100 Subject: [PATCH 018/124] see events on page --- account_microservice/user.py | 36 ++++++++-- frontend_microservice/frontend.py | 70 +++++++++++-------- frontend_microservice/static/auth.js | 2 +- frontend_microservice/static/script.js | 23 +++++- .../templates/accountEvents.html | 1 + .../templates/eventSummary.html | 2 + 6 files changed, 96 insertions(+), 38 deletions(-) diff --git a/account_microservice/user.py b/account_microservice/user.py index 3d454cf..40abd0e 100644 --- a/account_microservice/user.py +++ b/account_microservice/user.py @@ -124,23 +124,45 @@ def login(): logging.error(f"Login error: {str(e)}", exc_info=True) return jsonify({"error": "Login failed"}), 500 -@app.route('/get_events', methods=['GET']) +@app.route('/get_events', methods=['GET','POST']) def get_events(): + with open("loging.txt","w") as file: + file.write('test') + data = request.get_json() + file.write(str(data['account_id'])) + account_id = data['account_id'] + + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute('SELECT event_id FROM account_events WHERE user_id = %s', (account_id,)) + ids = cursor.fetchall() + + + event_ids = [event_id[0] for event_id in ids] + + file.write('events') + file.write(str(event_ids[1])) + + cursor.close() + conn.close() + + return jsonify({'event_ids':event_ids}), 201 + +@app.route('/add_events', methods=['POST']) +def add_events(): data = request.get_json() - account_id = data['account_id'] + user_id = data['user_id'] + event_id = data['event_id'] conn = get_db_connection() cursor = conn.cursor() - cursor.execute('SELECT event_id FROM account_events WHERE user_id = %s', (account_id,)) - ids = cursor.fetchall() - - event_ids = [event_id[0] for event_id in ids] + cursor.execute('INSERT INTO account_events (user_id, event_id) VALUES (%s, %s)', (user_id,event_id,)) cursor.close() conn.close() - return jsonify({'event_ids':event_ids}), 201 + return jsonify({"message": "Event added"}), 201 @app.errorhandler(429) def ratelimit_handler(e): diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index d7f4b60..c79f00f 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -88,14 +88,9 @@ def availability(event_id): return render_template('availability.html') -@app.route('/eventSummary/<int:event_id>', methods =['GET']) +@app.route('/eventSummary/<int:event_id>', methods =['GET', 'POST']) def eventSummary(event_id): - '''This is for ''' if request.method == 'GET': - #event_name='help' - - # sends list 'to_return' that contains all data requested more details in event_microservice docstring - # TODO: create correct 'to_return' for info I need data_input = {"id":event_id, "to_return": ['event_name','event_possible_dates','availabilities']} response = requests.get(f"{EVENT_API_URL}/get_event_info", json = data_input) data_output = response.json() if response.status_code == 200 else [] @@ -103,8 +98,17 @@ def eventSummary(event_id): dates = data_output['event_possible_dates'] availabilities = data_output['availabilities'] - # TODO: fix how this is being returned return render_template('eventSummary.html', id=event_id, event=event_name, dates=dates, availabilities=availabilities) + + if request.method == 'POST': + data = request.get_json() + response = requests.post(f"{USER_API_URL}/add_events", json=data) + + if response.status_code == 201: # Check if was added successfully + return jsonify({'message': 'Event added'}) + else: + return f"Error: {response.text}", response.status_code + return render_template('eventSummary.html') @app.route('/share_link/<int:event_id>', methods =['GET']) @@ -135,29 +139,39 @@ def getDatesp(): return data_output return render_template('eventSummary.html') -@app.route('/account/<int:account_id>', methods=['GET']) +@app.route('/account/<int:account_id>', methods=['GET','POST']) def get_events(account_id): if request.method == 'GET': - data = {'account_id':account_id} - account_response = requests.get(f"{USER_API_URL}/get_events", json = data) - event_ids = account_response.json() if account_response.status_code == 200 else [] - - events = [] - - for event_id in event_ids: - event ={} - data_input = {"id":event_id, "to_return": ['event_name']} - response = requests.get(f"{EVENT_API_URL}/get_event_info", json = data_input) - data_output = response.json() if response.status_code == 200 else [] - event['name'] = data_output['name'] - event['url'] = url_for('eventSummary', event_id=event_id) - events.append(event) - ''' - #Test data - event1= {'name':'An event','url':url_for('eventSummary', event_id=1)} - event2= {'name':'An event 2','url':'a url'} - events = [event1, event2] - ''' + with open("loging.txt","w") as file: + + data = {'account_id':account_id} + file.write('in front') + file.write(str(data['account_id'])) + account_response = requests.get(f"{USER_API_URL}/get_events", json = data) + response = account_response.json() #if account_response.status_code == 201 else [] + + event_ids = response['event_ids'] + file.write(str(type(event_ids))) + + events = [] + + for event_id in event_ids: + event ={} + file.write(str(event_id)) + #event_id = 2 + data_input = {"id":int(event_id), "to_return": ['event_name']} + response = requests.get(f"{EVENT_API_URL}/get_event_info", json = data_input) + file.write(str(response.status_code)) + data_output = response.json() if response.status_code == 200 else [] + event['name'] = data_output['name'] + event['url'] = url_for('eventSummary', event_id=event_id) + events.append(event) + ''' + #Test data + event1= {'name':'An event','url':url_for('eventSummary', event_id=1)} + event2= {'name':'An event 2','url':'a url'} + events = [event1, event2] + ''' return render_template('accountEvents.html', event=events) return render_template('accountEvents.html') diff --git a/frontend_microservice/static/auth.js b/frontend_microservice/static/auth.js index 4fe3816..e88616b 100644 --- a/frontend_microservice/static/auth.js +++ b/frontend_microservice/static/auth.js @@ -72,7 +72,7 @@ document.addEventListener('DOMContentLoaded', () => { messageDiv.style.color = 'green'; messageDiv.textContent = 'Logged in! Redirecting...'; localStorage.setItem('user_id', data.user_id); - setTimeout(() => window.location.href = '/', 1500); + setTimeout(() => window.location.href = document.referrer || '/', 1500); } else { messageDiv.style.color = 'red'; messageDiv.textContent = data.error || 'Login failed'; diff --git a/frontend_microservice/static/script.js b/frontend_microservice/static/script.js index e1562f8..fff30c1 100644 --- a/frontend_microservice/static/script.js +++ b/frontend_microservice/static/script.js @@ -377,5 +377,24 @@ function show_account_events(events){ } } - - +function addEventToAccount(event_id){ + const userId = localStorage.getItem('user_id'); + if (userId){ + $.ajax({ + url: `/eventSummary/${event_id}`, + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({'event_id':event_id, 'user_id':userId}), + success: function(response) { + alert("Added to your Account") + }, + error: function(xhr, status, error) { + alert("Error: " + error); + } + }); + } + else{ + alert("You're not logged in ... sending to login page") + window.location.href = '/login'; + } +} \ No newline at end of file diff --git a/frontend_microservice/templates/accountEvents.html b/frontend_microservice/templates/accountEvents.html index 6e1202f..f516e1f 100644 --- a/frontend_microservice/templates/accountEvents.html +++ b/frontend_microservice/templates/accountEvents.html @@ -5,6 +5,7 @@ <link rel="icon" href="{{ url_for('static', filename='favicon.PNG') }}" type="image/png"> <script> const events = {{event | tojson}}; + console.log(events) </script> </head> <body> diff --git a/frontend_microservice/templates/eventSummary.html b/frontend_microservice/templates/eventSummary.html index ea8a641..03d160b 100644 --- a/frontend_microservice/templates/eventSummary.html +++ b/frontend_microservice/templates/eventSummary.html @@ -5,6 +5,7 @@ <title>Document</title> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> + </head> <body class="centered"> <script> @@ -17,6 +18,7 @@ <h1>'{{ event }}' Summary</h1> <a href="/share_link/{{ id }}"> Copy the event link </a> <button id="best_time_button" onclick="getDates(availabilities)">PRESS HERE TO FIND YOUR BEST DATE</button> + <button id="addEvent" onclick="addEventToAccount(event_id)">Add {{event}} to your Account</button> <div id="best_dates"> <h2 id="date_heading"></h2> -- GitLab From abe42f4cdfe0ecbd9bada2d497c8b9417f5a4553 Mon Sep 17 00:00:00 2001 From: hwilks <hazel.wilks@gmail.com> Date: Fri, 11 Apr 2025 16:23:20 +0100 Subject: [PATCH 019/124] committing nginx --- nginx/nginx.conf | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index ced0533..474cefc 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -33,6 +33,14 @@ server { proxy_pass http://account_service/login; } + location /add_events { + proxy_pass http://account_service/add_events; + } + + location /get_events { + proxy_pass http://account_service/get_events; + } + location /get_best_dates { proxy_pass http://event_service/get_best_dates; } -- GitLab From 88fc017b4658ff399a025463529f4b246db21216 Mon Sep 17 00:00:00 2001 From: hwilks <hazel.wilks@gmail.com> Date: Sat, 12 Apr 2025 14:38:52 +0100 Subject: [PATCH 020/124] remove debugging comments --- account_microservice/user.py | 8 ++++---- frontend_microservice/frontend.py | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/account_microservice/user.py b/account_microservice/user.py index 40abd0e..db4e1d6 100644 --- a/account_microservice/user.py +++ b/account_microservice/user.py @@ -127,9 +127,9 @@ def login(): @app.route('/get_events', methods=['GET','POST']) def get_events(): with open("loging.txt","w") as file: - file.write('test') + #file.write('test') data = request.get_json() - file.write(str(data['account_id'])) + #file.write(str(data['account_id'])) account_id = data['account_id'] conn = get_db_connection() @@ -140,8 +140,8 @@ def get_events(): event_ids = [event_id[0] for event_id in ids] - file.write('events') - file.write(str(event_ids[1])) + #file.write('events') + #file.write(str(event_ids[1])) cursor.close() conn.close() diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index c79f00f..4b51dc8 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -145,23 +145,23 @@ def get_events(account_id): with open("loging.txt","w") as file: data = {'account_id':account_id} - file.write('in front') - file.write(str(data['account_id'])) + #file.write('in front') + #file.write(str(data['account_id'])) account_response = requests.get(f"{USER_API_URL}/get_events", json = data) response = account_response.json() #if account_response.status_code == 201 else [] event_ids = response['event_ids'] - file.write(str(type(event_ids))) + #file.write(str(type(event_ids))) events = [] for event_id in event_ids: event ={} - file.write(str(event_id)) + #file.write(str(event_id)) #event_id = 2 data_input = {"id":int(event_id), "to_return": ['event_name']} response = requests.get(f"{EVENT_API_URL}/get_event_info", json = data_input) - file.write(str(response.status_code)) + #file.write(str(response.status_code)) data_output = response.json() if response.status_code == 200 else [] event['name'] = data_output['name'] event['url'] = url_for('eventSummary', event_id=event_id) -- GitLab From c97510ec9674d6db39269f9932428079b8ced973 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Sat, 12 Apr 2025 15:14:03 +0000 Subject: [PATCH 021/124] Limit deployment to only main branch --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f3a2109..005bbdc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -107,4 +107,3 @@ deploy: url: http://$SERVER_IP only: - main - - staging/deploy-test -- GitLab From 0293d5f845fb63b946a441334de12c573d838650 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Sat, 12 Apr 2025 15:15:24 +0000 Subject: [PATCH 022/124] Uncomment python unit tests and add test tag --- .gitlab-ci.yml | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 005bbdc..961b044 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,23 +9,22 @@ variables: TAG_LATEST: $DOCKERHUB_REGISTRY:latest # tag the docker image with "latest" (overriden by current commit every deployment) TAG_COMMIT: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA # docker image tag for current commit -#unit_tests: -# stage: test -# image: python:3.9 -# #tags: -# # - pages -# script: -# - echo "Running unittest for event_finder_test.py" -# - python -m unittest tests/event_finder_test.py -# #environment: -# #- testing +unit_tests: + stage: test + image: python:3.9 + tags: + - test + script: + - echo "Running unittest for event_finder_test.py" + - python -m unittest tests/event_finder_test.py + #environment: + # - testing publish: # Publish containers to gitlab container registry image: docker:stable stage: publish only: - main - - staging/deploy-test variables: # Tell docker CLI how to talk to Docker daemon; see # https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker-executor -- GitLab From 9e2a0643931b15bbf454d32844af59a16df9ab20 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Sat, 12 Apr 2025 15:22:06 +0000 Subject: [PATCH 023/124] Add environment and implement consistent code ordering for each stage --- .gitlab-ci.yml | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 961b044..79d7e0a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,26 +5,31 @@ stages: - publish - deploy + variables: TAG_LATEST: $DOCKERHUB_REGISTRY:latest # tag the docker image with "latest" (overriden by current commit every deployment) TAG_COMMIT: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA # docker image tag for current commit + unit_tests: - stage: test image: python:3.9 + stage: test tags: - test + environment: + name: testing script: - echo "Running unittest for event_finder_test.py" - python -m unittest tests/event_finder_test.py - #environment: - # - testing + publish: # Publish containers to gitlab container registry image: docker:stable stage: publish only: - main + tags: + - deployment variables: # Tell docker CLI how to talk to Docker daemon; see # https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker-executor @@ -32,16 +37,9 @@ publish: # Publish containers to gitlab container registry # Use the overlayfs driver for improved performance: DOCKER_DRIVER: overlay2 DOCKER_TLS_CERTDIR: "" - tags: - - deployment # Temporarily pages tag, since Surrey's pre-existing runner only picks up jobs with this tag services: - docker:dind # Runs Docker Daemon in a separate container, which the job communicates with - #before_script: - #- docker rmi -f $(docker images -aq) # remove all existing images - #- docker info # debugging - #- echo "$TAG_LATEST" - #- echo "$TAG_COMMIT" - #- ls -R + script: - echo "$DOCKERHUB_ACCESS_TOKEN" | docker login -u $DOCKERHUB_USER --password-stdin # Login to dockerhub @@ -76,8 +74,14 @@ publish: # Publish containers to gitlab container registry deploy: image: alpine:latest # Lightweight Linux image stage: deploy + only: + - main tags: - deployment + environment: + name: production + url: http://$SERVER_IP + before_script: # Install SSH client and gettext (useful for templating functionality) - apk add gettext openssh-client @@ -96,13 +100,3 @@ deploy: # Run docker-compose - ssh -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP "docker-compose down && docker-compose up -d" #"docker-compose down && docker-compose up -d" - - #- ssh -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP "docker pull $TAG_COMMIT" # Pull current commit's image - #- ssh -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP "docker container ls || true" - #- ssh -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP "docker container rm -f gregc-frontend || true" # Delete "GregC" image if it exists. - #- ssh -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP "docker run -d -p 8080:80 --name gregc-frontend $TAG_COMMIT" - environment: - name: production - url: http://$SERVER_IP - only: - - main -- GitLab From e0b230eb594d79d603fb2b15cd6e24852e619fd3 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Sat, 12 Apr 2025 15:54:20 +0000 Subject: [PATCH 024/124] Install requirements for unit tests --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 79d7e0a..7fb6c09 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,11 +18,14 @@ unit_tests: - test environment: name: testing + before_script: + - pip install -r datefinder_microservice/requirements.txt script: - echo "Running unittest for event_finder_test.py" - python -m unittest tests/event_finder_test.py + publish: # Publish containers to gitlab container registry image: docker:stable stage: publish -- GitLab From 044f9661b1b4c31a692f9480c3d4c9028998e1b9 Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Tue, 15 Apr 2025 15:43:53 +0100 Subject: [PATCH 025/124] add which users are available to best dates --- datefinder_microservice/event_finder.py | 32 ++++++++++++------------- frontend_microservice/static/script.js | 20 ++++++++++++++-- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/datefinder_microservice/event_finder.py b/datefinder_microservice/event_finder.py index 6347df4..e28a95b 100644 --- a/datefinder_microservice/event_finder.py +++ b/datefinder_microservice/event_finder.py @@ -9,7 +9,7 @@ PORT = 6000 import logging # Set up logging to a file -logging.basicConfig(filename='app.log', level=logging.DEBUG) +logging.basicConfig(filename='app.log', level=logging.INFO) @@ -31,15 +31,12 @@ def best_date(): Each dictionary entry has the username as a key and a list of datetime objects as a value""" users_dict = request.get_json() - print(users_dict) - logging.info(users_dict) date_dict = {} max_count = 0 - best_dates = [] + best_dates = {} users = users_dict["availabilities"] - logging.info(users) # Iterate through all event users for user in users: @@ -56,22 +53,25 @@ def best_date(): # If it is then wipe the list of best_date choices, add the new date and set the new max_count # If the count is equal to max count then also add date to list of best_dates if date_dict[slot] > max_count: - best_dates = [] + best_dates = {} max_count = date_dict[slot] - best_dates.append(slot) + best_dates[slot] = [] elif date_dict[slot] == max_count: - best_dates.append(slot) - - output_dates = {key: date for key, date in enumerate(best_dates)} + best_dates[slot] = [] + + + for best_date in best_dates: + logging.info("We made it into the loop") + for user in users: + logging.info("We made it into the loop") + if best_date in users[user]: + logging.info(best_date) + logging.info(user) + best_dates[best_date].append(str(user)) + logging.info(best_dates) return best_dates, 200 -# This is for readablility and testing -# Format the dates -#print(best_dates_list) -#best_dates_formatted = [date.strftime('%Y-%m-%d') for date in best_dates_list] -#print(f"The best event dates are: {best_dates_formatted}") - if __name__ == "__main__": app.run(debug=True, host="0.0.0.0", port=PORT) \ No newline at end of file diff --git a/frontend_microservice/static/script.js b/frontend_microservice/static/script.js index ae2938f..aafd355 100644 --- a/frontend_microservice/static/script.js +++ b/frontend_microservice/static/script.js @@ -280,14 +280,29 @@ function getDates(availabilities){ // https://www.w3schools.com/jsref/prop_element_classlist.asp container.classList.add("columns"); date_heading.textContent = "POSSIBLE DATES"; + console.log(response) // Loop through the response and add new div elements for each date for (const date in response){ var date_div = document.createElement("div"); - best_date = response[date] date_div.classList.add("card", "internal"); + // https://www.w3schools.com/js/js_dates.asp - date_div.textContent = new Date(best_date).toDateString(); + var date_div_heading = document.createElement("h3"); + date_div_heading.textContent = new Date(date).toDateString(); + + date_div.appendChild(date_div_heading); + + // create that dates list of available users + var available_users_list = document.createElement("ul"); + for (const user in response[date]){ + var li = document.createElement("li"); + var available_user = response[date][user]; + li.textContent = available_user; + available_users_list.appendChild(li); + }; + + date_div.appendChild(available_users_list) container.appendChild(date_div); } }, @@ -315,6 +330,7 @@ function new_list_element(availabilities, dates){ div_heading.textContent = heading; av_div.append(div_heading); + // Create list element of possible dates var dateslist = document.createElement("ul"); for (const date in dates){ var li = document.createElement("li"); -- GitLab From cfbd1ae752fa5fcb24ee409ca947a203a7553346 Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Wed, 16 Apr 2025 12:05:18 +0100 Subject: [PATCH 026/124] update unittests for new algorithm --- tests/event_finder_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/event_finder_test.py b/tests/event_finder_test.py index b67812f..5278187 100644 --- a/tests/event_finder_test.py +++ b/tests/event_finder_test.py @@ -66,8 +66,8 @@ class TestAlgorithmWorks(unittest.TestCase): response_data = response.get_json() # Assert that the response is a list and contains the correct dates - self.assertIsInstance(response_data, list) - self.assertEqual(response_data, ['2025-05-03T00:00:00']) + self.assertIsInstance(response_data, dict) + self.assertEqual(response_data, {'2025-05-03T00:00:00': ["user1", "user2", "user3", "user4"]}) def test_gets_correct_dates_when_two_possible(self): @@ -95,7 +95,7 @@ class TestAlgorithmWorks(unittest.TestCase): # Check that the correct dates are found (3rd and 6th) - self.assertEqual(response.get_json(), ['2025-05-03T00:00:00', '2025-05-06T00:00:00']) + self.assertEqual(response.get_json(), {'2025-05-03T00:00:00': ["user1", "user2", "user3", "user4"], '2025-05-06T00:00:00': ["user1", "user2", "user3", "user4"]}) def test_user_with_no_availability(self): input_data = { @@ -119,7 +119,7 @@ class TestAlgorithmWorks(unittest.TestCase): response = self.client.post('/calculate', json=input_data) # Check that the correct dates are found (3rd and 6th) - self.assertEqual(response.get_json(), ['2025-05-03T00:00:00', '2025-05-06T00:00:00']) + self.assertEqual(response.get_json(), {'2025-05-03T00:00:00':["user1", "user2", "user3"], '2025-05-06T00:00:00': ["user1", "user2", "user3"]}) class TestWrongDataTypes(unittest.TestCase): @classmethod -- GitLab From cbee8c43a2dcff42f2046f0a17c0b1faf781450b Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Wed, 16 Apr 2025 12:06:10 +0100 Subject: [PATCH 027/124] improve styling --- frontend_microservice/static/styles.css | 6 ++++++ frontend_microservice/templates/eventSummary.html | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend_microservice/static/styles.css b/frontend_microservice/static/styles.css index 6e88f0a..f966198 100644 --- a/frontend_microservice/static/styles.css +++ b/frontend_microservice/static/styles.css @@ -14,6 +14,12 @@ h1{ font-size: 50px; } +ul{ + list-style-type: none; + margin: 0; + padding: 0; +} + button{ color: #fff; display: inline-block; diff --git a/frontend_microservice/templates/eventSummary.html b/frontend_microservice/templates/eventSummary.html index 03d160b..eb0bc4e 100644 --- a/frontend_microservice/templates/eventSummary.html +++ b/frontend_microservice/templates/eventSummary.html @@ -10,9 +10,9 @@ <body class="centered"> <script> var availabilities = {{ availabilities | tojson }}; - console.log(availabilities) + console.log(availabilities); var dates = {{dates | tojson }}; - var event_id = {{id | tojson}} + var event_id = {{id | tojson}}; </script> <h1>'{{ event }}' Summary</h1> -- GitLab From fee9c809bc7fc84722b8080f80f071d898e0966c Mon Sep 17 00:00:00 2001 From: hwilks <hazel.wilks@gmail.com> Date: Wed, 16 Apr 2025 14:32:15 +0100 Subject: [PATCH 028/124] change url so doesn't go back to register page --- frontend_microservice/frontend.py | 2 +- frontend_microservice/static/auth.js | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index 4b51dc8..2af7f8e 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -49,7 +49,7 @@ def login(): try: data = request.get_json() response = requests.post(f"{USER_API_URL}/login", json=data) - return jsonify(response.json()), response.status_code + return jsonify(response.json()),response.status_code except Exception as e: return jsonify({"error": str(e)}), 500 return render_template('login.html') diff --git a/frontend_microservice/static/auth.js b/frontend_microservice/static/auth.js index e88616b..ed2f420 100644 --- a/frontend_microservice/static/auth.js +++ b/frontend_microservice/static/auth.js @@ -65,14 +65,24 @@ document.addEventListener('DOMContentLoaded', () => { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); - + + const data = await response.json(); if (response.ok) { messageDiv.style.color = 'green'; messageDiv.textContent = 'Logged in! Redirecting...'; + console.log(document.referrer) localStorage.setItem('user_id', data.user_id); - setTimeout(() => window.location.href = document.referrer || '/', 1500); + setTimeout(() => { + const registrationPage = 'http://127.0.0.1:8000/register'; + + if (document.referrer === registrationPage || document.referrer === '') { + window.location.href = '/'; + } else { + window.location.href = document.referrer; + } + }, 1500); } else { messageDiv.style.color = 'red'; messageDiv.textContent = data.error || 'Login failed'; -- GitLab From e8fe7fe70ad4a4bd722a7da481e4f21f6f9c1eec Mon Sep 17 00:00:00 2001 From: sid <sk03140@surrey.ac.uk> Date: Thu, 17 Apr 2025 19:44:22 +0100 Subject: [PATCH 029/124] updated schema for password changes --- account_database/schema.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/account_database/schema.sql b/account_database/schema.sql index 2b71ebc..87037bf 100644 --- a/account_database/schema.sql +++ b/account_database/schema.sql @@ -2,7 +2,9 @@ CREATE TABLE IF NOT EXISTS users ( user_id SERIAL PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + reset_token VARCHAR(100) UNIQUE, + reset_token_expiry TIMESTAMP ); CREATE TABLE IF NOT EXISTS account_events ( -- GitLab From 8b4040bf779b4b8e6ad6a015f65864aa39a4e9da Mon Sep 17 00:00:00 2001 From: sid <sk03140@surrey.ac.uk> Date: Thu, 17 Apr 2025 19:44:51 +0100 Subject: [PATCH 030/124] updated req for password changes --- account_microservice/requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/account_microservice/requirements.txt b/account_microservice/requirements.txt index b92756f..89a59d4 100644 --- a/account_microservice/requirements.txt +++ b/account_microservice/requirements.txt @@ -3,4 +3,6 @@ psycopg2-binary werkzeug>=3.0.0 flask-limiter email-validator -cryptography \ No newline at end of file +cryptography +itsdangerous +Flask-Mail \ No newline at end of file -- GitLab From a6665ffa4407f3ca9a77203486616e8bd110567c Mon Sep 17 00:00:00 2001 From: sid <sk03140@surrey.ac.uk> Date: Thu, 17 Apr 2025 19:45:50 +0100 Subject: [PATCH 031/124] added password forget and reset functions --- account_microservice/user.py | 138 ++++++++++++++++++++++++++++++++++- 1 file changed, 137 insertions(+), 1 deletion(-) diff --git a/account_microservice/user.py b/account_microservice/user.py index db4e1d6..216a81f 100644 --- a/account_microservice/user.py +++ b/account_microservice/user.py @@ -7,9 +7,24 @@ from flask_limiter import Limiter from flask_limiter.util import get_remote_address import re import os +from itsdangerous import URLSafeTimedSerializer, SignatureExpired, BadTimeSignature +from flask_mail import Mail, Message +import datetime + app = Flask(__name__) +app.config['MAIL_SERVER'] = os.environ.get('MAIL_SERVER', 'smtp.example.com') +app.config['MAIL_PORT'] = int(os.environ.get('MAIL_PORT', 587)) +app.config['MAIL_USE_TLS'] = os.environ.get('MAIL_USE_TLS', 'true').lower() == 'true' +app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME') +app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD') +app.config['MAIL_DEFAULT_SENDER'] = os.environ.get('MAIL_DEFAULT_SENDER', 'noreply@example.com') +app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'your-very-secret-key-here') + +mail = Mail(app) +ts = URLSafeTimedSerializer(app.config["SECRET_KEY"]) + logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', @@ -113,7 +128,7 @@ def login(): conn.close() if not result or not check_password_hash(result[1], password): - return jsonify({"error": "Invalid credentials"}), 401 + return jsonify({"error": "Invalid credentials. If you are not registered, please register first!"}), 401 return jsonify({ "message": "Login successful", @@ -164,6 +179,127 @@ def add_events(): return jsonify({"message": "Event added"}), 201 +@app.route('/request_password_reset', methods=['POST']) +@limiter.limit("3 per 15 minute") +def request_password_reset(): + """ + Handles the initial request to reset a password. + Validates email, generates a token, stores it with expiry, and sends reset email. + """ + data = request.get_json() + if not data or 'email' not in data: + return jsonify({"error": "Email is required"}), 400 + + email = data['email'].strip().lower() + try: + valid = validate_email(email) + email = valid.normalized + except EmailNotValidError: + logging.warning(f"Password reset attempt for invalid email format: {email}") + return jsonify({"message": "If your email is registered, you will receive a reset link."}), 200 + + conn = None + try: + conn = get_db_connection() + cursor = conn.cursor() + + cursor.execute('SELECT user_id FROM users WHERE email = %s', (email,)) + user = cursor.fetchone() + + if user: + user_id = user[0] + token = ts.dumps(email, salt='password-reset-salt') + expiry_time = datetime.datetime.utcnow() + datetime.timedelta(hours=1) + + cursor.execute( + 'UPDATE users SET reset_token = %s, reset_token_expiry = %s WHERE user_id = %s', + (token, expiry_time, user_id) + ) + conn.commit() + + reset_url = f"http://localhost:8000/reset_password/{token}" + + subject = "Password Reset Request" + body = f"Click the following link to reset your password: {reset_url}\n\nThis link will expire in 1 hour.\n\nIf you did not request this, please ignore this email." + msg = Message(subject, recipients=[email], body=body) + + try: + mail.send(msg) + logging.info(f"Password reset email successfully sent to {email} for user_id {user_id}") + except Exception as mail_error: + logging.error(f"Failed to send password reset email to {email}: {str(mail_error)}", exc_info=True) + + else: + logging.warning(f"Password reset attempt for non-existent email: {email}") + + return jsonify({"message": "If your email is registered, you will receive a reset link."}), 200 + + except Exception as e: + logging.error(f"Password reset request processing error for {email}: {str(e)}", exc_info=True) + return jsonify({"error": "An error occurred while processing your request."}), 500 + finally: + if conn: + conn.close() + +@app.route('/reset_password', methods=['POST']) +@limiter.limit("5 per minute") +def reset_password(): + """ + Handles the submission of the new password using the token. + Verifies the token, validates the new password, updates the hash, and invalidates the token. + """ + data = request.get_json() + if not data or not all(k in data for k in ['token', 'password']): + return jsonify({"error": "Token and new password are required"}), 400 + + token = data['token'] + new_password = data['password'] + + if pass_error := validate_password(new_password): + return jsonify({"error": pass_error}), 400 + + conn = None + try: + email = ts.loads(token, salt='password-reset-salt', max_age=3600) + + conn = get_db_connection() + cursor = conn.cursor() + + cursor.execute( + 'SELECT user_id FROM users WHERE email = %s AND reset_token = %s AND reset_token_expiry > NOW()', + (email, token) + ) + user = cursor.fetchone() + + if not user: + logging.warning(f"Attempt to reset password with invalid/used/expired token for email: {email}") + return jsonify({"error": "Invalid or expired password reset link."}), 400 + + user_id = user[0] + new_password_hash = generate_password_hash(new_password, method='scrypt', salt_length=16) + + cursor.execute( + 'UPDATE users SET password_hash = %s, reset_token = NULL, reset_token_expiry = NULL WHERE user_id = %s', + (new_password_hash, user_id) + ) + conn.commit() + + logging.info(f"Password successfully reset for user_id: {user_id} via token") + return jsonify({"message": "Password successfully updated"}), 200 + + except SignatureExpired: + logging.warning(f"Password reset attempt with expired token: {token[:10]}...") + return jsonify({"error": "The password reset link has expired."}), 400 + except BadTimeSignature: + logging.warning(f"Password reset attempt with invalid token signature: {token[:10]}...") + return jsonify({"error": "Invalid password reset link."}), 400 + except Exception as e: + logging.error(f"Password reset processing error: {str(e)}", exc_info=True) + return jsonify({"error": "An error occurred while resetting the password."}), 500 + finally: + if conn: + conn.close() + @app.errorhandler(429) def ratelimit_handler(e): return jsonify({"error": "Too many requests"}), 429 -- GitLab From 41b4e5f3a24d1c78da39151bec216bc3590d15c0 Mon Sep 17 00:00:00 2001 From: sid <sk03140@surrey.ac.uk> Date: Thu, 17 Apr 2025 19:47:41 +0100 Subject: [PATCH 032/124] added password forget and reset functions --- frontend_microservice/frontend.py | 41 ++++++++ frontend_microservice/static/auth.js | 149 +++++++++++++++++++++------ nginx/nginx.conf | 8 ++ 3 files changed, 164 insertions(+), 34 deletions(-) diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index 2af7f8e..5a254fa 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -3,6 +3,8 @@ import requests import json import datetime import os +import logging + app = Flask(__name__) PORT = 5001 @@ -180,6 +182,45 @@ def account(): """User account page where they can see their profile and logout""" return render_template('account.html') +@app.route('/forgot_password', methods=['GET', 'POST']) +def forgot_password(): + """ + Handles the request to send a password reset link. + GET: Renders the forgot password page. + POST: Forwards the email to the account microservice. + """ + if request.method == 'POST': + try: + data = request.get_json() + response = requests.post(f"{USER_API_URL}/request_password_reset", json=data) + return jsonify(response.json()), response.status_code + except Exception as e: + print(f"Frontend forgot password error: {str(e)}") + return jsonify({"error": "An internal error occurred while contacting the account service"}), 500 + return render_template('forgot_password.html') + +@app.route('/reset_password/<token>', methods=['GET']) +def reset_password_token(token): + """ + Renders the page where the user can enter their new password. + The token from the URL is passed to the template. + """ + return render_template('reset_password.html', token=token) + +@app.route('/reset_password', methods=['POST']) +def reset_password_submit(): + """ + Handles the submission of the new password form. + POST: Forwards the token and new password to the account microservice. + """ + try: + data = request.get_json() + response = requests.post(f"{USER_API_URL}/reset_password", json=data) + return jsonify(response.json()), response.status_code + except Exception as e: + print(f"Frontend reset password submission error: {str(e)}") + return jsonify({"error": "An internal error occurred while contacting the account service"}), 500 + if __name__ == "__main__": # checks for hostname to see if running in containers then sets host appropriately hostname = os.environ.get('HOSTNAME', '') diff --git a/frontend_microservice/static/auth.js b/frontend_microservice/static/auth.js index ed2f420..24e0fff 100644 --- a/frontend_microservice/static/auth.js +++ b/frontend_microservice/static/auth.js @@ -1,41 +1,38 @@ document.addEventListener('DOMContentLoaded', () => { - // Function to update navigation based on login status function updateNavigation() { const userId = localStorage.getItem('user_id'); const loginLink = document.getElementById('login-link'); const accountLink = document.getElementById('account-link'); - + if (userId && loginLink && accountLink) { - // User is logged in loginLink.style.display = 'none'; accountLink.style.display = 'inline'; } else if (loginLink && accountLink) { - // User is not logged in loginLink.style.display = 'inline'; accountLink.style.display = 'none'; } } - // Run navigation update on every page load updateNavigation(); - // Registration form handler - if (document.getElementById('registerForm')) { - document.getElementById('registerForm').addEventListener('submit', async (e) => { + const registerForm = document.getElementById('registerForm'); + if (registerForm) { + registerForm.addEventListener('submit', async (e) => { e.preventDefault(); const email = document.getElementById('email').value; const password = document.getElementById('password').value; const messageDiv = document.getElementById('message'); + messageDiv.textContent = ''; try { - const response = await fetch('/register', { + const response = await fetch('/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); const data = await response.json(); - + if (response.status === 201) { messageDiv.style.color = 'green'; messageDiv.textContent = 'Registered successfully! Redirecting to login...'; @@ -51,56 +48,140 @@ document.addEventListener('DOMContentLoaded', () => { }); } - // Login form handler - if (document.getElementById('loginForm')) { - document.getElementById('loginForm').addEventListener('submit', async (e) => { +const loginForm = document.getElementById('loginForm'); +if (loginForm) { + loginForm.addEventListener('submit', async (e) => { + e.preventDefault(); + const email = document.getElementById('email').value; + const password = document.getElementById('password').value; + const messageDiv = document.getElementById('message'); + messageDiv.textContent = ''; + + try { + const response = await fetch('/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email, password }) + }); + + + const data = await response.json(); + + if (response.ok) { + messageDiv.style.color = 'green'; + messageDiv.textContent = 'Logged in! Redirecting...'; + console.log(document.referrer) + localStorage.setItem('user_id', data.user_id); + setTimeout(() => { + const registrationPage = window.location.origin + '/register'; + const referrer = document.referrer; + const shouldRedirectHome = referrer === registrationPage || + referrer.includes('/reset_password') || + referrer === ''; + + if (shouldRedirectHome) { + window.location.href = '/'; + } else { + window.location.href = document.referrer; + } + }, 1500); + } else { + messageDiv.style.color = 'red'; + messageDiv.textContent = data.error || 'Login failed'; + } + } catch (error) { + messageDiv.style.color = 'red'; + messageDiv.textContent = 'Failed to connect to server'; + } + }); +} + const forgotPasswordForm = document.getElementById('forgotPasswordForm'); + if (forgotPasswordForm) { + forgotPasswordForm.addEventListener('submit', async (e) => { e.preventDefault(); const email = document.getElementById('email').value; + const messageDiv = document.getElementById('message'); + messageDiv.textContent = ''; + + try { + const response = await fetch('/forgot_password', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email }) + }); + + const data = await response.json(); + + if (response.ok) { + messageDiv.style.color = 'green'; + + messageDiv.textContent = data.message || 'If your email is registered, you will receive a reset link.'; + forgotPasswordForm.reset(); + } else { + messageDiv.style.color = 'red'; + messageDiv.textContent = data.error || 'Failed to send reset link.'; + } + } catch (error) { + messageDiv.style.color = 'red'; + messageDiv.textContent = 'Network error. Please try again.'; + } + }); + } + + const resetPasswordForm = document.getElementById('resetPasswordForm'); + if (resetPasswordForm) { + resetPasswordForm.addEventListener('submit', async (e) => { + e.preventDefault(); + const token = document.getElementById('token').value; const password = document.getElementById('password').value; + const confirm_password = document.getElementById('confirm_password').value; const messageDiv = document.getElementById('message'); + messageDiv.textContent = ''; + + if (password !== confirm_password) { + messageDiv.style.color = 'red'; + messageDiv.textContent = 'Passwords do not match.'; + return; + } + + if (password.length < 8 || !/[A-Z]/.test(password) || !/[a-z]/.test(password) || !/[0-9]/.test(password)) { + messageDiv.style.color = 'red'; + messageDiv.textContent = 'Password must be at least 8 characters with uppercase, lowercase, and numbers.'; + return; + } + try { - const response = await fetch('/login', { + const response = await fetch('/reset_password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ email, password }) + body: JSON.stringify({ token, password }) }); - - + const data = await response.json(); - + if (response.ok) { messageDiv.style.color = 'green'; - messageDiv.textContent = 'Logged in! Redirecting...'; - console.log(document.referrer) - localStorage.setItem('user_id', data.user_id); - setTimeout(() => { - const registrationPage = 'http://127.0.0.1:8000/register'; - - if (document.referrer === registrationPage || document.referrer === '') { - window.location.href = '/'; - } else { - window.location.href = document.referrer; - } - }, 1500); + messageDiv.textContent = data.message || 'Password reset successful! Redirecting to login...'; + resetPasswordForm.reset(); + setTimeout(() => window.location.href = '/login', 2500); } else { messageDiv.style.color = 'red'; - messageDiv.textContent = data.error || 'Login failed'; + messageDiv.textContent = data.error || 'Failed to reset password. The link may be invalid or expired.'; } } catch (error) { messageDiv.style.color = 'red'; - messageDiv.textContent = 'Failed to connect to server'; + messageDiv.textContent = 'Network error. Please try again.'; } }); } - // Logout button handler const logoutButton = document.getElementById('logout-button'); if (logoutButton) { logoutButton.addEventListener('click', () => { localStorage.removeItem('user_id'); alert('Logged out successfully!'); - window.location.href = '/'; + window.location.href = '/'; }); } }); \ No newline at end of file diff --git a/nginx/nginx.conf b/nginx/nginx.conf index d6fb835..00db418 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -52,4 +52,12 @@ server { proxy_pass http://datefinder/calculate; } + location /request_password_reset { + proxy_pass http://account_service; + } + + location /reset_password { + proxy_pass http://account_service; + } + } \ No newline at end of file -- GitLab From 1a9ce7623afbb0db6300b7fe9092c432fdfdf4ec Mon Sep 17 00:00:00 2001 From: sid <sk03140@surrey.ac.uk> Date: Thu, 17 Apr 2025 19:48:33 +0100 Subject: [PATCH 033/124] added forgot password button --- frontend_microservice/templates/login.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend_microservice/templates/login.html b/frontend_microservice/templates/login.html index 8b2f8c2..e5e3124 100644 --- a/frontend_microservice/templates/login.html +++ b/frontend_microservice/templates/login.html @@ -5,7 +5,6 @@ <link rel="stylesheet" href="{{ url_for('static', filename='auth.css') }}"> <link rel="icon" href="{{ url_for('static', filename='favicon.PNG') }}" type="image/png"> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> - </head> <body class="centered"> <div class="card"> @@ -19,6 +18,9 @@ <label>Password:</label> <input type="password" id="password" required> </div> + <div style="text-align: right; margin-bottom: 10px;"> + <a href="{{ url_for('forgot_password') }}" style="font-size: 0.9em; background: none; color: midnightblue; text-decoration: underline;">Forgot Password?</a> + </div> <button type="submit">Login</button> </form> <div class="auth-links"> -- GitLab From 97d4d481370beaaf77045003fc90ec9c767beb72 Mon Sep 17 00:00:00 2001 From: sid <sk03140@surrey.ac.uk> Date: Thu, 17 Apr 2025 19:50:06 +0100 Subject: [PATCH 034/124] added mailpit and related variables for dummy password reset requests --- docker-compose.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index e1cb407..0a0583d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,14 @@ services: + mailpit: + image: axllent/mailpit:latest + container_name: mailpit + ports: + - "1025:1025" + - "8025:8025" + networks: + - backend + restart: unless-stopped + event_service: build: context: event_microservice @@ -39,7 +49,13 @@ services: - loadbalancing environment: - PORT=5003 - + - MAIL_SERVER=mailpit # Point to the mailpit service name + - MAIL_PORT=1025 # Mailpit's default SMTP port + - MAIL_USE_TLS=false # Mailpit doesn't use TLS by default + - MAIL_USERNAME= # No username needed for Mailpit by default + - MAIL_PASSWORD= # No password needed for Mailpit by default + - MAIL_DEFAULT_SENDER=noreply@gregc.com # Can be anything + - SECRET_KEY=your_very_long_random_and_unguessable_string nginx: image: nginx -- GitLab From af48ad06dba5c16003e6a89ffe5914e7d251c24a Mon Sep 17 00:00:00 2001 From: sid <sk03140@surrey.ac.uk> Date: Thu, 17 Apr 2025 19:50:46 +0100 Subject: [PATCH 035/124] added forget password html --- .../templates/forgot_password.html | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 frontend_microservice/templates/forgot_password.html diff --git a/frontend_microservice/templates/forgot_password.html b/frontend_microservice/templates/forgot_password.html new file mode 100644 index 0000000..fc25474 --- /dev/null +++ b/frontend_microservice/templates/forgot_password.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html> +<head> + <title>Forgot Password</title> + <link rel="stylesheet" href="{{ url_for('static', filename='auth.css') }}"> + <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> + <script src="{{ url_for('static', filename='auth.js') }}" defer></script> +</head> +<body class="centered"> + <div class="card"> + <h2>Reset Password</h2> + <p>Enter your email address and we'll send you a link to reset your password.</p> + <form id="forgotPasswordForm" class="card internal"> + <div class="form-group"> + <label>Email:</label> + <input type="email" id="email" required> + </div> + <button type="submit">Send Reset Link</button> + </form> + <div class="auth-links"> + <p><a href="{{ url_for('login') }}">Back to Login</a></p> + </div> + <div id="message"></div> + </div> + </body> +</html> \ No newline at end of file -- GitLab From 52faa4e90d40193eaee79f1f48d4b7b1225c49df Mon Sep 17 00:00:00 2001 From: sid <sk03140@surrey.ac.uk> Date: Thu, 17 Apr 2025 19:51:09 +0100 Subject: [PATCH 036/124] added reset password html --- .../templates/reset_password.html | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 frontend_microservice/templates/reset_password.html diff --git a/frontend_microservice/templates/reset_password.html b/frontend_microservice/templates/reset_password.html new file mode 100644 index 0000000..f30a0ee --- /dev/null +++ b/frontend_microservice/templates/reset_password.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<html> +<head> + <title>Reset Password</title> + <link rel="stylesheet" href="{{ url_for('static', filename='auth.css') }}"> + <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> + <script src="{{ url_for('static', filename='auth.js') }}" defer></script> +</head> +<body class="centered"> + <div class="card"> + <h2>Enter New Password</h2> + <form id="resetPasswordForm" class="card internal"> + <input type="hidden" id="token" value="{{ token }}"> + <div class="form-group"> + <label>New Password:</label> + <input type="password" id="password" required> + <small>Must be at least 8 characters with uppercase, lowercase, and numbers</small> + </div> + <div class="form-group"> + <label>Confirm New Password:</label> + <input type="password" id="confirm_password" required> + </div> + <button type="submit">Reset Password</button> + </form> + <div id="message"></div> + </div> + </body> +</html> \ No newline at end of file -- GitLab From 1f75ac6eb1638c8c5d48e0359da923cb7598a20d Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Mon, 21 Apr 2025 10:35:11 +0100 Subject: [PATCH 037/124] add references i had noted but forgotten to add to code --- frontend_microservice/frontend.py | 4 ---- frontend_microservice/static/script.js | 5 ++++- frontend_microservice/static/styles.css | 17 ----------------- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index 1b65bae..4228b95 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -98,13 +98,10 @@ def eventSummary(event_id): dates = data_output['event_possible_dates'] availabilities = data_output['availabilities'] -<<<<<<< HEAD link = f"http://127.0.0.1:8000/eventSummary/{event_id}" # TODO: fix how this is being returned return render_template('eventSummary.html', id=event_id, link=link, event=event_name, dates=dates, availabilities=availabilities) -======= - return render_template('eventSummary.html', id=event_id, event=event_name, dates=dates, availabilities=availabilities) if request.method == 'POST': data = request.get_json() @@ -115,7 +112,6 @@ def eventSummary(event_id): else: return f"Error: {response.text}", response.status_code ->>>>>>> development return render_template('eventSummary.html') @app.route('/share_link/<int:event_id>', methods =['GET']) diff --git a/frontend_microservice/static/script.js b/frontend_microservice/static/script.js index aafd355..aed9c50 100644 --- a/frontend_microservice/static/script.js +++ b/frontend_microservice/static/script.js @@ -258,7 +258,6 @@ function getDates(availabilities){ date_area.classList.add("card"); var container = document.getElementById("date_container"); - //container.innerHTML = ""; all_old_div = container.querySelectorAll('div'); for (let i = 0; i < all_old_div.length; i++) { const div_element = all_old_div[i]; @@ -279,6 +278,7 @@ function getDates(availabilities){ // Format the section in which the dates will be listed // https://www.w3schools.com/jsref/prop_element_classlist.asp container.classList.add("columns"); + // https://www.w3schools.com/jsref/prop_node_textcontent.asp date_heading.textContent = "POSSIBLE DATES"; console.log(response) @@ -318,7 +318,9 @@ function new_list_element(availabilities, dates){ var summary_block = document.getElementById("summary_block"); var sub_block = document.createElement("div"); sub_block.classList.add("columns") + // https://www.w3schools.com/jsref/met_node_appendchild.asp summary_block.appendChild(sub_block); + // Creates list of possible event dates var av_div = document.createElement("div"); @@ -374,6 +376,7 @@ function new_list_element(availabilities, dates){ for (const av in user_dates){ var li = document.createElement("li"); var date = user_dates[av]; + // https://www.w3schools.com/js/js_dates.asp var output_date = new Date(date).toDateString(); li.textContent = output_date; availabilitieslist.appendChild(li); diff --git a/frontend_microservice/static/styles.css b/frontend_microservice/static/styles.css index 75d4c73..61295a8 100644 --- a/frontend_microservice/static/styles.css +++ b/frontend_microservice/static/styles.css @@ -172,23 +172,6 @@ a:active{ margin: 20px; } -/* -.two_column { - display: flex; - justify-content: space-between; - gap: 15px; - text-align: center; -} -.two_column .column { - flex: 1; -} -.two_column li { - list-style-type: none; -} -.two_column ul{ - padding-left: 0; -} -*/ .columns { text-align: center; -- GitLab From b9be95eb1bd0f1974274e58897bb8975a2c12484 Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Mon, 21 Apr 2025 10:36:02 +0100 Subject: [PATCH 038/124] add event link in event summary again --- frontend_microservice/templates/eventSummary.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frontend_microservice/templates/eventSummary.html b/frontend_microservice/templates/eventSummary.html index b9589bb..d1e6be6 100644 --- a/frontend_microservice/templates/eventSummary.html +++ b/frontend_microservice/templates/eventSummary.html @@ -16,7 +16,6 @@ </script> <h1>'{{ event }}' Summary</h1> -<<<<<<< HEAD <div class="centered"> <div class="card"> @@ -47,9 +46,6 @@ } </script> -======= - <a href="/share_link/{{ id }}"> Copy the event link </a> ->>>>>>> development <button id="best_time_button" onclick="getDates(availabilities)">PRESS HERE TO FIND YOUR BEST DATE</button> <button id="addEvent" onclick="addEventToAccount(event_id)">Add {{event}} to your Account</button> -- GitLab From 9da9eb5c1b34550d30e7c621c03d862f3e85fa17 Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Mon, 21 Apr 2025 11:16:13 +0100 Subject: [PATCH 039/124] add home button (and nav bar to all pages) --- frontend_microservice/static/styles.css | 9 +++++ frontend_microservice/templates/account.html | 3 +- .../templates/accountEvents.html | 10 +++++- .../templates/availability.html | 10 +++++- frontend_microservice/templates/create.html | 15 ++++++-- .../templates/eventSummary.html | 34 ++++++++++++------- frontend_microservice/templates/index.html | 3 +- frontend_microservice/templates/login.html | 10 +++++- frontend_microservice/templates/register.html | 10 +++++- 9 files changed, 83 insertions(+), 21 deletions(-) diff --git a/frontend_microservice/static/styles.css b/frontend_microservice/static/styles.css index f966198..46b3eb4 100644 --- a/frontend_microservice/static/styles.css +++ b/frontend_microservice/static/styles.css @@ -1,4 +1,8 @@ /* Core Elements */ + +/* https://www.w3schools.com/css/css3_flexbox_container.asp used to learn about flex containers */ + + body{ font-family: Verdana, Geneva, Tahoma, sans-serif; background-color: hsl(270, 4%, 90%); @@ -241,3 +245,8 @@ a:active{ width: 200px; } +.lowered { + position: absolute; + top: 130px; +} + diff --git a/frontend_microservice/templates/account.html b/frontend_microservice/templates/account.html index 8a8a10c..8b0f2b1 100644 --- a/frontend_microservice/templates/account.html +++ b/frontend_microservice/templates/account.html @@ -9,10 +9,11 @@ <nav class="top-nav"> <div class="auth-links"> <a href="/login" id="login-link">Login/Signup</a> + <a href="/index" id = "home-link" style="display: inline;">Home</a> <a href="/account" id="account-link" style="display: none;">My Account</a> </div> </nav> - <div class="centered"> + <div class="centered lowered"> <div class="card"> <h1>My Account</h1> <div id="user-status"> diff --git a/frontend_microservice/templates/accountEvents.html b/frontend_microservice/templates/accountEvents.html index f516e1f..e1c6842 100644 --- a/frontend_microservice/templates/accountEvents.html +++ b/frontend_microservice/templates/accountEvents.html @@ -3,13 +3,21 @@ <head> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> <link rel="icon" href="{{ url_for('static', filename='favicon.PNG') }}" type="image/png"> + <script src="{{ url_for('static', filename='auth.js') }}"></script> <script> const events = {{event | tojson}}; console.log(events) </script> </head> <body> - <div class="centered"> + <nav class="top-nav"> + <div class="auth-links"> + <a href="/login" id="login-link">Login/Signup</a> + <a href="/index" id = "home-link" style="display: inline;">Home</a> + <a href="/account" id="account-link" style="display: none;">My Account</a> + </div> + </nav> + <div class="centered lowered"> <div class="card"> <h1>These are your events</h1> <h3>Click to see the details</h3> diff --git a/frontend_microservice/templates/availability.html b/frontend_microservice/templates/availability.html index 0c238e6..060b586 100644 --- a/frontend_microservice/templates/availability.html +++ b/frontend_microservice/templates/availability.html @@ -8,6 +8,7 @@ <link rel="stylesheet" href="{{ url_for('static', filename='calendar-styles.css') }}"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200"> <link rel="icon" href="{{ url_for('static', filename='favicon.PNG') }}" type="image/png"> + <script src="{{ url_for('static', filename='auth.js') }}"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <script type="text/javascript"> @@ -21,7 +22,14 @@ <body class = "centered"> - <div class ="card" style="width:50vw"> + <nav class="top-nav"> + <div class="auth-links"> + <a href="/login" id="login-link">Login/Signup</a> + <a href="/index" id = "home-link" style="display: inline;">Home</a> + <a href="/account" id="account-link" style="display: none;">My Account</a> + </div> + </nav> + <div class ="card lowered" style="width:50vw"> <header class="page-header"> <div class="centered"> <h1>{{ event }}</h1> diff --git a/frontend_microservice/templates/create.html b/frontend_microservice/templates/create.html index c6e5867..7b8a0fb 100644 --- a/frontend_microservice/templates/create.html +++ b/frontend_microservice/templates/create.html @@ -9,13 +9,21 @@ <link rel="stylesheet" href="{{ url_for('static', filename='calendar-styles.css') }}"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200"> <link rel="icon" href="{{ url_for('static', filename='favicon.PNG') }}" type="image/png"> + <script src="{{ url_for('static', filename='auth.js') }}"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> </head> <body class = "centered"> + <nav class="top-nav"> + <div class="auth-links"> + <a href="/login" id="login-link">Login/Signup</a> + <a href="/index" id = "home-link" style="display: inline;">Home</a> + <a href="/account" id="account-link" style="display: none;">My Account</a> + </div> + </nav> - <div class="card"> + <div class="card lowered"> <!-- TODO: Set up a grid container for this section --> <div class="event_title"> <input type="text" id="event_name" placeholder="ENTER NAME OF EVENT HERE"></input> @@ -51,9 +59,10 @@ </div> </div> </div> - </div> + - <button id="send-dates" onclick="sendDates()">Save Dates</button> + <button id="send-dates" onclick="sendDates()">Save Dates</button> + </div> <script src="{{ url_for('static', filename='script.js') }}"></script> </body> </html> \ No newline at end of file diff --git a/frontend_microservice/templates/eventSummary.html b/frontend_microservice/templates/eventSummary.html index eb0bc4e..b29a1af 100644 --- a/frontend_microservice/templates/eventSummary.html +++ b/frontend_microservice/templates/eventSummary.html @@ -5,30 +5,40 @@ <title>Document</title> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> + <script src="{{ url_for('static', filename='auth.js') }}"></script> </head> <body class="centered"> + <nav class="top-nav"> + <div class="auth-links"> + <a href="/login" id="login-link">Login/Signup</a> + <a href="/index" id = "home-link" style="display: inline;">Home</a> + <a href="/account" id="account-link" style="display: none;">My Account</a> + </div> + </nav> + <script> var availabilities = {{ availabilities | tojson }}; console.log(availabilities); var dates = {{dates | tojson }}; var event_id = {{id | tojson}}; </script> + <div class="lowered centered"> + <h1>'{{ event }}' Summary</h1> + <a href="/share_link/{{ id }}"> Copy the event link </a> + <button id="best_time_button" onclick="getDates(availabilities)">PRESS HERE TO FIND YOUR BEST DATE</button> + <button id="addEvent" onclick="addEventToAccount(event_id)">Add {{event}} to your Account</button> - <h1>'{{ event }}' Summary</h1> - <a href="/share_link/{{ id }}"> Copy the event link </a> - <button id="best_time_button" onclick="getDates(availabilities)">PRESS HERE TO FIND YOUR BEST DATE</button> - <button id="addEvent" onclick="addEventToAccount(event_id)">Add {{event}} to your Account</button> - - <div id="best_dates"> - <h2 id="date_heading"></h2> - <div id="date_container"></div> - </div> + <div id="best_dates"> + <h2 id="date_heading"></h2> + <div id="date_container"></div> + </div> - <div id="summary_block" class="card"> - <h2>Availabilities</h2> - <a href="/availability/{{ id }}"> Enter your availabilities </a> + <div id="summary_block" class="card"> + <h2>Availabilities</h2> + <a href="/availability/{{ id }}"> Enter your availabilities </a> + </div> </div> diff --git a/frontend_microservice/templates/index.html b/frontend_microservice/templates/index.html index 695e900..22c2515 100644 --- a/frontend_microservice/templates/index.html +++ b/frontend_microservice/templates/index.html @@ -9,10 +9,11 @@ <nav class="top-nav"> <div class="auth-links"> <a href="/login" id="login-link">Login/Signup</a> + <a href="/index" id = "home-link" style="display: inline;">Home</a> <a href="/account" id="account-link" style="display: none;">My Account</a> </div> </nav> - <div class="centered"> + <div class="centered lowered"> <div class="card"> <h1>Welcome</h1> <div class="card internal"> diff --git a/frontend_microservice/templates/login.html b/frontend_microservice/templates/login.html index 8b2f8c2..80b8c9c 100644 --- a/frontend_microservice/templates/login.html +++ b/frontend_microservice/templates/login.html @@ -5,10 +5,18 @@ <link rel="stylesheet" href="{{ url_for('static', filename='auth.css') }}"> <link rel="icon" href="{{ url_for('static', filename='favicon.PNG') }}" type="image/png"> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> + <script src="{{ url_for('static', filename='auth.js') }}"></script> </head> <body class="centered"> - <div class="card"> + <nav class="top-nav"> + <div class="auth-links"> + <a href="/login" id="login-link">Login/Signup</a> + <a href="/index" id = "home-link" style="display: inline;">Home</a> + <a href="/account" id="account-link" style="display: none;">My Account</a> + </div> + </nav> + <div class="card lowered"> <h2>Login</h2> <form id="loginForm" class="card internal"> <div class="form-group"> diff --git a/frontend_microservice/templates/register.html b/frontend_microservice/templates/register.html index ef1d239..68a4610 100644 --- a/frontend_microservice/templates/register.html +++ b/frontend_microservice/templates/register.html @@ -4,9 +4,17 @@ <title>Register</title> <link rel="stylesheet" href="{{ url_for('static', filename='auth.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> + <script src="{{ url_for('static', filename='auth.js') }}"></script> </head> <body class="centered"> - <div class="card"> + <nav class="top-nav"> + <div class="auth-links"> + <a href="/login" id="login-link">Login/Signup</a> + <a href="/index" id = "home-link" style="display: inline;">Home</a> + <a href="/account" id="account-link" style="display: none;">My Account</a> + </div> + </nav> + <div class="card lowered"> <h2>Register</h2> <form id="registerForm" class="card internal"> <div class="form-group"> -- GitLab From 7e6dabee0db8f69595aba0ba6f2234d9a6e52cc2 Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Mon, 28 Apr 2025 10:01:21 +0100 Subject: [PATCH 040/124] Fix page titles --- frontend_microservice/templates/account.html | 1 + frontend_microservice/templates/accountEvents.html | 1 + frontend_microservice/templates/eventSummary.html | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend_microservice/templates/account.html b/frontend_microservice/templates/account.html index 8b0f2b1..74c9442 100644 --- a/frontend_microservice/templates/account.html +++ b/frontend_microservice/templates/account.html @@ -4,6 +4,7 @@ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> <link rel="icon" href="{{ url_for('static', filename='favicon.PNG') }}" type="image/png"> <script src="{{ url_for('static', filename='auth.js') }}"></script> + <title>Account</title> </head> <body> <nav class="top-nav"> diff --git a/frontend_microservice/templates/accountEvents.html b/frontend_microservice/templates/accountEvents.html index e1c6842..d817d1c 100644 --- a/frontend_microservice/templates/accountEvents.html +++ b/frontend_microservice/templates/accountEvents.html @@ -8,6 +8,7 @@ const events = {{event | tojson}}; console.log(events) </script> + <title>Account Events</title> </head> <body> <nav class="top-nav"> diff --git a/frontend_microservice/templates/eventSummary.html b/frontend_microservice/templates/eventSummary.html index b29a1af..5c090e2 100644 --- a/frontend_microservice/templates/eventSummary.html +++ b/frontend_microservice/templates/eventSummary.html @@ -2,7 +2,7 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Document</title> + <title>EventSummary</title> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <script src="{{ url_for('static', filename='auth.js') }}"></script> -- GitLab From 194b36ec3fc7e91984825f87962b1b2edafb0de6 Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Mon, 28 Apr 2025 10:20:29 +0100 Subject: [PATCH 041/124] fix eventSummary page --- .../templates/eventSummary.html | 75 +++++++++---------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/frontend_microservice/templates/eventSummary.html b/frontend_microservice/templates/eventSummary.html index 03c4bee..2d1a32f 100644 --- a/frontend_microservice/templates/eventSummary.html +++ b/frontend_microservice/templates/eventSummary.html @@ -23,55 +23,52 @@ var dates = {{dates | tojson }}; var event_id = {{id | tojson}}; </script> + <div class="lowered centered"> <h1>'{{ event }}' Summary</h1> - <a href="/share_link/{{ id }}"> Copy the event link </a> - <button id="best_time_button" onclick="getDates(availabilities)">PRESS HERE TO FIND YOUR BEST DATE</button> - <button id="addEvent" onclick="addEventToAccount(event_id)">Add {{event}} to your Account</button> - - <h1>'{{ event }}' Summary</h1> - <div class="centered"> - <div class="card"> - <h2>Here's the link to share for {{event}}</h2> - <div class="link-input-button"> - <input type="text" class="link-input" id="link" value="{{link}}" readonly> - <button class="copy" onclick="copyToClipboard()">Copy</button> + <div class="centered"> + <div class="card"> + <h2>Here's the link to share for {{event}}</h2> + <div class="link-input-button"> + <input type="text" class="link-input" id="link" value="{{link}}" readonly> + <button class="copy" onclick="copyToClipboard()">Copy</button> + </div> </div> </div> - </div> - + - <script> - function copyToClipboard() { - var copyText = document.getElementById("link"); - - copyText.select(); - copyText.setSelectionRange(0, 99999); // For mobile devices - - // Use the Clipboard API - navigator.clipboard.writeText(copyText.value) - .then(function() { - alert("Text copied to clipboard!"); - }) - .catch(function(error) { - alert("Failed to copy text: " + error); - }); - } - </script> + <script> + function copyToClipboard() { + var copyText = document.getElementById("link"); + + copyText.select(); + copyText.setSelectionRange(0, 99999); // For mobile devices + + // Use the Clipboard API + navigator.clipboard.writeText(copyText.value) + .then(function() { + alert("Text copied to clipboard!"); + }) + .catch(function(error) { + alert("Failed to copy text: " + error); + }); + } + </script> - <button id="best_time_button" onclick="getDates(availabilities)">PRESS HERE TO FIND YOUR BEST DATE</button> - <button id="addEvent" onclick="addEventToAccount(event_id)">Add {{event}} to your Account</button> + <button id="best_time_button" onclick="getDates(availabilities)">PRESS HERE TO FIND YOUR BEST DATE</button> + <button id="addEvent" onclick="addEventToAccount(event_id)">Add {{event}} to your Account</button> - <div id="best_dates"> - <h2 id="date_heading"></h2> - <div id="date_container"></div> - </div> + <div id="best_dates"> + <h2 id="date_heading"></h2> + <div id="date_container"></div> + </div> - <div id="summary_block" class="card"> - <h2>Availabilities</h2> - <a href="/availability/{{ id }}"> Enter your availabilities </a> + <div id="summary_block" class="card"> + <h2>Availabilities</h2> + <a href="/availability/{{ id }}"> Enter your availabilities </a> + </div> </div> </div> -- GitLab From 22e5434756c40c2df8609b00d30348095c87e39a Mon Sep 17 00:00:00 2001 From: sid <sk03140@surrey.ac.uk> Date: Mon, 28 Apr 2025 11:46:05 +0100 Subject: [PATCH 042/124] change email service;smtp --- docker-compose.yml | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 0a0583d..21aceb3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,14 +1,5 @@ services: - mailpit: - image: axllent/mailpit:latest - container_name: mailpit - ports: - - "1025:1025" - - "8025:8025" - networks: - - backend - restart: unless-stopped - + event_service: build: context: event_microservice @@ -48,14 +39,13 @@ services: - backend - loadbalancing environment: - - PORT=5003 - - MAIL_SERVER=mailpit # Point to the mailpit service name - - MAIL_PORT=1025 # Mailpit's default SMTP port - - MAIL_USE_TLS=false # Mailpit doesn't use TLS by default - - MAIL_USERNAME= # No username needed for Mailpit by default - - MAIL_PASSWORD= # No password needed for Mailpit by default - - MAIL_DEFAULT_SENDER=noreply@gregc.com # Can be anything - - SECRET_KEY=your_very_long_random_and_unguessable_string + - MAIL_SERVER=smtp.gmail.com + - MAIL_PORT=587 + - MAIL_USE_TLS=true + - MAIL_USERNAME=gregcmailer@gmail.com + - MAIL_PASSWORD=bkxu hkqq wiax fuke + - MAIL_DEFAULT_SENDER=gregcmailer@gmail.com + - SECRET_KEY=f361e60c60f0aaf062ba9b9fc727f08eb525ad93ac715e012b224c0506596025 nginx: image: nginx -- GitLab From 1a01fb905fd1bc27a4162b500fc4cb81f713a29c Mon Sep 17 00:00:00 2001 From: "Singh, Siddhartha (PG/T - Comp Sci & Elec Eng)" <sk03140@surrey.ac.uk> Date: Wed, 30 Apr 2025 21:33:56 +0000 Subject: [PATCH 043/124] add favicon --- frontend_microservice/templates/forgot_password.html | 1 + frontend_microservice/templates/register.html | 1 + frontend_microservice/templates/reset_password.html | 1 + 3 files changed, 3 insertions(+) diff --git a/frontend_microservice/templates/forgot_password.html b/frontend_microservice/templates/forgot_password.html index fc25474..abc8be5 100644 --- a/frontend_microservice/templates/forgot_password.html +++ b/frontend_microservice/templates/forgot_password.html @@ -3,6 +3,7 @@ <head> <title>Forgot Password</title> <link rel="stylesheet" href="{{ url_for('static', filename='auth.css') }}"> + <link rel="icon" href="{{ url_for('static', filename='favicon.PNG') }}" type="image/png"> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> <script src="{{ url_for('static', filename='auth.js') }}" defer></script> </head> diff --git a/frontend_microservice/templates/register.html b/frontend_microservice/templates/register.html index 68a4610..3463696 100644 --- a/frontend_microservice/templates/register.html +++ b/frontend_microservice/templates/register.html @@ -3,6 +3,7 @@ <head> <title>Register</title> <link rel="stylesheet" href="{{ url_for('static', filename='auth.css') }}"> + <link rel="icon" href="{{ url_for('static', filename='favicon.PNG') }}" type="image/png"> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> <script src="{{ url_for('static', filename='auth.js') }}"></script> </head> diff --git a/frontend_microservice/templates/reset_password.html b/frontend_microservice/templates/reset_password.html index f30a0ee..0eee3a9 100644 --- a/frontend_microservice/templates/reset_password.html +++ b/frontend_microservice/templates/reset_password.html @@ -3,6 +3,7 @@ <head> <title>Reset Password</title> <link rel="stylesheet" href="{{ url_for('static', filename='auth.css') }}"> + <link rel="icon" href="{{ url_for('static', filename='favicon.PNG') }}" type="image/png"> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> <script src="{{ url_for('static', filename='auth.js') }}" defer></script> </head> -- GitLab From 75bfafe98e6611de48f38b01bed299046541225a Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Fri, 2 May 2025 16:04:12 +0100 Subject: [PATCH 044/124] improve element sizing and rescaling --- frontend_microservice/static/styles.css | 21 ++++++++++++++++--- .../templates/eventSummary.html | 12 ++++++----- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/frontend_microservice/static/styles.css b/frontend_microservice/static/styles.css index ae41260..2bee7e0 100644 --- a/frontend_microservice/static/styles.css +++ b/frontend_microservice/static/styles.css @@ -186,11 +186,26 @@ a:active{ flex-wrap: wrap; } /* https://www.w3schools.com/cssref/atrule_media.php */ +/* @media only screen and (max-width: 1800px) { - #summary_block { - width: 100%; + #summary_block{ + width: 90%; } - } +} + */ + +/* These blocks set the width of the event summary cards to 80% of the screen width +This enables the flex containers to move elements around depending on screen size */ +#summary_block, #best_dates, #link_container, #EventSummary_buttons{ + width: 80%; +} + +#best_time_button, #addEvent{ + width: 70%; + font-size: large; + font-weight: bold; +} +/* Some other elements inside EventSummary have had their widths set using inline CSS */ .fixed_h { height: 200px; diff --git a/frontend_microservice/templates/eventSummary.html b/frontend_microservice/templates/eventSummary.html index 2d1a32f..61907b6 100644 --- a/frontend_microservice/templates/eventSummary.html +++ b/frontend_microservice/templates/eventSummary.html @@ -24,11 +24,11 @@ var event_id = {{id | tojson}}; </script> - <div class="lowered centered"> + <div class="lowered centered" style="width: 100%;"> <h1>'{{ event }}' Summary</h1> - <div class="centered"> - <div class="card"> + <div class="centered" style="width: 100%;"> + <div class="card" id="link_container"> <h2>Here's the link to share for {{event}}</h2> <div class="link-input-button"> <input type="text" class="link-input" id="link" value="{{link}}" readonly> @@ -56,8 +56,10 @@ } </script> - <button id="best_time_button" onclick="getDates(availabilities)">PRESS HERE TO FIND YOUR BEST DATE</button> - <button id="addEvent" onclick="addEventToAccount(event_id)">Add {{event}} to your Account</button> + <div class = "card" id="EventSummary_buttons"> + <button id="best_time_button" onclick="getDates(availabilities)">PRESS HERE TO FIND YOUR BEST DATE</button> + <button id="addEvent" onclick="addEventToAccount(event_id)">Add {{event}} to your Account</button> + </div> <div id="best_dates"> <h2 id="date_heading"></h2> -- GitLab From f553d3cf8ed3f774287309208f304a734b622fab Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Fri, 2 May 2025 16:20:44 +0100 Subject: [PATCH 045/124] rewrite some element text --- frontend_microservice/templates/eventSummary.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend_microservice/templates/eventSummary.html b/frontend_microservice/templates/eventSummary.html index 61907b6..3e82539 100644 --- a/frontend_microservice/templates/eventSummary.html +++ b/frontend_microservice/templates/eventSummary.html @@ -25,7 +25,7 @@ </script> <div class="lowered centered" style="width: 100%;"> - <h1>'{{ event }}' Summary</h1> + <h1>{{ event }} Summary</h1> <div class="centered" style="width: 100%;"> <div class="card" id="link_container"> @@ -57,7 +57,7 @@ </script> <div class = "card" id="EventSummary_buttons"> - <button id="best_time_button" onclick="getDates(availabilities)">PRESS HERE TO FIND YOUR BEST DATE</button> + <button id="best_time_button" onclick="getDates(availabilities)">Press here to find the best date to meet!</button> <button id="addEvent" onclick="addEventToAccount(event_id)">Add {{event}} to your Account</button> </div> -- GitLab From 60d34d988a9b3af8e276ae79f712dafdbea827d3 Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Fri, 2 May 2025 16:37:11 +0100 Subject: [PATCH 046/124] improve CSS on eventSummary --- frontend_microservice/static/auth.css | 4 ---- frontend_microservice/static/styles.css | 14 +++++++++++--- frontend_microservice/templates/eventSummary.html | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/frontend_microservice/static/auth.css b/frontend_microservice/static/auth.css index 3e5c97c..f3b3f9c 100644 --- a/frontend_microservice/static/auth.css +++ b/frontend_microservice/static/auth.css @@ -72,10 +72,6 @@ h2 { font-weight: bold; } -.auth-links a:hover { - text-decoration: underline; -} - #message { text-align: center; margin-top: 1rem; diff --git a/frontend_microservice/static/styles.css b/frontend_microservice/static/styles.css index 2bee7e0..40a8294 100644 --- a/frontend_microservice/static/styles.css +++ b/frontend_microservice/static/styles.css @@ -26,6 +26,7 @@ ul{ button{ color: #fff; + font-weight: bold; display: inline-block; background-color: midnightblue; width: 300px; @@ -39,7 +40,7 @@ button{ } button:hover{ /*background-color: rgb(38, 38, 172);*/ - background-color: #3C5A99; + background-color: rgb(38, 38, 172); } button:active{ background-color: hsl(270, 4%, 90%); @@ -52,6 +53,11 @@ button:active{ color: black } +.copy:hover{ + color: #fff; +} + + hr{ border-color: midnightblue; border-style: dotted; @@ -105,6 +111,7 @@ form{ a{ color: white; + font-weight: bold; display: block; background-color: midnightblue; padding: 10px; @@ -200,8 +207,9 @@ This enables the flex containers to move elements around depending on screen siz width: 80%; } -#best_time_button, #addEvent{ +#best_time_button, #addEvent, #availabilities_a_tag{ width: 70%; + height: max-content; font-size: large; font-weight: bold; } @@ -236,7 +244,7 @@ This enables the flex containers to move elements around depending on screen siz } .auth-links a:hover { - background-color: midnightblue; + background-color: rgb(38, 38, 172); } diff --git a/frontend_microservice/templates/eventSummary.html b/frontend_microservice/templates/eventSummary.html index 3e82539..4bed6a5 100644 --- a/frontend_microservice/templates/eventSummary.html +++ b/frontend_microservice/templates/eventSummary.html @@ -69,7 +69,7 @@ <div id="summary_block" class="card"> <h2>Availabilities</h2> - <a href="/availability/{{ id }}"> Enter your availabilities </a> + <a href="/availability/{{ id }}" id="availabilities_a_tag"> Enter your availabilities </a> </div> </div> </div> -- GitLab From 96686482b88e13ba6cb32b8c6ad33812398aa883 Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Mon, 5 May 2025 15:20:48 +0100 Subject: [PATCH 047/124] Add rescaling UI elements in accountEvents page --- frontend_microservice/templates/accountEvents.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend_microservice/templates/accountEvents.html b/frontend_microservice/templates/accountEvents.html index d817d1c..c4e2823 100644 --- a/frontend_microservice/templates/accountEvents.html +++ b/frontend_microservice/templates/accountEvents.html @@ -19,10 +19,10 @@ </div> </nav> <div class="centered lowered"> - <div class="card"> + <div class="card" style="width: 80%;"> <h1>These are your events</h1> <h3>Click to see the details</h3> - <div class="card internal"> + <div class="card internal" style="width: 80%;"> <div id="event_block"> </div> </div> -- GitLab From b5f82c2dbb29a1fd70e77a53a7ade87302642833 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Tue, 13 May 2025 16:07:56 +0100 Subject: [PATCH 048/124] add datefinder service to docker-compose.tmpl and change nginx port mapping back to 80:80 --- .gitlab-ci.yml | 5 +++++ docker-compose.tmpl | 29 +++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7fb6c09..84ad3e5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -61,6 +61,11 @@ publish: # Publish containers to gitlab container registry - docker push $TAG_COMMIT-account-ms - docker push $TAG_LATEST-account-ms + # Build and push datefinder MS + - docker build -f ./datefinder_microservice/Dockerfile-datefinder -t $TAG_COMMIT-datefinder-ms -t $TAG_LATEST-datefinder-ms ./datefinder_microservice + - docker push $TAG_COMMIT-datefinder-ms + - docker push $TAG_LATEST-datefinder-ms + # Build and push event DB - docker build -f ./event_database/Dockerfile -t $TAG_COMMIT-event-db -t $TAG_LATEST-event-db ./event_database - docker push $TAG_COMMIT-event-db diff --git a/docker-compose.tmpl b/docker-compose.tmpl index 8142f7f..a6736e6 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -34,7 +34,14 @@ services: - backend - loadbalancing environment: - - PORT=5003 + - MAIL_SERVER=smtp.gmail.com + - MAIL_PORT=587 + - MAIL_USE_TLS=true + - MAIL_USERNAME=gregcmailer@gmail.com + - MAIL_PASSWORD=bkxu hkqq wiax fuke + - MAIL_DEFAULT_SENDER=gregcmailer@gmail.com + - SECRET_KEY=f361e60c60f0aaf062ba9b9fc727f08eb525ad93ac715e012b224c0506596025 + nginx: image: nginx @@ -42,7 +49,7 @@ services: - ./nginx:/etc/nginx/conf.d #- ./frontend_microservice/templates:/usr/share/nginx/html ports: - - "5435:80" + - "80:80" networks: - loadbalancing depends_on: @@ -59,6 +66,24 @@ services: networks: - backend + datefinder: + image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-datefinder-ms + depends_on: + - postgres + deploy: + replicas: 2 + restart_policy: + condition: on-failure + delay: 1s + max_attempts: 3 + window: 120s + networks: + - backend + - loadbalancing + environment: + - PORT=6000 + + postgres: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-event-db container_name: event_database -- GitLab From d4a0630f3b8a819a43e15edd90f688a96d41b79e Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Tue, 13 May 2025 16:09:47 +0100 Subject: [PATCH 049/124] allow (temporary) publishing and deployment on feature/deployment_update branch --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 84ad3e5..aff712f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,6 +31,7 @@ publish: # Publish containers to gitlab container registry stage: publish only: - main + - feature/deployment_update tags: - deployment variables: @@ -84,6 +85,7 @@ deploy: stage: deploy only: - main + - feature/deployment_update tags: - deployment environment: -- GitLab From 02c1264b888948a5f7f63f28b8854ed8c4e6a377 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Tue, 13 May 2025 15:26:10 +0000 Subject: [PATCH 050/124] retry deployment having deactivated port 80 --- docker-compose.tmpl | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose.tmpl b/docker-compose.tmpl index a6736e6..81b521c 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -1,6 +1,5 @@ version: "3.8" - services: event_service: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-event-ms -- GitLab From 3c1189467028b0653b6710509626ace1204b70ba Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Tue, 13 May 2025 15:52:17 +0000 Subject: [PATCH 051/124] restrict deployment to only main branch --- .gitlab-ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index aff712f..84ad3e5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,7 +31,6 @@ publish: # Publish containers to gitlab container registry stage: publish only: - main - - feature/deployment_update tags: - deployment variables: @@ -85,7 +84,6 @@ deploy: stage: deploy only: - main - - feature/deployment_update tags: - deployment environment: -- GitLab From a2d128074718c3e20a8a2046f7a4a7c5f5da4892 Mon Sep 17 00:00:00 2001 From: hwilks <hazel.wilks@gmail.com> Date: Fri, 16 May 2025 15:12:31 +0100 Subject: [PATCH 052/124] fix issue, now gives sensible error if you try to add the event twice --- account_microservice/user.py | 12 ++++++++---- frontend_microservice/frontend.py | 4 ++-- frontend_microservice/static/script.js | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/account_microservice/user.py b/account_microservice/user.py index 216a81f..78855e7 100644 --- a/account_microservice/user.py +++ b/account_microservice/user.py @@ -172,12 +172,16 @@ def add_events(): conn = get_db_connection() cursor = conn.cursor() - cursor.execute('INSERT INTO account_events (user_id, event_id) VALUES (%s, %s)', (user_id,event_id,)) + try: + cursor.execute('INSERT INTO account_events (user_id, event_id) VALUES (%s, %s)', (user_id,event_id,)) - cursor.close() - conn.close() + cursor.close() + conn.close() - return jsonify({"message": "Event added"}), 201 + return jsonify({"message": "Event added"}), 201 + except Exception as e: + logging.error(f"Add event error error: {str(e)}", exc_info=True) + return jsonify({"error": "Event already added"}), 300 @app.route('/request_password_reset', methods=['POST']) @limiter.limit("3 per 15 minute") diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index b0f475f..22f973e 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -102,7 +102,7 @@ def eventSummary(event_id): link = f"http://127.0.0.1:8000/eventSummary/{event_id}" - # TODO: fix how this is being returned + return render_template('eventSummary.html', id=event_id, link=link, event=event_name, dates=dates, availabilities=availabilities) if request.method == 'POST': @@ -112,7 +112,7 @@ def eventSummary(event_id): if response.status_code == 201: # Check if was added successfully return jsonify({'message': 'Event added'}) else: - return f"Error: {response.text}", response.status_code + return jsonify({'message':response.text}) return render_template('eventSummary.html') diff --git a/frontend_microservice/static/script.js b/frontend_microservice/static/script.js index aed9c50..285b458 100644 --- a/frontend_microservice/static/script.js +++ b/frontend_microservice/static/script.js @@ -416,7 +416,7 @@ function addEventToAccount(event_id){ contentType: 'application/json', data: JSON.stringify({'event_id':event_id, 'user_id':userId}), success: function(response) { - alert("Added to your Account") + alert(response['message']) }, error: function(xhr, status, error) { alert("Error: " + error); -- GitLab From ee32022eb287064f6d3c57daadc9ce6a0ddadc12 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Sun, 18 May 2025 16:49:34 +0100 Subject: [PATCH 053/124] make database persistent --- docker-compose.tmpl | 15 +++++++-------- docker-compose.yml | 15 +++++++-------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/docker-compose.tmpl b/docker-compose.tmpl index 81b521c..d1b50f2 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -90,9 +90,8 @@ services: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: event - #volumes: - #- postgres-db-volume:/var/lib/postgresql/data - #- ./schema.sql:/docker-entrypoint-initdb.d/schema.sql + volumes: + - event-db-volume:/var/lib/postgresql/data ports: - 5436:5436 networks: @@ -112,9 +111,8 @@ services: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: account - #volumes: - #- postgres-db-volume:/var/lib/postgresql/data - #- ./schema.sql:/docker-entrypoint-initdb.d/schema.sql + volumes: + - account-db-volume:/var/lib/postgresql/data ports: - 5437:5437 networks: @@ -127,8 +125,9 @@ services: restart: always command: -p 5437 -#volumes: - #postgres-db-volume: +volumes: + event-db-volume: + account-db-volume: networks: #frontend: #driver: bridge diff --git a/docker-compose.yml b/docker-compose.yml index 21aceb3..c429d51 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -103,9 +103,8 @@ services: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: event - #volumes: - #- postgres-db-volume:/var/lib/postgresql/data - #- ./schema.sql:/docker-entrypoint-initdb.d/schema.sql + volumes: + - event-db-volume:/var/lib/postgresql/data ports: - 5436:5436 networks: @@ -127,9 +126,8 @@ services: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: account - #volumes: - #- postgres-db-volume:/var/lib/postgresql/data - #- ./schema.sql:/docker-entrypoint-initdb.d/schema.sql + volumes: + - account-db-volume:/var/lib/postgresql/data ports: - 5437:5437 networks: @@ -142,8 +140,9 @@ services: restart: always command: -p 5437 -#volumes: - #postgres-db-volume: +volumes: + event-db-volume: + account-db-volume: networks: #frontend: #driver: bridge -- GitLab From b79f1db85aae0f83a6d5f63719b3b6b197a2a393 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Sun, 18 May 2025 17:33:01 +0100 Subject: [PATCH 054/124] move nginx config to main config location (as opposed to secondary configs in conf.d) --- nginx/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx/Dockerfile b/nginx/Dockerfile index a63d974..8e9e432 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -1,6 +1,6 @@ FROM nginx:stable-alpine -COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY nginx.conf /etc/nginx/nginx.conf EXPOSE 80 -- GitLab From 38a68f083d3475665f9f855dbca8c84a198708cc Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Sun, 18 May 2025 17:34:47 +0100 Subject: [PATCH 055/124] use existing nginx image from Dockerfile instead of creating new one --- docker-compose.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 21aceb3..d8bb894 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,6 +48,9 @@ services: - SECRET_KEY=f361e60c60f0aaf062ba9b9fc727f08eb525ad93ac715e012b224c0506596025 nginx: + build: + context: ./nginx + dockerfile: Dockerfile image: nginx volumes: - ./nginx:/etc/nginx/conf.d -- GitLab From 93c94a496acaca6c5f8cea252303b80532d5c9ef Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Sun, 18 May 2025 23:26:06 +0100 Subject: [PATCH 056/124] revert back to using nginx.conf as secondary config --- nginx/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx/Dockerfile b/nginx/Dockerfile index 8e9e432..d146212 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -1,6 +1,6 @@ FROM nginx:stable-alpine -COPY nginx.conf /etc/nginx/nginx.conf +COPY nginx.conf /etc/nginx/conf.d/nginx.conf EXPOSE 80 -- GitLab From bdca32de1871f0f35a316a54766ac82aaa077b90 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Sun, 18 May 2025 23:26:52 +0100 Subject: [PATCH 057/124] add frontend to loadbalancing network --- docker-compose.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index d8bb894..93eef1f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -51,7 +51,6 @@ services: build: context: ./nginx dockerfile: Dockerfile - image: nginx volumes: - ./nginx:/etc/nginx/conf.d #- ./frontend_microservice/templates:/usr/share/nginx/html @@ -68,13 +67,12 @@ services: build: context: frontend_microservice dockerfile: Dockerfile-frontend - ports: - - "8000:5001" depends_on: - postgres - postgres_ac networks: - backend + - loadbalancing datefinder: build: -- GitLab From 74eaafd41d240581bdf388f4fffc293537448da2 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Sun, 18 May 2025 23:27:13 +0100 Subject: [PATCH 058/124] attempt to add reverse proxy for frontend --- nginx/nginx.conf | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 00db418..ff68762 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -10,6 +10,10 @@ upstream datefinder { server datefinder:6000 ; } +upstream frontend { + server frontend:5001; +} + server { listen 80; @@ -17,6 +21,15 @@ server { include /etc/nginx/mime.types; + location / { + proxy_pass http://frontend; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + location /create_event { proxy_pass http://event_service/create_event; } -- GitLab From 2c3982b14002adb3688c0b0d8ef6a658b3fee0a7 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Sun, 18 May 2025 23:28:12 +0100 Subject: [PATCH 059/124] temporarily use only one replica for debugging --- docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 93eef1f..33f6564 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: #ports: #- "5002:5002" deploy: - replicas: 2 + replicas: 1 restart_policy: condition: on-failure delay: 1s @@ -25,7 +25,7 @@ services: context: account_microservice dockerfile: Dockerfile deploy: - replicas: 2 + replicas: 1 restart_policy: condition: on-failure delay: 1s @@ -83,7 +83,7 @@ services: depends_on: - postgres deploy: - replicas: 2 + replicas: 1 restart_policy: condition: on-failure delay: 1s -- GitLab From 18440bbbe9ba7383be8e32bed7121ac8dd90c076 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Sun, 18 May 2025 23:45:54 +0100 Subject: [PATCH 060/124] update docker-compose template to match --- docker-compose.tmpl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docker-compose.tmpl b/docker-compose.tmpl index 81b521c..785e565 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -57,20 +57,19 @@ services: frontend: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-frontend-ms - ports: - - "8000:5001" depends_on: - postgres - postgres_ac networks: - backend + - loadbalancing datefinder: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-datefinder-ms depends_on: - postgres deploy: - replicas: 2 + replicas: 1 restart_policy: condition: on-failure delay: 1s -- GitLab From f8497f80199f13e3c348df32dba98d4093678b9b Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Sun, 18 May 2025 23:49:17 +0100 Subject: [PATCH 061/124] test deployment --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 84ad3e5..cd2420f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -84,6 +84,7 @@ deploy: stage: deploy only: - main + - feature/frontend-reverse-proxy tags: - deployment environment: -- GitLab From dccd2f08933b5e8cc85a9ec749154f6662179874 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Sun, 18 May 2025 23:51:37 +0100 Subject: [PATCH 062/124] actually test deployment (allow publish and deploy stages in .gitlab-ci.yml) --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cd2420f..3fed445 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,6 +31,7 @@ publish: # Publish containers to gitlab container registry stage: publish only: - main + - feature/frontend-reverse-proxy tags: - deployment variables: -- GitLab From 841d7308a4fd627c3d9abfbd934fcfef21c1d08e Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 00:02:00 +0100 Subject: [PATCH 063/124] add container names for readability --- docker-compose.tmpl | 5 +++++ docker-compose.yml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/docker-compose.tmpl b/docker-compose.tmpl index 785e565..e627844 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -3,6 +3,7 @@ version: "3.8" services: event_service: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-event-ms + container_name: event_ms deploy: replicas: 1 restart_policy: @@ -20,6 +21,7 @@ services: account_service: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-account-ms + container_name: acount_ms deploy: replicas: 1 restart_policy: @@ -44,6 +46,7 @@ services: nginx: image: nginx + container_name: nginx_container volumes: - ./nginx:/etc/nginx/conf.d #- ./frontend_microservice/templates:/usr/share/nginx/html @@ -57,6 +60,7 @@ services: frontend: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-frontend-ms + container_name: frontend_ms depends_on: - postgres - postgres_ac @@ -66,6 +70,7 @@ services: datefinder: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-datefinder-ms + container_name: datefinder_ms depends_on: - postgres deploy: diff --git a/docker-compose.yml b/docker-compose.yml index 33f6564..24099b9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,6 +4,7 @@ services: build: context: event_microservice dockerfile: Dockerfile-backend + container_name: event_ms #ports: #- "5002:5002" deploy: @@ -24,6 +25,7 @@ services: build: context: account_microservice dockerfile: Dockerfile + container_name: acount_ms deploy: replicas: 1 restart_policy: @@ -51,6 +53,7 @@ services: build: context: ./nginx dockerfile: Dockerfile + container_name: nginx_container volumes: - ./nginx:/etc/nginx/conf.d #- ./frontend_microservice/templates:/usr/share/nginx/html @@ -67,6 +70,7 @@ services: build: context: frontend_microservice dockerfile: Dockerfile-frontend + container_name: frontend_ms depends_on: - postgres - postgres_ac @@ -78,6 +82,7 @@ services: build: context: datefinder_microservice dockerfile: Dockerfile-datefinder + container_name: datefinder_ms #ports: # - "6000:6000" depends_on: -- GitLab From 7404051236132075f6532419f01fe060c57ebd4f Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 00:43:46 +0100 Subject: [PATCH 064/124] fix typo --- docker-compose.tmpl | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.tmpl b/docker-compose.tmpl index e627844..9ac16af 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -21,7 +21,7 @@ services: account_service: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-account-ms - container_name: acount_ms + container_name: account_ms deploy: replicas: 1 restart_policy: diff --git a/docker-compose.yml b/docker-compose.yml index 24099b9..5cd384d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,7 +25,7 @@ services: build: context: account_microservice dockerfile: Dockerfile - container_name: acount_ms + container_name: account_ms deploy: replicas: 1 restart_policy: -- GitLab From bd3775fd01da5b4f2eeede01d82ebcac88f7367c Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 00:53:58 +0100 Subject: [PATCH 065/124] use prebuilt nginx image --- docker-compose.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.tmpl b/docker-compose.tmpl index 9ac16af..1370c62 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -45,7 +45,7 @@ services: nginx: - image: nginx + image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-nginx container_name: nginx_container volumes: - ./nginx:/etc/nginx/conf.d -- GitLab From 2a554ace1b5b3e12449ac939065a6d290f5df9aa Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 00:59:40 +0100 Subject: [PATCH 066/124] build and publish nginx image --- .gitlab-ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3fed445..3cc7e0f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -76,6 +76,11 @@ publish: # Publish containers to gitlab container registry - docker build -f ./account_database/Dockerfile -t $TAG_COMMIT-account-db -t $TAG_LATEST-account-db ./account_database - docker push $TAG_COMMIT-account-db - docker push $TAG_LATEST-account-db + + # Build and push nginx container + - docker build -f ./nginx/Dockerfile -t $TAG_COMMIT-nginx -t $TAG_LATEST-nginx ./nginx + - docker push $TAG_COMMIT-nginx + - docker push $TAG_LATEST-nginx -- GitLab From 52f1ffbc5c58dbebb9d8b21a253fc1f48e6108d4 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 01:00:27 +0100 Subject: [PATCH 067/124] change back to default.conf because that just works --- nginx/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx/Dockerfile b/nginx/Dockerfile index d146212..a63d974 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -1,6 +1,6 @@ FROM nginx:stable-alpine -COPY nginx.conf /etc/nginx/conf.d/nginx.conf +COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 -- GitLab From 3c47081b3367e2a94ba2220c462ddbe2cedcfe86 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 01:01:45 +0100 Subject: [PATCH 068/124] remove all images not listed in the docker-compose.yml (--remove-orphans) --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3cc7e0f..c40868b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -114,4 +114,4 @@ deploy: - echo "$DOCKERHUB_ACCESS_TOKEN" | ssh -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP "docker login -u $DOCKERHUB_USER --password-stdin" # Run docker-compose - - ssh -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP "docker-compose down && docker-compose up -d" #"docker-compose down && docker-compose up -d" + - ssh -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP "docker-compose down --remove-orphans && docker-compose up -d" #"docker-compose down && docker-compose up -d" -- GitLab From e5c615ecd46bfff53629d92317e9e8ee02db3f2d Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 15:37:09 +0100 Subject: [PATCH 069/124] create JS test in gitlab ci yml --- .gitlab-ci.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 84ad3e5..fd64783 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,7 +11,7 @@ variables: TAG_COMMIT: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA # docker image tag for current commit -unit_tests: +python_tests: image: python:3.9 stage: test tags: @@ -24,6 +24,13 @@ unit_tests: - echo "Running unittest for event_finder_test.py" - python -m unittest tests/event_finder_test.py +javascript_tests: + stage: test + image: node:18 + before_script: + - npm ci # Like npm install but witha few changes for automated env + script: + - npm test publish: # Publish containers to gitlab container registry -- GitLab From d65042bf1e12a4d164e85dbec87cb99dd483f6ab Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 15:37:56 +0100 Subject: [PATCH 070/124] make sendDates function accessible to Jest --- frontend_microservice/static/script.js | 4 +++- tests/sendDates.test.js | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend_microservice/static/script.js b/frontend_microservice/static/script.js index 285b458..d25fc99 100644 --- a/frontend_microservice/static/script.js +++ b/frontend_microservice/static/script.js @@ -427,4 +427,6 @@ function addEventToAccount(event_id){ alert("You're not logged in ... sending to login page") window.location.href = '/login'; } -} \ No newline at end of file +} + +module.exports = { sendDates }; \ No newline at end of file diff --git a/tests/sendDates.test.js b/tests/sendDates.test.js index bf7a548..a8159e5 100644 --- a/tests/sendDates.test.js +++ b/tests/sendDates.test.js @@ -15,6 +15,8 @@ global.$ = { }; const { sendDates } = require('../frontend_microservice/static/script') +// import { sendDates } from '../frontend_microservice/static/script.js'; + describe("sendDates function", () => { -- GitLab From 6401b21d4c8a8f127e7150d4f064b47ee18cf015 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 15:40:16 +0100 Subject: [PATCH 071/124] ignore changes to node_modules folder --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a26b269..9a64afd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ __pycache__/ *.pyc *.pyo -.idea/* \ No newline at end of file +.idea/* +node_modules/ \ No newline at end of file -- GitLab From 990e0f7dac439dea84e6a6e2b6004fb146604348 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 14:42:21 +0000 Subject: [PATCH 072/124] add test tag to JS test job --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fd64783..b8b035e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -27,9 +27,12 @@ python_tests: javascript_tests: stage: test image: node:18 + tags: + - test before_script: - npm ci # Like npm install but witha few changes for automated env script: + - echo "Running JS unit tests" - npm test -- GitLab From 052730ecd8cb300863ea710b04f92d32f0ae7f69 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 17:08:07 +0100 Subject: [PATCH 073/124] prune dangling images on server --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c40868b..4eb8b12 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -113,5 +113,5 @@ deploy: # Login to DockerHub registry - echo "$DOCKERHUB_ACCESS_TOKEN" | ssh -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP "docker login -u $DOCKERHUB_USER --password-stdin" - # Run docker-compose - - ssh -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP "docker-compose down --remove-orphans && docker-compose up -d" #"docker-compose down && docker-compose up -d" + # Run docker-compose (remove dangling images) + - ssh -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP "docker-compose down --remove-orphans && docker image prune -f && docker-compose up -d" -- GitLab From f9c022e6ad06a7fc4a609d448164e90c295aae0a Mon Sep 17 00:00:00 2001 From: sid <sk03140@surrey.ac.uk> Date: Mon, 19 May 2025 18:12:45 +0100 Subject: [PATCH 074/124] add password input validation across login, register, and reset password forms --- frontend_microservice/templates/login.html | 10 ++++++--- frontend_microservice/templates/register.html | 10 ++++++--- .../templates/reset_password.html | 21 +++++++++++++------ 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/frontend_microservice/templates/login.html b/frontend_microservice/templates/login.html index d70bd90..f7a52c4 100644 --- a/frontend_microservice/templates/login.html +++ b/frontend_microservice/templates/login.html @@ -22,10 +22,14 @@ <div class="form-group"> <label>Email:</label> <input type="email" id="email" required> - </div> - <div class="form-group"> + </div> <div class="form-group"> <label>Password:</label> - <input type="password" id="password" required> + <input type="password" + id="password" + required + pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}" + oninvalid="this.setCustomValidity('Password must be at least 8 characters long and include uppercase, lowercase letters, and a number.')" + oninput="this.setCustomValidity('')"> </div> <div style="text-align: right; margin-bottom: 10px;"> <a href="{{ url_for('forgot_password') }}" style="font-size: 0.9em; background: none; color: midnightblue; text-decoration: underline;">Forgot Password?</a> diff --git a/frontend_microservice/templates/register.html b/frontend_microservice/templates/register.html index 3463696..d8ff092 100644 --- a/frontend_microservice/templates/register.html +++ b/frontend_microservice/templates/register.html @@ -21,10 +21,14 @@ <div class="form-group"> <label>Email:</label> <input type="email" id="email" required> - </div> - <div class="form-group"> + </div> <div class="form-group"> <label>Password:</label> - <input type="password" id="password" required> + <input type="password" + id="password" + required + pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}" + oninvalid="this.setCustomValidity('Password must be at least 8 characters long and include uppercase, lowercase letters, and a number.')" + oninput="this.setCustomValidity('')"> <small>Must be at least 8 characters with uppercase, lowercase, and numbers</small> </div> <button type="submit">Register</button> diff --git a/frontend_microservice/templates/reset_password.html b/frontend_microservice/templates/reset_password.html index 0eee3a9..67d5cbb 100644 --- a/frontend_microservice/templates/reset_password.html +++ b/frontend_microservice/templates/reset_password.html @@ -11,15 +11,24 @@ <div class="card"> <h2>Enter New Password</h2> <form id="resetPasswordForm" class="card internal"> - <input type="hidden" id="token" value="{{ token }}"> - <div class="form-group"> + <input type="hidden" id="token" value="{{ token }}"> <div class="form-group"> <label>New Password:</label> - <input type="password" id="password" required> - <small>Must be at least 8 characters with uppercase, lowercase, and numbers</small> + <input type="password" + id="password" + required + pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}" + oninvalid="this.setCustomValidity('Password must be at least 8 characters long and include uppercase, lowercase letters, and a number.')" + oninput="this.setCustomValidity('')"> + <small>Must be at least 8 characters with uppercase, lowercase, and numbers</small> </div> - <div class="form-group"> + <div class="form-group"> <label>Confirm New Password:</label> - <input type="password" id="confirm_password" required> + <input type="password" + id="confirm_password" + required + pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}" + oninvalid="this.setCustomValidity('Password must be at least 8 characters long and include uppercase, lowercase letters, and a number.')" + oninput="this.setCustomValidity('')"> </div> <button type="submit">Reset Password</button> </form> -- GitLab From d5fd68b65ef39f6956ed8da170bcf9d1ba2400d1 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 21:48:59 +0100 Subject: [PATCH 075/124] attempt to make frontend load balance work --- nginx/nginx.conf | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index ff68762..0c7fcfd 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -31,45 +31,56 @@ server { } location /create_event { + limit_except POST { deny all; } proxy_pass http://event_service/create_event; } location /add_availabilities { + limit_except POST { deny all; } proxy_pass http://event_service/add_availabilities; } location /get_event_info { + limit_except GET POST { deny all; } proxy_pass http://event_service/get_event_info; } location /register { + limit_except GET POST { deny all; } proxy_pass http://account_service/register; } location /login { + limit_except GET POST { deny all; } proxy_pass http://account_service/login; - } + } location /add_events { + limit_except POST { deny all; } proxy_pass http://account_service/add_events; } location /get_events { + limit_except GET POST { deny all; } proxy_pass http://account_service/get_events; } location /get_best_dates { + limit_except GET POST { deny all; } proxy_pass http://event_service/get_best_dates; } location /calculate { + limit_except GET POST { deny all; } proxy_pass http://datefinder/calculate; } location /request_password_reset { + limit_except POST { deny all; } proxy_pass http://account_service; } location /reset_password { + limit_except POST { deny all; } proxy_pass http://account_service; } -- GitLab From d2b03de7c15ae4727dcc185a677a7803e9d16504 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 22:04:41 +0100 Subject: [PATCH 076/124] remove frontend from load balance --- docker-compose.tmpl | 2 ++ docker-compose.yml | 3 ++- nginx/nginx.conf | 23 ----------------------- 3 files changed, 4 insertions(+), 24 deletions(-) diff --git a/docker-compose.tmpl b/docker-compose.tmpl index 1370c62..216ee7d 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -61,6 +61,8 @@ services: frontend: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-frontend-ms container_name: frontend_ms + ports: + - "8000:5001" depends_on: - postgres - postgres_ac diff --git a/docker-compose.yml b/docker-compose.yml index 5cd384d..38c7f67 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -71,12 +71,13 @@ services: context: frontend_microservice dockerfile: Dockerfile-frontend container_name: frontend_ms + ports: + - "8000:5001" depends_on: - postgres - postgres_ac networks: - backend - - loadbalancing datefinder: build: diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 0c7fcfd..32784b5 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -10,10 +10,6 @@ upstream datefinder { server datefinder:6000 ; } -upstream frontend { - server frontend:5001; -} - server { listen 80; @@ -21,66 +17,47 @@ server { include /etc/nginx/mime.types; - location / { - proxy_pass http://frontend; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } location /create_event { - limit_except POST { deny all; } proxy_pass http://event_service/create_event; } location /add_availabilities { - limit_except POST { deny all; } proxy_pass http://event_service/add_availabilities; } location /get_event_info { - limit_except GET POST { deny all; } proxy_pass http://event_service/get_event_info; } location /register { - limit_except GET POST { deny all; } proxy_pass http://account_service/register; } location /login { - limit_except GET POST { deny all; } proxy_pass http://account_service/login; } location /add_events { - limit_except POST { deny all; } proxy_pass http://account_service/add_events; } location /get_events { - limit_except GET POST { deny all; } proxy_pass http://account_service/get_events; } location /get_best_dates { - limit_except GET POST { deny all; } proxy_pass http://event_service/get_best_dates; } location /calculate { - limit_except GET POST { deny all; } proxy_pass http://datefinder/calculate; } location /request_password_reset { - limit_except POST { deny all; } proxy_pass http://account_service; } location /reset_password { - limit_except POST { deny all; } proxy_pass http://account_service; } -- GitLab From 9d621c8e070806e0946c2626b81a7361a6e48393 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 22:07:43 +0100 Subject: [PATCH 077/124] Set nginx to listen to 8080 instead of 80 --- docker-compose.tmpl | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.tmpl b/docker-compose.tmpl index 216ee7d..98d6caa 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -51,7 +51,7 @@ services: - ./nginx:/etc/nginx/conf.d #- ./frontend_microservice/templates:/usr/share/nginx/html ports: - - "80:80" + - "8080:80" networks: - loadbalancing depends_on: diff --git a/docker-compose.yml b/docker-compose.yml index 38c7f67..6b65f13 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -58,7 +58,7 @@ services: - ./nginx:/etc/nginx/conf.d #- ./frontend_microservice/templates:/usr/share/nginx/html ports: - - "80:80" + - "8080:80" networks: - loadbalancing depends_on: -- GitLab From a27781197199b64ee8b43358a9198239c231067a Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 22:08:30 +0100 Subject: [PATCH 078/124] temporarily disable publish and deploy jobs on this branch --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4eb8b12..4bd998b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,7 +31,7 @@ publish: # Publish containers to gitlab container registry stage: publish only: - main - - feature/frontend-reverse-proxy + # - feature/frontend-reverse-proxy tags: - deployment variables: @@ -90,7 +90,7 @@ deploy: stage: deploy only: - main - - feature/frontend-reverse-proxy + # - feature/frontend-reverse-proxy tags: - deployment environment: -- GitLab From a37c24e6ead45bd18c6d07fa71db4ba3321f74a5 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 22:18:43 +0100 Subject: [PATCH 079/124] test continuous deployment (now with reverse proxy using caddy on server) --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4bd998b..4eb8b12 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,7 +31,7 @@ publish: # Publish containers to gitlab container registry stage: publish only: - main - # - feature/frontend-reverse-proxy + - feature/frontend-reverse-proxy tags: - deployment variables: @@ -90,7 +90,7 @@ deploy: stage: deploy only: - main - # - feature/frontend-reverse-proxy + - feature/frontend-reverse-proxy tags: - deployment environment: -- GitLab From 7775f1606c12d6acaa245e667ef29156d514ee33 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 21:25:25 +0000 Subject: [PATCH 080/124] Reset to only publish and deploy on main branch (continuous deployment test success) --- .gitlab-ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4eb8b12..3c8f2d0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,7 +31,6 @@ publish: # Publish containers to gitlab container registry stage: publish only: - main - - feature/frontend-reverse-proxy tags: - deployment variables: @@ -90,7 +89,6 @@ deploy: stage: deploy only: - main - - feature/frontend-reverse-proxy tags: - deployment environment: -- GitLab From f50a2c4da606419ce10d29c3cad62c96e3b8dbc8 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 22:31:06 +0100 Subject: [PATCH 081/124] take frontend off load balancing netwrok in docker compose template --- docker-compose.tmpl | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose.tmpl b/docker-compose.tmpl index 98d6caa..fb79ae3 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -68,7 +68,6 @@ services: - postgres_ac networks: - backend - - loadbalancing datefinder: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-datefinder-ms -- GitLab From a3c93e781c804d2a4d1a4a78e307bcac06eb13a2 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 22:32:22 +0100 Subject: [PATCH 082/124] add replicas so loadbalancing actually occurs --- docker-compose.tmpl | 6 +++--- docker-compose.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docker-compose.tmpl b/docker-compose.tmpl index fb79ae3..ac64460 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -5,7 +5,7 @@ services: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-event-ms container_name: event_ms deploy: - replicas: 1 + replicas: 2 restart_policy: condition: on-failure delay: 1s @@ -23,7 +23,7 @@ services: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-account-ms container_name: account_ms deploy: - replicas: 1 + replicas: 2 restart_policy: condition: on-failure delay: 1s @@ -75,7 +75,7 @@ services: depends_on: - postgres deploy: - replicas: 1 + replicas: 2 restart_policy: condition: on-failure delay: 1s diff --git a/docker-compose.yml b/docker-compose.yml index 6b65f13..f07fc51 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: #ports: #- "5002:5002" deploy: - replicas: 1 + replicas: 2 restart_policy: condition: on-failure delay: 1s @@ -27,7 +27,7 @@ services: dockerfile: Dockerfile container_name: account_ms deploy: - replicas: 1 + replicas: 2 restart_policy: condition: on-failure delay: 1s @@ -89,7 +89,7 @@ services: depends_on: - postgres deploy: - replicas: 1 + replicas: 2 restart_policy: condition: on-failure delay: 1s -- GitLab From b6d65723047691e7b4bf6a6524352c432bf4f4cc Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 22:33:08 +0100 Subject: [PATCH 083/124] test again with replicas and network change --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3c8f2d0..4eb8b12 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,6 +31,7 @@ publish: # Publish containers to gitlab container registry stage: publish only: - main + - feature/frontend-reverse-proxy tags: - deployment variables: @@ -89,6 +90,7 @@ deploy: stage: deploy only: - main + - feature/frontend-reverse-proxy tags: - deployment environment: -- GitLab From 5472a263719aa07fed867016658844143a3396a4 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 22:40:44 +0100 Subject: [PATCH 084/124] remove container names because it can't make two containers with same name --- docker-compose.tmpl | 14 +++++++------- docker-compose.yml | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docker-compose.tmpl b/docker-compose.tmpl index ac64460..ecb06e9 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -3,7 +3,7 @@ version: "3.8" services: event_service: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-event-ms - container_name: event_ms + #container_name: event_ms deploy: replicas: 2 restart_policy: @@ -21,7 +21,7 @@ services: account_service: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-account-ms - container_name: account_ms + #container_name: account_ms deploy: replicas: 2 restart_policy: @@ -46,7 +46,7 @@ services: nginx: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-nginx - container_name: nginx_container + #container_name: nginx_container volumes: - ./nginx:/etc/nginx/conf.d #- ./frontend_microservice/templates:/usr/share/nginx/html @@ -60,7 +60,7 @@ services: frontend: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-frontend-ms - container_name: frontend_ms + #container_name: frontend_ms ports: - "8000:5001" depends_on: @@ -71,7 +71,7 @@ services: datefinder: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-datefinder-ms - container_name: datefinder_ms + #container_name: datefinder_ms depends_on: - postgres deploy: @@ -90,7 +90,7 @@ services: postgres: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-event-db - container_name: event_database + #container_name: event_database environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -112,7 +112,7 @@ services: postgres_ac: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-account-db - container_name: account_database + #container_name: account_database environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres diff --git a/docker-compose.yml b/docker-compose.yml index f07fc51..ab1dfae 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: build: context: event_microservice dockerfile: Dockerfile-backend - container_name: event_ms + # container_name: event_ms #ports: #- "5002:5002" deploy: @@ -25,7 +25,7 @@ services: build: context: account_microservice dockerfile: Dockerfile - container_name: account_ms + # container_name: account_ms deploy: replicas: 2 restart_policy: @@ -53,7 +53,7 @@ services: build: context: ./nginx dockerfile: Dockerfile - container_name: nginx_container + # container_name: nginx_container volumes: - ./nginx:/etc/nginx/conf.d #- ./frontend_microservice/templates:/usr/share/nginx/html @@ -70,7 +70,7 @@ services: build: context: frontend_microservice dockerfile: Dockerfile-frontend - container_name: frontend_ms + # container_name: frontend_ms ports: - "8000:5001" depends_on: @@ -83,7 +83,7 @@ services: build: context: datefinder_microservice dockerfile: Dockerfile-datefinder - container_name: datefinder_ms + # container_name: datefinder_ms #ports: # - "6000:6000" depends_on: @@ -105,7 +105,7 @@ services: build: context: event_database dockerfile: Dockerfile - container_name: event_database + # container_name: event_database environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -129,7 +129,7 @@ services: build: context: account_database dockerfile: Dockerfile - container_name: account_database + # container_name: account_database environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres -- GitLab From 44fb593081453165b9ba6a1b0f4f54dfb2206f47 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 21:52:22 +0000 Subject: [PATCH 085/124] Only allow publish and deploy on main (second test success) --- .gitlab-ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4eb8b12..3c8f2d0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,7 +31,6 @@ publish: # Publish containers to gitlab container registry stage: publish only: - main - - feature/frontend-reverse-proxy tags: - deployment variables: @@ -90,7 +89,6 @@ deploy: stage: deploy only: - main - - feature/frontend-reverse-proxy tags: - deployment environment: -- GitLab From e53f12abdc652322a9dc2a8e70581ec6da6f1537 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 23:07:36 +0100 Subject: [PATCH 086/124] remove nginx volume because redundant --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index ab1dfae..7bf8f59 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -54,8 +54,8 @@ services: context: ./nginx dockerfile: Dockerfile # container_name: nginx_container - volumes: - - ./nginx:/etc/nginx/conf.d + # volumes: + # - ./nginx:/etc/nginx/conf.d #- ./frontend_microservice/templates:/usr/share/nginx/html ports: - "8080:80" -- GitLab From bd2b05f6a8108109756b7b9c5fcd8ff73f11142a Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 23:11:14 +0100 Subject: [PATCH 087/124] troubleshoot: reset nginx port to 80 --- docker-compose.yml | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 7bf8f59..21aceb3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,6 @@ services: build: context: event_microservice dockerfile: Dockerfile-backend - # container_name: event_ms #ports: #- "5002:5002" deploy: @@ -25,7 +24,6 @@ services: build: context: account_microservice dockerfile: Dockerfile - # container_name: account_ms deploy: replicas: 2 restart_policy: @@ -50,15 +48,12 @@ services: - SECRET_KEY=f361e60c60f0aaf062ba9b9fc727f08eb525ad93ac715e012b224c0506596025 nginx: - build: - context: ./nginx - dockerfile: Dockerfile - # container_name: nginx_container - # volumes: - # - ./nginx:/etc/nginx/conf.d + image: nginx + volumes: + - ./nginx:/etc/nginx/conf.d #- ./frontend_microservice/templates:/usr/share/nginx/html ports: - - "8080:80" + - "80:80" networks: - loadbalancing depends_on: @@ -70,7 +65,6 @@ services: build: context: frontend_microservice dockerfile: Dockerfile-frontend - # container_name: frontend_ms ports: - "8000:5001" depends_on: @@ -83,7 +77,6 @@ services: build: context: datefinder_microservice dockerfile: Dockerfile-datefinder - # container_name: datefinder_ms #ports: # - "6000:6000" depends_on: @@ -105,7 +98,7 @@ services: build: context: event_database dockerfile: Dockerfile - # container_name: event_database + container_name: event_database environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -129,7 +122,7 @@ services: build: context: account_database dockerfile: Dockerfile - # container_name: account_database + container_name: account_database environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres -- GitLab From 6362c95632186d3ef1a36cb25cc1a6d39c8ba705 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 23:19:36 +0100 Subject: [PATCH 088/124] change all nginx ports to 8080 --- docker-compose.yml | 2 +- event_microservice/backend.py | 2 +- frontend_microservice/frontend.py | 4 ++-- nginx/Dockerfile | 2 +- nginx/nginx.conf | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 21aceb3..19b5748 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,7 +53,7 @@ services: - ./nginx:/etc/nginx/conf.d #- ./frontend_microservice/templates:/usr/share/nginx/html ports: - - "80:80" + - "8080:8080" networks: - loadbalancing depends_on: diff --git a/event_microservice/backend.py b/event_microservice/backend.py index f744405..a1465cd 100644 --- a/event_microservice/backend.py +++ b/event_microservice/backend.py @@ -6,7 +6,7 @@ import os #TODO make sure the port is correct #Comment -ALGORITHM_PORT = 80 +ALGORITHM_PORT = 8080 EVENT_DATABASE_PORT = 5436 PORT = 5002 diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index 22f973e..9a14e89 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -8,8 +8,8 @@ import logging app = Flask(__name__) PORT = 5001 -EVENT_PORT = 80 -USER_PORT = 80 +EVENT_PORT = 8080 +USER_PORT = 8080 @app.route('/') @app.route('/index') diff --git a/nginx/Dockerfile b/nginx/Dockerfile index a63d974..87f4a57 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -2,6 +2,6 @@ FROM nginx:stable-alpine COPY nginx.conf /etc/nginx/conf.d/default.conf -EXPOSE 80 +EXPOSE 8080 CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 32784b5..a1108fd 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -11,7 +11,7 @@ upstream datefinder { } server { - listen 80; + listen 8080; resolver 127.0.0.11 valid=5s; -- GitLab From 8f070fe29448b8f3fa7f4540a64e6c8ec8bbf616 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 23:29:03 +0100 Subject: [PATCH 089/124] update docker compose template --- docker-compose.tmpl | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docker-compose.tmpl b/docker-compose.tmpl index ecb06e9..53a3f6c 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -3,7 +3,6 @@ version: "3.8" services: event_service: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-event-ms - #container_name: event_ms deploy: replicas: 2 restart_policy: @@ -21,7 +20,6 @@ services: account_service: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-account-ms - #container_name: account_ms deploy: replicas: 2 restart_policy: @@ -46,21 +44,20 @@ services: nginx: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-nginx - #container_name: nginx_container volumes: - ./nginx:/etc/nginx/conf.d #- ./frontend_microservice/templates:/usr/share/nginx/html ports: - - "8080:80" + - "8080:8080" networks: - loadbalancing depends_on: - event_service - account_service + - datefinder frontend: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-frontend-ms - #container_name: frontend_ms ports: - "8000:5001" depends_on: -- GitLab From 79c96f5c57b5c8f3bdd6872886b632657831be02 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 23:30:46 +0100 Subject: [PATCH 090/124] test again on deployment runner --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3c8f2d0..4eb8b12 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,6 +31,7 @@ publish: # Publish containers to gitlab container registry stage: publish only: - main + - feature/frontend-reverse-proxy tags: - deployment variables: @@ -89,6 +90,7 @@ deploy: stage: deploy only: - main + - feature/frontend-reverse-proxy tags: - deployment environment: -- GitLab From a49a49b6e22a3b9df003f26996573c1f29ad21dc Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Mon, 19 May 2025 23:44:47 +0100 Subject: [PATCH 091/124] allow frontend to see external host during build --- docker-compose.tmpl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.tmpl b/docker-compose.tmpl index 53a3f6c..293ca60 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -65,6 +65,8 @@ services: - postgres_ac networks: - backend + extra_hosts: + - "host.docker.internal:host-gateway" datefinder: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-datefinder-ms -- GitLab From 9784c3b4bb8ba2ecfdc5d5c75530c1d39dc2a21f Mon Sep 17 00:00:00 2001 From: hwilks <hazel.wilks@gmail.com> Date: Tue, 20 May 2025 15:10:06 +0100 Subject: [PATCH 092/124] add documentation to functions in script.js --- frontend_microservice/static/script.js | 241 ++++++++++++++++++------- 1 file changed, 179 insertions(+), 62 deletions(-) diff --git a/frontend_microservice/static/script.js b/frontend_microservice/static/script.js index d25fc99..9a9e48b 100644 --- a/frontend_microservice/static/script.js +++ b/frontend_microservice/static/script.js @@ -25,14 +25,20 @@ document.addEventListener('DOMContentLoaded', function() { "December" ]; - - //eventdates = [datetime.datetime(2025, 3, 27, 0, 0, 0), datetime.datetime(2025, 3, 28,0,0,0), datetime.datetime(2025,3,31,0,0,0)] const title = document.getElementById("title").textContent; console.log("Title: ",title); - //Convert dates to string function convertDatesToString(dates){ - //depending on data from database, can convert to date + /** + * Converts an array of date values into formatted date strings. + * @param {Array} dates - An array of date values (e.g., ISO strings, timestamps). + * @returns {Array} dates-str - An array of strings in 'YYYY-MM-DD' format. + * + * Example: + * Input: ["2025-05-20T12:00:00Z", "2024-12-31"] + * Output: ["2025-5-20", "2024-12-31"] + */ + dates_str = [] for (i=0; i<dates.length;i++){ let date_ = new Date(dates[i]); @@ -51,10 +57,9 @@ document.addEventListener('DOMContentLoaded', function() { event_dates_str = convertDatesToString(eventdates); } - - // Function to generate the calendar + // Function to generate the calendar, called at every load of the availability and create event pages const manipulate = () => { - //console.log(possible_dates) + // Get the first day of the month let dayone = new Date(year, month, 1).getDay(); @@ -88,11 +93,15 @@ document.addEventListener('DOMContentLoaded', function() { ? "today" : ""; + // If on availability page, any dates not associated with the event are not selectable let isSelectable = "" if (title=="Availability"){ isSelectable = event_dates_str.includes(fullDate) ? "" : "not_selectable"; } - let isSelected = possible_dates.includes(fullDate) ? "selected" : ""; // Check if the date is selected + // Check if the date is selected + let isSelected = possible_dates.includes(fullDate) ? "selected" : ""; + + // Add css classes to the date to give is the correct apperance lit += `<li class="${isToday} ${isSelected} ${isSelectable}"data-date="${fullDate}">${i}</li>`; } @@ -101,12 +110,10 @@ document.addEventListener('DOMContentLoaded', function() { lit += `<li class="not_this_month not_selectable">${i - dayend + 1}</li>` } - // Update the text of the current date element - // with the formatted current month and year + // Update the text of the current date element with the formatted current month and year currdate.innerText = `${months[month]} ${year}`; - // update the HTML of the dates element - // with the generated calendar + // update the HTML of the dates element with the generated calendar day.innerHTML = lit; // Add event listeners for the new dates @@ -136,19 +143,18 @@ document.addEventListener('DOMContentLoaded', function() { // Function to toggle the selection state of buttons function toggleDaySelection(button) { button.classList.toggle('selected'); - } + // call the load of the calendar manipulate(); - // Attach a click event listener to each icon + // Attach a click event listener to each next/prev icons prenexIcons.forEach(icon => { // When an icon is clicked icon.addEventListener("click", () => { - // Check if the icon is "calendar-prev" - // or "calendar-next" + // Check if the icon is "calendar-prev" or "calendar-next" month = icon.id === "calendar-prev" ? month - 1 : month + 1; // Check if the month is out of range @@ -171,54 +177,85 @@ document.addEventListener('DOMContentLoaded', function() { date = new Date(); } - // Call the manipulate function to - // update the calendar display + // Call the manipulate function to update the calendar display manipulate(); }); }); }) - function sendDates(){ - console.log("button pressed") - console.log(possible_dates) - let name = document.getElementById('event_name').value; +//for create event page +function sendDates(){ + /** + * Sends selected event dates and event name to the server to create a new event. + * Inputs: + * - Reads 'event_name' from an input element with ID 'event_name' + * - Uses a global variable 'possible_dates' + * + * Output: + * - Sends a POST request to the `/create_event` endpoint with JSON payload + * - On success: alerts the user and redirects to a URL provided in the response (event summary page) + * - On error: displays an error message. + */ + + console.log("button pressed") + console.log(possible_dates) + + + let name = document.getElementById('event_name').value; + + //check that event name is not empty + if (name == "") { + alert("Event name cannot be empty."); + return; + } - //check that event name is not empty - if (name == "") { - alert("Event name cannot be empty."); - return; + $.ajax({ + url: `/create_event`, + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({ 'possible_dates': possible_dates, + 'name':name }), + success: function(response) { + alert("You've created the event!") + window.location.href = response.url; + }, + error: function(xhr, status, error) { + alert("Error: " + error); } - - $.ajax({ - url: `/create_event`, - type: 'POST', - contentType: 'application/json', - data: JSON.stringify({ 'possible_dates': possible_dates, - 'name':name }), - success: function(response) { - alert("You've created the event!") - window.location.href = response.url; - }, - error: function(xhr, status, error) { - alert("Error: " + error); - } - }); - } + }); +} - - function nameEntered(){ +//For availabilities page +function nameEntered(){ +/** + * Handles logic when a user enters their name into the availability form. + * + * Inputs: + * - Reads the user name from an input field with ID 'user_name'. + * - Uses global variable 'previous_availabilities', loaded from database when html page is loaded + * - Uses global variable 'possible_dates', loaded from database when html page is loaded + * + * Outputs: + * - Updates the 'possible_dates' array + * - Displays a message if the name already exists in the system. + * - Updates calendar UI + */ const message = document.getElementById('message'); let name = document.getElementById('user_name').value; let names = Object.keys(previous_availabilities) let nameInList = false possible_dates=[] message.textContent = "" + + //Finds if the name has already been entered before for(i=0; i<names.length;i++){ if (names[i]==name){ nameInList=true break; } } + + //If name does exist, show availabilites already added if (nameInList){ let already_selected = convertDatesToString(previous_availabilities[name]) console.log(already_selected) @@ -232,26 +269,58 @@ document.addEventListener('DOMContentLoaded', function() { manipulate() } +//For Availabilities page function sendAvailabilities(event_id){ - console.log("button pressed") - let user_name = document.getElementById('user_name').value; +/** + * Submits a user's selected availabilities for a specific event to the server. + * + * Inputs: + * - event_id (parameter): The unique ID of the event being responded to. + * - Reads user name from an input field with ID 'user_name'. + * - Uses global variable 'possible_dates' + * + * Output: + * - Sends a POST request to `/availability/{event_id}` with JSON payload. + * - On success: redirects the user to a URL provided in the server response (event summary page) + * - Error: displays error message + * */ + console.log("button pressed") + let user_name = document.getElementById('user_name').value; + $.ajax({ - url: `/availability/${event_id}`, - type: 'POST', - contentType: 'application/json', - data: JSON.stringify({ 'availability': possible_dates, 'event_id':event_id, 'user_name':user_name}), - success: function(response) { - /*TODO: Create page to go to*/ - window.location.href = response.url; - }, - error: function(xhr, status, error) { - alert("Error: " + error); - } - }); - } + url: `/availability/${event_id}`, + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({ 'availability': possible_dates, 'event_id':event_id, 'user_name':user_name}), + success: function(response) { + window.location.href = response.url; + }, + error: function(xhr, status, error) { + alert("Error: " + error); + } + }); +} +// For event summary page function getDates(availabilities){ +/** + * Sends user availabilities to the server and displays the best possible dates for an event, + * including which users are available on each date. + * + * Input: + * - availabilities (Object): A mapping of user names to arrays of date strings. + * Example: { "Alice": ["2025-05-20", "2025-05-21"], "Bob": ["2025-05-21"]... } + * + * Output: + * - Sends a POST request to `/getDates` with the availabilities data. + * - On success: + * - Clears any previous result cards. + * - Populates the UI with new cards for each suggested date. + * - Displays a list of users available on each date. + * - On error: + * - Displays an error message and updates the heading. + **/ console.log("button pressed"); // Create card for best date section var date_area = document.getElementById("best_dates"); @@ -315,14 +384,30 @@ function getDates(availabilities){ // This runs when the eventSummary page loads function new_list_element(availabilities, dates){ +/** + * Dynamically creates and displays a summary of all possible event dates + * and each user's selected availabilities on the page. + * + * Inputs: + * - availabilities (Object): A mapping of usernames to arrays of date strings. + * Example: { "Alice": ["2025-05-20", "2025-05-21"], "Bob": ... } + * - dates (Array): An array of possible event date strings. + * Example: ["2025-10-20", "2025-10-21", ...] + * + * Output: + * - Modifies the DOM by appending cards to the element with ID 'summary_block'. + * These cards show: + * 1. The list of dates to choose from. + * 2. Each user's availability in grouped blocks. + * */ + var summary_block = document.getElementById("summary_block"); var sub_block = document.createElement("div"); sub_block.classList.add("columns") // https://www.w3schools.com/jsref/met_node_appendchild.asp summary_block.appendChild(sub_block); - // Creates list of possible event dates - + // Create list of possible event dates var av_div = document.createElement("div"); av_div.classList.add("card", "fixed_h", "internal"); sub_block.append(av_div); @@ -346,6 +431,7 @@ function new_list_element(availabilities, dates){ // This section creates a list for each user of their availabilities // summary_block is the area in which these appear + // Max 4 on each row for (const user in availabilities) { // get the dates for the user var user_dates = availabilities[user]; @@ -386,16 +472,31 @@ function new_list_element(availabilities, dates){ }; } +//Runs on loading of account events page function show_account_events(events){ +/** + * Dynamically displays a list of buttons for events associated with a user account. + * + * Input: + * - events (Array): An array of event objects, each containing: + * - name (String): The name of the event. + * - url (String): A URL to navigate to when the event button is clicked. + * Example: + * { name: "Team Meeting", url: "/event/5" } + * + * Output: + * - Appends a button for each event to the DOM element with ID 'event_block'. + * Each button navigates to the associated event URL when clicked. + */ event_block = document.getElementById('event_block') console.log(events) + for (i=0;i<events.length;i++){ event_i=events[i] var button = document.createElement("button"); button.classList.add('show_event'); button.textContent = event_i['name']; - console.log(event_i['url']) button.onclick = button.onclick = function(event_i) { return function() { console.log('button pressed') @@ -407,7 +508,22 @@ function show_account_events(events){ } } +//On event Summary page function addEventToAccount(event_id){ +/** + * Adds the current event with the logged-in user's account. + * + * Input: + * - event_id (String): The ID of the event to add to the user's account. + * - Retrieves the user ID from localStorage under the key 'user_id'. + * + * Output: + * - If user logged in: + * - Sends a POST request to the server at `/eventSummary/{event_id}` with the user ID. + * - On success: shows a confirmation message from the server response. + * - On error: alerts the user with the error message. + * - If no user is logged in: redirects the user to the login page. + **/ const userId = localStorage.getItem('user_id'); if (userId){ $.ajax({ @@ -429,4 +545,5 @@ function addEventToAccount(event_id){ } } +// For automated testing module.exports = { sendDates }; \ No newline at end of file -- GitLab From 3c662b05d1cff4aeb9326fc089b7fdc0322b04ae Mon Sep 17 00:00:00 2001 From: hwilks <hazel.wilks@gmail.com> Date: Tue, 20 May 2025 15:12:17 +0100 Subject: [PATCH 093/124] add consitency --- frontend_microservice/static/script.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend_microservice/static/script.js b/frontend_microservice/static/script.js index 9a9e48b..58a7f16 100644 --- a/frontend_microservice/static/script.js +++ b/frontend_microservice/static/script.js @@ -275,7 +275,7 @@ function sendAvailabilities(event_id){ * Submits a user's selected availabilities for a specific event to the server. * * Inputs: - * - event_id (parameter): The unique ID of the event being responded to. + * - @param event_id : The unique ID of the event being responded to. * - Reads user name from an input field with ID 'user_name'. * - Uses global variable 'possible_dates' * @@ -309,7 +309,7 @@ function getDates(availabilities){ * including which users are available on each date. * * Input: - * - availabilities (Object): A mapping of user names to arrays of date strings. + * - @param availabilities (Object): A mapping of user names to arrays of date strings. * Example: { "Alice": ["2025-05-20", "2025-05-21"], "Bob": ["2025-05-21"]... } * * Output: @@ -389,9 +389,9 @@ function new_list_element(availabilities, dates){ * and each user's selected availabilities on the page. * * Inputs: - * - availabilities (Object): A mapping of usernames to arrays of date strings. + * - @param availabilities (Object): A mapping of usernames to arrays of date strings. * Example: { "Alice": ["2025-05-20", "2025-05-21"], "Bob": ... } - * - dates (Array): An array of possible event date strings. + * - @param dates (Array): An array of possible event date strings. * Example: ["2025-10-20", "2025-10-21", ...] * * Output: @@ -478,7 +478,7 @@ function show_account_events(events){ * Dynamically displays a list of buttons for events associated with a user account. * * Input: - * - events (Array): An array of event objects, each containing: + * - @param events (Array): An array of event objects, each containing: * - name (String): The name of the event. * - url (String): A URL to navigate to when the event button is clicked. * Example: @@ -514,7 +514,7 @@ function addEventToAccount(event_id){ * Adds the current event with the logged-in user's account. * * Input: - * - event_id (String): The ID of the event to add to the user's account. + * - @param event_id (String): The ID of the event to add to the user's account. * - Retrieves the user ID from localStorage under the key 'user_id'. * * Output: -- GitLab From 18d907fd636d96c44db90a886dc889b735c7f3b2 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Tue, 20 May 2025 15:41:18 +0100 Subject: [PATCH 094/124] remove extra hosts from frontend in docker-compose template --- docker-compose.tmpl | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker-compose.tmpl b/docker-compose.tmpl index 293ca60..53a3f6c 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -65,8 +65,6 @@ services: - postgres_ac networks: - backend - extra_hosts: - - "host.docker.internal:host-gateway" datefinder: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-datefinder-ms -- GitLab From 44c0ceaad8c0f8dd42210d5ada942874ba18aa24 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Tue, 20 May 2025 18:04:03 +0000 Subject: [PATCH 095/124] Edit README.md --- README.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 70ebc29..2238104 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,17 @@ -# Coursework -Change the name of the repo when we get a better group name. +# GregC +- [COM3014](https://catalogue.surrey.ac.uk/2024-5/module/COM3014/SEMR2/2) Coursework Submission +- **Authors**: William Young, Hazel Wilks, Hoang Hiep Trieu, Loic Peron, Siddhartha Singh, Thomas Smith -## Installation -Here we can put installation instructions once we have the beginnings of an app. +**GregC** is a platform that helps people pla events by synchronizing their availablities. + -## Test and Deploy -I temporarily left this in from the original README template so when we want to learn CI/CD in the near future, this is easily accessible. +## Tech Stack -Use the built-in continuous integration in GitLab. -- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) -- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) -- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) -- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) -- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) +## Getting Started +Here we can put installation instructions once we have the beginnings of an app. +(or access on srver IP in uni network) + +## Demo \ No newline at end of file -- GitLab From 34103b3efdcb3fb5e5ceee95beba1b6779f8bd94 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Tue, 20 May 2025 19:49:17 +0000 Subject: [PATCH 096/124] Edit README.md --- README.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2238104..236e1f0 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,37 @@ ## Tech Stack +* [![Flask][Flask]][Flask-url] +* [![CSS][CSS]][CSS-url] +* [![PostgreSQL][PostgreSQL]][PostgreSQL-url] +* [![Docker][Docker]][Docker-url] +* Javascript +* NGINX + +* [![TypeScript][TypeScript]][TypeScript-url] +* [![Vite][Vite]][Vite-url] +* [![Tailwind][Tailwind]][Tailwind-url] +* [![SolanaWeb3][SolanaWeb3]][SolanaWeb3-url] +* [![QuickNode][QuickNode]][QuickNode-url] + ## Getting Started Here we can put installation instructions once we have the beginnings of an app. (or access on srver IP in uni network) -## Demo \ No newline at end of file +## Demo + +<!-- LINKS --> +[Flask]: https://img.shields.io/badge/Flask-000000?logo=flask&logoColor=white +[Flask-url]: https://flask.palletsprojects.com/en/stable/ + +[CSS]: https://img.shields.io/badge/CSS3-1572B6?style=for-the-badge&logo=css3&logoColor=white +[CSS-url]: https://www.w3.org/Style/CSS/Overview.en.html + +[PostgreSQL]: https://img.shields.io/badge/PostgreSQL-316192?logo=postgresql&logoColor=white +[PostgreSQL-url]: https://www.postgresql.org/ + +[Docker]: +[Docker-url]: https://www.docker.com/ + -- GitLab From 67ce79894a4dcd993393f3f5ce257d3692addd07 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Tue, 20 May 2025 21:35:54 +0000 Subject: [PATCH 097/124] Edit README.md --- README.md | 58 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 236e1f0..096fe69 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,16 @@ # GregC -- [COM3014](https://catalogue.surrey.ac.uk/2024-5/module/COM3014/SEMR2/2) Coursework Submission -- **Authors**: William Young, Hazel Wilks, Hoang Hiep Trieu, Loic Peron, Siddhartha Singh, Thomas Smith +**[COM3014](https://catalogue.surrey.ac.uk/2024-5/module/COM3014/SEMR2/2) Coursework Submission** +**Authors**: +- Hazel Wilks (6898203) +- Hoang Hiep Trieu (6860616) +- Loïc Péron (6899778) +- Siddhartha Singh (6893314) +- Thomas Smith (6521482) +- William Young (6481863) -**GregC** is a platform that helps people pla events by synchronizing their availablities. +**GregC** is a multi-service application that helps you plan events with friends by synchronizing your availablities. ## Tech Stack @@ -12,33 +18,53 @@ * [![CSS][CSS]][CSS-url] * [![PostgreSQL][PostgreSQL]][PostgreSQL-url] * [![Docker][Docker]][Docker-url] +* [![JavaScript][JavaScript]][JavaScript-url] +* [![NginX][NginX]][NginX-url] +* [![GitLab-CI][GitLab-CI]][GitLab-CI-url] -* Javascript -* NGINX +## Getting Started -* [![TypeScript][TypeScript]][TypeScript-url] -* [![Vite][Vite]][Vite-url] -* [![Tailwind][Tailwind]][Tailwind-url] -* [![SolanaWeb3][SolanaWeb3]][SolanaWeb3-url] -* [![QuickNode][QuickNode]][QuickNode-url] +For the purposes of this coursework, GregC is deployed on the university-provided instance. -## Getting Started -Here we can put installation instructions once we have the beginnings of an app. -(or access on srver IP in uni network) +To run it locally intead, make sure that Docker is installed, then follow these steps: -## Demo +1. Clone the repo + ```sh + git clone https://gitlab.surrey.ac.uk/web-tech/coursework.git + ``` +2. Navigate to the project folder + ```sh + cd coursework + ``` +3. Spin up docker containers + ```sh + docker-compose up --build + ``` +4. Navigate to the webpage by typing the following in your browser search bar: + ```sh + localhost:8000 + ``` <!-- LINKS --> [Flask]: https://img.shields.io/badge/Flask-000000?logo=flask&logoColor=white [Flask-url]: https://flask.palletsprojects.com/en/stable/ -[CSS]: https://img.shields.io/badge/CSS3-1572B6?style=for-the-badge&logo=css3&logoColor=white +[CSS]: https://img.shields.io/badge/CSS-1572B6?logo=css3&logoColor=fff [CSS-url]: https://www.w3.org/Style/CSS/Overview.en.html [PostgreSQL]: https://img.shields.io/badge/PostgreSQL-316192?logo=postgresql&logoColor=white [PostgreSQL-url]: https://www.postgresql.org/ -[Docker]: +[Docker]: https://img.shields.io/badge/Docker-2496ED?logo=docker&logoColor=fff [Docker-url]: https://www.docker.com/ +[GitLab-CI]: https://img.shields.io/badge/GitLab%20CI-FC6D26?logo=gitlab&logoColor=fff +[GitLab-CI-url]: https://docs.gitlab.com/ci/ + +[NginX]: https://img.shields.io/badge/Nginx-222?&logo=nginx&logoColor=009639 +[NginX-url]: https://nginx.org/ + +[JavaScript]: https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=000 +[JavaScript-url]: https://developer.mozilla.org/en-US/docs/Web/JavaScript + -- GitLab From 7d94374214d7a5e07656a13a5a89876e955ea6a3 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Tue, 20 May 2025 21:40:52 +0000 Subject: [PATCH 098/124] Edit README.md (typo) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 096fe69..380949d 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ For the purposes of this coursework, GregC is deployed on the university-provided instance. -To run it locally intead, make sure that Docker is installed, then follow these steps: +To run it locally instead, make sure that Docker is installed, then follow these steps: 1. Clone the repo ```sh -- GitLab From 4d803ec4f59f12dd2ec2a105392dce1ec33a3db9 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Tue, 20 May 2025 23:13:42 +0100 Subject: [PATCH 099/124] debugging step for host name --- .gitlab-ci.yml | 4 ++-- event_microservice/backend.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4eb8b12..4bd998b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,7 +31,7 @@ publish: # Publish containers to gitlab container registry stage: publish only: - main - - feature/frontend-reverse-proxy + # - feature/frontend-reverse-proxy tags: - deployment variables: @@ -90,7 +90,7 @@ deploy: stage: deploy only: - main - - feature/frontend-reverse-proxy + # - feature/frontend-reverse-proxy tags: - deployment environment: diff --git a/event_microservice/backend.py b/event_microservice/backend.py index a1465cd..095e844 100644 --- a/event_microservice/backend.py +++ b/event_microservice/backend.py @@ -138,6 +138,7 @@ def get_best_dates(): if __name__ == "__main__": # checks for hostname to see if running in containers then sets host appropriately hostname = os.environ.get('HOSTNAME', '') + print(f"DEBUGGING: {hostname}") if hostname: HOST = 'host.docker.internal' print(f"running on docker, container hostname: {hostname}") -- GitLab From 5eb07caff725e228c43319436e070a69d213d75c Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Tue, 20 May 2025 23:22:42 +0100 Subject: [PATCH 100/124] try simple fix --- event_microservice/backend.py | 2 +- frontend_microservice/frontend.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/event_microservice/backend.py b/event_microservice/backend.py index 095e844..a585f04 100644 --- a/event_microservice/backend.py +++ b/event_microservice/backend.py @@ -143,7 +143,7 @@ if __name__ == "__main__": HOST = 'host.docker.internal' print(f"running on docker, container hostname: {hostname}") else: - HOST = 'localhost' + HOST = hostname print("running locally") #initialising local database diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index 9a14e89..3b4ede7 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -228,7 +228,7 @@ if __name__ == "__main__": # checks for hostname to see if running in containers then sets host appropriately hostname = os.environ.get('HOSTNAME', '') if hostname: - HOST = 'host.docker.internal' + HOST = hostname print(f"running on docker, container hostname: {hostname}") else: HOST = 'localhost' -- GitLab From 4a9be106f39d4f6f881840415ae116317a84cc6f Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Tue, 20 May 2025 23:59:35 +0100 Subject: [PATCH 101/124] fix porting issues --- docker-compose.yml | 1 + event_microservice/backend.py | 63 ++++++++++++++++--------------- frontend_microservice/frontend.py | 29 ++++++++------ 3 files changed, 50 insertions(+), 43 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 19b5748..21a41eb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,7 @@ services: depends_on: - postgres networks: + - backend - loadbalancing environment: - PORT=5002 diff --git a/event_microservice/backend.py b/event_microservice/backend.py index a585f04..8a2d5d5 100644 --- a/event_microservice/backend.py +++ b/event_microservice/backend.py @@ -6,14 +6,19 @@ import os #TODO make sure the port is correct #Comment -ALGORITHM_PORT = 8080 +ALGORITHM_PORT = 6000 +ALGORITHM_HOST = "datefinder" +ALGORITHM_API_URL = f"http://{ALGORITHM_HOST}:{ALGORITHM_PORT}" + EVENT_DATABASE_PORT = 5436 +EVENT_DB_HOST = "postgres" + PORT = 5002 app = Flask(__name__) def get_db_connection(): # Connect to database - conn = psycopg2.connect(database = "event", user = "postgres", password = "postgres", host = HOST, port = EVENT_DATABASE_PORT) + conn = psycopg2.connect(database = "event", user = "postgres", password = "postgres", host = EVENT_DB_HOST, port = EVENT_DATABASE_PORT) conn.autocommit = True return conn @@ -136,45 +141,41 @@ def get_best_dates(): if __name__ == "__main__": - # checks for hostname to see if running in containers then sets host appropriately - hostname = os.environ.get('HOSTNAME', '') - print(f"DEBUGGING: {hostname}") - if hostname: - HOST = 'host.docker.internal' - print(f"running on docker, container hostname: {hostname}") - else: - HOST = hostname - print("running locally") + + # if hostname: + # HOST = 'host.docker.internal' + # print(f"running on docker, container hostname: {hostname}") + # else: + # HOST = hostname + # print("running locally") #initialising local database #connects to base postgres - conn = psycopg2.connect(database="postgres", user='postgres', password='postgres', host=HOST, port=EVENT_DATABASE_PORT) - conn.autocommit = True - cursor = conn.cursor() - - #checks if already exists - cursor.execute("SELECT 1 FROM pg_database WHERE datname = %s;", ('event',)) - exists = cursor.fetchone() - - if not exists: - cursor.execute('CREATE DATABASE event') - print(f"Database created successfully.") + conn = psycopg2.connect(database="postgres", user='postgres', password='postgres', host=EVENT_DB_HOST, port=EVENT_DATABASE_PORT) + conn.autocommit = True + cursor = conn.cursor() - cursor.close() - conn.close() + #checks if already exists + cursor.execute("SELECT 1 FROM pg_database WHERE datname = %s;", ('event',)) + exists = cursor.fetchone() - #inserts db schema - conn = get_db_connection() - cursor = conn.cursor() - with open('database/schema.sql', 'r') as schema: - cursor.execute(schema.read()) + if not exists: + cursor.execute('CREATE DATABASE event') + print(f"Database created successfully.") - conn.commit() cursor.close() conn.close() + #inserts db schema + conn = get_db_connection() + cursor = conn.cursor() + with open('database/schema.sql', 'r') as schema: + cursor.execute(schema.read()) + + conn.commit() + cursor.close() + conn.close() - ALGORITHM_API_URL = f"http://{HOST}:{ALGORITHM_PORT}" app.debug = True app.run(host="0.0.0.0", port=PORT) \ No newline at end of file diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index 3b4ede7..5a84f4d 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -8,8 +8,14 @@ import logging app = Flask(__name__) PORT = 5001 -EVENT_PORT = 8080 -USER_PORT = 8080 +EVENT_HOST = "event_service" +EVENT_PORT = 5002 + +USER_HOST = "account_service" +USER_PORT = 5003 + +EVENT_API_URL = f"http://{EVENT_HOST}:{EVENT_PORT}" +USER_API_URL = f"http://{USER_HOST}:{USER_PORT}" @app.route('/') @app.route('/index') @@ -225,14 +231,13 @@ def reset_password_submit(): return jsonify({"error": "An internal error occurred while contacting the account service"}), 500 if __name__ == "__main__": - # checks for hostname to see if running in containers then sets host appropriately - hostname = os.environ.get('HOSTNAME', '') - if hostname: - HOST = hostname - print(f"running on docker, container hostname: {hostname}") - else: - HOST = 'localhost' - print("running locally") - EVENT_API_URL = f"http://{HOST}:{EVENT_PORT}" - USER_API_URL = f"http://{HOST}:{USER_PORT}" + # # checks for hostname to see if running in containers then sets host appropriately + # hostname = os.environ.get('HOSTNAME', '') + # if hostname: + # HOST = hostname + # print(f"running on docker, container hostname: {hostname}") + # else: + # HOST = 'localhost' + # print("running locally") + app.run(debug=True, host="0.0.0.0", port=PORT) \ No newline at end of file -- GitLab From 3ca16082129e303aded36087b32d1370bbfa880a Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Wed, 21 May 2025 00:06:12 +0100 Subject: [PATCH 102/124] Test deployment with fixed porting --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4bd998b..4eb8b12 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,7 +31,7 @@ publish: # Publish containers to gitlab container registry stage: publish only: - main - # - feature/frontend-reverse-proxy + - feature/frontend-reverse-proxy tags: - deployment variables: @@ -90,7 +90,7 @@ deploy: stage: deploy only: - main - # - feature/frontend-reverse-proxy + - feature/frontend-reverse-proxy tags: - deployment environment: -- GitLab From e4f1e3f02334e7604b4f49102505aa04ec001cb7 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Wed, 21 May 2025 00:16:21 +0100 Subject: [PATCH 103/124] update docker-compose template to match --- docker-compose.tmpl | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.tmpl b/docker-compose.tmpl index 53a3f6c..a2b6283 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -13,6 +13,7 @@ services: depends_on: - postgres networks: + -backend - loadbalancing environment: - PORT=5002 -- GitLab From 413d7c9c088028e04fe0b535b490159b9fb66405 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Wed, 21 May 2025 00:26:11 +0100 Subject: [PATCH 104/124] cleanup --- docker-compose.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 21a41eb..fce5580 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,8 +4,6 @@ services: build: context: event_microservice dockerfile: Dockerfile-backend - #ports: - #- "5002:5002" deploy: replicas: 2 restart_policy: @@ -32,8 +30,6 @@ services: delay: 1s max_attempts: 3 window: 120s - #ports: - # - "5003:5003" depends_on: - postgres_ac networks: @@ -78,8 +74,6 @@ services: build: context: datefinder_microservice dockerfile: Dockerfile-datefinder - #ports: - # - "6000:6000" depends_on: - postgres deploy: -- GitLab From 0e332a1164f78b8343c40f589287c36273d7c7ca Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Wed, 21 May 2025 00:30:42 +0100 Subject: [PATCH 105/124] typo --- docker-compose.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.tmpl b/docker-compose.tmpl index a2b6283..0df8e00 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -13,7 +13,7 @@ services: depends_on: - postgres networks: - -backend + - backend - loadbalancing environment: - PORT=5002 -- GitLab From d38f7ef9c3e1959818c2ddf4a763f2528cc92fc8 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Tue, 20 May 2025 23:37:56 +0000 Subject: [PATCH 106/124] Edit README.md (add IP to server) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 380949d..5b48f6e 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ ## Getting Started -For the purposes of this coursework, GregC is deployed on the university-provided instance. +For the purposes of this coursework, GregC is deployed on the university-provided instance accessible at http://10.2.8.53/ from within the university network. To run it locally instead, make sure that Docker is installed, then follow these steps: -- GitLab From ceb1fdaa008de09f1a0c5aa395a46a22ff27a5ad Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Wed, 21 May 2025 00:40:08 +0100 Subject: [PATCH 107/124] cleanup --- docker-compose.tmpl | 5 ----- docker-compose.yml | 2 -- event_microservice/backend.py | 10 ---------- frontend_microservice/frontend.py | 9 --------- 4 files changed, 26 deletions(-) diff --git a/docker-compose.tmpl b/docker-compose.tmpl index 0df8e00..f11d78a 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -69,7 +69,6 @@ services: datefinder: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-datefinder-ms - #container_name: datefinder_ms depends_on: - postgres deploy: @@ -88,7 +87,6 @@ services: postgres: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-event-db - #container_name: event_database environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -110,7 +108,6 @@ services: postgres_ac: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-account-db - #container_name: account_database environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -133,8 +130,6 @@ services: #volumes: #postgres-db-volume: networks: - #frontend: - #driver: bridge backend: driver: bridge loadbalancing: diff --git a/docker-compose.yml b/docker-compose.yml index fce5580..82058bf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -140,8 +140,6 @@ services: #volumes: #postgres-db-volume: networks: - #frontend: - #driver: bridge backend: driver: bridge loadbalancing: diff --git a/event_microservice/backend.py b/event_microservice/backend.py index 8a2d5d5..3694e25 100644 --- a/event_microservice/backend.py +++ b/event_microservice/backend.py @@ -141,16 +141,6 @@ def get_best_dates(): if __name__ == "__main__": - - # if hostname: - # HOST = 'host.docker.internal' - # print(f"running on docker, container hostname: {hostname}") - # else: - # HOST = hostname - # print("running locally") - - #initialising local database - #connects to base postgres conn = psycopg2.connect(database="postgres", user='postgres', password='postgres', host=EVENT_DB_HOST, port=EVENT_DATABASE_PORT) conn.autocommit = True cursor = conn.cursor() diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index 5a84f4d..8128dc0 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -231,13 +231,4 @@ def reset_password_submit(): return jsonify({"error": "An internal error occurred while contacting the account service"}), 500 if __name__ == "__main__": - # # checks for hostname to see if running in containers then sets host appropriately - # hostname = os.environ.get('HOSTNAME', '') - # if hostname: - # HOST = hostname - # print(f"running on docker, container hostname: {hostname}") - # else: - # HOST = 'localhost' - # print("running locally") - app.run(debug=True, host="0.0.0.0", port=PORT) \ No newline at end of file -- GitLab From bebe28d4b00641c89861cd1bb221d58415e9327a Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Wed, 21 May 2025 00:40:44 +0100 Subject: [PATCH 108/124] restrict deployment to main --- .gitlab-ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4eb8b12..3c8f2d0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,7 +31,6 @@ publish: # Publish containers to gitlab container registry stage: publish only: - main - - feature/frontend-reverse-proxy tags: - deployment variables: @@ -90,7 +89,6 @@ deploy: stage: deploy only: - main - - feature/frontend-reverse-proxy tags: - deployment environment: -- GitLab From 5450c6e37ea33d91f0507d9f7349b25a59545d06 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Wed, 21 May 2025 00:45:27 +0100 Subject: [PATCH 109/124] disable debug mode --- account_microservice/user.py | 4 +++- docker-compose.tmpl | 4 ++++ docker-compose.yml | 4 ++++ event_microservice/backend.py | 4 ++-- frontend_microservice/frontend.py | 4 +++- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/account_microservice/user.py b/account_microservice/user.py index 78855e7..3cb8da0 100644 --- a/account_microservice/user.py +++ b/account_microservice/user.py @@ -313,4 +313,6 @@ def internal_error(e): return jsonify({"error": "Internal server error"}), 500 if __name__ == "__main__": - app.run(host="0.0.0.0", port=PORT, debug=False) \ No newline at end of file + debug_mode = os.environ.get("FLASK_DEBUG", "False").lower() == "true" + app.debug = debug_mode + app.run(host="0.0.0.0", port=PORT) \ No newline at end of file diff --git a/docker-compose.tmpl b/docker-compose.tmpl index f11d78a..3516938 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -17,6 +17,7 @@ services: - loadbalancing environment: - PORT=5002 + - FLASK_DEBUG=FALSE account_service: @@ -41,6 +42,7 @@ services: - MAIL_PASSWORD=bkxu hkqq wiax fuke - MAIL_DEFAULT_SENDER=gregcmailer@gmail.com - SECRET_KEY=f361e60c60f0aaf062ba9b9fc727f08eb525ad93ac715e012b224c0506596025 + - FLASK_DEBUG=FALSE nginx: @@ -66,6 +68,8 @@ services: - postgres_ac networks: - backend + environment: + - FLASK_DEBUG=FALSE datefinder: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-datefinder-ms diff --git a/docker-compose.yml b/docker-compose.yml index 82058bf..64a0bc3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,7 @@ services: - loadbalancing environment: - PORT=5002 + - FLASK_DEBUG=FALSE account_service: build: @@ -43,6 +44,7 @@ services: - MAIL_PASSWORD=bkxu hkqq wiax fuke - MAIL_DEFAULT_SENDER=gregcmailer@gmail.com - SECRET_KEY=f361e60c60f0aaf062ba9b9fc727f08eb525ad93ac715e012b224c0506596025 + - FLASK_DEBUG=FALSE nginx: image: nginx @@ -69,6 +71,8 @@ services: - postgres_ac networks: - backend + environment: + - FLASK_DEBUG=FALSE=value datefinder: build: diff --git a/event_microservice/backend.py b/event_microservice/backend.py index 3694e25..5c2f310 100644 --- a/event_microservice/backend.py +++ b/event_microservice/backend.py @@ -166,6 +166,6 @@ if __name__ == "__main__": cursor.close() conn.close() - - app.debug = True + debug_mode = os.environ.get("FLASK_DEBUG", "False").lower() == "true" + app.debug = debug_mode app.run(host="0.0.0.0", port=PORT) \ No newline at end of file diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index 8128dc0..a2ef82f 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -231,4 +231,6 @@ def reset_password_submit(): return jsonify({"error": "An internal error occurred while contacting the account service"}), 500 if __name__ == "__main__": - app.run(debug=True, host="0.0.0.0", port=PORT) \ No newline at end of file + debug_mode = os.environ.get("FLASK_DEBUG", "False").lower() == "true" + app.debug = debug_mode + app.run(host="0.0.0.0", port=PORT) \ No newline at end of file -- GitLab From 06f221ffc66d9b4f734479af6b3007e9fbedb426 Mon Sep 17 00:00:00 2001 From: hwilks <hazel.wilks@gmail.com> Date: Wed, 21 May 2025 10:17:50 +0100 Subject: [PATCH 110/124] delete share_link page --- frontend_microservice/frontend.py | 17 +------- .../templates/share_link.html | 40 ------------------- 2 files changed, 2 insertions(+), 55 deletions(-) delete mode 100644 frontend_microservice/templates/share_link.html diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index 22f973e..b540ede 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -94,15 +94,15 @@ def availability(event_id): def eventSummary(event_id): if request.method == 'GET': data_input = {"id":event_id, "to_return": ['event_name','event_possible_dates','availabilities']} + response = requests.get(f"{EVENT_API_URL}/get_event_info", json = data_input) data_output = response.json() if response.status_code == 200 else [] + event_name = data_output['name'] dates = data_output['event_possible_dates'] availabilities = data_output['availabilities'] - link = f"http://127.0.0.1:8000/eventSummary/{event_id}" - return render_template('eventSummary.html', id=event_id, link=link, event=event_name, dates=dates, availabilities=availabilities) if request.method == 'POST': @@ -116,19 +116,6 @@ def eventSummary(event_id): return render_template('eventSummary.html') -@app.route('/share_link/<int:event_id>', methods =['GET']) -def share_link(event_id): - if request.method == 'GET': - data_input = {"id":event_id, "to_return": ['event_name']} - response = requests.get(f"{EVENT_API_URL}/get_event_info", json = data_input) - data_output = response.json() if response.status_code == 200 else [] - event_name = data_output['name'] - link = f"http://127.0.0.1:8000/eventSummary/{event_id}" - redirect_url = url_for('eventSummary', event_id=event_id) - return render_template('share_link.html', event=event_name, link=link,url=redirect_url) - return render_template('share_link.html') - - @app.route('/getDates', methods=['POST']) def getDatesp(): if request.method == 'POST': diff --git a/frontend_microservice/templates/share_link.html b/frontend_microservice/templates/share_link.html deleted file mode 100644 index a340718..0000000 --- a/frontend_microservice/templates/share_link.html +++ /dev/null @@ -1,40 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> - <title>Share Link</title> - - </head> - <body> - <div class="centered"> - <div> - <h2>Here's the link to share for {{event}}</h2> - <div class="link-input-button"> - <input type="text" class="link-input" id="link" value="{{link}}" readonly> - <button class="copy" onclick="copyToClipboard()">Copy</button> - </div> - </div> - <a href="{{url}}">Go to link</a> - </div> - - - <script> - function copyToClipboard() { - var copyText = document.getElementById("link"); - - copyText.select(); - copyText.setSelectionRange(0, 99999); // For mobile devices - - // Use the Clipboard API - navigator.clipboard.writeText(copyText.value) - .then(function() { - alert("Text copied to clipboard!"); - }) - .catch(function(error) { - alert("Failed to copy text: " + error); - }); - } - </script> - </body> - -</html> \ No newline at end of file -- GitLab From bc573e7f4936e71356e9c953b3fe70ecdce6e013 Mon Sep 17 00:00:00 2001 From: "Trieu, Hoang Hiep (PG/T - Comp Sci & Elec Eng)" <ht00865@surrey.ac.uk> Date: Wed, 21 May 2025 09:54:55 +0000 Subject: [PATCH 111/124] fix the nginx problem --- docker-compose.yml | 1 + event_microservice/backend.py | 4 ++-- frontend_microservice/frontend.py | 8 ++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 64a0bc3..f0c45eb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -71,6 +71,7 @@ services: - postgres_ac networks: - backend + - loadbalancing environment: - FLASK_DEBUG=FALSE=value diff --git a/event_microservice/backend.py b/event_microservice/backend.py index 5c2f310..0665d63 100644 --- a/event_microservice/backend.py +++ b/event_microservice/backend.py @@ -6,8 +6,8 @@ import os #TODO make sure the port is correct #Comment -ALGORITHM_PORT = 6000 -ALGORITHM_HOST = "datefinder" +ALGORITHM_PORT = 8080 +ALGORITHM_HOST = "nginx" ALGORITHM_API_URL = f"http://{ALGORITHM_HOST}:{ALGORITHM_PORT}" EVENT_DATABASE_PORT = 5436 diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index a2ef82f..75e352d 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -8,11 +8,11 @@ import logging app = Flask(__name__) PORT = 5001 -EVENT_HOST = "event_service" -EVENT_PORT = 5002 +EVENT_HOST = "nginx" +EVENT_PORT = 8080 -USER_HOST = "account_service" -USER_PORT = 5003 +USER_HOST = "nginx" +USER_PORT = 8080 EVENT_API_URL = f"http://{EVENT_HOST}:{EVENT_PORT}" USER_API_URL = f"http://{USER_HOST}:{USER_PORT}" -- GitLab From dea7510c02433b9818a76720d424acb537eb293c Mon Sep 17 00:00:00 2001 From: hwilks <hazel.wilks@gmail.com> Date: Wed, 21 May 2025 10:59:51 +0100 Subject: [PATCH 112/124] make link dynamic so it always displays right link --- frontend_microservice/frontend.py | 3 +-- frontend_microservice/templates/eventSummary.html | 6 +++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index b540ede..3f4ecd6 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -101,9 +101,8 @@ def eventSummary(event_id): event_name = data_output['name'] dates = data_output['event_possible_dates'] availabilities = data_output['availabilities'] - link = f"http://127.0.0.1:8000/eventSummary/{event_id}" - return render_template('eventSummary.html', id=event_id, link=link, event=event_name, dates=dates, availabilities=availabilities) + return render_template('eventSummary.html', id=event_id, event=event_name, dates=dates, availabilities=availabilities) if request.method == 'POST': data = request.get_json() diff --git a/frontend_microservice/templates/eventSummary.html b/frontend_microservice/templates/eventSummary.html index 4bed6a5..9f7f199 100644 --- a/frontend_microservice/templates/eventSummary.html +++ b/frontend_microservice/templates/eventSummary.html @@ -22,7 +22,10 @@ console.log(availabilities); var dates = {{dates | tojson }}; var event_id = {{id | tojson}}; + var link = window.location.href + console.log('This is the link:',link) </script> + <div class="lowered centered" style="width: 100%;"> <h1>{{ event }} Summary</h1> @@ -31,7 +34,7 @@ <div class="card" id="link_container"> <h2>Here's the link to share for {{event}}</h2> <div class="link-input-button"> - <input type="text" class="link-input" id="link" value="{{link}}" readonly> + <input type="text" class="link-input" id="link" readonly> <button class="copy" onclick="copyToClipboard()">Copy</button> </div> </div> @@ -79,5 +82,6 @@ <script src="{{ url_for('static', filename='script.js') }}"></script> <script>new_list_element(availabilities,dates)</script> + <script>document.getElementById('link').value = link</script> </body> </html> -- GitLab From d957fe5e0f1ea6c77458471467f740fea91135dd Mon Sep 17 00:00:00 2001 From: hwilks <hazel.wilks@gmail.com> Date: Wed, 21 May 2025 11:02:54 +0100 Subject: [PATCH 113/124] remove commented out code from frontend.py --- frontend_microservice/frontend.py | 30 +++--------------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/frontend_microservice/frontend.py b/frontend_microservice/frontend.py index 3f4ecd6..0fd3c31 100644 --- a/frontend_microservice/frontend.py +++ b/frontend_microservice/frontend.py @@ -27,7 +27,6 @@ def create_event(): event_id=data_output['event_id'] if response.status_code == 201: # Check if event was successfully createds redirect_url = url_for('eventSummary', event_id=event_id) - #print(redirect_url) return jsonify({'url': redirect_url}) else: return f"Error: {response.text}", response.status_code @@ -60,26 +59,20 @@ def login(): @app.route("/availability/<int:event_id>", methods=['GET', 'POST']) def availability(event_id): if request.method == 'GET': - #sends list 'to_return' that contains all data requested more details in event_microservice docstring + data_input = {"id":event_id, "to_return": ['event_name','event_possible_dates','availabilities']} - #json_data = json.dumps(data_input, indent=4) response = requests.get(f"{EVENT_API_URL}/get_event_info", json = data_input) data_output = response.json() if response.status_code == 200 else [] event_name = data_output['name'] dates = data_output['event_possible_dates'] availabilities = data_output['availabilities'] - #availabilities now outputs a dict with format user_id:[dates] - #print(data_output['availabilities']) return render_template('availability.html', event=event_name, event_id=event_id, event_dates = dates, availabilities = availabilities) if request.method == 'POST': data = request.get_json() #data contains event_id, user_name and availability - #To test frontend - #data = {"event_id":1,"availability":data['availability'], "user_id":9} - #TODO: backend needs to be changed to cope with: changing availabilites, user_name instead of user_id response = requests.post(f"{EVENT_API_URL}/add_availabilities", json=data) if response.status_code == 201: # Check if was added successfully @@ -118,13 +111,7 @@ def eventSummary(event_id): @app.route('/getDates', methods=['POST']) def getDatesp(): if request.method == 'POST': - print("Hello") - # sends list 'to_return' that contains all data requested more details in event_microservice docstring - #data_input = requests.get(f"{EVENT_API_URL}/get_event_info", json = {"id":event_id, "to_return": ['availabilities']}) data_input = request.get_json() - #data_input = {'availabilities':data['availabilities']} - print(data_input) - # TODO: Just send event ID and sort in backend response = requests.get(f"{EVENT_API_URL}/get_best_dates", json = data_input) data_output = response.json() if response.status_code == 200 else [] return data_output @@ -136,33 +123,22 @@ def get_events(account_id): with open("loging.txt","w") as file: data = {'account_id':account_id} - #file.write('in front') - #file.write(str(data['account_id'])) account_response = requests.get(f"{USER_API_URL}/get_events", json = data) - response = account_response.json() #if account_response.status_code == 201 else [] + response = account_response.json() event_ids = response['event_ids'] - #file.write(str(type(event_ids))) events = [] for event_id in event_ids: event ={} - #file.write(str(event_id)) - #event_id = 2 data_input = {"id":int(event_id), "to_return": ['event_name']} response = requests.get(f"{EVENT_API_URL}/get_event_info", json = data_input) - #file.write(str(response.status_code)) data_output = response.json() if response.status_code == 200 else [] event['name'] = data_output['name'] event['url'] = url_for('eventSummary', event_id=event_id) events.append(event) - ''' - #Test data - event1= {'name':'An event','url':url_for('eventSummary', event_id=1)} - event2= {'name':'An event 2','url':'a url'} - events = [event1, event2] - ''' + return render_template('accountEvents.html', event=events) return render_template('accountEvents.html') -- GitLab From 20848d5b5933fb8f742da6c8cde03f71f217e956 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Wed, 21 May 2025 10:18:13 +0000 Subject: [PATCH 114/124] test deployment of development branch --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3c8f2d0..f057df1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,6 +31,7 @@ publish: # Publish containers to gitlab container registry stage: publish only: - main + - development tags: - deployment variables: @@ -89,6 +90,7 @@ deploy: stage: deploy only: - main + - development tags: - deployment environment: -- GitLab From 22c21f7c2e2f1e80c392a6384792b45ae43e101b Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Wed, 21 May 2025 11:27:34 +0100 Subject: [PATCH 115/124] update docker-compose tmpl to match --- docker-compose.tmpl | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.tmpl b/docker-compose.tmpl index 3516938..ba28611 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -68,6 +68,7 @@ services: - postgres_ac networks: - backend + - loadbalancing environment: - FLASK_DEBUG=FALSE -- GitLab From 1589d204d6648b1f0a6055eb48d19084b39fbddb Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Wed, 21 May 2025 12:08:50 +0100 Subject: [PATCH 116/124] remove old logging statements --- datefinder_microservice/event_finder.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/datefinder_microservice/event_finder.py b/datefinder_microservice/event_finder.py index e28a95b..9a1ed13 100644 --- a/datefinder_microservice/event_finder.py +++ b/datefinder_microservice/event_finder.py @@ -6,24 +6,6 @@ import json app = Flask(__name__) PORT = 6000 -import logging - -# Set up logging to a file -logging.basicConfig(filename='app.log', level=logging.INFO) - - - - - -# Simulating event with 4 users - -users = { - "user1": [datetime(2025, 5, 1), datetime(2025, 5, 2), datetime(2025, 5, 3), datetime(2025, 5, 4), datetime(2025, 5, 5), datetime(2025, 5, 6)], - "user2": [datetime(2025, 5, 2), datetime(2025, 5, 3), datetime(2025, 5, 4), datetime(2025, 5, 6), datetime(2025, 5, 7)], - "user3": [datetime(2025, 5, 3), datetime(2025, 5, 9), datetime(2025, 5, 5), datetime(2025, 5, 6)], - "user4": [datetime(2025, 5, 3), datetime(2025, 5, 6)] - } -#print("expected output is:\ndict_values([1, 2, 4, 2, 2, 4, 1, 1]) and [3rd, 6th]") @app.route("/calculate" , methods = ['GET', 'POST']) def best_date(): @@ -61,14 +43,9 @@ def best_date(): for best_date in best_dates: - logging.info("We made it into the loop") for user in users: - logging.info("We made it into the loop") if best_date in users[user]: - logging.info(best_date) - logging.info(user) best_dates[best_date].append(str(user)) - logging.info(best_dates) return best_dates, 200 -- GitLab From 27a9ad1afb22ae5ee9a1b342aae92fa8c900eec6 Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Wed, 21 May 2025 12:12:10 +0100 Subject: [PATCH 117/124] remove unnecessary imports --- datefinder_microservice/event_finder.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/datefinder_microservice/event_finder.py b/datefinder_microservice/event_finder.py index 9a1ed13..36bdf65 100644 --- a/datefinder_microservice/event_finder.py +++ b/datefinder_microservice/event_finder.py @@ -1,7 +1,4 @@ -from datetime import datetime -from flask import Flask, render_template, request, redirect, url_for, jsonify -import requests -import json +from flask import Flask, request app = Flask(__name__) PORT = 6000 -- GitLab From e840fd7ef725a9350c98b974f33b377f3d0391b2 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Wed, 21 May 2025 12:12:11 +0100 Subject: [PATCH 118/124] remove nginx volume mount in deployment --- docker-compose.tmpl | 3 --- 1 file changed, 3 deletions(-) diff --git a/docker-compose.tmpl b/docker-compose.tmpl index baba906..415ea91 100644 --- a/docker-compose.tmpl +++ b/docker-compose.tmpl @@ -47,9 +47,6 @@ services: nginx: image: $DOCKERHUB_REGISTRY:$CI_COMMIT_SHORT_SHA-nginx - volumes: - - ./nginx:/etc/nginx/conf.d - #- ./frontend_microservice/templates:/usr/share/nginx/html ports: - "8080:8080" networks: -- GitLab From b0a3fb9faee028c8fbdd32627590054aeaa8852a Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Wed, 21 May 2025 12:13:56 +0100 Subject: [PATCH 119/124] remove unnecessary requirements --- datefinder_microservice/requirements.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/datefinder_microservice/requirements.txt b/datefinder_microservice/requirements.txt index a4fa4f3..8ab6294 100644 --- a/datefinder_microservice/requirements.txt +++ b/datefinder_microservice/requirements.txt @@ -1,4 +1 @@ -flask -#sqlite3 -psycopg2-binary -requests \ No newline at end of file +flask \ No newline at end of file -- GitLab From f8eec1eb990e8f97dbe59db0c98efa19ec3fbdbb Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Wed, 21 May 2025 12:14:27 +0100 Subject: [PATCH 120/124] test deployment on this branch --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bc93f18..ffbd633 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -41,7 +41,7 @@ publish: # Publish containers to gitlab container registry stage: publish only: - main - - development + - issue/nginx-empty-config tags: - deployment variables: @@ -100,7 +100,7 @@ deploy: stage: deploy only: - main - - development + - issue/nginx-empty-config tags: - deployment environment: -- GitLab From 6632a369e07cf39ea2cd294c08942bac857d45b1 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Wed, 21 May 2025 12:28:26 +0100 Subject: [PATCH 121/124] correct DB_HOST in account service --- account_microservice/user.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/account_microservice/user.py b/account_microservice/user.py index 3cb8da0..782989b 100644 --- a/account_microservice/user.py +++ b/account_microservice/user.py @@ -36,15 +36,7 @@ limiter = Limiter( key_func=get_remote_address, default_limits=["200 per day", "50 per hour"] ) - -hostname = os.environ.get('HOSTNAME', '') -if hostname: - HOST = 'account_database' - print(f"Running on docker, container hostname: {hostname}") -else: - HOST = 'localhost' - print("Running locally") - +DB_HOST = "postgres_ac" PORT = 5003 DB_PORT = 5437 @@ -54,7 +46,7 @@ def get_db_connection(): database="account", user="postgres", password="postgres", - host=HOST, + host=DB_HOST, port=DB_PORT ) conn.autocommit = True -- GitLab From 245a2d8610a2f0520328e9b598e188068203430d Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Wed, 21 May 2025 12:43:40 +0100 Subject: [PATCH 122/124] restrict deployment to main --- .gitlab-ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ffbd633..e52cfab 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -41,7 +41,6 @@ publish: # Publish containers to gitlab container registry stage: publish only: - main - - issue/nginx-empty-config tags: - deployment variables: @@ -100,7 +99,6 @@ deploy: stage: deploy only: - main - - issue/nginx-empty-config tags: - deployment environment: -- GitLab From 6a2bd5bd3239a8e3e686f6a0edfc735f66ba62df Mon Sep 17 00:00:00 2001 From: "Smith, Thomas E (PG/T - Comp Sci & Elec Eng)" <ts00738@surrey.ac.uk> Date: Wed, 21 May 2025 13:20:10 +0100 Subject: [PATCH 123/124] fix url in reset email --- account_microservice/user.py | 3 ++- frontend_microservice/static/auth.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/account_microservice/user.py b/account_microservice/user.py index 3cb8da0..dfa6c3f 100644 --- a/account_microservice/user.py +++ b/account_microservice/user.py @@ -195,6 +195,7 @@ def request_password_reset(): return jsonify({"error": "Email is required"}), 400 email = data['email'].strip().lower() + url = data['url'] try: valid = validate_email(email) email = valid.normalized @@ -221,7 +222,7 @@ def request_password_reset(): ) conn.commit() - reset_url = f"http://localhost:8000/reset_password/{token}" + reset_url = f"{url}/reset_password/{token}" subject = "Password Reset Request" body = f"Click the following link to reset your password: {reset_url}\n\nThis link will expire in 1 hour.\n\nIf you did not request this, please ignore this email." diff --git a/frontend_microservice/static/auth.js b/frontend_microservice/static/auth.js index 24e0fff..ccb91f3 100644 --- a/frontend_microservice/static/auth.js +++ b/frontend_microservice/static/auth.js @@ -102,12 +102,13 @@ if (loginForm) { const email = document.getElementById('email').value; const messageDiv = document.getElementById('message'); messageDiv.textContent = ''; + const url = window.location.hostname try { const response = await fetch('/forgot_password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ email }) + body: JSON.stringify({url, email }) }); const data = await response.json(); -- GitLab From 043a4ffafa9122106db39cdd451e54b88b679744 Mon Sep 17 00:00:00 2001 From: "Peron, Loic R (PG/T - Comp Sci & Elec Eng)" <lp01242@surrey.ac.uk> Date: Wed, 21 May 2025 13:29:12 +0100 Subject: [PATCH 124/124] https to http --- account_microservice/user.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/account_microservice/user.py b/account_microservice/user.py index dfa6c3f..d464c0b 100644 --- a/account_microservice/user.py +++ b/account_microservice/user.py @@ -224,6 +224,10 @@ def request_password_reset(): reset_url = f"{url}/reset_password/{token}" + if reset_url.startswith("https://"): + reset_url = reset_url.replace("https://", "http://", 1) + + subject = "Password Reset Request" body = f"Click the following link to reset your password: {reset_url}\n\nThis link will expire in 1 hour.\n\nIf you did not request this, please ignore this email." msg = Message(subject, recipients=[email], body=body) -- GitLab