diff --git a/.gitignore b/.gitignore index 2d7ec5c..e581507 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .env node_modules/ +project.zip +.gitignore diff --git a/checkinData.js b/checkinData.js new file mode 100644 index 0000000..aca6934 --- /dev/null +++ b/checkinData.js @@ -0,0 +1,174 @@ +export default { + general: [ + { + id: "q1", + question: "What was your water intake for today?", + responses: [ + {value: 1, icon: "bi-droplet", label: "0-2 glasses"}, + {value: 2, icon: "bi-droplet", label: "3-4 glasses"}, + {value: 3, icon: "bi-droplet-half", label: "5-6 glasses"}, + {value: 4, icon: "bi-droplet-fill", label: "7-8 glasses"}, + {value: 5, icon: "bi-water", label: "9+ glasses"} + ] + }, + { + id: "q2", + question: "How was your meal consistency today?", + responses: [ + {value: 1, icon: "", label: "Skipped meals"}, + {value: 2, icon: "", label: "Only one meal"}, + {value: 3, icon: "", label: "Two meals"}, + {value: 4, icon: "", label: "Three meals"}, + {value: 5, icon: "", label: "Balanced meals & snacks"} + ] + }, + { + id: "q3", + question: "How is your energy today?", + responses: [ + { value: 1, icon:"bi-battery", label: "Exhausted" }, + { value: 2, icon: "bi-battery-low", label: "Sluggish" }, + { value: 3, icon: "bi-battery-half", label: "Steady" }, + { value: 4, icon: "bi-battery-full", label: "Energetic" }, + { value: 5, icon: "", label: "Peak power"} + ] + }, + { + id: "q4", + question: "How do you feel about the future?", + responses: [ + { value: 1, icon: "", label: "Very Pessimistic"}, + { value: 2, icon: "", label: "Uncertain"}, + { value: 3, icon: "", label: "Neutral"}, + { value: 4, icon: "", label: "Hopeful"}, + { value: 5, icon: "", label: "Very Optimistic"} + ] + }, + { + id: "q5", + question: "Do you feel satisfied with your daily life?", + responses: [ + { value: 1, icon: "", label: "Not at all"}, + { value: 2, icon: "", label: "Rarely"}, + { value: 3, icon: "", label: "Sometimes"}, + { value: 4, icon: "", label: "Mostly"}, + { value: 5, icon: "", label: "Completely"} + ] + }, + ], + mental: [ + { + id: "q1", + question: "How was your ability to focus throughout the day?", + responses: [ + { value: 1, icon:"", label: "Constant Distraction" }, + { value: 2, icon: "", label: "Low Focus"}, + { value: 3, icon: "", label: "Occasional Drift"}, + { value: 4, icon: "", label: "Mostly Focused" }, + { value: 5, icon: "", label: "Total Flow State"} + ] + }, + { + id: "q2", + question: "Did you feel a sense of support", + responses: [ + { value: 1, icon:"", label: "Isolated" }, + { value: 2, icon: "", label: "Misunderstood"}, + { value: 3, icon: "", label: "Somewhat Supported"}, + { value: 4, icon: "", label: "Well Connected" }, + { value: 5, icon: "", label: "Strongly Supported"} + ] + }, + { + id: "q3", + question: "What is your perspective of the challenges you're currently facing?", + responses: [ + { value: 1, icon:"", label: "Feels Alone" }, + { value: 2, icon: "", label: "Struggling"}, + { value: 3, icon: "", label: "Hanging in There"}, + { value: 4, icon: "", label: "Managing Well" }, + { value: 5, icon: "", label: "Empowered"} + ] + }, + { + id: "q4", + question: "Where do you feel the level of your self-confidence is at?", + responses: [ + { value: 1, icon:"", label: "Very Low" }, + { value: 2, icon: "", label: "Doubtful"}, + { value: 3, icon: "", label: "Average"}, + { value: 4, icon: "", label: "Healthy Confidence"}, + { value: 5, icon: "", label: "High Confidence"} + ] + }, + { + id: "q5", + question: "How do you feel about your current emotional balance?", + responses: [ + { value: 1, icon:"", label: "Overwhelmed" }, + { value: 2, icon: "", label: "Unstable"}, + { value: 3, icon: "", label: "Neutral"}, + { value: 4, icon: "", label: "Balanced" }, + { value: 5, icon: "", label: "Very Peaceful"} + ] + } + ], + physical: [ + { + id: "q1", + question: "Till when do you use electronic devices after midnight?", + responses: [ + { value: 1, icon:"", label: "3 AM +" }, + { value: 2, icon: "", label: "2 AM"}, + { value: 3, icon: "", label: "1 AM"}, + { value: 4, icon: "", label: "Just after 12 AM" }, + { value: 5, icon: "", label: "No screens"} + ] + }, + { + id: "q2", + question: "How long did you exercise for today?", + responses: [ + {value: 1, icon:"bi-person-standing", label: "None", range: ""}, + {value: 2, icon:"bi-person-walking", label: "Light", range: "30 minutes"}, + {value: 3, icon: "", label: "Moderate", range: "1 hour"}, + {value: 4, icon: "bi-person-arms-up", label: "Moderate-Intensity", range: "1-2 hours"}, + {value: 5, icon: "bi-lightning-charge-fill", label: "Heavy", range:"3+ hours"}, + ] + }, + { + id: "q3", + question: "Around how many minutes of sun exposure did you get today?", + responses: [ + {value: 1, icon: "", label: "Indoors All Day"}, + {value: 2, icon: "", label: "< 5 minutes"}, + {value: 3, icon: "", label: "5 - 10 minutes"}, + {value: 4, icon: "", label: "10 - 20 minutes"}, + {value: 5, icon: "", label: "30+ minutes"} + ] + }, + { + id: "q4", + question: "Around how many hours of sleep did you get last night?", + responses: [ + {value: 1, icon: "", label: "< 4 hours"}, + {value: 2, icon: "", label: "5 hours"}, + {value: 3, icon: "", label: "6 hours"}, + {value: 4, icon: "", label: "7 hours"}, + {value: 5, icon: "", label: "8+ hours"} + ] + }, + { + id: "q5", + question: "What was your level of caffine consumption today", + responses: [ + {value: 1, icon:"", label: "3+ drinks"}, + {value: 2, icon: "", label: "2 drinks"}, + {value: 3, icon: "", label: "1 drink"}, + {value: 4, icon: "", label: "A few sips"}, + {value: 5, icon: "", label: "None"} + ] + } + ] +}; + diff --git a/db.js b/db.js index 12805ae..a9c4bd2 100644 --- a/db.js +++ b/db.js @@ -5,14 +5,17 @@ import dotenv from 'dotenv'; dotenv.config(); const db = mysql.createPool({ - host: process.env.DB_HOST || 'localhost', - user: process.env.DB_USER || 'root', - password: process.env.DB_PASSWORD || '', - database: process.env.DB_NAME || 'my_database', - waitForConnections: true, - connectionLimit: 10, - queueLimit: 0, -}); + host: process.env.DB_HOST || 'localhost', + port: Number(process.env.DB_PORT || 3306), + user: process.env.DB_USER || 'root', + password: process.env.DB_PASSWORD || '', + database: process.env.DB_NAME || 'my_database', + waitForConnections: true, + connectionLimit: 10, + queueLimit: 0, + }); + + (async () => { try { diff --git a/index.js b/index.js index 9cabaec..edc13c5 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ import express from "express"; import helmet from "helmet"; import session from "express-session"; import dotenv from "dotenv"; +import { createRequire } from "module"; import bcrypt from "bcrypt"; import { handleLogin } from "./login.js"; import { handleSignup } from "./signup.js"; @@ -9,26 +10,91 @@ import db from "./db.js"; import cron from "node-cron"; import { adviceMap, questionMap } from "./advice.js"; // import { scheduleReminderJob } from "./sendReminders.js"; +import { markDayComplete, getCurrentStreak } from "./streak.js"; import { getAdviceFor } from './advice.js'; import OpenAI from "openai"; +import checkinData from "./checkinData.js"; +import { + createEmailVerificationToken, + ensureEmailVerificationColumns, + getEmailVerificationExpiryDate, + sendVerificationEmail, +} from "./verification.js"; +import { + DEFAULT_BUDDY_NAME, + BUDDY_COSTS, + BUDDY_OPTIONS, + normalizeBuddyProfile, + buildBuddyStatusRedirect, +} from "./utils/buddy.js"; +import { getLowestScoringQuestion } from "./utils/survey.js"; dotenv.config(); +const require = createRequire(import.meta.url); const app = express(); const PORT = process.env.PORT || 8000; +const isProduction = process.env.NODE_ENV === "production"; + +let trustProxySetting = 1; +if (process.env.TRUST_PROXY === "true") { + trustProxySetting = true; +} else if (process.env.TRUST_PROXY === "false") { + trustProxySetting = false; +} else if (process.env.TRUST_PROXY) { + const parsedTrustProxy = Number(process.env.TRUST_PROXY); + if (!Number.isNaN(parsedTrustProxy)) { + trustProxySetting = parsedTrustProxy; + } +} + +let sessionCookieSecure = "auto"; +if (process.env.SESSION_COOKIE_SECURE === "true") { + sessionCookieSecure = true; +} else if (process.env.SESSION_COOKIE_SECURE === "false") { + sessionCookieSecure = false; +} else if (!isProduction) { + sessionCookieSecure = false; +} + +const sessionStoreConfig = { + host: process.env.DB_HOST || "localhost", + port: Number(process.env.DB_PORT || 3306), + user: process.env.DB_USER || "root", + password: process.env.DB_PASSWORD || "", + database: process.env.DB_NAME || "my_database", + clearExpired: true, + checkExpirationInterval: 15 * 60 * 1000, + expiration: 24 * 60 * 60 * 1000, + createDatabaseTable: true, +}; + +let sessionStore; +try { + const MySQLStoreFactory = require("express-mysql-session"); + const MySQLStore = MySQLStoreFactory(session); + sessionStore = new MySQLStore(sessionStoreConfig); + console.log("MySQL session store enabled."); +} catch (err) { + console.warn("express-mysql-session is not installed. Falling back to MemoryStore."); +} app.use(express.urlencoded({ extended: true })); app.use(express.json()); app.use(express.static("public")); app.set("view engine", "ejs"); +app.set("trust proxy", trustProxySetting); app.use( session({ secret: process.env.SESSION_SECRET, + store: sessionStore, resave: false, - saveUninitialized: true, - cookie: { - secure: process.env.NODE_ENV === "production", - httpOnly: true, - maxAge: 1000 * 60 * 60 * 24, + saveUninitialized: false, + proxy: trustProxySetting !== false, + cookie: { + secure: sessionCookieSecure, + httpOnly: true, + sameSite: "lax", + maxAge: 1000 * 60 * 60 * 24, }, }) ); @@ -55,6 +121,56 @@ const calendarTimeline = { physical: [] }; +const tableMap = { + general: 'general_survey', + mental: 'mental_survey', + physical: 'physical_survey' +}; + +let buddyColumnsReady = false; +let buddyColumnsPromise = null; + +async function ensureBuddyCustomizationColumns() { + if (buddyColumnsReady) return; + if (buddyColumnsPromise) return buddyColumnsPromise; + + const requiredColumns = [ + { + name: "buddy_type", + sql: "ADD COLUMN buddy_type VARCHAR(20) NOT NULL DEFAULT 'dog'", + }, + { + name: "buddy_name", + sql: `ADD COLUMN buddy_name VARCHAR(100) NOT NULL DEFAULT '${DEFAULT_BUDDY_NAME}'`, + }, + { + name: "buddy_has_collar", + sql: "ADD COLUMN buddy_has_collar TINYINT(1) NOT NULL DEFAULT 0", + }, + { + name: "owned_buddy_types", + sql: "ADD COLUMN owned_buddy_types TEXT NULL", + }, + ]; + + buddyColumnsPromise = (async () => { + for (const column of requiredColumns) { + const [rows] = await db.query("SHOW COLUMNS FROM users LIKE ?", [column.name]); + if (!rows.length) { + await db.query(`ALTER TABLE users ${column.sql}`); + } + } + buddyColumnsReady = true; + })(); + + try { + await buddyColumnsPromise; + } catch (err) { + buddyColumnsPromise = null; + throw err; + } +} + function getLocalDateString() { const now = new Date(); return new Intl.DateTimeFormat('en-CA', { @@ -65,21 +181,6 @@ function getLocalDateString() { }).format(now); } -function getLowestScoringQuestion(scores) { - const entries = Object.entries(scores); - const values = entries.map(([, val]) => val); - const avg = values.reduce((a, b) => a + b, 0) / values.length; - - const threshold = avg - 2; - const standout = entries.find(([, val]) => val <= threshold); - if (standout) return { key: standout[0], value: standout[1], reason: 'standout' }; - - const minVal = Math.min(...values); - const lowest = entries.find(([, val]) => val === minVal); - return { key: lowest[0], value: lowest[1], reason: 'low' }; -} - - app.get("/", (req, res) => { res.redirect("/welcome"); }); @@ -88,12 +189,155 @@ app.get("/welcome", (req, res) => { res.render("welcome"); }); -app.get("/login", (req, res) => res.render("login")); +app.get("/login", (req, res) => { + res.render("login", { + error: null, + message: req.query.message || null, + verificationEmail: req.query.verificationEmail || null, + }); +}); app.post("/login", handleLogin); app.get("/signup", (req, res) => res.render("signup")); app.post("/signup", handleSignup); +app.get("/verify-email", async (req, res) => { + const { token } = req.query; + + if (!token || typeof token !== "string") { + return res.render("login", { + error: "Verification link is invalid.", + message: null, + verificationEmail: null, + }); + } + + try { + await ensureEmailVerificationColumns(); + + const [[user]] = await db.query( + `SELECT email, email_verification_expires_at, email_verified + FROM users + WHERE email_verification_token = ?`, + [token] + ); + + if (!user) { + return res.render("login", { + error: "Verification link is invalid or has already been used.", + message: null, + verificationEmail: null, + }); + } + + if (user.email_verified) { + return res.render("login", { + error: null, + message: "Your email is already verified. You can log in now.", + verificationEmail: null, + }); + } + + const expiresAt = user.email_verification_expires_at + ? new Date(user.email_verification_expires_at) + : null; + if (!expiresAt || expiresAt < new Date()) { + return res.render("login", { + error: "Your verification link has expired. Please resend verification below.", + message: null, + verificationEmail: user.email, + }); + } + + await db.query( + `UPDATE users + SET email_verified = 1, + email_verification_token = NULL, + email_verification_expires_at = NULL + WHERE email_verification_token = ?`, + [token] + ); + + return res.render("login", { + error: null, + message: "Email verified successfully. You can now log in.", + verificationEmail: null, + }); + } catch (err) { + console.error("Error verifying email:", err); + return res.status(500).send("Internal Server Error"); + } +}); + +app.post("/resend-verification", async (req, res) => { + const email = req.body.email?.trim().toLowerCase(); + if (!email) { + return res.render("login", { + error: "Email is required to resend verification.", + message: null, + verificationEmail: null, + }); + } + + try { + await ensureEmailVerificationColumns(); + + const [[user]] = await db.query( + `SELECT id, full_name, email, email_verified + FROM users + WHERE email = ?`, + [email] + ); + + if (!user) { + return res.render("login", { + error: "No account was found for that email address.", + message: null, + verificationEmail: null, + }); + } + + if (user.email_verified) { + return res.render("login", { + error: null, + message: "That email is already verified. You can log in now.", + verificationEmail: null, + }); + } + + const verificationToken = createEmailVerificationToken(); + const verificationExpiresAt = getEmailVerificationExpiryDate(); + + await db.query( + `UPDATE users + SET email_verification_token = ?, + email_verification_expires_at = ? + WHERE id = ?`, + [verificationToken, verificationExpiresAt, user.id] + ); + + await sendVerificationEmail({ + email: user.email, + name: user.full_name, + token: verificationToken, + req, + }); + + return res.render("login", { + error: null, + message: "A new verification email has been sent.", + verificationEmail: user.email, + }); + } catch (err) { + console.error("Error resending verification email:", err); + return res.render("login", { + error: "We could not resend verification right now. Please try again later.", + message: null, + verificationEmail: email, + }); + } +}); + app.get("/chatbot", (req, res) => { res.render("chatbot"); }); @@ -296,6 +540,7 @@ app.get("/calendar", async (req, res) => { }); }); +/* app.get("/home", async (req, res) => { if (!req.session.user) return res.redirect("/login"); const userId = req.session.user.id; @@ -309,6 +554,126 @@ app.get("/home", async (req, res) => { plantedFlowers: planted }); }); +*/ + +async function getTodayCheckinContext(userId) { + const today = getLocalDateString(); + + const tables = ["general_survey", "mental_survey", "physical_survey"]; + const context = {}; + + for (const table of tables) { + const [rows] = await db.query( + `SELECT question, score + FROM ${table} + WHERE user_id = ? + AND DATE(created_at) = ?`, + [userId, today] + ); + + if (!rows.length) continue; + + const short = table.split("_")[0]; // "general", "mental", "physical" + + context[short] = rows.map((r) => { + // questionMap.general.q1, questionMap.mental.q3, etc. + const text = + (questionMap[short] && questionMap[short][r.question]) || r.question; + return { + id: r.question, // q1, q2, etc. + text, // full question string + score: r.score, // 1–10 + }; + }); + } + + return context; +} + +app.get("/home", async (req, res) => { + if (!req.session.user) return res.redirect("/login"); + + const userId = req.session.user.id; + await ensureBuddyCustomizationColumns(); + + let petMood = "neutral"; + let petThirsty = false; + + try { + const [moodRows] = await db.query( + "SELECT score FROM mental_survey WHERE user_id = ? AND question = ? ORDER BY created_at DESC LIMIT 1", + [userId, "q5"] // q5 = "I generally feel happy and emotionally balanced." + ); + + if (moodRows.length > 0) { + const score = moodRows[0].score; // 1–10 + if (score >= 8) petMood = "happy"; + else if (score <= 3) petMood = "sad"; + else petMood = "neutral"; + } + + const [waterRows] = await db.query( + "SELECT score FROM general_survey WHERE user_id = ? AND question = ? ORDER BY created_at DESC LIMIT 1", + [userId, "q1"] // q1 = "I drink 8 glasses of water daily." + ); + + if (waterRows.length > 0) { + const waterScore = waterRows[0].score; // 1–10 + if (waterScore <= 6) petThirsty = true; + } + } catch (err) { + console.error("Error loading pet state:", err); + } + + let checkinContext = {}; + try { + checkinContext = await getTodayCheckinContext(userId); + } catch (err) { + console.error("Error building check-in context:", err); + } + + const [planted] = await db.query( + `SELECT pf.spot_index, f.image + FROM planted_flowers pf + JOIN flowers f ON f.id = pf.flower_id + WHERE pf.user_id = ?`, + [userId] + ); + + const [[userRow]] = await db.query( + `SELECT coins, buddy_type, buddy_name, buddy_has_collar, owned_buddy_types + FROM users + WHERE id = ?`, + [userId] + ); + const streak = await getCurrentStreak(userId); + const buddyProfile = normalizeBuddyProfile(userRow); + + let buddyCoins = 0; + if (userRow && typeof userRow.coins !== "undefined") { + buddyCoins = userRow.coins; + } + + if (req.session.user) { + req.session.user.coins = buddyCoins; + } + + res.render("home", { + user: req.session.user, + petMood, + petThirsty, + plantedFlowers: planted, + checkinContext, + streak, + buddyCoins, + buddyProfile, + buddyStatus: req.query.buddyStatus || null, + buddyStatusType: req.query.buddyStatusType || "success", + openBuddyModal: req.query.openBuddyModal === "1", + buddyCosts: BUDDY_COSTS, + buddyOptions: BUDDY_OPTIONS, + }); +}); app.get("/feedback", async (req, res) => { @@ -401,32 +766,39 @@ app.get("/chart", async (req, res) => { }); }); -app.get("/survey", async (req, res) => { +app.get("/checkin", async (req, res) => { if (!req.session.user) return res.redirect("/login"); const section = req.query.section; const userId = req.session.user.id; const today = getLocalDateString(); - let advice = null; - const feedback = req.session.feedback || null; - if (feedback && feedback.question) { - advice = getAdviceFor(feedback.section, feedback.question); - if (advice) { - advice.section = feedback.section; + try { + const [[user]] = await db.query("SELECT coins FROM users WHERE id = ?", [userId]); + const coins = user?.coins || 0; + + let advice = null; + const feedback = req.session.feedback || null; + if (feedback && feedback.question) { + advice = getAdviceFor(feedback.section, feedback.question); + if (advice) { + advice.section = feedback.section; + } } - } - delete req.session.feedback; + delete req.session.feedback; - if (section === "completed") { - const coinsEarned = req.session.coinsEarned || null; - delete req.session.coinsEarned; - return res.render("survey", { section: "completed", userId, coinsEarned, advice }); - } + if (section === "completed") { + const coinsEarned = req.session.coinsEarned || null; + const calcTime = req.session.insightCalcTime || null; - const surveySection = section || "choice"; + delete req.session.coinsEarned; + delete req.session.insightCalcTime; - try { + return res.render("checkin", { section: "completed", userId, coins, coinsEarned, advice, calcTime }); + } + + const checkinSection = section || "choice"; + const questions = checkinData[checkinSection]; const [generalCount] = await db.query( `SELECT COUNT(*) AS count FROM general_survey WHERE user_id = ? AND DATE(created_at) = ?`, [userId, today] @@ -448,7 +820,7 @@ app.get("/survey", async (req, res) => { if (allCompletedToday) { const coinsEarned = req.session.coinsEarned || null; delete req.session.coinsEarned; - return res.render("survey", { section: "completed", userId, coinsEarned, advice }); + return res.render("checkin", { section: "completed", userId, coins, coinsEarned, advice }); } const sectionTableMap = { @@ -457,77 +829,107 @@ app.get("/survey", async (req, res) => { physical: physicalCount }; - if (sectionTableMap[surveySection]?.[0]?.count > 0) { - return res.redirect("/survey-choice"); + if (sectionTableMap[checkinSection]?.[0]?.count > 0) { + return res.redirect("/checkin-choice"); } - res.render("survey", { section: surveySection, userId, advice }); + res.render("checkin", { section: checkinSection, userId, coins, advice, questions: questions }); } catch (err) { - console.error("Survey section check error:", err); - res.status(500).send("Error checking survey status"); + console.error("Check-in section check error:", err); + res.status(500).send("Error checking check-in status"); } }); -app.get("/survey-choice", async (req, res) => { +app.get("/checkin-choice", async (req, res) => { if (!req.session.user) return res.redirect("/login"); const userId = req.session.user.id; const today = getLocalDateString(); - const sections = ["general_survey", "mental_survey", "physical_survey"]; - const progress = { general: false, mental: false, physical: false }; + try { + const [[user]] = await db.query("SELECT coins FROM users WHERE id = ?", [userId]); + const coins = user?.coins || 0; - for (const section of sections) { - const [rows] = await db.query( - `SELECT COUNT(*) AS count FROM ${section} WHERE user_id = ? AND DATE(created_at) = ?`, - [userId, today] - ); - const shortName = section.split("_")[0]; - progress[shortName] = rows[0].count > 0; - } + const sections = ["general_survey", "mental_survey", "physical_survey"]; + const progress = { general: false, mental: false, physical: false }; - const coinsEarned = req.session.coinsEarned || null; - delete req.session.coinsEarned; + for (const section of sections) { + const [rows] = await db.query( + `SELECT COUNT(*) AS count FROM ${section} WHERE user_id = ? AND DATE(created_at) = ?`, + [userId, today] + ); + const shortName = section.split("_")[0]; + progress[shortName] = rows[0].count > 0; + } - const feedback = req.session.feedback || null; - let advice = null; + const coinsEarned = req.session.coinsEarned || null; + delete req.session.coinsEarned; - if (feedback && feedback.question) { - advice = getAdviceFor(feedback.section, feedback.question); - if (advice) { - advice.section = feedback.section; - } - } - delete req.session.feedback; + const feedback = req.session.feedback || null; + let advice = null; - res.render("survey-choice", { userProgress: progress, coinsEarned, advice }); + if (feedback && feedback.question) { + advice = getAdviceFor(feedback.section, feedback.question); + if (advice) { + advice.section = feedback.section; + } + } + delete req.session.feedback; + + res.render("checkin-choice", { userProgress: progress, coinsEarned, coins, advice }); + } catch (err) { + console.error("Checkin-choice error:", err); + res.status(500).send("Error loading check-in choice page"); + } }); -app.post("/submit-survey", async (req, res) => { - const { section, userId, ...responses } = req.body; - const localDate = getLocalDateString(); +app.post("/submit-checkin", async (req, res) => { + const startTime = Date.now(); + const localDate = getLocalDateString(); + + const { section, clientCoinDelta, checkinResults } = req.body; + + if (!req.session.user) { + return res.status(401).send("Not authenticated"); + } + + const userId = req.session.user.id; + + let responses; + try { + if (typeof checkinResults === "string") { + responses = JSON.parse(checkinResults); + } else if (checkinResults && typeof checkinResults === "object") { + responses = checkinResults; + } else { + return res.status(400).send("Missing checkinResults"); + } + } catch (err) { + return res.status(400).send("Invalid check-in data format"); + } try { - const tableMap = { - general: "general_survey", - mental: "mental_survey", - physical: "physical_survey", - }; const table = tableMap[section]; const entries = Object.entries(responses); let total = 0; + for (const [question, score] of entries) { total += parseInt(score); await db.query( - `INSERT INTO ${table} (user_id, question, score, created_at) VALUES (?, ?, ?, ?)`, + `INSERT INTO ${table} (user_id, question, score, created_at) VALUES (?, ?, ?, ?)`, // add created_at and extra ? [userId, question, parseInt(score), localDate] ); } + await db.query( + `INSERT IGNORE INTO daily_checkins (user_id, checkin_date) + VALUES (?, ?)`, + [userId, localDate] + ); const avgScore = Math.round(total / entries.length); const [generalCount] = await db.query( - `SELECT COUNT(*) AS count FROM general_survey WHERE user_id = ? AND DATE(created_at) = ?`, - [userId, localDate] + `SELECT COUNT(*) AS count FROM general_survey WHERE user_id = ? AND DATE(created_at) = ?`, + [userId, localDate] ); const [mentalCount] = await db.query( `SELECT COUNT(*) AS count FROM mental_survey WHERE user_id = ? AND DATE(created_at) = ?`, @@ -543,8 +945,17 @@ app.post("/submit-survey", async (req, res) => { mentalCount[0].count > 0 && physicalCount[0].count > 0; + if (allCompleted) { + await markDayComplete(userId, localDate); + } + const coinsEarned = avgScore >= 8 ? 10 : avgScore >= 5 ? 5 : 2; await db.query("UPDATE users SET coins = coins + ? WHERE id = ?", [coinsEarned, userId]); + // add any client-side accumulated coins (clientCoinDelta) to user's coins in DB + const delta = parseInt(clientCoinDelta, 10) || 0; + if (delta > 0) { + await db.query("UPDATE users SET coins = coins + ? WHERE id = ?", [delta, userId]); + } await db.query("UPDATE users SET survey_count = survey_count + 1 WHERE id = ?", [userId]); req.session.coinsEarned = coinsEarned; @@ -562,6 +973,20 @@ app.post("/submit-survey", async (req, res) => { }; + // refresh user's coins in session if available + try { + const [[userRow]] = await db.query('SELECT coins FROM users WHERE id = ?', [userId]); + if (req.session.user) req.session.user.coins = userRow.coins; + } catch (e) { + console.error('Failed to refresh session coins:', e); + } + + const endTime = Date.now(); + const duration = endTime - startTime; + req.session.insightCalcTime = duration; + + console.log('Wellness insight processing time:', duration, 'ms'); + req.session.save((err) => { if (err) { console.error("Session Save Error:", err); @@ -569,13 +994,31 @@ app.post("/submit-survey", async (req, res) => { } if (allCompleted) { - return res.redirect("/survey?section=completed"); + return res.redirect("/checkin?section=completed"); } - return res.redirect("/survey-choice"); + return res.redirect("/checkin-choice"); }); } catch (err) { console.error("Survey Submit DB Error:", err); - res.status(500).send("Failed to save survey"); + res.status(500).send("Failed to save check-in"); + } +}); + +// Increment user's coins (DB) β€” called from client when user first selects a question +app.post('/api/increment-coin', async (req, res) => { + if (!req.session.user) return res.status(401).json({ error: 'Not authenticated' }); + const userId = req.session.user.id; + try { + await db.query('UPDATE users SET coins = coins + 1 WHERE id = ?', [userId]); + const [[userRow]] = await db.query('SELECT coins FROM users WHERE id = ?', [userId]); + // update session copy + req.session.user.coins = userRow.coins; + req.session.save(() => { + return res.json({ coins: userRow.coins }); + }); + } catch (err) { + console.error('Error incrementing coins:', err); + res.status(500).json({ error: 'Failed to increment coins' }); } }); @@ -583,9 +1026,9 @@ app.get("/games", async (req, res) => { if (!req.session.user) return res.redirect("/login"); const userId = req.session.user.id; - const [[{ survey_count }]] = await db.query("SELECT survey_count FROM users WHERE id = ?", [userId]); + const [[{ checkin_count }]] = await db.query("SELECT survey_count FROM users WHERE id = ?", [userId]); - res.render("games", { totalSurveys: survey_count }); + res.render("games", { totalSurveys: checkin_count }); }); app.get("/shop", async (req, res) => { @@ -628,6 +1071,105 @@ app.post("/plant", async (req, res) => { res.redirect("/home"); }); +app.post("/customize-buddy", async (req, res) => { + if (!req.session.user) return res.redirect("/login"); + + const userId = req.session.user.id; + const { customAction, petType, buddyName } = req.body; + + try { + await ensureBuddyCustomizationColumns(); + + const [[userRow]] = await db.query( + `SELECT coins, buddy_type, buddy_name, buddy_has_collar, owned_buddy_types + FROM users + WHERE id = ?`, + [userId] + ); + + if (!userRow) { + return res.redirect(buildBuddyStatusRedirect("Could not load your buddy profile.", "error", true)); + } + + const buddyProfile = normalizeBuddyProfile(userRow); + + if (customAction === "pet") { + if (!BUDDY_OPTIONS[petType]) { + return res.redirect(buildBuddyStatusRedirect("That buddy option is not available.", "error", true)); + } + + if (buddyProfile.ownedBuddyTypes.includes(petType)) { + await db.query("UPDATE users SET buddy_type = ? WHERE id = ?", [petType, userId]); + return res.redirect(buildBuddyStatusRedirect(`${BUDDY_OPTIONS[petType].label} equipped.`)); + } + + if ((userRow.coins || 0) < BUDDY_COSTS.pet) { + return res.redirect(buildBuddyStatusRedirect("You need 30 coins to unlock that buddy.", "error", true)); + } + + const nextOwned = [...buddyProfile.ownedBuddyTypes, petType]; + await db.query( + `UPDATE users + SET coins = coins - ?, + buddy_type = ?, + owned_buddy_types = ? + WHERE id = ?`, + [BUDDY_COSTS.pet, petType, JSON.stringify(nextOwned), userId] + ); + + req.session.user.coins = (userRow.coins || 0) - BUDDY_COSTS.pet; + return res.redirect(buildBuddyStatusRedirect(`${BUDDY_OPTIONS[petType].label} unlocked and equipped.`)); + } + + if (customAction === "collar") { + if (buddyProfile.buddyHasCollar) { + return res.redirect(buildBuddyStatusRedirect("Your buddy already has a collar.", "error", true)); + } + + if ((userRow.coins || 0) < BUDDY_COSTS.collar) { + return res.redirect(buildBuddyStatusRedirect("You need 20 coins to buy a collar.", "error", true)); + } + + await db.query( + "UPDATE users SET coins = coins - ?, buddy_has_collar = 1 WHERE id = ?", + [BUDDY_COSTS.collar, userId] + ); + + req.session.user.coins = (userRow.coins || 0) - BUDDY_COSTS.collar; + return res.redirect(buildBuddyStatusRedirect("Collar purchased for your buddy.")); + } + + if (customAction === "name") { + const trimmedName = (buddyName || "").trim(); + + if (trimmedName.length < 2 || trimmedName.length > 30) { + return res.redirect(buildBuddyStatusRedirect("Buddy names must be between 2 and 30 characters.", "error", true)); + } + + if (trimmedName === buddyProfile.buddyName) { + return res.redirect(buildBuddyStatusRedirect("Pick a new name to rename your buddy.", "error", true)); + } + + if ((userRow.coins || 0) < BUDDY_COSTS.rename) { + return res.redirect(buildBuddyStatusRedirect("You need 10 coins to rename your buddy.", "error", true)); + } + + await db.query( + "UPDATE users SET coins = coins - ?, buddy_name = ? WHERE id = ?", + [BUDDY_COSTS.rename, trimmedName, userId] + ); + + req.session.user.coins = (userRow.coins || 0) - BUDDY_COSTS.rename; + return res.redirect(buildBuddyStatusRedirect(`${trimmedName} is ready to hang out.`)); + } + + return res.redirect(buildBuddyStatusRedirect("Unknown buddy customization request.", "error", true)); + } catch (err) { + console.error("Error customizing buddy:", err); + return res.redirect(buildBuddyStatusRedirect("Buddy customization failed. Please try again.", "error", true)); + } +}); + // Run every night at 1 AM cron.schedule("0 1 * * *", async () => { try { @@ -635,9 +1177,9 @@ cron.schedule("0 1 * * *", async () => { await db.execute("DELETE FROM mental_survey WHERE created_at < NOW() - INTERVAL 3 MONTH"); await db.execute("DELETE FROM physical_survey WHERE created_at < NOW() - INTERVAL 3 MONTH"); - console.log("Old surveys cleaned up successfully."); + console.log("Old check-ins cleaned up successfully."); } catch (error) { - console.error("Error cleaning up old surveys:", error.message); + console.error("Error cleaning up old check-ins:", error.message); } }); diff --git a/login.js b/login.js index 18a607f..be0bd9b 100644 --- a/login.js +++ b/login.js @@ -1,8 +1,12 @@ // login.js - handles POST login logic and session setup import bcrypt from "bcrypt"; import db from "./db.js"; +import { ensureEmailVerificationColumns } from "./verification.js"; const INVALID_LOGIN_MSG = "Invalid email or password. Please try again."; +// Valid bcrypt hash for a throwaway string used to normalize timing on missing users. +const DUMMY_BCRYPT_HASH = + "$2b$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy"; export async function handleLogin(req, res) { const { email, password } = req.body; @@ -14,6 +18,7 @@ export async function handleLogin(req, res) { let conn; try { + await ensureEmailVerificationColumns(); conn = await db.getConnection(); // Query the database for the user @@ -24,7 +29,7 @@ export async function handleLogin(req, res) { if (rows.length === 0) { // Fake hash check for security - await bcrypt.compare(password, "$2b$10$invalidsalt12345678901234567890"); + await bcrypt.compare(password, DUMMY_BCRYPT_HASH); return res.render("login", { error: INVALID_LOGIN_MSG }); } @@ -41,6 +46,14 @@ export async function handleLogin(req, res) { return res.render("login", { error: INVALID_LOGIN_MSG }); } + if (!user.email_verified) { + return res.render("login", { + error: "Please verify your email before logging in.", + message: null, + verificationEmail: normalizedEmail, + }); + } + // Store user session req.session.user = { id: user.id, @@ -50,7 +63,14 @@ export async function handleLogin(req, res) { country: user.country, }; - res.redirect("/home"); + req.session.save((saveErr) => { + if (saveErr) { + console.error("Error saving login session:", saveErr); + return res.status(500).send("Internal Server Error"); + } + + return res.redirect("/home"); + }); } catch (err) { console.error("Error during login:", err); res.status(500).send("Internal Server Error"); diff --git a/package-lock.json b/package-lock.json index ad26c45..2b2820b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,10 @@ "@knocklabs/node": "^0.6.19", "bcrypt": "^5.1.1", "cjs": "^0.0.11", - "dotenv": "^16.5.0", + "dotenv": "^16.6.1", "ejs": "^3.1.10", "express": "^4.21.2", + "express-mysql-session": "^3.0.3", "express-session": "^1.18.1", "fs": "^0.0.1-security", "helmet": "^8.1.0", @@ -22,13 +23,15 @@ "node-cron": "^3.0.3", "node-schedule": "^2.1.1", "nodemon": "^3.1.9", - "openai": "^6.8.1" + "openai": "^6.8.1", + "pg": "^8.18.0" } }, "node_modules/@knocklabs/node": { "version": "0.6.19", "resolved": "https://registry.npmjs.org/@knocklabs/node/-/node-0.6.19.tgz", "integrity": "sha512-hmu0g7BJiHz20ApkRnesM0SeOugvYhwHilwDMDk1TmpdQ2Z7jmWrYRsmR7L78EZ5zvEBR6bExICqmW9bz4oXQw==", + "license": "MIT", "dependencies": { "jose": "^5.2.0" }, @@ -40,6 +43,7 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", "dependencies": { "detect-libc": "^2.0.0", "https-proxy-agent": "^5.0.0", @@ -58,12 +62,14 @@ "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC" }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -76,6 +82,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", "dependencies": { "debug": "4" }, @@ -84,9 +91,10 @@ } }, "node_modules/agent-base/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==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -102,34 +110,23 @@ "node_modules/agent-base/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==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "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==", + "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==", - "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==", + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -139,15 +136,17 @@ } }, "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "license": "ISC" }, "node_modules/are-we-there-yet": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", "deprecated": "This package is no longer supported.", + "license": "ISC", "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" @@ -159,17 +158,20 @@ "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" }, "node_modules/aws-ssl-profiles": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", "engines": { "node": ">= 6.0.0" } @@ -177,13 +179,15 @@ "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==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" }, "node_modules/bcrypt": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", "hasInstallScript": true, + "license": "MIT", "dependencies": { "@mapbox/node-pre-gyp": "^1.0.11", "node-addon-api": "^5.0.0" @@ -196,6 +200,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", "engines": { "node": ">=8" }, @@ -204,22 +209,23 @@ } }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -227,18 +233,19 @@ } }, "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==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "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==", + "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -250,6 +257,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -258,6 +266,7 @@ "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==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -270,6 +279,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -281,25 +291,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "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/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -323,6 +319,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", "engines": { "node": ">=10" } @@ -331,30 +328,16 @@ "version": "0.0.11", "resolved": "https://registry.npmjs.org/cjs/-/cjs-0.0.11.tgz", "integrity": "sha512-aLndk8BnpIOy/ZxmLGCNTSFoLm0+OyZDtxNCV6jUBHBkLICanUAlkIGtnaQrCBMYTebOmWHNg8+vxtaYZ8LSfA==", + "license": "BSD", "dependencies": { "sync-channel": "*" } }, - "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==", - "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==" - }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", "bin": { "color-support": "bin.js" } @@ -362,17 +345,20 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC" }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" }, @@ -384,27 +370,31 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" }, "node_modules/cron-parser": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "license": "MIT", "dependencies": { "luxon": "^3.2.1" }, @@ -416,6 +406,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -423,12 +414,14 @@ "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" }, "node_modules/denque": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", "engines": { "node": ">=0.10" } @@ -437,6 +430,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -445,23 +439,26 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", "engines": { "node": ">=8" } }, "node_modules/dotenv": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", - "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", "engines": { "node": ">=12" }, @@ -473,6 +470,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -485,12 +483,14 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "license": "Apache-2.0", "dependencies": { "jake": "^10.8.5" }, @@ -504,12 +504,14 @@ "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==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -518,6 +520,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -526,6 +529,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -534,6 +538,7 @@ "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==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -544,49 +549,52 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -599,68 +607,107 @@ "url": "https://opencollective.com/express" } }, - "node_modules/express-session": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", - "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", + "node_modules/express-mysql-session": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/express-mysql-session/-/express-mysql-session-3.0.3.tgz", + "integrity": "sha512-sEYrzFrOs3er+Ie/uk1dt93qz4AQ9SU1mpJJ0HPs0MJ4t4hE9AcDRNq0sZQUwy2F/SbXusBt1E5+FY6KzSqXNg==", + "license": "MIT", "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.7", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-headers": "~1.0.2", - "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", - "uid-safe": "~2.1.5" + "debug": "4.3.4", + "mysql2": "3.10.2" + } + }, + "node_modules/express-mysql-session/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" }, "engines": { - "node": ">= 0.8.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/express-session/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "node_modules/express-mysql-session/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==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=0.10.0" } }, - "node_modules/express-session/node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" + "node_modules/express-mysql-session/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "node_modules/express-mysql-session/node_modules/mysql2": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.10.2.tgz", + "integrity": "sha512-KCXPEvAkO0RcHPr362O5N8tFY2fXvbjfkPvRY/wGumh4EOemo9Hm5FjQZqv/pCmrnuxGu5OxnSENG0gTXqKMgQ==", + "license": "MIT", "dependencies": { - "minimatch": "^5.0.1" + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru-cache": "^8.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" } }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" + "node_modules/express-session": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.19.0.tgz", + "integrity": "sha512-0csaMkGq+vaiZTmSMMGkfdCOabYv192VbytFypcvI0MANrp+4i/7yEkJ0sbAEhycQjntaKGzYfjfXQyVb7BHMA==", + "license": "MIT", + "dependencies": { + "cookie": "~0.7.2", + "cookie-signature": "~1.0.7", + "debug": "~2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.1.0", + "parseurl": "~1.3.3", + "safe-buffer": "~5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "license": "Apache-2.0", "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" + "minimatch": "^5.0.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==", + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -669,16 +716,17 @@ } }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "statuses": "2.0.1", + "statuses": "~2.0.2", "unpipe": "~1.0.0" }, "engines": { @@ -689,6 +737,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -697,6 +746,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -704,12 +754,14 @@ "node_modules/fs": { "version": "0.0.1-security", "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", - "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==" + "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==", + "license": "ISC" }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -721,6 +773,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -731,13 +784,15 @@ "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==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "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==", "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -750,6 +805,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -759,6 +815,7 @@ "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", "deprecated": "This package is no longer supported.", + "license": "ISC", "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.2", @@ -778,6 +835,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", "dependencies": { "is-property": "^1.0.2" } @@ -786,6 +844,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -809,6 +868,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -821,7 +881,8 @@ "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", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -841,6 +902,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -848,10 +910,33 @@ "node": ">= 6" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -860,17 +945,19 @@ } }, "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==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=4" } }, "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==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -881,12 +968,14 @@ "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC" }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -898,29 +987,36 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", + "license": "MIT", "engines": { "node": ">=18.0.0" } }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "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==", + "license": "MIT", "dependencies": { "agent-base": "6", "debug": "4" @@ -930,9 +1026,10 @@ } }, "node_modules/https-proxy-agent/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==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -948,12 +1045,14 @@ "node_modules/https-proxy-agent/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==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -964,13 +1063,15 @@ "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "license": "ISC" }, "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.", + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -979,12 +1080,14 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", "engines": { "node": ">= 0.10" } @@ -993,6 +1096,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -1004,6 +1108,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -1012,6 +1117,7 @@ "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==", + "license": "MIT", "engines": { "node": ">=8" } @@ -1020,6 +1126,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -1031,6 +1138,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -1038,17 +1146,18 @@ "node_modules/is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" }, "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "license": "Apache-2.0", "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", + "async": "^3.2.6", "filelist": "^1.0.4", - "minimatch": "^3.1.2" + "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" @@ -1061,6 +1170,7 @@ "version": "5.10.0", "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } @@ -1068,25 +1178,29 @@ "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" }, "node_modules/long-timeout": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", - "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==" + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==", + "license": "MIT" }, "node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", + "license": "ISC", "engines": { - "node": ">=12" + "node": ">=16.14" } }, "node_modules/lru.min": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz", - "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", + "license": "MIT", "engines": { "bun": ">=1.0.0", "deno": ">=1.30.0", @@ -1098,9 +1212,10 @@ } }, "node_modules/luxon": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz", - "integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "license": "MIT", "engines": { "node": ">=12" } @@ -1109,6 +1224,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", "dependencies": { "semver": "^6.0.0" }, @@ -1123,6 +1239,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -1131,6 +1248,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -1139,6 +1257,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1147,6 +1266,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -1155,6 +1275,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1163,6 +1284,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -1174,6 +1296,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1182,6 +1305,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -1190,20 +1314,22 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=10" } }, "node_modules/minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", "engines": { "node": ">=8" } @@ -1212,6 +1338,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -1224,6 +1351,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -1235,6 +1363,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" }, @@ -1245,53 +1374,62 @@ "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/mysql2": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.0.tgz", - "integrity": "sha512-8eMhmG6gt/hRkU1G+8KlGOdQi2w+CgtNoD1ksXZq9gQfkfDsX4LHaBwTe1SY0Imx//t2iZA03DFnyYKPinxSRw==", + "version": "3.16.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.16.3.tgz", + "integrity": "sha512-+3XhQEt4FEFuvGV0JjIDj4eP2OT/oIj/54dYvqhblnSzlfcxVOuj+cd15Xz6hsG4HU1a+A5+BA9gm0618C4z7A==", + "license": "MIT", "dependencies": { - "aws-ssl-profiles": "^1.1.1", + "aws-ssl-profiles": "^1.1.2", "denque": "^2.1.0", "generate-function": "^2.3.1", - "iconv-lite": "^0.6.3", - "long": "^5.2.1", - "lru.min": "^1.0.0", - "named-placeholders": "^1.1.3", + "iconv-lite": "^0.7.2", + "long": "^5.3.2", + "lru.min": "^1.1.3", + "named-placeholders": "^1.1.6", "seq-queue": "^0.0.5", - "sqlstring": "^2.3.2" + "sqlstring": "^2.3.3" }, "engines": { "node": ">= 8.0" } }, "node_modules/mysql2/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==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/named-placeholders": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", - "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", + "license": "MIT", "dependencies": { - "lru-cache": "^7.14.1" + "lru.min": "^1.1.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=8.0.0" } }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1299,12 +1437,14 @@ "node_modules/node-addon-api": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "license": "MIT" }, "node_modules/node-cron": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "license": "ISC", "dependencies": { "uuid": "8.3.2" }, @@ -1316,6 +1456,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -1335,6 +1476,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", + "license": "MIT", "dependencies": { "cron-parser": "^4.2.0", "long-timeout": "0.1.1", @@ -1345,9 +1487,10 @@ } }, "node_modules/nodemon": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", - "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "license": "MIT", "dependencies": { "chokidar": "^3.5.2", "debug": "^4", @@ -1371,10 +1514,21 @@ "url": "https://opencollective.com/nodemon" } }, + "node_modules/nodemon/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/nodemon/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==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -1387,34 +1541,29 @@ } } }, - "node_modules/nodemon/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "node_modules/nodemon/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">=4" + "node": "*" } }, "node_modules/nodemon/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==" - }, - "node_modules/nodemon/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", "dependencies": { "abbrev": "1" }, @@ -1429,6 +1578,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -1438,6 +1588,7 @@ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", "deprecated": "This package is no longer supported.", + "license": "ISC", "dependencies": { "are-we-there-yet": "^2.0.0", "console-control-strings": "^1.1.0", @@ -1449,6 +1600,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -1457,6 +1609,7 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -1468,6 +1621,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -1476,9 +1630,10 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1487,14 +1642,15 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", "dependencies": { "wrappy": "1" } }, "node_modules/openai": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.8.1.tgz", - "integrity": "sha512-ACifslrVgf+maMz9vqwMP4+v9qvx5Yzssydizks8n+YUJ6YwUoxj51sKRQ8HYMfR6wgKLSIlaI108ZwCk+8yig==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.21.0.tgz", + "integrity": "sha512-26dQFi76dB8IiN/WKGQOV+yKKTTlRCxQjoi2WLt0kMcH8pvxVyvfdBDkld5GTl7W1qvBpwVOtFcsqktj3fBRpA==", "license": "Apache-2.0", "bin": { "openai": "bin/cli" @@ -1516,6 +1672,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1524,6 +1681,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -1531,12 +1689,109 @@ "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/pg": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", + "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.11.0", + "pg-pool": "^3.11.0", + "pg-protocol": "^1.11.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.3.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", + "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.11.0.tgz", + "integrity": "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.11.0.tgz", + "integrity": "sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.11.0.tgz", + "integrity": "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -1544,10 +1799,50 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -1559,14 +1854,16 @@ "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "license": "MIT" }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -1579,6 +1876,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1587,19 +1885,21 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" @@ -1609,6 +1909,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -1622,6 +1923,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", "dependencies": { "picomatch": "^2.2.1" }, @@ -1634,6 +1936,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -1661,17 +1964,20 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -1680,40 +1986,34 @@ } }, "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "2.0.1" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/send/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==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/seq-queue": { "version": "0.0.5", @@ -1721,14 +2021,15 @@ "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.19.0" + "send": "~0.19.1" }, "engines": { "node": ">= 0.8.0" @@ -1737,17 +2038,20 @@ "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -1766,6 +2070,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" @@ -1781,6 +2086,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -1798,6 +2104,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -1815,12 +2122,14 @@ "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==" + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "license": "MIT", "dependencies": { "semver": "^7.5.3" }, @@ -1831,20 +2140,32 @@ "node_modules/sorted-array-functions": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", - "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==" + "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==", + "license": "MIT" + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } }, "node_modules/sqlstring": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1853,6 +2174,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } @@ -1861,6 +2183,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -1874,6 +2197,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -1882,25 +2206,29 @@ } }, "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==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "has-flag": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=4" } }, "node_modules/sync-channel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/sync-channel/-/sync-channel-0.0.6.tgz", - "integrity": "sha512-rjHHukZeQW3hGgwMuOkrITv1e87nxuAKRGgwPNtCmS3Az+YdO826hBy1IDjRsTXKGc2WDWUaDU5Zx8uodXWwgg==" + "integrity": "sha512-rjHHukZeQW3hGgwMuOkrITv1e87nxuAKRGgwPNtCmS3Az+YdO826hBy1IDjRsTXKGc2WDWUaDU5Zx8uodXWwgg==", + "license": "BSD" }, "node_modules/tar": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -1917,6 +2245,7 @@ "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==", + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -1928,6 +2257,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } @@ -1936,6 +2266,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "license": "ISC", "bin": { "nodetouch": "bin/nodetouch.js" } @@ -1943,12 +2274,14 @@ "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -1961,6 +2294,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "license": "MIT", "dependencies": { "random-bytes": "~1.0.0" }, @@ -1971,12 +2305,14 @@ "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "license": "MIT" }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1984,12 +2320,14 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", "engines": { "node": ">= 0.4.0" } @@ -1998,6 +2336,7 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } @@ -2006,6 +2345,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -2013,12 +2353,14 @@ "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -2028,6 +2370,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } @@ -2035,12 +2378,23 @@ "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==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" } } } diff --git a/package.json b/package.json index 54f55e9..9c94985 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,9 @@ "type": "module", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "reminders": "node sendReminders.js" + "test": "node --test --experimental-test-isolation=none", + "reminders": "node sendReminders.js", + "start": "node index.js" }, "keywords": [], "author": "", @@ -15,9 +16,10 @@ "@knocklabs/node": "^0.6.19", "bcrypt": "^5.1.1", "cjs": "^0.0.11", - "dotenv": "^16.5.0", + "dotenv": "^16.6.1", "ejs": "^3.1.10", "express": "^4.21.2", + "express-mysql-session": "^3.0.3", "express-session": "^1.18.1", "fs": "^0.0.1-security", "helmet": "^8.1.0", @@ -25,6 +27,7 @@ "node-cron": "^3.0.3", "node-schedule": "^2.1.1", "nodemon": "^3.1.9", - "openai": "^6.8.1" + "openai": "^6.8.1", + "pg": "^8.18.0" } } diff --git a/public/favicon.ico b/public/favicon.ico index 5e4f926..863b662 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/images/MeBalanced.png b/public/images/MeBalanced.png new file mode 100644 index 0000000..7ee0c60 Binary files /dev/null and b/public/images/MeBalanced.png differ diff --git a/public/images/MeBalanced8.png b/public/images/MeBalanced8.png new file mode 100644 index 0000000..98b6e1b Binary files /dev/null and b/public/images/MeBalanced8.png differ diff --git a/public/images/MeBalanced9.ico b/public/images/MeBalanced9.ico new file mode 100644 index 0000000..863b662 Binary files /dev/null and b/public/images/MeBalanced9.ico differ diff --git a/public/images/MeBalanced9.png b/public/images/MeBalanced9.png new file mode 100644 index 0000000..05c9361 Binary files /dev/null and b/public/images/MeBalanced9.png differ diff --git a/public/images/favicon.ico b/public/images/favicon.ico new file mode 100644 index 0000000..5e4f926 Binary files /dev/null and b/public/images/favicon.ico differ diff --git a/public/images/robot-emoji.png b/public/images/robot-emoji.png new file mode 100644 index 0000000..4d1474d Binary files /dev/null and b/public/images/robot-emoji.png differ diff --git a/public/images/sprites/cat_collar.png b/public/images/sprites/cat_collar.png new file mode 100644 index 0000000..38bc11e Binary files /dev/null and b/public/images/sprites/cat_collar.png differ diff --git a/public/images/sprites/cat_neutral.png b/public/images/sprites/cat_neutral.png new file mode 100644 index 0000000..2eef3f3 Binary files /dev/null and b/public/images/sprites/cat_neutral.png differ diff --git a/public/images/sprites/cat_sad.png b/public/images/sprites/cat_sad.png new file mode 100644 index 0000000..e280f04 Binary files /dev/null and b/public/images/sprites/cat_sad.png differ diff --git a/public/images/sprites/dog_collar.png b/public/images/sprites/dog_collar.png new file mode 100644 index 0000000..9b96cff Binary files /dev/null and b/public/images/sprites/dog_collar.png differ diff --git a/public/images/sprites/dog_happy.png b/public/images/sprites/dog_happy.png new file mode 100644 index 0000000..e0f7a05 Binary files /dev/null and b/public/images/sprites/dog_happy.png differ diff --git a/public/images/sprites/dog_neutral.png b/public/images/sprites/dog_neutral.png new file mode 100644 index 0000000..11e587b Binary files /dev/null and b/public/images/sprites/dog_neutral.png differ diff --git a/public/images/sprites/dog_sad.png b/public/images/sprites/dog_sad.png new file mode 100644 index 0000000..e7ab00c Binary files /dev/null and b/public/images/sprites/dog_sad.png differ diff --git a/public/images/sprites/hungry.png b/public/images/sprites/hungry.png new file mode 100644 index 0000000..c95978b Binary files /dev/null and b/public/images/sprites/hungry.png differ diff --git a/public/images/sprites/penguin_collar.png b/public/images/sprites/penguin_collar.png new file mode 100644 index 0000000..e0b1935 Binary files /dev/null and b/public/images/sprites/penguin_collar.png differ diff --git a/public/images/sprites/penguin_neutral.png b/public/images/sprites/penguin_neutral.png new file mode 100644 index 0000000..01f2052 Binary files /dev/null and b/public/images/sprites/penguin_neutral.png differ diff --git a/public/images/sprites/penguin_sad.png b/public/images/sprites/penguin_sad.png new file mode 100644 index 0000000..41fac32 Binary files /dev/null and b/public/images/sprites/penguin_sad.png differ diff --git a/public/images/sprites/thirsty.png b/public/images/sprites/thirsty.png new file mode 100644 index 0000000..157c372 Binary files /dev/null and b/public/images/sprites/thirsty.png differ diff --git a/public/sounds/coin_collect.mp3 b/public/sounds/coin_collect.mp3 new file mode 100644 index 0000000..1e66bfe Binary files /dev/null and b/public/sounds/coin_collect.mp3 differ diff --git a/public/styles/chatbot.css b/public/styles/chatbot.css new file mode 100644 index 0000000..52e5071 --- /dev/null +++ b/public/styles/chatbot.css @@ -0,0 +1,156 @@ +html, body { + /*height: 100%;*/ + margin: 0; + background: linear-gradient( + 135deg, + #B8C6FF 0%, + #FFD6C9 100% + ); + /* remove this: overflow: hidden; */ + font-family: Arial, sans-serif; +} + +.chat-page { + /* instead of strict height with no room for the navbar */ + /* height: calc(100vh - 60px); */ + + min-height: calc(100vh - 60px); /* leave room for navbar */ + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; /* start at top instead of dead center */ + gap: 20px; + padding-top: 80px; /* pushes chat below navbar */ + padding-bottom: 20px; +} + +.messages-box { + width: 80%; + max-width: 900px; + height: 380px; + background: rgba(255,255, 255, 0.35); + border: 1px solid #ddd; + border-radius: 6px; + padding: 15px 20px; + box-sizing: border-box; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 6px; + + animation: page-load 1s ease-out forwards; +} + +.input-row { + width: 80%; + max-width: 900px; + display: flex; + gap: 8px; + animation: page-load 1s ease-out forwards; +} + +.input-row input { + flex: 1; + border-radius: 10px; + border: white; + padding: 6px 8px; +} + +#messages { + width: 80%; + max-width: 900px; + background: rgba(255,255, 255, 0.5); + border: 1px solid #ddd; + border-radius: 6px; + padding: 15px 20px; + box-sizing: border-box; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 6px; +} + +.loading { + font-size: 0.8rem; + color: #555; + visibility: hidden; + margin-top: auto; +} + +.dots::after { + content: ""; + animation: dots 1.2s steps(4, end) infinite; +} + +@keyframes dots { + 0%, 20% { content: ""; } + 40% { content: "."; } + 60% { content: ".."; } + 80%, 100% { content: "..."; } +} + +@keyframes page-load { + from { + opacity: 0; + } + to{ + opacity: 1; + } +} + +#send{ + border-radius: 12px; + background-color: white; + color: black; + animation: page-load 1s ease-out forwards; +} + +.two-my-ai-message{ + display: flex; + align-items: start; +} + +.two-user-message{ + display: flex; + align-self: end; + background-color: #F4A261; + padding: 8px 12px; +} + +.two-ai-message-content{ + display: flex; + background-color: #6FAF9B; + padding: 8px 12px; +} + +.ai-icon{ + text-align: left; + padding-right: 15px; + width: 3vh; + height: 3vh; + +} + +.chatbot-user-message { + align-self: flex-end; + padding: 8px 12px; + border-radius: 12px; + max-width: 80%; + opacity: 100%; +} + +.chatbot-ai-message { + display: flex; + align-items: start; +} + +h1 { + font-weight: 600; + margin-bottom: 4px; +} + +h3 { + font-weight: 600; + margin-top: 2px; + margin-bottom: 6px; +} \ No newline at end of file diff --git a/public/styles/checkin.css b/public/styles/checkin.css new file mode 100644 index 0000000..1146005 --- /dev/null +++ b/public/styles/checkin.css @@ -0,0 +1,340 @@ +html, body { + height: 100%; + margin: 0; + background: linear-gradient( + 135deg, + #B8C6FF 0%, + #FFD6C9 100% + ); + /* remove this: overflow: hidden; */ + font-family: Arial, sans-serif; +} + +html { + overflow-y: scroll; +} + +/* General Styles */ +body { + font-family: Arial, sans-serif; + background-size: cover; + color: #333; +} + +.coins-display { + padding: 10px; + border-radius: 6px; + display: flex; + align-items: center; + justify-content: center; + margin-left: auto; + margin-right: 0; + width: fit-content; + height: 10vh; +} + +.coins-display p { + font-size: 1.3em; + font-weight: bold; + color: #333; + margin: 0; + line-height: 1; + background-color: rgba(255, 255, 255, 0.5); + border-radius: 6px; + padding: 20px; +} + + + +.checkin-choice-container{ + background: rgba(255, 255, 255, 0.8); + padding: 20px; + margin: 50px auto; + max-width: 600px; + border-radius: 8px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); + text-align: center; +} +.checkin-choice-container h1 { + font-size: 2em; + margin-bottom: 10px; + clear: both; +} + +.checkin-choice-container p { + margin-bottom: 20px; + font-size: 1.2em; +} + +.progress { + height: 20px !important; + background-color: #e9ecef; + border-radius: 10px; + margin-bottom: 20px; + display: flex;; +} + +#progressBar { + height: 100%; + background-color: #ffaa00 !important; + transition: width 0.4s ease; +} + + + +.row.g-3 { + display: flex !important; + flex-direction: row !important; + flex-wrap: nowrap !important; + justify-content: space-around; + align-items: center; +} + +/* .circular-card { + width: 100px; + height: 100px; + aspect-ratio: 1/1; + border-radius: 50% !important; + border: 3px solid #ddd; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + flex-shrink: 0; + margin: 0 auto; + padding: 10px; + text-align: center; + box-sizing: border-box; +} */ + +.circular-card { + width: 110px; + height: 70px; + padding: 10px; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + box-sizing: border-box; + border-radius: 10px; +} + +.circular-card div { + width: 100%; + word-wrap: break-word; + font-style: italic; +} + +.circular-card:hover{ + transform: translateY(-3px); + box-shadow: 0px 10px 15px rgba(0, 0, 0, 0.1); + background-color: #F0F0F0; +} + +.hidden-radio{ + display: none; +} + +.step { + display: none; +} + +.step.active{ + display: block; +} + +.circular-card:has(input[value="1"]){ + border-color: #8c0438; background: #f6ccdc; +} + +.circular-card:has(input[value="1"]:checked){ + background: #8c0438; border-color: #000000; color: white;; +} + +.circular-card:has(input[value="2"]){ + border-color: #d65a6f; background: #f0d8dc; +} + +.circular-card:has(input[value="2"]:checked){ + background: #d65a6f; border-color: #000000; color: white; +} + +.circular-card:has(input[value="3"]){ + border-color: #f7c277; background: #f4e1c6; + } + +.circular-card:has(input[value="3"]:checked){ + background: #f7c277; border-color: #000000; color: white; +} + +.circular-card:has(input[value="4"]){ + border-color: #7aa613; background: #e6f0cb; +} + +.circular-card:has(input[value="4"]:checked){ + background: #7aa613; border-color: #000000; color: white; +} + +.circular-card:has(input[value="5"]){ + border-color: #21570e; background: #aecba4; +} + +.circular-card:has(input[value="5"]:checked){ + background: #21570e; border-color: #000000; color: white; +} + +#iconContainer{ + background-color: #ffffff99; +} + +#nextButton{ + background-color: #333; + color: #ffd700; + border-color: #333; +} + +/* Form Selection Styling*/ +.checkin-container{ + background: rgba(255, 255, 255, 0.8); + padding: 20px; + margin: 45px auto; + max-width: 75%; + max-height: 100%; + border-radius: 8px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); + text-align: center; +} + +.checkin-card { + background-color: rgba(255, 255, 255, 0.8); + cursor: pointer; + transition: all 0.2s ease; +} + +.checkin-card:hover{ + transform: translateY(-3px); + box-shadow: 0 10px 15px rgba(0, 0, 0, 0.2); +} + +.scale-labels { + grid-column: 1 / -1; + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 12px; +} + +.scale-labels span { + text-align: center; + font-size: 0.9em; +} +.response-buttons input[type="radio"] { + display: none; +} + +/* screen readable text */ +.sr-only { + position: absolute !important; + width: 1px !important; + height: 1px !important; + padding: 0 !important; + margin: -1px !important; + overflow: hidden !important; + clip: rect(0, 0, 0, 0) !important; + white-space: nowrap !important; + border: 0 !important; +} + +/* Coin emoji bounce animation */ +#coin-emoji { + display: inline-block; + margin-left: 6px; + transform-origin: center bottom; + will-change: transform; +} + +#coin-emoji.bounce { + animation: coin-bounce 0.7s cubic-bezier(0.25, 0.8, 0.25, 1); +} + +@keyframes coin-bounce { + 0% { transform: translateY(0px) scale(1); } + 30% { transform: translateY(-10px) scale(1.30); } + 50% { transform: translateY(0px) scale(0.90); } + 65% { transform: translateY(-4px) scale(1.10); } + 100% { transform: translateY(0px) scale(1); } +} + +/* Button Styling */ +.btn { + background: #333; + color: #ffd700; + padding: 10px 15px; + border: none; + border-radius: 4px; + cursor: pointer; + margin: 10px; + display: inline-block; + text-decoration: none; + font-size: 1em; +} + +.btn:hover { + background: #555; +} + +.reward-message { + background-color: #fff3cd; + border: 1px solid #ffeeba; + color: #856404; + padding: 10px; + border-radius: 8px; + margin-bottom: 15px; + text-align: center; +} + +@media screen and (max-width: 970px){ + #iconResponse { + flex-direction: column; + align-items: center; + } + + #iconResponse > * { + width: 50%; + max-width: 100%; + flex: 0 0 100%; + } + + .survey-container { + max-height: 970px; + } + + .coins-display p { + padding: 10px; + font-size: 1rem; + } + + #surveyTitle { + font-size: 1.2rem; + } + + #progressText { + font-size: 0.75rem; + } + + #questionText { + font-size: 1rem; + } + + .circular-card { + width: 90px; + height: 50px; + } + + .circular-card div { + font-size: 10px; + } + + #backButton, #nextButton { + font-size: 0.75rem; + } +} \ No newline at end of file diff --git a/public/styles/home.css b/public/styles/home.css index 96690d9..f3afe6f 100644 --- a/public/styles/home.css +++ b/public/styles/home.css @@ -1,7 +1,21 @@ +html, body { + /*height: 100%;*/ + margin: 0; + background: linear-gradient( + 135deg, + #B8C6FF 0%, + #FFD6C9 100% + ); + /* remove this: overflow: hidden; */ + font-family: Arial, sans-serif; +} + +html { + overflow-y: scroll; +} /* General Styles */ body { font-family: Arial, sans-serif; - background: url('/images/background.jpg') no-repeat center center fixed; background-size: cover; color: #333; text-align: center; @@ -17,11 +31,17 @@ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); } + .home-container h1 { font-size: 2em; margin-bottom: 10px; - color: #ffd700; - } + color: #f5b580; + text-shadow: + -1px -1px 0 #3A506B, + 1px -1px 0 #3A506B, + -1px 1px 0 #3A506B, + 1px 1px 0 #3A506B; + } .home-container p { font-size: 1.2em; @@ -32,7 +52,7 @@ margin-top: 40px; padding: 20px; text-align: center; - background-color: #f0f4f8; + background-color: rgba(255, 255, 255, .35); color: #333; font-size: 14px; } @@ -142,9 +162,10 @@ font-style: italic; } - /* Navigation Bar */ + /* Navigation Bar .navbar { display: flex; + flex-wrap: wrap; align-items: center; justify-content: space-between; background-color: #222; @@ -173,7 +194,7 @@ .navbar a:hover { background-color: #444; border-radius: 5px; - } + } */ /* Select */ select { @@ -522,6 +543,41 @@ text-align: left; animation: fadeIn 0.4s ease; } + +.resource-box { + background: rgba(255, 255, 255, 0.5); + padding: 20px; + margin: 20px auto; + max-width: 700px; + border-radius: 8px; + box-shadow: 0 4px 10px rgba(0,0,0,0.15); + text-align: center; + animation: fadeIn 0.5 ease; +} + +.resource-box h2 { + color: #222; + margin-bottom: 20px; + font-size: 2rem; + padding-bottom: 5px; +} + +.resource-box h3 { + color: #222; + margin-bottom: 15px; + font-size: 1.5rem; + padding-bottom: 5px; +} + +.resource-box a { + font-size: 1rem; +} + +.resource-box a:hover{ + font-size: 1.1rem; + transition: all 1.5 ease-out; +} + .advice-box h2 { color: #222; margin-bottom: 15px; @@ -565,3 +621,469 @@ display: none; animation: fadeIn 0.3s ease; } + +#pet-container { + text-align: center; + margin-top: 20px; +} + +#pet-wrapper { + position: relative; + display: inline-block; +} + +#pet-sprite { + width: 128px; + image-rendering: pixelated; +} + +#pet-collar { + position: absolute; + left: 50%; + bottom: 32px; + width: 72px; + transform: translateX(-50%); + image-rendering: pixelated; + pointer-events: none; + z-index: 2; +} + +#pet-collar.pet-collar-dog { + bottom: 20px; + width: 68px; +} + +#pet-collar.pet-collar-cat { + bottom: 50px; + width: 60px; +} + +#pet-collar.pet-collar-penguin { + bottom: 45px; + width: 80px; +} + +#pet-collar.hidden { + display: none; +} + +#pet-collar.visible { + display: block; +} + +#thirsty-icon { + position: absolute; + right: -10px; + top: -10px; + width: 32px; + image-rendering: pixelated; + z-index: 3; +} + +#thirsty-icon.hidden { + display: none; +} + +#thirsty-icon.visible { + display: block; +} + +.home-main-row { + display: flex; + gap: 24px; + align-items: flex-start; + margin-top: 24px; +} + +#pet-container { + flex: 0 0 260px; + text-align: center; +} + +#pet-chat-container { + flex: 1; +} + +.pet-messages-box { + width: 100%; + max-width: 600px; + height: 260px; + background: (0,0,0,.3); + /*border: 1px solid #ddd;*/ + border-radius: 6px; + padding: 12px 16px; + box-sizing: border-box; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 6px; + text-align: left; +} + +.user-message { + align-self: flex-end; + background-color: #F4A261; + padding: 8px 12px; + border-radius: 12px; + max-width: 80%; + opacity: 100%; +} + +.ai-message { + align-self: flex-start; + padding: 8px 12px; + border-radius: 12px; + max-width: 90%; + opacity: 100%; +} + + +.pet-input-row { + margin-top: 10px; + width: 100%; + max-width: 600px; + display: flex; + gap: 8px; +} + +.pet-input-row input { + flex: 1; + padding: 6px 8px; +} + +.pet-loading { + font-size: 0.8rem; + color: #555; + visibility: hidden; + margin-top: auto; + opacity: 100%; +} + +.dots::after { + content: ""; + animation: dots 1.2s steps(4, end) infinite; +} + +@keyframes dots { + 0%, 20% { content: ""; } + 40% { content: "."; } + 60% { content: ".."; } + 80%, 100% { content: "..."; } +} + +#pet-chat-container h2 { + text-align: center; + width: 100%; + margin-bottom: 10px; +} + +.pet-messages-box { + width: 100%; + max-width: 800px; + margin: 0 auto; +} + +.pet-input-row { + width: 100%; + max-width: 800px; + margin: 10px auto; +} + +.buddy-toolbar { + display: flex; + justify-content: center; + align-items: center; + gap: 16px; + flex-wrap: wrap; + margin: 24px 0 10px; +} + +.buddy-coins-card { + min-width: 150px; + padding: 12px 18px; + border-radius: 14px; + background: linear-gradient(135deg, #fff7d6, #ffe090); + box-shadow: 0 10px 25px rgba(177, 129, 17, 0.18); + color: #6b4b00; +} + +.buddy-coins-card strong { + display: block; + font-size: 1.6rem; + margin-top: 4px; +} + +.buddy-coins-label { + font-size: 0.85rem; + text-transform: uppercase; + letter-spacing: 0.08em; +} + +.buddy-status { + max-width: 720px; + margin: 16px auto 0; + padding: 14px 18px; + border-radius: 12px; + font-size: 1rem; + box-shadow: 0 10px 20px rgba(0,0,0,0.08); +} + +.buddy-status-success { + background: #effaf1; + color: #1c6a36; + border: 1px solid #bfe6c9; +} + +.buddy-status-error { + background: #fff1ef; + color: #9c2f25; + border: 1px solid #efc1bb; +} + +.buddy-nameplate { + display: flex; + justify-content: center; + align-items: center; + gap: 10px; + flex-wrap: wrap; + margin-top: 8px; +} + +.buddy-accessory-tag { + padding: 6px 10px; + border-radius: 999px; + font-size: 0.8rem; + background: #222; + color: #ffd700; +} + +.buddy-modal.hidden { + display: none; +} + +.buddy-modal { + position: fixed; + inset: 0; + z-index: 1000; +} + +.buddy-modal-backdrop { + position: absolute; + inset: 0; + background: rgba(22, 18, 14, 0.55); + backdrop-filter: blur(3px); +} + +.buddy-modal-panel { + position: relative; + width: min(920px, calc(100vw - 32px)); + max-height: calc(100vh - 40px); + overflow-y: auto; + margin: 20px auto; + padding: 28px; + border-radius: 24px; + background: linear-gradient(180deg, #fffdf7 0%, #fef5e8 100%); + box-shadow: 0 24px 60px rgba(0, 0, 0, 0.22); +} + +.buddy-modal-close { + position: absolute; + top: 14px; + right: 14px; + width: 38px; + height: 38px; + border: none; + border-radius: 50%; + background: #222; + color: #fff; + cursor: pointer; + font-size: 1rem; +} + +.buddy-modal-subtitle { + max-width: 620px; + margin: 0 auto 20px; +} + +.buddy-modal-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 18px; +} + +.buddy-option-card { + background: rgba(255, 255, 255, 0.88); + border: 1px solid #f1deba; + border-radius: 18px; + padding: 20px; + box-shadow: 0 12px 24px rgba(117, 90, 34, 0.08); +} + +.buddy-option-card h3 { + margin-top: 0; + margin-bottom: 10px; +} + +.buddy-pet-options { + display: grid; + grid-template-columns: 1fr; + gap: 10px; + margin-top: 12px; +} + +.buddy-pet-form { + margin: 0; +} + +.buddy-pet-tile { + width: 100%; + display: flex; + align-items: center; + gap: 12px; + padding: 12px; + border: 1px solid #ead7b3; + border-radius: 14px; + background: #fffdfa; + text-align: left; + cursor: pointer; +} + +.buddy-pet-tile.active { + border-color: #e0ae1a; + background: #fff3c8; +} + +.buddy-pet-tile img { + width: 64px; + height: 64px; + object-fit: contain; + image-rendering: pixelated; +} + +.buddy-pet-label, +.buddy-pet-meta { + display: block; +} + +.buddy-pet-label { + font-weight: 700; + color: #2f2517; +} + +.buddy-pet-meta { + font-size: 0.9rem; + color: #69553a; +} + +.buddy-action-form { + display: flex; + flex-direction: column; + gap: 12px; + align-items: center; + margin-top: 16px; +} + +.buddy-name-input { + width: 100%; + padding: 12px 14px; + border-radius: 12px; + border: 1px solid #d8c4a0; + font-size: 1rem; + box-sizing: border-box; +} + +.btn[disabled] { + opacity: 0.6; + cursor: not-allowed; +} + +body.buddy-modal-open { + overflow: hidden; +} + +@media (max-width: 768px) { + .home-main-row { + flex-direction: column; + align-items: center; + } + + #pet-container, + #pet-chat-container { + width: 100%; + } + + .buddy-modal-panel { + padding: 22px 18px; + } + +} + +#send{ + border-radius: 12px; + background-color: white; + color: black; + animation: page-load 1s ease-out forwards; +} + +.my-ai-message{ + display: flex; + align-items: start; +} + +.user-message{ + display: flex; + align-self: end; + background-color: #F4A261; + padding: 8px 12px; +} + +.ai-message-content{ + display: flex; + background-color: #6FAF9B; + padding: 8px 12px; +} + +.ai-icon{ + text-align: left; + padding-right: 15px; + width: 5vh; + height: 5vh; + +} + +.user-message { + align-self: flex-end; + padding: 8px 12px; + border-radius: 12px; + max-width: 80%; + opacity: 100%; +} + +.ai-message { + display: flex; + align-items: start; +} + +h1 { + font-weight: 600; + margin-bottom: 4px; +} + +h3 { + font-weight: 600; + margin-top: 2px; + margin-bottom: 6px; +} + +#initial-message { + display: flex; + align-items: start; + background-color: #6FAF9B; + padding: 8px 12px; + align-self: flex-start; + border-radius: 12px; + max-width: 90%; + opacity: 100%; + font-size: normal 400 1em; +} + diff --git a/public/styles/login_signup.css b/public/styles/login_signup.css index fc917e9..c6233d5 100644 --- a/public/styles/login_signup.css +++ b/public/styles/login_signup.css @@ -99,3 +99,25 @@ body{ margin-bottom: 20px; border-radius: 5px; } + +.success-box{ + background-color: #d4edda; + color:#155724; + border:1px solid #c3e6cb; + padding:10px; + margin-bottom: 20px; + border-radius: 5px; +} + +.resend-form { + margin-top: 15px; +} + +.secondary-btn { + background-color: #fff2b3; + color: #333; +} + +.secondary-btn:hover { + background-color: #ffe27a; +} diff --git a/public/styles/navbar.css b/public/styles/navbar.css new file mode 100644 index 0000000..c2fad6b --- /dev/null +++ b/public/styles/navbar.css @@ -0,0 +1,132 @@ +.hamburger-menu { + font-size: 20px; + background-color: transparent; + color: #ffffff; + cursor: pointer; + display: none; + border: none; + } + +.custom-navbar { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + background: linear-gradient(90deg, #1f1f1f 0%, #2a2a2a 100%); + padding: 14px 32px; + box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25); + border-bottom: 1px solid rgba(255, 215, 0, 0.12); + position: relative; + z-index: 1000; + } + +.custom-navbar .logo { + display: flex; + align-items: center; + gap: 12px; +} + +.custom-navbar .bee-icon { + width: 200px; + object-fit: cover; +} + +.logo-text { + font-size: 2rem; + font-weight: 800; + font-family: Arial, sans-serif; + line-height: 1; + letter-spacing: 0.4px; + color: #ffd700; + text-shadow: 0 1px 4px rgba(0, 0, 0, 0.25); +} + +.custom-navbar .custom-nav-links { + display: flex; + align-items: center; + gap: 8px; +} + +.custom-navbar a, +.custom-dropbtn { + color: #f5f5f5; + text-decoration: none; + font-size: 1.05rem; + font-weight: 600; + padding: 10px 16px; + border-radius: 10px; + transition: all 0.25s ease; + background: transparent; + border: none; + cursor: pointer; + font-family: Arial, sans-serif; + display: inline-block; + line-height: 1.2; +} + +.custom-navbar a:hover, +.custom-dropdown:hover .custom-dropbtn { + background: rgba(255, 215, 0, 0.12); + color: #ffd700; + transform: translateY(-1px); +} + +.custom-dropdown { + position: relative; + display: inline-block; +} + +.custom-dropdown-content { + display: none; + position: absolute; + top: 100%; + right: 0; + left: auto; + min-width: 210px; + background: #2f2f2f; + border-radius: 12px; + box-shadow: 0 12px 24px rgba(0, 0, 0, 0.28); + border: 1px solid rgba(255, 215, 0, 0.12); + overflow: hidden; + z-index: 1001; +} + +.custom-dropdown-content a { + display: block; + padding: 14px 16px; + font-size: 0.98rem; + font-weight: 500; + color: #f5f5f5; + border-radius: 0; +} + +.custom-dropdown-content a:hover { + background: rgba(255, 215, 0, 0.12); + color: #ffd700; + transform: none; +} + +.custom-dropdown:hover .custom-dropdown-content { + display: block; +} + +@media screen and (max-width: 970px) { + .custom-navbar .custom-nav-links { + display: none; + width: 100%; + flex-direction: column; + } + + + .custom-navbar { + flex-direction: row; + } + + .hamburger-menu { + display: block; + } + + .custom-nav-links.active { + display: flex; + } +} \ No newline at end of file diff --git a/public/styles/survey.css b/public/styles/survey.css deleted file mode 100644 index 981f367..0000000 --- a/public/styles/survey.css +++ /dev/null @@ -1,88 +0,0 @@ -/* General Styles */ -body { - font-family: Arial, sans-serif; - background: url('/images/background.jpg') no-repeat center center fixed; - background-size: cover; - color: #333; -} - -.survey-container { - background: rgba(255, 255, 255, 0.8); - padding: 20px; - margin: 50px auto; - max-width: 600px; - border-radius: 8px; - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); - text-align: center; -} - -.survey-container h1 { - font-size: 2em; - margin-bottom: 10px; -} - -.survey-container p { - margin-bottom: 20px; - font-size: 1.2em; -} - -/* Question Style */ -.question { - margin-bottom: 20px; - text-align: center; -} - -.question label { - display: block; - margin-bottom: 10px; - font-weight: bold; - font-size: 1.1em; -} - -.question input[type="range"] { - width: 80%; - display: block; - margin: 10px auto; -} - -/* Scale Labels Styling */ -.scale-labels { - display: flex; - justify-content: space-between; - width: 80%; - margin: 5px auto 15px; -} - -.scale-labels span { - flex: 1; - text-align: center; - font-size: 0.9em; -} - -/* Button Styling */ -.btn { - background: #333; - color: #ffd700; - padding: 10px 15px; - border: none; - border-radius: 4px; - cursor: pointer; - margin: 10px; - display: inline-block; - text-decoration: none; - font-size: 1em; -} - -.btn:hover { - background: #555; -} - -.reward-message { - background-color: #fff3cd; - border: 1px solid #ffeeba; - color: #856404; - padding: 10px; - border-radius: 8px; - margin-bottom: 15px; - text-align: center; -} diff --git a/signup.js b/signup.js index 5d25c73..c6b5425 100644 --- a/signup.js +++ b/signup.js @@ -1,6 +1,12 @@ // signup.js - handles POST signup logic and new user creation import bcrypt from "bcrypt"; import db from "./db.js"; +import { + createEmailVerificationToken, + ensureEmailVerificationColumns, + getEmailVerificationExpiryDate, + sendVerificationEmail, +} from "./verification.js"; // Basic email validation regex const isValidEmail = (email) => { @@ -46,6 +52,7 @@ export async function handleSignup(req, res) { let conn; try { + await ensureEmailVerificationColumns(); conn = await db.getConnection(); const [existingUser] = await conn.query("SELECT id FROM users WHERE LOWER(email) = ?", [email]); @@ -54,13 +61,53 @@ export async function handleSignup(req, res) { } const hashedPassword = await bcrypt.hash(password, 10); + const verificationToken = createEmailVerificationToken(); + const verificationExpiresAt = getEmailVerificationExpiryDate(); await conn.query( - "INSERT INTO users (full_name, email, password, age, gender, country, unsubscribed) VALUES (?, ?, ?, ?, ?, ?, ?)", - [name, email, hashedPassword, age || null, gender, country, false] + `INSERT INTO users ( + full_name, + email, + password, + age, + gender, + country, + unsubscribed, + email_verified, + email_verification_token, + email_verification_expires_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [ + name, + email, + hashedPassword, + age || null, + gender, + country, + false, + false, + verificationToken, + verificationExpiresAt, + ] ); - res.redirect("/login"); + try { + await sendVerificationEmail({ + email, + name, + token: verificationToken, + req, + }); + } catch (emailErr) { + console.error("Error sending verification email:", emailErr); + return res.render("login", { + error: "Account created, but we could not send the verification email yet. Please resend verification below.", + message: null, + verificationEmail: email, + }); + } + + res.redirect(`/login?message=${encodeURIComponent("Account created. Please check your email to verify your account.")}&verificationEmail=${encodeURIComponent(email)}`); } catch (err) { console.error("Error during signup:", err); res.status(500).send("Internal Server Error"); diff --git a/streak.js b/streak.js new file mode 100644 index 0000000..c8530ec --- /dev/null +++ b/streak.js @@ -0,0 +1,43 @@ +// streak.js +import db from "./db.js"; + +export async function markDayComplete(userId, dateStr) { + // dateStr must be "YYYY-MM-DD" + await db.query( + `INSERT IGNORE INTO daily_checkins (user_id, checkin_date) + VALUES (?, ?)`, + [userId, dateStr] + ); +} + +export async function getCurrentStreak(userId) { + const [rows] = await db.query( + `SELECT checkin_date + FROM daily_checkins + WHERE user_id = ? + ORDER BY checkin_date DESC`, + [userId] + ); + + const set = new Set(rows.map(r => toYMD(r.checkin_date))); + + // If today isn't complete, start from yesterday + let cursor = new Date(); + if (!set.has(toYMD(cursor))) cursor.setDate(cursor.getDate() - 1); + + let streak = 0; + while (set.has(toYMD(cursor))) { + streak++; + cursor.setDate(cursor.getDate() - 1); + } + + return streak; +} + +function toYMD(d) { + const dt = d instanceof Date ? d : new Date(d); + const y = dt.getFullYear(); + const m = String(dt.getMonth() + 1).padStart(2, "0"); + const day = String(dt.getDate()).padStart(2, "0"); + return `${y}-${m}-${day}`; +} diff --git a/test/buddy.test.js b/test/buddy.test.js new file mode 100644 index 0000000..ddc36fd --- /dev/null +++ b/test/buddy.test.js @@ -0,0 +1,49 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { + DEFAULT_BUDDY_NAME, + buildBuddyStatusRedirect, + normalizeBuddyProfile, + parseOwnedBuddyTypes, +} from "../utils/buddy.js"; + +test("parseOwnedBuddyTypes falls back to dog when value is missing", () => { + assert.deepEqual(parseOwnedBuddyTypes(null), ["dog"]); +}); + +test("parseOwnedBuddyTypes filters unknown pets and preserves dog ownership", () => { + const owned = parseOwnedBuddyTypes(JSON.stringify(["cat", "dragon"])); + assert.deepEqual(owned, ["dog", "cat"]); +}); + +test("normalizeBuddyProfile returns safe defaults", () => { + const profile = normalizeBuddyProfile({}); + + assert.equal(profile.buddyType, "dog"); + assert.equal(profile.buddyName, DEFAULT_BUDDY_NAME); + assert.equal(profile.buddyHasCollar, false); + assert.deepEqual(profile.ownedBuddyTypes, ["dog"]); +}); + +test("normalizeBuddyProfile keeps valid pet data and trims the name", () => { + const profile = normalizeBuddyProfile({ + buddy_type: "penguin", + buddy_name: " Robbie ", + buddy_has_collar: 1, + owned_buddy_types: JSON.stringify(["dog", "penguin"]), + }); + + assert.equal(profile.buddyType, "penguin"); + assert.equal(profile.buddyName, "Robbie"); + assert.equal(profile.buddyHasCollar, true); + assert.deepEqual(profile.ownedBuddyTypes, ["dog", "penguin"]); +}); + +test("buildBuddyStatusRedirect encodes status text and optional modal flag", () => { + const redirect = buildBuddyStatusRedirect("Collar purchased for Buddy.", "success", true); + + assert.equal( + redirect, + "/home?buddyStatus=Collar+purchased+for+Buddy.&buddyStatusType=success&openBuddyModal=1" + ); +}); diff --git a/test/survey.test.js b/test/survey.test.js new file mode 100644 index 0000000..3bd4360 --- /dev/null +++ b/test/survey.test.js @@ -0,0 +1,33 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { getLowestScoringQuestion } from "../utils/survey.js"; + +test("getLowestScoringQuestion marks a standout low score", () => { + const lowest = getLowestScoringQuestion({ + q1: 8, + q2: 9, + q3: 3, + q4: 8, + }); + + assert.deepEqual(lowest, { + key: "q3", + value: 3, + reason: "standout", + }); +}); + +test("getLowestScoringQuestion falls back to the minimum score", () => { + const lowest = getLowestScoringQuestion({ + q1: 6, + q2: 5, + q3: 7, + q4: 6, + }); + + assert.deepEqual(lowest, { + key: "q2", + value: 5, + reason: "low", + }); +}); diff --git a/utils/buddy.js b/utils/buddy.js new file mode 100644 index 0000000..c99f2f5 --- /dev/null +++ b/utils/buddy.js @@ -0,0 +1,67 @@ +export const DEFAULT_BUDDY_NAME = "Buddy"; +export const BUDDY_COSTS = { + pet: 30, + collar: 20, + rename: 10, +}; +export const BUDDY_OPTIONS = { + dog: { label: "Dog" }, + cat: { label: "Cat" }, + penguin: { label: "Penguin" }, +}; + +export function parseOwnedBuddyTypes(value) { + if (!value) return ["dog"]; + + try { + const parsed = JSON.parse(value); + if (Array.isArray(parsed)) { + const cleaned = parsed.filter((type) => BUDDY_OPTIONS[type]); + if (cleaned.includes("dog")) { + return cleaned; + } + return ["dog", ...cleaned]; + } + } catch (err) { + console.warn("Could not parse owned buddy types:", err.message); + } + + return ["dog"]; +} + +export function normalizeBuddyProfile(userRow = {}) { + const ownedBuddyTypes = parseOwnedBuddyTypes(userRow.owned_buddy_types); + let buddyType = "dog"; + if (BUDDY_OPTIONS[userRow.buddy_type]) { + buddyType = userRow.buddy_type; + } + + if (!ownedBuddyTypes.includes(buddyType)) { + ownedBuddyTypes.push(buddyType); + } + + let buddyName = DEFAULT_BUDDY_NAME; + if (userRow.buddy_name && userRow.buddy_name.trim()) { + buddyName = userRow.buddy_name.trim(); + } + + return { + buddyType, + buddyName, + buddyHasCollar: Boolean(userRow.buddy_has_collar), + ownedBuddyTypes, + }; +} + +export function buildBuddyStatusRedirect(message, status = "success", openModal = false) { + const params = new URLSearchParams({ + buddyStatus: message, + buddyStatusType: status, + }); + + if (openModal) { + params.set("openBuddyModal", "1"); + } + + return `/home?${params.toString()}`; +} diff --git a/utils/survey.js b/utils/survey.js new file mode 100644 index 0000000..518ac49 --- /dev/null +++ b/utils/survey.js @@ -0,0 +1,13 @@ +export function getLowestScoringQuestion(scores) { + const entries = Object.entries(scores); + const values = entries.map(([, val]) => val); + const avg = values.reduce((a, b) => a + b, 0) / values.length; + + const threshold = avg - 2; + const standout = entries.find(([, val]) => val <= threshold); + if (standout) return { key: standout[0], value: standout[1], reason: "standout" }; + + const minVal = Math.min(...values); + const lowest = entries.find(([, val]) => val === minVal); + return { key: lowest[0], value: lowest[1], reason: "low" }; +} diff --git a/verification.js b/verification.js new file mode 100644 index 0000000..c247135 --- /dev/null +++ b/verification.js @@ -0,0 +1,121 @@ +import crypto from "crypto"; +import dotenv from "dotenv"; +import { Knock } from "@knocklabs/node"; +import db from "./db.js"; + +dotenv.config(); + +const DEFAULT_TOKEN_TTL_HOURS = 24; + +let verificationColumnsReady = false; +let verificationColumnsPromise = null; + +function getEnvValue(name) { + const value = process.env[name]; + if (typeof value !== "string") return ""; + return value.trim(); +} + +function getTokenTtlHours() { + const configured = Number(process.env.EMAIL_VERIFICATION_TOKEN_TTL_HOURS); + if (!Number.isNaN(configured) && configured > 0) { + return configured; + } + return DEFAULT_TOKEN_TTL_HOURS; +} + +export function createEmailVerificationToken() { + return crypto.randomBytes(32).toString("hex"); +} + +export function getEmailVerificationExpiryDate() { + const expiresAt = new Date(); + expiresAt.setHours(expiresAt.getHours() + getTokenTtlHours()); + return expiresAt; +} + +export function getAppBaseUrl(req) { + if (process.env.APP_BASE_URL) { + return process.env.APP_BASE_URL.replace(/\/+$/, ""); + } + + if (req) { + return `${req.protocol}://${req.get("host")}`; + } + + return "http://localhost:8000"; +} + +export async function ensureEmailVerificationColumns() { + if (verificationColumnsReady) return; + if (verificationColumnsPromise) return verificationColumnsPromise; + + const requiredColumns = [ + { + name: "email_verified", + sql: "ADD COLUMN email_verified TINYINT(1) NOT NULL DEFAULT 0", + }, + { + name: "email_verification_token", + sql: "ADD COLUMN email_verification_token VARCHAR(128) NULL", + }, + { + name: "email_verification_expires_at", + sql: "ADD COLUMN email_verification_expires_at DATETIME NULL", + }, + ]; + + verificationColumnsPromise = (async () => { + for (const column of requiredColumns) { + const [rows] = await db.query("SHOW COLUMNS FROM users LIKE ?", [column.name]); + if (!rows.length) { + await db.query(`ALTER TABLE users ${column.sql}`); + } + } + verificationColumnsReady = true; + })(); + + try { + await verificationColumnsPromise; + } catch (err) { + verificationColumnsPromise = null; + throw err; + } +} + +export async function sendVerificationEmail({ email, name, token, req }) { + const apiKey = getEnvValue("KNOCK_API_KEY"); + const workflowKey = getEnvValue("KNOCK_VERIFICATION_WORKFLOW_KEY"); + + if (!apiKey || !workflowKey) { + const missing = []; + if (!apiKey) missing.push("KNOCK_API_KEY"); + if (!workflowKey) missing.push("KNOCK_VERIFICATION_WORKFLOW_KEY"); + throw new Error(`Knock email verification is not configured. Missing: ${missing.join(", ")}`); + } + + const knock = new Knock(apiKey); + const verificationUrl = `${getAppBaseUrl(req)}/verify-email?token=${encodeURIComponent(token)}`; + + const workflowRun = await knock.workflows.trigger(workflowKey, { + recipients: [ + { + id: email, + email, + name, + }, + ], + data: { + user_name: name, + verification_url: verificationUrl, + }, + }); + + console.log("Knock verification workflow triggered:", { + workflowKey, + recipientEmail: email, + workflowRunId: workflowRun.workflow_run_id, + }); + + return workflowRun; +} diff --git a/views/calendar.ejs b/views/calendar.ejs index 2a4f37a..5b336c3 100644 --- a/views/calendar.ejs +++ b/views/calendar.ejs @@ -7,6 +7,8 @@ + + <%- include('partials/navbar') %> diff --git a/views/chart.ejs b/views/chart.ejs index 04a865c..0923ee7 100644 --- a/views/chart.ejs +++ b/views/chart.ejs @@ -7,6 +7,8 @@ + + <%- include('partials/navbar') %> @@ -20,7 +22,7 @@
Take <%= section.charAt(0).toUpperCase() + section.slice(1) %> Survey diff --git a/views/chatbot.ejs b/views/chatbot.ejs index b716df6..b85f375 100644 --- a/views/chatbot.ejs +++ b/views/chatbot.ejs @@ -5,92 +5,292 @@ Chatbot + - - + + + - - - -

Chatbot

- - <% if (user) { %> -

Hi, <%= user.full_name %>!

- <% } %> - -
- -
- - -
- - + aiText += data; + const formatted = formatAiText(aiText); + // keep AI: on the same line + aiElContent.innerHTML = formatted; + messagesDiv.scrollTop = messagesDiv.scrollHeight; + + }); + } + + // in case no chunks + loadingEl.style.visibility = "hidden"; + + conversation.push({ role: "assistant", content: aiText }); + messagesDiv.scrollTop = messagesDiv.scrollHeight; + } + + sendBtn.onclick = sendMessage; + promptInput.onkeydown = (e) => { + if (e.key === "Enter") sendMessage(); + }; + diff --git a/views/survey-choice.ejs b/views/checkin-choice.ejs similarity index 68% rename from views/survey-choice.ejs rename to views/checkin-choice.ejs index 822d93b..7f3c0ee 100644 --- a/views/survey-choice.ejs +++ b/views/checkin-choice.ejs @@ -4,14 +4,21 @@ Survey Choice - + + + -
+ <%- include('partials/navbar') %> + +
+
+

<%= coins ?? 0 %> πŸͺ™

+
<% if (typeof coinsEarned !== 'undefined' && coinsEarned !== null) { %>
-

You earned <%= coinsEarned %> coins from that survey!

+

You earned <%= coinsEarned %> extra coins for doing your check-in!

<% } %> @@ -41,24 +48,24 @@

Next Steps

<% if (!userProgress.general || !userProgress.mental || !userProgress.physical) { %> -

Select a survey section to complete:

+

Select a check-in section to complete:

<% if (!userProgress.general) { %> - General + General <% } %> <% if (!userProgress.mental) { %> - Mental + Mental <% } %> <% if (!userProgress.physical) { %> - Physical + Physical <% } %> Home <% } else { %> -

All surveys are completed for today!

+

All check-ins are completed for today!

Home <% } %>
- + \ No newline at end of file diff --git a/views/checkin.ejs b/views/checkin.ejs new file mode 100644 index 0000000..578f2c8 --- /dev/null +++ b/views/checkin.ejs @@ -0,0 +1,294 @@ + + + + + + Survey + + + + + + + + + + <%- include('partials/navbar') %> + +
+ <% if (section === "completed") { %> + <% if (typeof coinsEarned !== 'undefined' && coinsEarned !== null) { %> +
+

You earned <%= coinsEarned %> extra coins for doing your check-in!

+
+ <% } %> +

All check-ins completed for today!

+ + <% if (advice) { %> + <% if (advice.section == 'general') { %> +
+

General Focus Area: <%= advice.question %>

+

<%= advice.advice %>

+
+ <% } else if (advice.section == 'mental') { %> +
+

Mental Focus Area: <%= advice.question %>

+

<%= advice.advice %>

+
+ <% } else if (advice.section == 'physical') { %> +
+

Physical Focus Area: <%= advice.question %>

+

<%= advice.advice %>

+
+ <% } %> + <% } else { %> +
+

No advice available at this time.

+
+ <% } %> + + <% if (typeof calcTime !== 'undefined' && calcTime !== null) { %> +
+ Wellness insights calculated in <%= calcTime %> ms +
+ <% } %> + + Home + Game + Shop + + <% } else if (section === "choice") { %> + + <% } else if (section === "next") { %> +

Would you like to continue?

+ <% if (!userProgress.mental) { %> + Mental + <% } %> + <% if (!userProgress.physical) { %> + Physical + <% } %> + Home + Game + Shop + + <%}%> <% if (section !== "choice" && section !== "completed" && typeof questions !== 'undefined') { %> +
+
+

+ <%= coins ?? 0 %> + +

+
+
+

<%= section.charAt(0).toUpperCase() + section.slice(1) %> Check-In

+
+ Question 1 of <%= questions.length %> +
+
+
+
+ +
+ +
+ + + + + + +
+ <% questions.forEach((q, index) => { %> +
+

<%= q.question %>

+
+ <% q.responses.forEach((response) => { %> +
+ +
+ <% }) %> +
+
+ <% }) %> +
+ +
+ + +
+
+
+ <% } %> +
+ + + diff --git a/views/feedback.ejs b/views/feedback.ejs index 9cf1f09..1932c41 100644 --- a/views/feedback.ejs +++ b/views/feedback.ejs @@ -7,6 +7,8 @@ + + <%- include('partials/navbar') %> @@ -41,6 +43,28 @@ <% } %> +
+ +
+

Resources

+
+

Partner Links

+ United Way of Northern Arizona +

United Way of Northern Arizona is a non-profit orginization. If you are able, dontations can be made + + at this link. + +

+ +

Additional Resources

+ Arizona Youth Partnership

+ Arizona Department of Mental Health Services + + +
+ + +
diff --git a/views/games.ejs b/views/games.ejs index 06e3e6e..5ecf451 100644 --- a/views/games.ejs +++ b/views/games.ejs @@ -5,12 +5,24 @@ Mini Games + + + hamburgerButton.addEventListener("click", () => { + navLinks.classList.toggle('active'); + }); + diff --git a/views/survey.ejs b/views/survey.ejs deleted file mode 100644 index 5d42302..0000000 --- a/views/survey.ejs +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - Survey - - - - - <%- include('partials/navbar') %> - -
-

Daily Survey

- - <% if (section === "completed") { %> - <% if (typeof coinsEarned !== 'undefined' && coinsEarned !== null) { %> -
-

You earned <%= coinsEarned %> coins from your last survey!

-
- <% } %> -

All surveys completed for today!

- - <% if (advice) { %> - <% if (advice.section == 'general') { %> -
-

General Focus Area: <%= advice.question %>

-

<%= advice.advice %>

-
- <% } else if (advice.section == 'mental') { %> -
-

Mental Focus Area: <%= advice.question %>

-

<%= advice.advice %>

-
- <% } else if (advice.section == 'physical') { %> -
-

Physical Focus Area: <%= advice.question %>

-

<%= advice.advice %>

-
- <% } %> - <% } else { %> -
-

No advice available at this time.

-
- <% } %> - - Home - Game - - <% } else if (section === "choice") { %> -
-

Which survey would you like to take?

- - - - -
- Home - Game -
-
- <% } else if (section === "next") { %> -

Would you like to continue?

- <% if (!userProgress.mental) { %> - Mental - <% } %> - <% if (!userProgress.physical) { %> - Physical - <% } %> - Home - Game - - <% } else { %> -
- - - <% let questions = { - general: [ - "I drink 8 glasses of water daily.", - "I eat meals regularly.", - "I feel energized and well-rested throughout the day.", - "I am hopeful about the future.", - "I am satisfied with my daily life." - ], - mental: [ - "I am able to concentrate and stay focused.", - "I feel connected to others and supported.", - "I know I'm not alone in my struggles and challenges.", - "I believe I am just as capable as everyone else.", - "I generally feel happy and emotionally balanced." - ], - physical: [ - "I avoid using electronic devices after midnight.", - "I exercise for 30 minutes or more every day.", - "I go outside for the sun at least 10 minutes a day.", - "I sleep for 7 to 8 hours.", - "I limit my intake of caffeinated drinks." - ] - }; %> - - <% if (questions[section]) { %> - <% questions[section].forEach((question, index) => { %> -
- - -
- 1
Strongly Disagree
- 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10
Strongly Agree
-
-
- <% }) %> - <% } %> - - -
- <% } %> -
- - diff --git a/views/welcome.ejs b/views/welcome.ejs index 0614fe1..945caa3 100644 --- a/views/welcome.ejs +++ b/views/welcome.ejs @@ -87,9 +87,9 @@ } .splash-container img { - width: 120px; + width: 300px; height: auto; - margin-bottom: 20px; + margin-bottom: 5px; animation: logoBounce 1s ease-out 2.3s forwards; opacity: 0; } @@ -190,9 +190,9 @@
- Bee Balanced Logo -

Welcome to Bee Balanced

-

We are a healthy lifestyle coach! Let’s have fun and BEE healthy!

+ Me Balanced Logo +

Welcome to Me Balanced

+

We are a healthy lifestyle coach! Let’s have fun and be healthy!