Skip to content

Add PermissionRequest schema and API for LED sign access requests#1972

Merged
evanugarte merged 12 commits into
devfrom
LED4MEMBERS
Mar 3, 2026
Merged

Add PermissionRequest schema and API for LED sign access requests#1972
evanugarte merged 12 commits into
devfrom
LED4MEMBERS

Conversation

@wayngo

@wayngo wayngo commented Dec 27, 2025

Copy link
Copy Markdown
Collaborator
  • 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

@charred-70 charred-70 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm w tree

@adarshm11 adarshm11 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is that a banana

type: Schema.Types.ObjectId,
ref: 'User',
required: true,
index: true,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we can remove this, we have the PermissionRequestSchema.index to take care of this

},
type: {
type: String,
enum: Object.keys(PermissionRequestTypes),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

i know they will be a 1 to 1 mapping, we will use the values when we create new data though

Suggested change
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 });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

instead of { unique: true } can it be

{ 
    unique: true, 
    partialFilterExpression: { deletedAt: null } 
}

that way we dont index deleted records

Comment on lines +24 to +26
const populated = await PermissionRequest.findById(permissionRequest._id)
.populate('userId', 'firstName lastName email');
res.status(OK).send(populated);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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' });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we have CONFLICT instead of BAD_REQUEST

we can send that and no text back, since conflict implies something exists

Comment thread api/main_endpoints/routes/PermissionRequest.js
}
});

router.get('/getAll', async (req, res) => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

how about / returns everything, up to you

}
});

router.get('/get', async (req, res) => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

Comment on lines +42 to +53
// 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;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

@evanugarte
evanugarte self-requested a review January 11, 2026 07:32

const { type, _id } = req.body;
if (!type || !Object.keys(PermissionRequestTypes).includes(type)) {
return res.status(BAD_REQUEST).send({ error: 'Invalid type' });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
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)}` });

Comment on lines +76 to +91
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,
});
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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);

Copy link
Copy Markdown
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

}
});

router.get('/get', async (req, res) => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
router.get('/get', async (req, res) => {
router.get('/', async (req, res) => {

Comment on lines +42 to +48
if (!isOfficer) {
query.userId = decoded.token._id.toString();
} else {
if (queryUserId) {
query.userId = queryUserId;
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this should also work

Suggested change
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();
}

@evanugarte
evanugarte self-requested a review January 23, 2026 19:28
@evanugarte
evanugarte merged commit 2639303 into dev Mar 3, 2026
4 checks passed
@evanugarte
evanugarte deleted the LED4MEMBERS branch March 3, 2026 05:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants