Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion api/main_endpoints/models/PermissionRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ const PermissionRequestSchema = new Schema(
enum: Object.values(PermissionRequestTypes),
required: true,
},
approved: {
type: Boolean,
default: false,
},
deletedAt: {
type: Date,
default: null,
Expand All @@ -23,7 +27,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, approved: false }});

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

35 changes: 34 additions & 1 deletion api/main_endpoints/routes/PermissionRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,20 @@ router.get('/get', async (req, res) => {
try {
const query = { deletedAt: null };

// If theres no userId, return all for officers and admins
// If theres no userId, return all for officers and admins (only pending requests)
if (!queryUserId) {
if (!isOfficer) {
return res.sendStatus(UNAUTHORIZED);
}
// For admin view, only show pending (non-approved) requests
query.approved = false;
} else {
// If there is a userId, check their perms
if (!isOfficer && queryUserId !== decoded.token._id.toString()) {
return res.sendStatus(FORBIDDEN);
}
query.userId = queryUserId;
// For member's own request, return it regardless of approval status
}

// If there is a type, filter by it
Expand All @@ -68,6 +71,36 @@ router.get('/get', async (req, res) => {
}
});

router.post('/approve', async (req, res) => {
const decoded = await decodeToken(req, membershipState.OFFICER);
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: 'Invalid type' });
}

if (!_id) {
return res.status(BAD_REQUEST).send({ error: '_id is required' });
}

try {
const request = await PermissionRequest.findOne({
_id,
type,
deletedAt: null,
});

if (!request) return res.sendStatus(NOT_FOUND);
request.approved = true;
await request.save();
res.sendStatus(OK);
} catch (error) {
logger.error('Failed to approve 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);
Expand Down
97 changes: 93 additions & 4 deletions src/APIFunctions/PermissionRequest.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { ApiResponse } from './ApiResponses';
import { BASE_API_URL } from '../Enums';

export async function getPermissionRequest(type, token) {
export async function getPermissionRequest(type, userId, token) {
const status = new ApiResponse();
const url = new URL('/api/PermissionRequest/get', BASE_API_URL);
url.searchParams.append('type', type);
if (userId) {
url.searchParams.append('userId', userId);
}

try {
const res = await fetch(url.toString(), {
Expand All @@ -15,11 +18,11 @@ export async function getPermissionRequest(type, token) {

if (res.ok) {
const data = await res.json();
status.responseData = data;
} else if (res.status === 404) {
status.responseData = null;
// API returns an array, return first item or null
status.responseData = Array.isArray(data) && data.length > 0 ? data[0] : null;
} else {
status.error = true;
status.responseData = null;
}
} catch (err) {
status.responseData = err;
Expand All @@ -43,6 +46,38 @@ export async function createPermissionRequest(type, token) {
body: JSON.stringify({ type }),
});

if (res.ok) {
// API returns 200 with no body, so we just mark success
status.responseData = true;
} else if (res.status === 409) {
// CONFLICT - duplicate request
status.error = true;
status.responseData = 'Request already exists';
} else {
status.error = true;
}
} catch (err) {
status.responseData = err;
status.error = true;
}

return status;
}

export async function getAllPermissionRequests(type, token) {
const status = new ApiResponse();
const url = new URL('/api/PermissionRequest/get', BASE_API_URL);
if (type) {
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;
Expand All @@ -57,3 +92,57 @@ export async function createPermissionRequest(type, token) {
return status;
}

export async function approvePermissionRequest(type, id, token) {
const status = new ApiResponse();
const url = new URL('/api/PermissionRequest/approve', BASE_API_URL);

try {
const res = await fetch(url.toString(), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ type, _id: id }),
});

if (res.ok) {
status.responseData = true;
} else {
status.error = true;
}
} catch (err) {
status.responseData = err;
status.error = true;
}

return status;
}

export async function deletePermissionRequest(type, id, token) {
const status = new ApiResponse();
const url = new URL('/api/PermissionRequest/delete', BASE_API_URL);

try {
const res = await fetch(url.toString(), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ type, _id: id }),
});

if (res.ok) {
status.responseData = true;
} else {
status.error = true;
}
} catch (err) {
status.responseData = err;
status.error = true;
}

return status;
}

Loading