-
Notifications
You must be signed in to change notification settings - Fork 17
Add admission service #101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 12 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
fdeafb5
finished get and put of admission service
npunati27 6a47437
got rid of jetbrain config files
npunati27 aac9cf7
remove nits
npunati27 5617c3b
add .idea to gitignire
npunati27 c73dfab
finish admission MVP
npunati27 7e26d5d
Merge branch 'main' of github.com:HackIllinois/adonix into dev/nikhit…
npunati27 70f9fe0
formatting issues
npunati27 153672f
changed formatting again
npunati27 77fe2c4
fixed comments and started tests
npunati27 da26c19
added tests
npunati27 ab7697d
added tests
npunati27 204a20b
resolved comments
npunati27 22077bf
Merge branch 'main' of github.com:HackIllinois/adonix into dev/nikhit…
npunati27 dd4fa56
resolved comments
npunati27 b32bcf2
fixed linter issue
npunati27 1bb44a3
formatter fix
npunati27 9ea3b9f
updated admission formats
npunati27 4a66044
Merge branch 'main' of github.com:HackIllinois/adonix into dev/nikhit…
npunati27 cf63b59
fixed nps
npunati27 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
export interface ApplicantDecisionFormat { | ||
_id?: string; | ||
npunati27 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
userId: string; | ||
name: string; | ||
status: DecisionStatus; | ||
} | ||
|
||
export enum DecisionStatus { | ||
TBD = "TBD", | ||
ACCEPTED = "ACCEPTED", | ||
REJECTED = "REJECTED", | ||
WAITLISTED = "WAITLISTED", | ||
} | ||
export enum DecisionResponse { | ||
PENDING = "PENDING", | ||
ACCEPTED = "ACCEPTED", | ||
DECLINED = "DECLINED", | ||
} | ||
npunati27 marked this conversation as resolved.
Show resolved
Hide resolved
|
npunati27 marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { beforeEach, describe, expect, it } from "@jest/globals"; | ||
import Models from "../../database/models.js"; | ||
import { DecisionStatus, DecisionResponse } from "./admission-formats.js"; | ||
import { getAsAttendee, getAsStaff, getAsUser, putAsAttendee, putAsStaff, putAsUser, TESTER } from "../../testTools.js"; | ||
import { DecisionInfo } from "../../database/decision-db.js"; | ||
import { StatusCode } from "status-code-enum"; | ||
|
||
const TESTER_USER = { | ||
userId: TESTER.id, | ||
status: DecisionStatus.ACCEPTED, | ||
response: DecisionResponse.PENDING, | ||
emailSent: false, | ||
reviewer: "tester-reviewer", | ||
} satisfies DecisionInfo; | ||
|
||
const OTHER_USER = { | ||
userId: "other-user", | ||
status: DecisionStatus.REJECTED, | ||
response: DecisionResponse.DECLINED, | ||
emailSent: true, | ||
reviewer: "other-reviewer", | ||
} satisfies DecisionInfo; | ||
|
||
beforeEach(async () => { | ||
Models.initialize(); | ||
await Models.DecisionInfo.create(TESTER_USER); | ||
await Models.DecisionInfo.create(OTHER_USER); | ||
}); | ||
|
||
describe("GET /admission", () => { | ||
it("gives forbidden error for user without elevated perms", async () => { | ||
npunati27 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const responseUser = await getAsUser("/admission/").expect(StatusCode.ClientErrorForbidden); | ||
expect(JSON.parse(responseUser.text)).toHaveProperty("error", "InvalidToken"); | ||
}); | ||
it("gives forbidden error for user without elevated perms - attendee", async () => { | ||
const responseAttendee = await getAsAttendee("/admission/").expect(StatusCode.ClientErrorForbidden); | ||
expect(JSON.parse(responseAttendee.text)).toHaveProperty("error", "InvalidToken"); | ||
}); | ||
it("should return a list of applicants without email sent", async () => { | ||
npunati27 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const response = await getAsStaff("/admission/").expect(StatusCode.SuccessOK); | ||
expect(JSON.parse(response.text)).toMatchObject(expect.arrayContaining([expect.objectContaining(TESTER_USER)])); | ||
}); | ||
}); | ||
|
||
describe("PUT /admission", () => { | ||
const updateData = [ | ||
npunati27 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
userId: TESTER.id, | ||
name: TESTER.name, | ||
status: DecisionStatus.WAITLISTED, | ||
}, | ||
{ | ||
userId: "other-user", | ||
name: "other-name", | ||
status: DecisionStatus.ACCEPTED, | ||
}, | ||
]; | ||
it("gives forbidden error for user without elevated perms (As User)", async () => { | ||
const responseAttendee = await putAsAttendee("/admission/").send(updateData).expect(StatusCode.ClientErrorForbidden); | ||
expect(JSON.parse(responseAttendee.text)).toHaveProperty("error", "InvalidToken"); | ||
}); | ||
it("gives forbidden error for user without elevated perms (As Attendee)", async () => { | ||
npunati27 marked this conversation as resolved.
Show resolved
Hide resolved
npunati27 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const responseUser = await putAsUser("/admission/").send(updateData).expect(StatusCode.ClientErrorForbidden); | ||
expect(JSON.parse(responseUser.text)).toHaveProperty("error", "InvalidToken"); | ||
}); | ||
it("should update application status of applicants", async () => { | ||
npunati27 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const response = await putAsStaff("/admission/").send(updateData).expect(StatusCode.SuccessOK); | ||
expect(JSON.parse(response.text)).toHaveProperty("message", "StatusSuccess"); | ||
const ops = updateData.map((entry) => { | ||
return Models.DecisionInfo.findOne({ userId: entry.userId }); | ||
}); | ||
const retrievedEntries = await Promise.all(ops); | ||
updateData.forEach((entry) => { | ||
npunati27 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
expect(retrievedEntries).toMatchObject( | ||
expect.arrayContaining([expect.objectContaining({ status: entry.status, userId: entry.userId })]), | ||
); | ||
}); | ||
}); | ||
}); |
npunati27 marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import { Router, Request, Response } from "express"; | ||
import { strongJwtVerification } from "../../middleware/verify-jwt.js"; | ||
|
||
import { JwtPayload } from "../auth/auth-models.js"; | ||
import { DecisionInfo } from "../../database/decision-db.js"; | ||
import Models from "../../database/models.js"; | ||
import { hasElevatedPerms } from "../auth/auth-lib.js"; | ||
import { ApplicantDecisionFormat } from "./admission-formats.js"; | ||
import { StatusCode } from "status-code-enum"; | ||
|
||
const admissionRouter: Router = Router(); | ||
|
||
/** | ||
* @api {get} /admission/ GET /admission/ | ||
* @apiGroup Admission | ||
* @apiDescription Gets all applicants who don't have an email sent | ||
* | ||
* @apiSuccess (200: Success) {Json} entries The list of applicants without email sent | ||
* @apiSuccessExample Example Success Response (Staff POV) | ||
* HTTP/1.1 200 OK | ||
* [ | ||
* { | ||
* "_id": "652c2f0f923bd80603c992f9", | ||
* "userId": "user1", | ||
* "status": "ACCEPTED", | ||
* "response": "ACCEPTED", | ||
* "reviewer": "reviewer1", | ||
* "emailSent": false | ||
* }, | ||
* { | ||
* "_id": "652c2f4a4e5cf39082bbaad8", | ||
* "userId": "user3", | ||
* "status": "WAITLISTED", | ||
* "response": "PENDING", | ||
* "reviewer": "reviewer1", | ||
* "emailSent": false | ||
* }, | ||
* { | ||
* "_id": "652c2f65867cc5b6728ee48c", | ||
* "userId": "user4", | ||
* "status": "WAITLISTED", | ||
* "response": "PENDING", | ||
* "reviewer": "reviewer1", | ||
* "emailSent": false | ||
* } | ||
* ] | ||
* @apiUser strongVerifyErrors | ||
* @apiError (500: Internal Server Error) {String} InternalError occurred on the server. | ||
* @apiError (403: Forbidden) {String} Forbidden API accessed by user without valid perms. | ||
* */ | ||
admissionRouter.get("/", strongJwtVerification, async (_: Request, res: Response) => { | ||
const token: JwtPayload = res.locals.payload as JwtPayload; | ||
if (!hasElevatedPerms(token)) { | ||
return res.status(StatusCode.ClientErrorForbidden).send({ error: "InvalidToken" }); | ||
} | ||
try { | ||
const filteredEntries: DecisionInfo[] = await Models.DecisionInfo.find({ emailSent: false }); | ||
return res.status(StatusCode.SuccessOK).send(filteredEntries); | ||
} catch (error) { | ||
console.error(error); | ||
} | ||
return res.status(StatusCode.ClientErrorBadRequest).send({ error: "InternalError" }); | ||
}); | ||
/** | ||
* @api {put} /admission/ PUT /admission/ | ||
* @apiGroup Admission | ||
* @apiDescription Updates the admission status of all applicants | ||
* | ||
* @apiHeader {String} Authorization Admin or Staff JWT Token | ||
* | ||
* @apiBody {Json} entries List of Applicants whose status needs to be updated | ||
* | ||
* @apiParamExample Example Request (Staff): | ||
* HTTP/1.1 PUT /admission/ | ||
* { | ||
* "entries": [ | ||
* { | ||
* "userId": "user1", | ||
* "name": "Jason", | ||
npunati27 marked this conversation as resolved.
Show resolved
Hide resolved
npunati27 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* "status": "ACCEPTED" | ||
* }, | ||
* { | ||
* "userId": "user2", | ||
* "name": "Fred", | ||
* "status": "REJECTED" | ||
* }, | ||
* { | ||
* "userId": "user3", | ||
* "name": "John", | ||
* "status": "WAITLISTED" | ||
* } | ||
* ] | ||
* } | ||
* | ||
* @apiSuccess (200: Success) {String} StatusSuccess | ||
* | ||
* @apiUse strongVerifyErrors | ||
* @apiError (500: Internal Server Error) {String} InternalError occurred on the server. | ||
* @apiError (403: Forbidden) {String} Forbidden API accessed by user without valid perms. | ||
* */ | ||
admissionRouter.put("/", strongJwtVerification, async (req: Request, res: Response) => { | ||
const token: JwtPayload = res.locals.payload as JwtPayload; | ||
if (!hasElevatedPerms(token)) { | ||
return res.status(StatusCode.ClientErrorForbidden).send({ error: "InvalidToken" }); | ||
npunati27 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
const updateEntries: ApplicantDecisionFormat[] = req.body as ApplicantDecisionFormat[]; | ||
const ops = updateEntries.map((entry) => { | ||
return Models.DecisionInfo.findOneAndUpdate({ userId: entry.userId }, { $set: { status: entry.status } }); | ||
}); | ||
try { | ||
await Promise.all(ops); | ||
return res.status(StatusCode.SuccessOK).send({ message: "StatusSuccess" }); | ||
} catch (error) { | ||
console.log(error); | ||
} | ||
return res.status(StatusCode.ClientErrorBadRequest).send("InternalError"); | ||
}); | ||
export default admissionRouter; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.