From abe34816f4010b4a2fea86a4f6203d7e4cb729dd Mon Sep 17 00:00:00 2001 From: Wayne Ngo Date: Fri, 26 Dec 2025 17:26:08 -0800 Subject: [PATCH 1/7] Add PermissionRequest schema and API for LED sign access requests --- .../models/PermissionRequest.js | 30 ++++++ .../routes/PermissionRequest.js | 94 ++++++++++++++++++ .../util/permissionRequestTypes.js | 6 ++ test/api/PermissionRequest.js | 96 +++++++++++++++++++ 4 files changed, 226 insertions(+) create mode 100644 api/main_endpoints/models/PermissionRequest.js create mode 100644 api/main_endpoints/routes/PermissionRequest.js create mode 100644 api/main_endpoints/util/permissionRequestTypes.js create mode 100644 test/api/PermissionRequest.js diff --git a/api/main_endpoints/models/PermissionRequest.js b/api/main_endpoints/models/PermissionRequest.js new file mode 100644 index 000000000..00bb533cb --- /dev/null +++ b/api/main_endpoints/models/PermissionRequest.js @@ -0,0 +1,30 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; +const PermissionRequestTypes = require('../util/permissionRequestTypes'); + +const PermissionRequestSchema = new Schema( + { + userId: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + index: true, + }, + type: { + type: String, + enum: Object.keys(PermissionRequestTypes), + required: true, + }, + deletedAt: { + type: Date, + default: null, + }, + }, + { timestamps: { createdAt: true, updatedAt: false } } +); + +// Compound unique index prevents duplicate active requests per user+type +PermissionRequestSchema.index({ userId: 1, type: 1 }, { unique: true }); + +module.exports = mongoose.model('PermissionRequest', PermissionRequestSchema); + diff --git a/api/main_endpoints/routes/PermissionRequest.js b/api/main_endpoints/routes/PermissionRequest.js new file mode 100644 index 000000000..6db47f48b --- /dev/null +++ b/api/main_endpoints/routes/PermissionRequest.js @@ -0,0 +1,94 @@ +const express = require('express'); +const router = express.Router(); +const PermissionRequest = require('../models/PermissionRequest'); +const { OK, UNAUTHORIZED, SERVER_ERROR, NOT_FOUND, BAD_REQUEST } = require('../../util/constants').STATUS_CODES; +const membershipState = require('../../util/constants.js').MEMBERSHIP_STATE; +const { decodeToken } = require('../util/token-functions.js'); +const logger = require('../../util/logger'); +const PermissionRequestTypes = require('../util/permissionRequestTypes'); + +router.post('/create', async (req, res) => { + const decoded = await decodeToken(req, membershipState.MEMBER); + if (decoded.status !== OK) return res.sendStatus(decoded.status); + + const { type } = req.body; + if (!type || !Object.keys(PermissionRequestTypes).includes(type)) { + return res.status(BAD_REQUEST).send({ error: 'Invalid type' }); + } + + try { + const permissionRequest = await PermissionRequest.create({ + userId: decoded.token._id, + type, + }); + const populated = await PermissionRequest.findById(permissionRequest._id) + .populate('userId', 'firstName lastName email'); + res.status(OK).send(populated); + } catch (error) { + if (error.code === 11000) return res.status(BAD_REQUEST).send({ error: 'Request already exists' }); + logger.error('Failed to create permission request:', error); + res.sendStatus(SERVER_ERROR); + } +}); + +router.get('/getAll', async (req, res) => { + const decoded = await decodeToken(req, membershipState.OFFICER); + if (decoded.status !== OK) return res.sendStatus(decoded.status); + + try { + const requests = await PermissionRequest.find({ deletedAt: null }) + .populate('userId', 'firstName lastName email') + .sort({ createdAt: -1 }); + res.status(OK).send(requests); + } catch (error) { + logger.error('Failed to get permission requests:', error); + res.sendStatus(SERVER_ERROR); + } +}); + +router.get('/get', async (req, res) => { + const decoded = await decodeToken(req, membershipState.MEMBER); + if (decoded.status !== OK) return res.sendStatus(decoded.status); + + try { + const request = await PermissionRequest.findOne({ + userId: decoded.token._id, + type: req.query.type, + deletedAt: null, + }).populate('userId', 'firstName lastName email'); + + if (!request) return res.sendStatus(NOT_FOUND); + res.status(OK).send(request); + } catch (error) { + logger.error('Failed to get permission request:', error); + res.sendStatus(SERVER_ERROR); + } +}); + +router.post('/delete', async (req, res) => { + const decoded = await decodeToken(req, membershipState.MEMBER); + if (decoded.status !== OK) return res.sendStatus(decoded.status); + + const { type } = req.body; + if (!type || !Object.keys(PermissionRequestTypes).includes(type)) { + return res.status(BAD_REQUEST).send({ error: 'Invalid type' }); + } + + try { + const request = await PermissionRequest.findOne({ + userId: decoded.token._id, + type, + deletedAt: null, + }); + if (!request) return res.sendStatus(NOT_FOUND); + request.deletedAt = new Date(); + await request.save(); + res.sendStatus(OK); + } catch (error) { + logger.error('Failed to delete permission request:', error); + res.sendStatus(SERVER_ERROR); + } +}); + +module.exports = router; + diff --git a/api/main_endpoints/util/permissionRequestTypes.js b/api/main_endpoints/util/permissionRequestTypes.js new file mode 100644 index 000000000..e81755573 --- /dev/null +++ b/api/main_endpoints/util/permissionRequestTypes.js @@ -0,0 +1,6 @@ +const PermissionRequestTypes = { + LED_SIGN: 'LED_SIGN', +}; + +module.exports = PermissionRequestTypes; + diff --git a/test/api/PermissionRequest.js b/test/api/PermissionRequest.js new file mode 100644 index 000000000..610a4c2ad --- /dev/null +++ b/test/api/PermissionRequest.js @@ -0,0 +1,96 @@ +process.env.NODE_ENV = 'test'; + +const PermissionRequest = require('../../api/main_endpoints/models/PermissionRequest'); +const chai = require('chai'); +const chaiHttp = require('chai-http'); +const constants = require('../../api/util/constants'); +const { OK, BAD_REQUEST, UNAUTHORIZED, NOT_FOUND } = constants.STATUS_CODES; +const SceApiTester = require('../../test/util/tools/SceApiTester'); +const { + initializeTokenMock, + setTokenStatus, + resetTokenMock, + restoreTokenMock, +} = require('../util/mocks/TokenValidFunctions'); +const mongoose = require('mongoose'); +const PermissionRequestTypes = require('../../api/main_endpoints/util/permissionRequestTypes'); + +let app = null; +let test = null; +const expect = chai.expect; +const tools = require('../util/tools/tools.js'); +chai.should(); +chai.use(chaiHttp); +const token = ''; + +describe('PermissionRequest', () => { + before(done => { + initializeTokenMock(); + app = tools.initializeServer(__dirname + '/../../api/main_endpoints/routes/PermissionRequest.js'); + test = new SceApiTester(app); + tools.emptySchema(PermissionRequest); + done(); + }); + + after(done => { + restoreTokenMock(); + tools.terminateServer(done); + }); + + beforeEach(() => { + setTokenStatus(false); + }); + + afterEach(async () => { + resetTokenMock(); + await PermissionRequest.deleteMany({}); + }); + + describe('/POST create', () => { + it('Should return 401 when token is not sent', async () => { + const res = await test.sendPostRequest('/api/PermissionRequest/create', { type: PermissionRequestTypes.LED_SIGN }); + expect(res).to.have.status(UNAUTHORIZED); + }); + + it('Should create permission request successfully', async () => { + const userId = new mongoose.Types.ObjectId(); + setTokenStatus(true, { _id: userId, email: 'test@test.com', accessLevel: 'MEMBER' }); + const res = await test.sendPostRequestWithToken(token, '/api/PermissionRequest/create', { type: PermissionRequestTypes.LED_SIGN }); + expect(res).to.have.status(OK); + const request = await PermissionRequest.findOne({ userId, type: PermissionRequestTypes.LED_SIGN }); + expect(request).to.exist; + expect(request.type).to.equal(PermissionRequestTypes.LED_SIGN); + }); + }); + + describe('/GET get', () => { + it('Should return 404 when request does not exist', async () => { + const userId = new mongoose.Types.ObjectId(); + setTokenStatus(true, { _id: userId, email: 'test@test.com', accessLevel: 'MEMBER' }); + const res = await test.sendGetRequest('/api/PermissionRequest/get?type=' + PermissionRequestTypes.LED_SIGN); + expect(res).to.have.status(NOT_FOUND); + }); + + it('Should return permission request when it exists', async () => { + const userId = new mongoose.Types.ObjectId(); + setTokenStatus(true, { _id: userId, email: 'test@test.com', accessLevel: 'MEMBER' }); + await new PermissionRequest({ userId, type: PermissionRequestTypes.LED_SIGN }).save(); + const res = await test.sendGetRequest('/api/PermissionRequest/get?type=' + PermissionRequestTypes.LED_SIGN); + expect(res).to.have.status(OK); + expect(res.body.type).to.equal(PermissionRequestTypes.LED_SIGN); + }); + }); + + describe('/POST delete', () => { + it('Should delete permission request successfully', async () => { + const userId = new mongoose.Types.ObjectId(); + setTokenStatus(true, { _id: userId, email: 'test@test.com', accessLevel: 'MEMBER' }); + const request = await new PermissionRequest({ userId, type: PermissionRequestTypes.LED_SIGN }).save(); + const res = await test.sendPostRequestWithToken(token, '/api/PermissionRequest/delete', { type: PermissionRequestTypes.LED_SIGN }); + expect(res).to.have.status(OK); + const deleted = await PermissionRequest.findById(request._id); + expect(deleted.deletedAt).to.not.be.null; + }); + }); +}); + From 52a33f56609de9be21cc8fcdb865a2ef6a559f1c Mon Sep 17 00:00:00 2001 From: Wayne Ngo Date: Fri, 26 Dec 2025 20:23:45 -0800 Subject: [PATCH 2/7] hook error --- test/api/PermissionRequest.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/api/PermissionRequest.js b/test/api/PermissionRequest.js index 610a4c2ad..d3aa7163d 100644 --- a/test/api/PermissionRequest.js +++ b/test/api/PermissionRequest.js @@ -41,9 +41,8 @@ describe('PermissionRequest', () => { setTokenStatus(false); }); - afterEach(async () => { + afterEach(() => { resetTokenMock(); - await PermissionRequest.deleteMany({}); }); describe('/POST create', () => { From 58d0b88046fad8568caa3930b99e78cc20c46c47 Mon Sep 17 00:00:00 2001 From: Wayne Ngo Date: Thu, 1 Jan 2026 22:07:10 -0800 Subject: [PATCH 3/7] feedback changes --- .../models/PermissionRequest.js | 6 +- .../routes/PermissionRequest.js | 84 +++++++++++-------- 2 files changed, 53 insertions(+), 37 deletions(-) diff --git a/api/main_endpoints/models/PermissionRequest.js b/api/main_endpoints/models/PermissionRequest.js index 00bb533cb..4aa8e548d 100644 --- a/api/main_endpoints/models/PermissionRequest.js +++ b/api/main_endpoints/models/PermissionRequest.js @@ -8,11 +8,10 @@ const PermissionRequestSchema = new Schema( type: Schema.Types.ObjectId, ref: 'User', required: true, - index: true, }, type: { type: String, - enum: Object.keys(PermissionRequestTypes), + enum: Object.values(PermissionRequestTypes), required: true, }, deletedAt: { @@ -24,7 +23,8 @@ const PermissionRequestSchema = new Schema( ); // Compound unique index prevents duplicate active requests per user+type -PermissionRequestSchema.index({ userId: 1, type: 1 }, { unique: true }); +PermissionRequestSchema.index({ userId: 1, type: 1 }, { unique: true, + partialFilterExpression: { deletedAt: null } }); module.exports = mongoose.model('PermissionRequest', PermissionRequestSchema); diff --git a/api/main_endpoints/routes/PermissionRequest.js b/api/main_endpoints/routes/PermissionRequest.js index 6db47f48b..480909191 100644 --- a/api/main_endpoints/routes/PermissionRequest.js +++ b/api/main_endpoints/routes/PermissionRequest.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); const PermissionRequest = require('../models/PermissionRequest'); -const { OK, UNAUTHORIZED, SERVER_ERROR, NOT_FOUND, BAD_REQUEST } = require('../../util/constants').STATUS_CODES; +const { OK, UNAUTHORIZED, FORBIDDEN, SERVER_ERROR, NOT_FOUND, BAD_REQUEST, CONFLICT } = require('../../util/constants').STATUS_CODES; const membershipState = require('../../util/constants.js').MEMBERSHIP_STATE; const { decodeToken } = require('../util/token-functions.js'); const logger = require('../../util/logger'); @@ -17,28 +17,50 @@ router.post('/create', async (req, res) => { } try { - const permissionRequest = await PermissionRequest.create({ + await PermissionRequest.create({ userId: decoded.token._id, type, }); - const populated = await PermissionRequest.findById(permissionRequest._id) - .populate('userId', 'firstName lastName email'); - res.status(OK).send(populated); + res.sendStatus(OK); } catch (error) { - if (error.code === 11000) return res.status(BAD_REQUEST).send({ error: 'Request already exists' }); + if (error.code === 11000) return res.sendStatus(CONFLICT); logger.error('Failed to create permission request:', error); res.sendStatus(SERVER_ERROR); } }); -router.get('/getAll', async (req, res) => { - const decoded = await decodeToken(req, membershipState.OFFICER); +router.get('/get', async (req, res) => { + const decoded = await decodeToken(req, membershipState.MEMBER); if (decoded.status !== OK) return res.sendStatus(decoded.status); + const { userId: queryUserId, type } = req.query; + const isOfficer = decoded.token.accessLevel >= membershipState.OFFICER; + try { - const requests = await PermissionRequest.find({ deletedAt: null }) + const query = { deletedAt: null }; + + // If no userId provided, return all (officer+ only) + if (!queryUserId) { + if (!isOfficer) { + return res.sendStatus(UNAUTHORIZED); + } + } else { + // If userId provided, check permissions + if (!isOfficer && queryUserId !== decoded.token._id.toString()) { + return res.sendStatus(FORBIDDEN); + } + query.userId = queryUserId; + } + + // Optional type filter + if (type && Object.keys(PermissionRequestTypes).includes(type)) { + query.type = type; + } + + const requests = await PermissionRequest.find(query) .populate('userId', 'firstName lastName email') .sort({ createdAt: -1 }); + res.status(OK).send(requests); } catch (error) { logger.error('Failed to get permission requests:', error); @@ -46,40 +68,34 @@ router.get('/getAll', async (req, res) => { } }); -router.get('/get', async (req, res) => { - const decoded = await decodeToken(req, membershipState.MEMBER); - if (decoded.status !== OK) return res.sendStatus(decoded.status); - - try { - const request = await PermissionRequest.findOne({ - userId: decoded.token._id, - type: req.query.type, - deletedAt: null, - }).populate('userId', 'firstName lastName email'); - - if (!request) return res.sendStatus(NOT_FOUND); - res.status(OK).send(request); - } catch (error) { - logger.error('Failed to get permission request:', error); - res.sendStatus(SERVER_ERROR); - } -}); - router.post('/delete', async (req, res) => { const decoded = await decodeToken(req, membershipState.MEMBER); if (decoded.status !== OK) return res.sendStatus(decoded.status); - const { type } = req.body; + const { type, _id } = req.body; if (!type || !Object.keys(PermissionRequestTypes).includes(type)) { return res.status(BAD_REQUEST).send({ error: 'Invalid type' }); } try { - const request = await PermissionRequest.findOne({ - userId: decoded.token._id, - type, - deletedAt: null, - }); + let request; + + // Officers or admins can delete any request by id + if (decoded.token.accessLevel >= membershipState.OFFICER && _id) { + request = await PermissionRequest.findOne({ + _id, + type, + deletedAt: null, + }); + } else { + // Members can delete their own requests and officers can delete their own requests without id + request = await PermissionRequest.findOne({ + userId: decoded.token._id, + type, + deletedAt: null, + }); + } + if (!request) return res.sendStatus(NOT_FOUND); request.deletedAt = new Date(); await request.save(); From 9a70800697d536dbd8fee483003b46bd89915b06 Mon Sep 17 00:00:00 2001 From: Wayne Ngo Date: Thu, 1 Jan 2026 22:17:22 -0800 Subject: [PATCH 4/7] lint fix --- api/main_endpoints/models/PermissionRequest.js | 3 +-- api/main_endpoints/routes/PermissionRequest.js | 7 +++---- test/api/PermissionRequest.js | 12 +++++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/api/main_endpoints/models/PermissionRequest.js b/api/main_endpoints/models/PermissionRequest.js index 4aa8e548d..5d42f4362 100644 --- a/api/main_endpoints/models/PermissionRequest.js +++ b/api/main_endpoints/models/PermissionRequest.js @@ -23,8 +23,7 @@ const PermissionRequestSchema = new Schema( ); // Compound unique index prevents duplicate active requests per user+type -PermissionRequestSchema.index({ userId: 1, type: 1 }, { unique: true, - partialFilterExpression: { deletedAt: null } }); +PermissionRequestSchema.index({ userId: 1, type: 1 }, { unique: true, partialFilterExpression: { deletedAt: null }}); module.exports = mongoose.model('PermissionRequest', PermissionRequestSchema); diff --git a/api/main_endpoints/routes/PermissionRequest.js b/api/main_endpoints/routes/PermissionRequest.js index 480909191..b2ad88eb9 100644 --- a/api/main_endpoints/routes/PermissionRequest.js +++ b/api/main_endpoints/routes/PermissionRequest.js @@ -39,20 +39,20 @@ router.get('/get', async (req, res) => { try { const query = { deletedAt: null }; - // If no userId provided, return all (officer+ only) + // If theres no userId, return all for officers and admins if (!queryUserId) { if (!isOfficer) { return res.sendStatus(UNAUTHORIZED); } } else { - // If userId provided, check permissions + // If there is a userId, check their perms if (!isOfficer && queryUserId !== decoded.token._id.toString()) { return res.sendStatus(FORBIDDEN); } query.userId = queryUserId; } - // Optional type filter + // If there is a type, filter by it if (type && Object.keys(PermissionRequestTypes).includes(type)) { query.type = type; } @@ -79,7 +79,6 @@ router.post('/delete', async (req, res) => { try { let request; - // Officers or admins can delete any request by id if (decoded.token.accessLevel >= membershipState.OFFICER && _id) { request = await PermissionRequest.findOne({ diff --git a/test/api/PermissionRequest.js b/test/api/PermissionRequest.js index d3aa7163d..b518b302a 100644 --- a/test/api/PermissionRequest.js +++ b/test/api/PermissionRequest.js @@ -63,20 +63,22 @@ describe('PermissionRequest', () => { }); describe('/GET get', () => { - it('Should return 404 when request does not exist', async () => { + it('Should return empty array when request does not exist', async () => { const userId = new mongoose.Types.ObjectId(); setTokenStatus(true, { _id: userId, email: 'test@test.com', accessLevel: 'MEMBER' }); - const res = await test.sendGetRequest('/api/PermissionRequest/get?type=' + PermissionRequestTypes.LED_SIGN); - expect(res).to.have.status(NOT_FOUND); + const res = await test.sendGetRequest('/api/PermissionRequest/get?userId=' + userId + '&type=' + PermissionRequestTypes.LED_SIGN); + expect(res).to.have.status(OK); + expect(res.body).to.be.an('array').that.is.empty; }); it('Should return permission request when it exists', async () => { const userId = new mongoose.Types.ObjectId(); setTokenStatus(true, { _id: userId, email: 'test@test.com', accessLevel: 'MEMBER' }); await new PermissionRequest({ userId, type: PermissionRequestTypes.LED_SIGN }).save(); - const res = await test.sendGetRequest('/api/PermissionRequest/get?type=' + PermissionRequestTypes.LED_SIGN); + const res = await test.sendGetRequest('/api/PermissionRequest/get?userId=' + userId + '&type=' + PermissionRequestTypes.LED_SIGN); expect(res).to.have.status(OK); - expect(res.body.type).to.equal(PermissionRequestTypes.LED_SIGN); + expect(res.body).to.be.an('array').with.length(1); + expect(res.body[0].type).to.equal(PermissionRequestTypes.LED_SIGN); }); }); From 30c86ff52b39c8285dca10c4658e110f07d15868 Mon Sep 17 00:00:00 2001 From: Wayne Ngo Date: Sat, 3 Jan 2026 00:55:45 -0800 Subject: [PATCH 5/7] permsision request button for led sign page --- src/APIFunctions/PermissionRequest.js | 59 ++++++++ src/Pages/LedSign/LedSign.js | 197 ++++++++++++++++++-------- src/Routes.js | 2 +- 3 files changed, 198 insertions(+), 60 deletions(-) create mode 100644 src/APIFunctions/PermissionRequest.js diff --git a/src/APIFunctions/PermissionRequest.js b/src/APIFunctions/PermissionRequest.js new file mode 100644 index 000000000..85adcc35a --- /dev/null +++ b/src/APIFunctions/PermissionRequest.js @@ -0,0 +1,59 @@ +import { ApiResponse } from './ApiResponses'; +import { BASE_API_URL } from '../Enums'; + +export async function getPermissionRequest(type, token) { + const status = new ApiResponse(); + const url = new URL('/api/PermissionRequest/get', BASE_API_URL); + url.searchParams.append('type', type); + + try { + const res = await fetch(url.toString(), { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + if (res.ok) { + const data = await res.json(); + status.responseData = data; + } else if (res.status === 404) { + status.responseData = null; + } else { + status.error = true; + } + } catch (err) { + status.responseData = err; + status.error = true; + } + + return status; +} + +export async function createPermissionRequest(type, token) { + const status = new ApiResponse(); + const url = new URL('/api/PermissionRequest/create', BASE_API_URL); + + try { + const res = await fetch(url.toString(), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ type }), + }); + + if (res.ok) { + const data = await res.json(); + status.responseData = data; + } else { + status.error = true; + } + } catch (err) { + status.responseData = err; + status.error = true; + } + + return status; +} + diff --git a/src/Pages/LedSign/LedSign.js b/src/Pages/LedSign/LedSign.js index a0cd1dfe7..1b9b1baed 100644 --- a/src/Pages/LedSign/LedSign.js +++ b/src/Pages/LedSign/LedSign.js @@ -1,6 +1,8 @@ import React, { useState, useEffect } from 'react'; import { healthCheck, updateSignText } from '../../APIFunctions/LedSign'; +import { getPermissionRequest, createPermissionRequest } from '../../APIFunctions/PermissionRequest'; import { useSCE } from '../../Components/context/SceContext'; +import { membershipState } from '../../Enums'; import './ledsign.css'; @@ -20,6 +22,9 @@ function LedSign() { const [awaitingSignResponse, setAwaitingSignResponse] = useState(false); const [requestSuccessful, setRequestSuccessful] = useState(); const [stopRequestSuccesful, setStopRequestSuccesful] = useState(); + const [permissionRequest, setPermissionRequest] = useState(null); + const [checkingPermission, setCheckingPermission] = useState(false); + const [requestingPermission, setRequestingPermission] = useState(false); const inputArray = [ { title: 'Sign Text:', @@ -211,11 +216,24 @@ function LedSign() { } setLoading(false); } + + async function checkPermission() { + if (user.accessLevel < membershipState.OFFICER) { + setCheckingPermission(true); + const result = await getPermissionRequest('LED_SIGN', user.token); + if (!result.error && result.responseData) { + setPermissionRequest(result.responseData); + } + setCheckingPermission(false); + } + } + checkSignHealth(); + checkPermission(); // eslint-disable-next-line }, []) - if (loading) { + if (loading || checkingPermission) { return ( @@ -231,6 +249,60 @@ function LedSign() { ); } + async function handleRequestAccess() { + setRequestingPermission(true); + const result = await createPermissionRequest('LED_SIGN', user.token); + if (!result.error) { + setPermissionRequest(result.responseData); + } + setRequestingPermission(false); + } + + function renderPermissionRequestUI() { + if (user.accessLevel >= membershipState.OFFICER) { + return null; + } + + if (checkingPermission) { + return ( +
+

Checking access...

+
+ ); + } + + if (permissionRequest) { + return ( +
+

+ You requested access to the sign on {getFormattedTime(permissionRequest.createdAt)}. +

+

+ Drop a message in Discord to speed up the process! +

+
+ ); + } + + return ( +
+

+ You need permission to access the LED sign. +

+ +

+ Drop a message in Discord to speed up the process +

+
+ ); + } + function getAnimationDuration() { // the scrollSpeed input can be can be anywhere from 0 to 10. the // lower the duration is, the faster the text scrolls. we divide by @@ -239,80 +311,87 @@ function LedSign() { return (11 - scrollSpeed); } + const hasAccess = user.accessLevel >= membershipState.OFFICER; + return (
-
-
-
-
- -
-
-
-
-
-

- {/* + {!hasAccess && ( +
+ {renderPermissionRequestUI()} +
+ )} + {hasAccess && ( +
+
+
+
+ +
+
+
+
+
+

+ {/* we add a padding of 28 characters of whitespace so the entire message scrolls to the end of the preview before repeating. the preview has a width of about 28 characters. */} - {text.padEnd(28, ' ')} -

+ {text.padEnd(28, ' ')} +

+
+
-
-
- {maybeShowExpirationDate()} - {getExpirationButtonOrInput()} - { - inputArray.map(({ - id, - title, - type, - value, - onChange, - ...rest - }) => ( -
-
- - + {maybeShowExpirationDate()} + {getExpirationButtonOrInput()} + { + inputArray.map(({ + id, + title, + type, + value, + onChange, + ...rest + }) => ( +
+
+ + +
-
- )) - } + )) + } - - + - {renderRequestStatus()} + + {renderRequestStatus()} +
- -
- + )} ); } diff --git a/src/Routes.js b/src/Routes.js index 379435419..b225442a9 100644 --- a/src/Routes.js +++ b/src/Routes.js @@ -105,7 +105,7 @@ export const officerOrAdminRoutes = [ Component: LedSign, path: '/led-sign', pageName: 'LED Sign', - allowedIf: allowedIf.OFFICER_OR_ADMIN, + allowedIf: allowedIf.MEMBER, redirect: '/', inAdminNavbar: true }, From 8db95fb66b03d8eb51cb2605d200ec8a71bdac88 Mon Sep 17 00:00:00 2001 From: Wayne Ngo Date: Sat, 3 Jan 2026 01:07:05 -0800 Subject: [PATCH 6/7] Revert "permsision request button for led sign page" (#1989) This reverts commit 30c86ff52b39c8285dca10c4658e110f07d15868. --- src/APIFunctions/PermissionRequest.js | 59 -------- src/Pages/LedSign/LedSign.js | 197 ++++++++------------------ src/Routes.js | 2 +- 3 files changed, 60 insertions(+), 198 deletions(-) delete mode 100644 src/APIFunctions/PermissionRequest.js diff --git a/src/APIFunctions/PermissionRequest.js b/src/APIFunctions/PermissionRequest.js deleted file mode 100644 index 85adcc35a..000000000 --- a/src/APIFunctions/PermissionRequest.js +++ /dev/null @@ -1,59 +0,0 @@ -import { ApiResponse } from './ApiResponses'; -import { BASE_API_URL } from '../Enums'; - -export async function getPermissionRequest(type, token) { - const status = new ApiResponse(); - const url = new URL('/api/PermissionRequest/get', BASE_API_URL); - url.searchParams.append('type', type); - - try { - const res = await fetch(url.toString(), { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - - if (res.ok) { - const data = await res.json(); - status.responseData = data; - } else if (res.status === 404) { - status.responseData = null; - } else { - status.error = true; - } - } catch (err) { - status.responseData = err; - status.error = true; - } - - return status; -} - -export async function createPermissionRequest(type, token) { - const status = new ApiResponse(); - const url = new URL('/api/PermissionRequest/create', BASE_API_URL); - - try { - const res = await fetch(url.toString(), { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, - }, - body: JSON.stringify({ type }), - }); - - if (res.ok) { - const data = await res.json(); - status.responseData = data; - } else { - status.error = true; - } - } catch (err) { - status.responseData = err; - status.error = true; - } - - return status; -} - diff --git a/src/Pages/LedSign/LedSign.js b/src/Pages/LedSign/LedSign.js index 1b9b1baed..a0cd1dfe7 100644 --- a/src/Pages/LedSign/LedSign.js +++ b/src/Pages/LedSign/LedSign.js @@ -1,8 +1,6 @@ import React, { useState, useEffect } from 'react'; import { healthCheck, updateSignText } from '../../APIFunctions/LedSign'; -import { getPermissionRequest, createPermissionRequest } from '../../APIFunctions/PermissionRequest'; import { useSCE } from '../../Components/context/SceContext'; -import { membershipState } from '../../Enums'; import './ledsign.css'; @@ -22,9 +20,6 @@ function LedSign() { const [awaitingSignResponse, setAwaitingSignResponse] = useState(false); const [requestSuccessful, setRequestSuccessful] = useState(); const [stopRequestSuccesful, setStopRequestSuccesful] = useState(); - const [permissionRequest, setPermissionRequest] = useState(null); - const [checkingPermission, setCheckingPermission] = useState(false); - const [requestingPermission, setRequestingPermission] = useState(false); const inputArray = [ { title: 'Sign Text:', @@ -216,24 +211,11 @@ function LedSign() { } setLoading(false); } - - async function checkPermission() { - if (user.accessLevel < membershipState.OFFICER) { - setCheckingPermission(true); - const result = await getPermissionRequest('LED_SIGN', user.token); - if (!result.error && result.responseData) { - setPermissionRequest(result.responseData); - } - setCheckingPermission(false); - } - } - checkSignHealth(); - checkPermission(); // eslint-disable-next-line }, []) - if (loading || checkingPermission) { + if (loading) { return ( @@ -249,60 +231,6 @@ function LedSign() { ); } - async function handleRequestAccess() { - setRequestingPermission(true); - const result = await createPermissionRequest('LED_SIGN', user.token); - if (!result.error) { - setPermissionRequest(result.responseData); - } - setRequestingPermission(false); - } - - function renderPermissionRequestUI() { - if (user.accessLevel >= membershipState.OFFICER) { - return null; - } - - if (checkingPermission) { - return ( -
-

Checking access...

-
- ); - } - - if (permissionRequest) { - return ( -
-

- You requested access to the sign on {getFormattedTime(permissionRequest.createdAt)}. -

-

- Drop a message in Discord to speed up the process! -

-
- ); - } - - return ( -
-

- You need permission to access the LED sign. -

- -

- Drop a message in Discord to speed up the process -

-
- ); - } - function getAnimationDuration() { // the scrollSpeed input can be can be anywhere from 0 to 10. the // lower the duration is, the faster the text scrolls. we divide by @@ -311,87 +239,80 @@ function LedSign() { return (11 - scrollSpeed); } - const hasAccess = user.accessLevel >= membershipState.OFFICER; - return (
- {!hasAccess && ( -
- {renderPermissionRequestUI()} -
- )} - {hasAccess && ( -
-
-
-
- -
-
-
-
-
-

- {/* +
+
+
+
+ +
+
+
+
+
+

+ {/* we add a padding of 28 characters of whitespace so the entire message scrolls to the end of the preview before repeating. the preview has a width of about 28 characters. */} - {text.padEnd(28, ' ')} -

-
+ {text.padEnd(28, ' ')} +

-
+
- {maybeShowExpirationDate()} - {getExpirationButtonOrInput()} - { - inputArray.map(({ - id, - title, - type, - value, - onChange, - ...rest - }) => ( -
-
- - -
+
+ {maybeShowExpirationDate()} + {getExpirationButtonOrInput()} + { + inputArray.map(({ + id, + title, + type, + value, + onChange, + ...rest + }) => ( +
+
+ +
- )) - } +
+ )) + } - - + - {renderRequestStatus()} -
+ + {renderRequestStatus()}
- )} + +
+
); } diff --git a/src/Routes.js b/src/Routes.js index b225442a9..379435419 100644 --- a/src/Routes.js +++ b/src/Routes.js @@ -105,7 +105,7 @@ export const officerOrAdminRoutes = [ Component: LedSign, path: '/led-sign', pageName: 'LED Sign', - allowedIf: allowedIf.MEMBER, + allowedIf: allowedIf.OFFICER_OR_ADMIN, redirect: '/', inAdminNavbar: true }, From a1bd28f870310feb2787de87dd529b740d3d0391 Mon Sep 17 00:00:00 2001 From: Wayne Ngo Date: Sun, 4 Jan 2026 17:46:41 -0800 Subject: [PATCH 7/7] query by id change --- api/main_endpoints/routes/PermissionRequest.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/api/main_endpoints/routes/PermissionRequest.js b/api/main_endpoints/routes/PermissionRequest.js index b2ad88eb9..50c0a70a9 100644 --- a/api/main_endpoints/routes/PermissionRequest.js +++ b/api/main_endpoints/routes/PermissionRequest.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); const PermissionRequest = require('../models/PermissionRequest'); -const { OK, UNAUTHORIZED, FORBIDDEN, SERVER_ERROR, NOT_FOUND, BAD_REQUEST, CONFLICT } = require('../../util/constants').STATUS_CODES; +const { OK, UNAUTHORIZED, SERVER_ERROR, NOT_FOUND, BAD_REQUEST, CONFLICT } = require('../../util/constants').STATUS_CODES; const membershipState = require('../../util/constants.js').MEMBERSHIP_STATE; const { decodeToken } = require('../util/token-functions.js'); const logger = require('../../util/logger'); @@ -45,11 +45,11 @@ router.get('/get', async (req, res) => { return res.sendStatus(UNAUTHORIZED); } } else { - // If there is a userId, check their perms - if (!isOfficer && queryUserId !== decoded.token._id.toString()) { - return res.sendStatus(FORBIDDEN); + if (isOfficer) { + query.userId = queryUserId; + } else { + query.userId = decoded.token._id.toString(); } - query.userId = queryUserId; } // If there is a type, filter by it