Skip to content
29 changes: 29 additions & 0 deletions api/main_endpoints/models/PermissionRequest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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,
},
type: {
type: String,
enum: Object.values(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, partialFilterExpression: { deletedAt: null }});

module.exports = mongoose.model('PermissionRequest', PermissionRequestSchema);

105 changes: 105 additions & 0 deletions api/main_endpoints/routes/PermissionRequest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
const express = require('express');
const router = express.Router();
const PermissionRequest = require('../models/PermissionRequest');
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');
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 {
await PermissionRequest.create({
userId: decoded.token._id,
type,
});
res.sendStatus(OK);
} catch (error) {
if (error.code === 11000) return res.sendStatus(CONFLICT);
logger.error('Failed to create permission request:', error);
res.sendStatus(SERVER_ERROR);
}
});

router.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 query = { deletedAt: null };

if (queryUserId) {
query.userId = queryUserId;
}
if (!isOfficer) {
query.userId = decoded.token._id.toString();
}

// If there is a type, filter by it
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);
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, _id } = req.body;
if (!type || !Object.keys(PermissionRequestTypes).includes(type)) {
return res.status(BAD_REQUEST).send({ error: `${type} is an invalid type, try
${Object.keys(PermissionRequestTypes)}` });
}

try {
let idToUse = _id;

if (!idToUse) {
idToUse = decoded.token._id;
}

if (decoded.token.accessLevel < membershipState.OFFICER) {
idToUse = decoded.token._id;
}

const query = {
_id: idToUse,
type,
deletedAt: null,
};

const request = await PermissionRequest.findOne(query);

if (!request) return res.sendStatus(NOT_FOUND);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we sure if no record if found, it returns null? lets add a unit test to /delete to ensure 404 is returned when we search for an _id that doesnt exist

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;

6 changes: 6 additions & 0 deletions api/main_endpoints/util/permissionRequestTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const PermissionRequestTypes = {
LED_SIGN: 'LED_SIGN',
};

module.exports = PermissionRequestTypes;

Loading