diff --git a/routes/day.js b/routes/day.js index faf7762..9f9b469 100644 --- a/routes/day.js +++ b/routes/day.js @@ -40,12 +40,25 @@ dayRouter.get('/:id', async (req, res) => { dayRouter.post('/', async (req, res) => { try { const { eventDate, location, notes } = req.body; - const inUse = await db.query(`SELECT * FROM day WHERE event_date = $1;`, [eventDate]); - if (inUse.length) { - res.status(201).json({ - status: 'Failed', - message: 'Day already exists', - }); + const existingDay = await db.query(`SELECT * FROM day WHERE event_date = $1;`, [eventDate]); + if (existingDay.length) { + // day exists but has no events --> update location + notes return existing day_id + if (existingDay[0].day_count === 0) { + await db.query(`UPDATE day SET location = $1, notes = $2 WHERE id = $3;`, [ + location, + notes, + existingDay[0].id, + ]); + res.status(201).json({ + status: 'Success', + id: existingDay[0].id, + }); + } else { + res.status(201).json({ + status: 'Failed', + message: 'Day already exists', + }); + } return; } diff --git a/routes/publishedSchedule.js b/routes/publishedSchedule.js index 30aa1af..749be96 100644 --- a/routes/publishedSchedule.js +++ b/routes/publishedSchedule.js @@ -217,6 +217,56 @@ publishedScheduleRouter.get('/season', async (req, res) => { } }); +// GET /published-schedule/stats - returns stats of event types and subjects for a specific season +publishedScheduleRouter.get('/stats', async (req, res) => { + try { + const { season, year } = req.query; + + const statResult = await db.query( + ` + WITH all_event_types AS ( + SELECT DISTINCT unnest(event_type) AS event_type + FROM catalog + ), + all_subjects AS ( + SELECT DISTINCT unnest(subject) AS subject + FROM catalog + ), + all_permutations AS ( + SELECT aet.event_type, asu.subject + FROM all_event_types aet + CROSS JOIN all_subjects asu + ) + SELECT + COALESCE(ap.event_type::text, 'Total') AS event_type, + COALESCE(ap.subject::text, 'Total') AS subject, + COALESCE(COUNT(c.catalog_id), 0) AS total_count + FROM all_permutations ap + LEFT JOIN ( + SELECT *, + ps.day_id AS ps_day_id, + c.id AS catalog_id + FROM catalog c + JOIN published_schedule ps ON c.id = ps.event_id + JOIN day d ON PS.day_id = d.id + WHERE $1 = ANY(c.season) + AND EXTRACT(YEAR FROM d.event_date) = $2 + ) c ON ap.event_type = ANY(c.event_type) AND ap.subject = ANY(c.subject) + GROUP BY ROLLUP (ap.event_type), ROLLUP (ap.subject) + ORDER BY CASE WHEN ap.event_type IS NULL THEN 1 ELSE 0 END, + CASE WHEN ap.subject IS NULL THEN 1 ELSE 0 END, + ap.event_type NULLS FIRST, + ap.subject NULLS FIRST; + `, + [season, year], + ); + + res.status(200).json(keysToCamel(statResult)); + } catch (error) { + res.status(500).json({ error: 'Internal server error' }); + } +}); + // GET /published-schedule/date - returns all events occurring on a specific date publishedScheduleRouter.get('/dayId', async (req, res) => { try { diff --git a/routes/users.js b/routes/users.js index 707a026..e72e1f6 100644 --- a/routes/users.js +++ b/routes/users.js @@ -1,5 +1,5 @@ const express = require('express'); -const { keysToCamel } = require('../common/utils'); +const { keysToCamel, isInteger } = require('../common/utils'); const { db } = require('../server/db'); const userRouter = express.Router(); @@ -17,9 +17,14 @@ userRouter.get('/', async (req, res) => { userRouter.get('/pending-accounts', async (req, res) => { try { - const pendingAccounts = await db.query( - `SELECT * FROM users WHERE approved = FALSE ORDER BY first_name ASC;`, - ); + const { accountType } = req.query; + let queryString = 'SELECT * FROM users WHERE approved = FALSE '; + if (accountType === 'admin') { + queryString += `AND type = 'admin'`; + } else if (accountType === 'student') { + queryString += `AND type = 'student'`; + } + const pendingAccounts = await db.query(`${queryString} ORDER BY first_name ASC;`); res.status(200).json(keysToCamel(pendingAccounts)); } catch (err) { res.status(500).send(err.message); @@ -28,10 +33,39 @@ userRouter.get('/pending-accounts', async (req, res) => { userRouter.get('/approved-accounts', async (req, res) => { try { - const pendingAccounts = await db.query( - `SELECT * FROM users WHERE approved = TRUE ORDER BY first_name ASC;`, - ); - res.status(200).json(keysToCamel(pendingAccounts)); + const { keyword, accountType } = req.query; + let { page, limit } = req.query; + page = isInteger(page) ? parseInt(page, 10) : 1; + limit = isInteger(limit) ? parseInt(limit, 10) : 10; + const offset = (page - 1) * limit; + let queryString = 'FROM users WHERE approved = TRUE '; + if (accountType === 'admin') { + queryString += `AND type = 'admin'`; + } else if (accountType === 'student') { + queryString += `AND type = 'student'`; + } + let approvedAccounts; + let userCount; + let params = []; + if (keyword) { + params = [`%${keyword}%`, `%${keyword}%`, `%${keyword}%`, `%${keyword}%`, limit, offset]; + approvedAccounts = await db.query( + `SELECT * ${queryString} AND (first_name ILIKE $1 OR last_name ILIKE $2 OR email ILIKE $3 OR CONCAT(first_name, ' ', last_name) ILIKE $4) ORDER BY first_name ASC LIMIT $5 OFFSET $6;`, + params, + ); + userCount = await db.query( + `SELECT COUNT(*) ${queryString} AND (first_name ILIKE $1 OR last_name ILIKE $2 OR email ILIKE $3 OR CONCAT(first_name, ' ', last_name) ILIKE $4);`, + params, + ); + } else { + params = [limit, offset]; + approvedAccounts = await db.query( + `SELECT * ${queryString} ORDER BY first_name ASC LIMIT $1 OFFSET $2;`, + params, + ); + userCount = await db.query(`SELECT COUNT(*) ${queryString};`, params); + } + res.status(200).json(keysToCamel({ accounts: approvedAccounts, count: userCount })); } catch (err) { res.status(500).send(err.message); } diff --git a/server/schema/catalog.sql b/server/schema/catalog.sql index 31b97a4..06544ad 100644 --- a/server/schema/catalog.sql +++ b/server/schema/catalog.sql @@ -1,6 +1,6 @@ CREATE TYPE event AS ENUM ('guest speaker', 'study-trip', 'workshop', 'other'); CREATE TYPE subject AS ENUM ('life skills', 'science', 'technology', 'engineering', 'math', 'college readiness'); -CREATE TYPE year AS ENUM ('junior', 'senior', 'both'); +CREATE TYPE year AS ENUM ('junior', 'senior'); CREATE TYPE season AS ENUM ('spring', 'summer', 'fall'); DROP TABLE IF EXISTS catalog;