Skip to content

Commit 26d91c6

Browse files
wayngoevanugarte
andauthored
Change ui on led sign page to have a tab, if the user is an officer or admin (#1988)
* getting closer * are we winning * bring back led sign in admin navbar for fun * back to arrays * getPermissionRequests * and yeah it works * tests work * lint * more lint --------- Co-authored-by: evan <evanuxd@gmail.com>
1 parent f979019 commit 26d91c6

9 files changed

Lines changed: 409 additions & 92 deletions

File tree

api/main_endpoints/models/PermissionRequest.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ const PermissionRequestSchema = new Schema(
99
ref: 'User',
1010
required: true,
1111
},
12+
status: {
13+
type: String,
14+
enum: ['PENDING', 'APPROVED', 'DENIED', 'REVOKED'],
15+
default: 'PENDING'
16+
},
1217
type: {
1318
type: String,
1419
enum: Object.values(PermissionRequestTypes),

api/main_endpoints/routes/LedSign.js

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const router = express.Router();
33
const {
44
OK,
55
SERVER_ERROR,
6-
UNAUTHORIZED
6+
FORBIDDEN,
77
} = require('../../util/constants').STATUS_CODES;
88
const { decodeToken } = require('../util/token-functions.js');
99
const logger = require('../../util/logger');
@@ -14,6 +14,8 @@ const {
1414
LED_SIGN = {}
1515
} = require('../../config/config.json');
1616
const { MEMBERSHIP_STATE } = require('../../util/constants.js');
17+
const PermissionRequest = require('../models/PermissionRequest');
18+
1719

1820
const runningInTest = process.env.NODE_ENV === 'test';
1921

@@ -35,8 +37,30 @@ router.get('/healthCheck', async (req, res) => {
3537
});
3638

3739
router.post('/updateSignText', async (req, res) => {
38-
const decoded = await decodeToken(req, MEMBERSHIP_STATE.OFFICER);
39-
if (decoded.status !== OK) {
40+
let decoded = await decodeToken(req, MEMBERSHIP_STATE.OFFICER);
41+
if (decoded.status === FORBIDDEN) {
42+
const memberDecoded = await decodeToken(req, MEMBERSHIP_STATE.MEMBER);
43+
if (memberDecoded.status !== OK) {
44+
// return whatever decoded status we originally had
45+
return res.sendStatus(decoded.status);
46+
}
47+
48+
try {
49+
const hasPermission = await PermissionRequest.findOne({
50+
userId: memberDecoded.token._id,
51+
type: 'LED_SIGN',
52+
status: 'APPROVED',
53+
deletedAt: null
54+
});
55+
if (!hasPermission) {
56+
return res.sendStatus(decoded.status);
57+
}
58+
// "elevate" the status to OK for the rest of the function
59+
decoded = memberDecoded;
60+
} catch(e) {
61+
logger.info('looking for a possible led sign permission didnt work', e);
62+
}
63+
} else if (decoded.status !== OK) {
4064
logger.warn('/updateSignText was requested with an invalid token');
4165
return res.sendStatus(decoded.status);
4266
}

api/main_endpoints/routes/PermissionRequest.js

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ router.post('/create', async (req, res) => {
2020
await PermissionRequest.create({
2121
userId: decoded.token._id,
2222
type,
23+
status: 'PENDING',
2324
});
2425
res.sendStatus(OK);
2526
} catch (error) {
@@ -66,37 +67,55 @@ router.post('/delete', async (req, res) => {
6667
const decoded = await decodeToken(req, membershipState.MEMBER);
6768
if (decoded.status !== OK) return res.sendStatus(decoded.status);
6869

69-
const { type, _id } = req.body;
70-
if (!type || !Object.keys(PermissionRequestTypes).includes(type)) {
71-
return res.status(BAD_REQUEST).send({ error: `${type} is an invalid type, try
72-
${Object.keys(PermissionRequestTypes)}` });
73-
}
70+
const { _id } = req.body;
71+
const isOfficer = decoded.token.accessLevel >= membershipState.OFFICER;
7472

7573
try {
76-
let idToUse = _id;
77-
78-
if (!idToUse) {
79-
idToUse = decoded.token._id;
80-
}
81-
82-
if (decoded.token.accessLevel < membershipState.OFFICER) {
83-
idToUse = decoded.token._id;
84-
}
85-
8674
const query = {
87-
_id: idToUse,
88-
type,
75+
_id,
8976
deletedAt: null,
9077
};
9178

92-
const request = await PermissionRequest.findOne(query);
79+
if (!isOfficer) {
80+
query.userId = decoded.token._id;
81+
query.status = 'PENDING';
82+
}
9383

84+
const request = await PermissionRequest.findOne(query);
9485
if (!request) return res.sendStatus(NOT_FOUND);
86+
87+
// if the officer deletes a pending request, consider it denied.
88+
// if a user deletes their pending request, consider they gave up asking
89+
if (request.status === 'PENDING' && isOfficer) {
90+
request.status = 'DENIED';
91+
} else if (request.status === 'APPROVED') {
92+
request.status = 'REVOKED';
93+
}
94+
9595
request.deletedAt = new Date();
9696
await request.save();
9797
res.sendStatus(OK);
9898
} catch (error) {
99-
logger.error('Failed to delete permission request:', error);
99+
logger.error('Failed to mark permission request as deleted:', error);
100+
res.sendStatus(SERVER_ERROR);
101+
}
102+
});
103+
104+
router.post('/approve', async (req, res) => {
105+
const decoded = await decodeToken(req, membershipState.OFFICER);
106+
if (decoded.status !== OK) return res.sendStatus(decoded.status);
107+
108+
const { _id } = req.body;
109+
110+
try {
111+
const request = await PermissionRequest.findOne({ _id, status: 'PENDING' });
112+
if (!request) return res.status(NOT_FOUND).send({ error: 'Pending request not found' });
113+
114+
request.status = 'APPROVED';
115+
await request.save();
116+
res.sendStatus(OK);
117+
} catch (error) {
118+
logger.error('Failed to approve permission request:', error);
100119
res.sendStatus(SERVER_ERROR);
101120
}
102121
});

src/APIFunctions/PermissionRequest.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export async function createPermissionRequest(type, token) {
4040
body: JSON.stringify({ type }),
4141
});
4242

43-
status.error = !!res.ok;
43+
status.error = !res.ok;
4444
if (res.ok || res.status === 409) {
4545
// Backend sends 200 with no body on success, so fetch the created request
4646
const existingRequest = await getPermissionRequests(type, token);
@@ -53,3 +53,41 @@ export async function createPermissionRequest(type, token) {
5353

5454
return status;
5555
}
56+
57+
export async function approvePermissionRequest(type, id, token) {
58+
const status = new ApiResponse();
59+
const url = new URL('/api/PermissionRequest/approve', BASE_API_URL);
60+
try {
61+
const res = await fetch(url.toString(), {
62+
method: 'POST',
63+
headers: {
64+
'Content-Type': 'application/json',
65+
Authorization: `Bearer ${token}`,
66+
},
67+
body: JSON.stringify({ type, _id: id }),
68+
});
69+
status.error = !res.ok;
70+
} catch (err) {
71+
status.error = true;
72+
}
73+
return status;
74+
}
75+
76+
export async function deletePermissionRequest(id, token) {
77+
const status = new ApiResponse();
78+
const url = new URL('/api/PermissionRequest/delete', BASE_API_URL);
79+
try {
80+
const res = await fetch(url.toString(), {
81+
method: 'POST',
82+
headers: {
83+
'Content-Type': 'application/json',
84+
Authorization: `Bearer ${token}`,
85+
},
86+
body: JSON.stringify({ _id: id }),
87+
});
88+
status.error = !res.ok;
89+
} catch (err) {
90+
status.error = true;
91+
}
92+
return status;
93+
}

src/Components/Navbar/AdminNavbar.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,26 @@ export default function UserNavBar(props) {
9191
</svg>
9292
)
9393
},
94+
{
95+
title: 'Permission Requests',
96+
route: '/permissions',
97+
icon: (
98+
<svg
99+
xmlns="http://www.w3.org/2000/svg"
100+
fill="none"
101+
viewBox="0 0 24 24"
102+
strokeWidth="1.5"
103+
stroke="currentColor"
104+
className="w-6 h-6"
105+
>
106+
<path
107+
strokeLinecap="round"
108+
strokeLinejoin="round"
109+
d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z"
110+
/>
111+
</svg>
112+
)
113+
},
94114
{
95115
title: 'Audit Logs',
96116
route: '/audit-logs',

src/Pages/LedSign/LedSign.js

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,9 @@ function LedSign() {
258258
setRequestingPermission(true);
259259
const result = await createPermissionRequest('LED_SIGN', user.token);
260260
if (!result.error) {
261-
setPermissionRequest(result.responseData);
261+
setPermissionRequest({
262+
status: 'PENDING',
263+
});
262264
}
263265
setRequestingPermission(false);
264266
}
@@ -276,7 +278,24 @@ function LedSign() {
276278
);
277279
}
278280

279-
if (permissionRequest) {
281+
if (!permissionRequest) {
282+
return (
283+
<div className="w-2/3 lg:w-1/2 text-center py-4 space-y-2 fade-in">
284+
<p className="text-gray-700 dark:text-gray-300">
285+
You need permission to access the LED sign.
286+
</p>
287+
<button
288+
className="btn bg-blue-500 hover:bg-blue-400 text-white"
289+
onClick={handleRequestAccess}
290+
disabled={requestingPermission}
291+
>
292+
{requestingPermission ? 'Requesting...' : 'Request Access'}
293+
</button>
294+
</div>
295+
);
296+
}
297+
298+
if (permissionRequest.status === 'PENDING') {
280299
return (
281300
<div className="w-2/3 lg:w-1/2 text-center py-4 space-y-2 fade-in">
282301
<p className="text-gray-700 dark:text-gray-300">
@@ -289,20 +308,7 @@ function LedSign() {
289308
);
290309
}
291310

292-
return (
293-
<div className="w-2/3 lg:w-1/2 text-center py-4 space-y-2 fade-in">
294-
<p className="text-gray-700 dark:text-gray-300">
295-
You need permission to access the LED sign.
296-
</p>
297-
<button
298-
className="btn bg-blue-500 hover:bg-blue-400 text-white"
299-
onClick={handleRequestAccess}
300-
disabled={requestingPermission}
301-
>
302-
{requestingPermission ? 'Requesting...' : 'Request Access'}
303-
</button>
304-
</div>
305-
);
311+
return <h1>{JSON.stringify(permissionRequest)}</h1>;
306312
}
307313

308314
function renderSignControls() {
@@ -367,7 +373,7 @@ function LedSign() {
367373
return (
368374
<div className="flex justify-center items-center mt-10 w-full">
369375
<div className="space-y-12 gap-x-6 gap-y-8 w-full flex flex-col items-center">
370-
{user.accessLevel >= membershipState.OFFICER
376+
{user.accessLevel >= membershipState.OFFICER || permissionRequest?.status === 'APPROVED'
371377
? renderSignControls()
372378
: renderPermissionRequestUI()
373379
}

0 commit comments

Comments
 (0)