Add PermissionRequest schema and API for LED sign access requests#1972
Add PermissionRequest schema and API for LED sign access requests#1972Embotic-Wayne wants to merge 12 commits intodevfrom
Conversation
Embotic-Wayne
commented
Dec 27, 2025
- added mongodb schema for user id, type, and when its deleted (excluding duplicates)
- new api to create, read, and delete permission requests sent by members
- unit tests to validate their behavior
| type: Schema.Types.ObjectId, | ||
| ref: 'User', | ||
| required: true, | ||
| index: true, |
There was a problem hiding this comment.
we can remove this, we have the PermissionRequestSchema.index to take care of this
| }, | ||
| type: { | ||
| type: String, | ||
| enum: Object.keys(PermissionRequestTypes), |
There was a problem hiding this comment.
i know they will be a 1 to 1 mapping, we will use the values when we create new data though
| enum: Object.keys(PermissionRequestTypes), | |
| enum: Object.values(PermissionRequestTypes), |
| ); | ||
|
|
||
| // Compound unique index prevents duplicate active requests per user+type | ||
| PermissionRequestSchema.index({ userId: 1, type: 1 }, { unique: true }); |
There was a problem hiding this comment.
instead of { unique: true } can it be
{
unique: true,
partialFilterExpression: { deletedAt: null }
}that way we dont index deleted records
| const populated = await PermissionRequest.findById(permissionRequest._id) | ||
| .populate('userId', 'firstName lastName email'); | ||
| res.status(OK).send(populated); |
There was a problem hiding this comment.
we can also return an empty 200, unless we want to do something with this data in the frontend after submitting
| .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' }); |
There was a problem hiding this comment.
we have CONFLICT instead of BAD_REQUEST
we can send that and no text back, since conflict implies something exists
| } | ||
| }); | ||
|
|
||
| router.get('/getAll', async (req, res) => { |
There was a problem hiding this comment.
how about / returns everything, up to you
| } | ||
| }); | ||
|
|
||
| router.get('/get', async (req, res) => { |
There was a problem hiding this comment.
can we consolidate both endpoints into one /
the endpoint can either take a parameter like userId, or nothing
if we didnt pass any filters in, return everything if the user is an officer or higher
if we passed in a user id, return the data if
- the user is less than an officer and its their own user id.
- the user is an officer or higher
| // If theres no userId, return all for officers and admins | ||
| if (!queryUserId) { | ||
| if (!isOfficer) { | ||
| return res.sendStatus(UNAUTHORIZED); | ||
| } | ||
| } else { | ||
| // If there is a userId, check their perms | ||
| if (!isOfficer && queryUserId !== decoded.token._id.toString()) { | ||
| return res.sendStatus(FORBIDDEN); | ||
| } | ||
| query.userId = queryUserId; | ||
| } |
There was a problem hiding this comment.
sorry how about this
if the user is not an officer, dont listen to any id they send them, and just use decoded.token._id.toString() as what to query for
|
|
||
| const { type, _id } = req.body; | ||
| if (!type || !Object.keys(PermissionRequestTypes).includes(type)) { | ||
| return res.status(BAD_REQUEST).send({ error: 'Invalid type' }); |
There was a problem hiding this comment.
| return res.status(BAD_REQUEST).send({ error: 'Invalid type' }); | |
| return res.status(BAD_REQUEST).send({ error: `${type} is an invalid type, try ${Object.keys(PermissionRequestTypes)}` }); |
| 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, | ||
| }); | ||
| } |
There was a problem hiding this comment.
lets do this like below, also before this, lets make sure _id is set, and if it isnt, use the decoded.token._id
let idToUse = _id;
if (decoded.token.accessLevel < membershipState.OFFICER) {
idToUse = decoded.token._id;
}
const query = {
_id: idToUse,
type,
deletedAt: null,
};
const request = await PermissionRequest.findOne({
userId: decoded.token._id,
type,
deletedAt: null,
});| }); | ||
| } | ||
|
|
||
| if (!request) return res.sendStatus(NOT_FOUND); |
There was a problem hiding this comment.
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
| } | ||
| }); | ||
|
|
||
| router.get('/get', async (req, res) => { |
There was a problem hiding this comment.
| router.get('/get', async (req, res) => { | |
| router.get('/', async (req, res) => { |
| if (!isOfficer) { | ||
| query.userId = decoded.token._id.toString(); | ||
| } else { | ||
| if (queryUserId) { | ||
| query.userId = queryUserId; | ||
| } | ||
| } |
There was a problem hiding this comment.
this should also work
| if (!isOfficer) { | |
| query.userId = decoded.token._id.toString(); | |
| } else { | |
| if (queryUserId) { | |
| query.userId = queryUserId; | |
| } | |
| } | |
| if (queryUserId) { | |
| query.userId = queryUserId; | |
| } | |
| if (!isOfficer) { | |
| query.userId = decoded.token._id.toString(); | |
| } |