Call Of Duty - DavidBK/shampoo GitHub Wiki
Estimation time: 20-30 Days
In the 'Call Of Duty' project, you will be managing soldiers and duties using a database and a RESTfull API server.
Send me back home
[[TOC]]
Before you start make sure you familiar with these concepts:
- The database should be a non relational database.
- The API server should be a RESTfull API server.
- You should test the project, and the coverage of the tests should be high as possible.
- You should PR Each task in the project.
- Log your logic and errors.
- The Commits separation decision is up to you.
You can choose which technologies you want to use for this project, but ask your mentor before choosing. Here is my recommended technologies:
-
Language: JavaScript or TypeScript
-
Test: Vitest or Node Test Runner or Jest
-
Database: MongoDB
Don't use Mongoose ODM, use MongoDB Node Driver. You can read more about it in the mongoose-vs-nodejs-driver article.
-
Logger: fastify built-in pino logger or pino or winston.
-
Schema validator: fastify built-in ajv validator or Ajv or Joi. If you using Typescript have a look at Runtime checks with TypeScript section.
-
Linter: eslint using airbnb style guide or XO style guide, or Biome linter and formatter.
-
Optional:
Your DB Will contain 2 collections:
- Soldiers:
interface Soldier {
_id: string;
name: string;
rank: {
name: string;
value: number;
};
limitations: string[];
createdAt: ISODate;
updatedAt: ISODate;
}- Duties:
interface Duty {
_id: ObjectId;
name: string;
description: string;
location: GeoJSON Point;
startTime: ISODate;
endTime: ISODate;
minRank: number;
maxRank: number;
constraints: string[];
soldiersRequired: number;
value: number;
soldiers: ObjectId[];
status: string;
statusHistory: {
status: string;
date: ISODate;
}[];
createdAt: ISODate;
updatedAt: ISODate;
}Estimation time: 3 Days
-
Create a server and an app.
- listen on port from ENV var and the default port should be
3000.
- listen on port from ENV var and the default port should be
-
Create a health check endpoint:
- GET
/health - Return a 200 status code if the server is running.
- Return a JSON response:
{"status": "ok"}
Run your app and test it using
curlcommand:curl http://localhost:3000/health
You can also use Postman, or any other tool you like (such as hoppscotch, Insomnia or Thunder Client vscode extension).
- GET
-
Create a database connection.
- Connect to the database using the connection string from ENV var.
- The connection should be established before the server starts listening.
- The connection should be closed when the server stops listening.
-
Create a db health check endpoint:
- GET
/health/db - Return a 200 status code if the database is connected.
- Return a JSON response:
{"status": "ok"}
- GET
-
Create a test for your health check endpoint.
What is the coverage of your tests?
-
Separate application code from the server code (Why?):
- Create
server.jsfile. - Create
app.jsfile.
- Create
-
Test your app and server.
You can use "HTTP injection" (Fastify built in
app.injectfunction).What is the coverage of your tests? Did you test the error case?
-
Add linter to your project.
- Add
lintscript to yourpackage.jsonfile. - Run the script before submit any merge request.
- Add
-
Log your app and server.
-
The log-level should be configurable, and the default level should be
info. In the test environment, the default log level should besilent. -
Add "pino-pretty" to your project (dev only) to format the logs.
node server.js | pino-pretty
-
Estimation time: 4 Days
-
Create endpoint for creating a new soldier:
-
POST
/soldiers -
Return a 201 status code if the soldier is created.
-
The body will include the following parameters:
{ _id, name, rankValue || rankName, limitations; }
-
The
_idshould be the soldier's private tt number - a 7 digits number. -
The
nameshould be a string with length between 3 and 50. -
The
rankValueshould be a number between 0 and 6. Use the following table to convert the number to the rank name:Rank Value Rank Name 0 private 1 corporal 2 sergeant 3 lieutenant 4 captain 5 major 6 colonel -
Save the limitations in lower case.
-
Validate that all the above parameters exist, any other property is invalid.
-
Add the
createdAtandupdatedAtproperties and initialize them to the current date. -
Return the inserted
Soldier.
-
-
Create endpoint for getting a soldier:
- GET
/soldiers/:id - Return a 200 status code if the soldier is found.
- Return a 404 status code if the soldier is not found.
Example: a request to
/soldiers/0112358should return the soldier withid0112358(if exists). - GET
-
Create endpoint for getting all soldiers:
- GET
/soldiers - A search query can be passed as a query parameter.
For example: a request to
/soldiers?name=davidshould return all soldiers (as an array) with the name 'david'. - When searching by limitations, the query should be an array of limitations.
For example: a request to
/soldiers?limitations=food,standingshould return all soldiers (as an array) with the limitations 'food' and 'standing'. - The rank can be passed as rankValue or rankName.
For example: a request to
/soldiers?rankValue=3should return all soldiers (as an array) with the rankValue 3. For example: a request to/soldiers?rankName=lieutenantshould return all soldiers (as an array) with the rankName 'lieutenant'.
- GET
-
Create endpoint for deleting a soldier:
- DELETE
/soldiers/:id - Return a 204 status code if the soldier is deleted.
- Return a 404 status code if the soldier is not found.
- DELETE
-
Create endpoint for updating a soldier:
- PATCH
/soldiers/:id - The body will include a
Soldierobject. - The updated
Soldierwill be an object and should contain the updated properties only. - The updated properties will override the existing ones.
- Update the
updatedAtproperty to the current date. - Do not allow this method to add any new properties nor to alter the
_id. - Return the updated
Soldierwith a 200 status code if the soldier is updated.
- PATCH
If you using fastify (and you should) Add schema also for the response (Why?).
Estimation time: 4 Days
-
Create endpoint for creating a new duty:
-
POST
/duties -
Return a 201 status code if the duty is created.
-
The body will include the following parameters:
{ name, description, location, startTime, endTime, constraints, soldiersRequired, value, minRank? maxRank? }
-
Generate a unique _id for the object (MongoDB will do it for you).
-
Validate:
-
nameis a string with length between 3 and 50. -
locationis a valid GeoJSON Point. -
startTimeis before theendTimeand that thestartTimeis in the future. -
valueis a positive number. -
minRankandmaxRankif exists are numbers between 0 and 6. - All the above parameters exist,
minRankandmaxRankare optional. Any other property is invalid.
-
-
When a duty is inserted to the database:
- Add the
soldiersproperty and initialize it to an empty array. - Add the
statusproperty and initialize it tounscheduled. - Add the
statusHistoryproperty and initialize it to an array with the current status and date.
- Add the
-
Return the inserted
Duty.
-
-
Create endpoint for getting all duties:
- GET
/duties - A search query can be passed as a query parameter.
For example: a request to
/duties?name=hagnashshould return all should return all 'hagnash' duties (as an array).
- GET
-
Create endpoint for getting a duty:
- GET
/duties/:id
- GET
-
Create endpoint for deleting a duty:
- DELETE
/duties/:id - Scheduled duties cannot be removed.
- DELETE
-
Create endpoint for updating a duty:
- PATCH
/duties/:id - The body will include a
Dutyobject. - The updated
Dutywill be an object and should contain the updated properties only. - The updated properties will override the existing ones.
- Scheduled duties cannot be updated.
- Do not allow this method to add any new properties nor to alter the id.
- Return the updated
Duty.
- PATCH
Estimation time: 2 Day
The justice board is an array of objects with the keys:
-
_id- The unique identifier of the soldier -
score- The total value of duties the soldier has been scheduled to.
For example:
[{ _id: '1123581', score: 13 }, { _id: '3141592', score: 12 }, { _id: '2718281', score: 94 } ...]-
Create endpoint for getting the Justice Board:
- GET
/justice-board - Use mongoDB aggregation to calculate the Justice Board (Why?).
- GET
Estimation time: 4 Days
The scheduling process is the process of assigning soldiers to duties. The soldiers' limitations (according to the duty's constraints), the Justice Board, and the rank should be taken into consideration.
The Duty status can be one of the following:
-
unscheduled- The duty is not scheduled yet. -
scheduled- The duty is scheduled. -
canceled- The duty is canceled.
-
Create endpoint for scheduling a duty:
- PUT
/duties/:id/schedule - Don't allow to schedule a duty that is already scheduled.
- Don't allow to schedule a duty that is canceled.
- Don't allow to schedule a duty that is in the past.
- The duty
statusshould be updated toscheduled. - The duty
statusHistoryshould be updated with the new status and the current date. - Use the
soldiersRequiredproperty to determine how many soldiers should be scheduled. - Make sure that the soldiers are not already scheduled to other duties at the same time.
- Update the
soldiersproperty in the Duty object with the scheduled soldiers.
- PUT
-
Create endpoint for canceling a duty:
- PUT
/duties/:id/cancel - Don't allow to cancel a duty that is already canceled.
- Don't allow to cancel a duty that is in the past.
- The duty status should be updated to
canceled. - The duty status history should be updated with the new status and the current date.
- Update the
soldiersproperty in the Duty object (to an empty array).
- PUT
-
Fix the Soldiers routes to take into consideration the scheduled duties.
The auto scheduling mechanism should schedule all unscheduled duties.
-
Add an auto scheduling mechanism to your app.
- The time should be configurable and the default time should be every 5 minutes.
- Duty with higher value should be prioritized.
- What will happen if there is a change in the Duties or Soldiers collection while the auto scheduling is running?
Estimation time: 2 Days
-
Add handlers for
unhandledRejectionanduncaughtExceptionevents. -
Add handlers for
SIGTERMandSIGINTevents. -
Validate your ENV vars before starting the server.
-
Add openAPI v3 documentation to your app.
You can use the fastify-swagger plugin to generate the documentation.
-
Add basic authentication to your app.
-
Add a rate limiter for your routes.
You can use fastify-rate-limit.
-
Add README.md to your app. Your readme should include explanation to how to use and test the app.
-
Use helmetJS to protect your app.
You can use the fastify-helmet plugin.
-
Optional: Add
_linkproperty to your responses.
Ask your mentor which extensions you should implement.
-
Multiple parameters
Extend the
/soldiersand/dutiesroutes functionality to multiple parameters search query.For example: A request to
/duties?name=hagnash&soldiers=mishel,shirshould return all"hagnash"duties that contains both"mishel"and"shir". (Note that a"hagnash"duty that was scheduled with"mishel","shir"and"david"should also be returned). -
Sorting
Extend the he
/justice-board/soldiersand/dutiesroutes to accept sorting queries also with desire order.For example:
- A request to
/duties?sort=valueshould return the duties sorted by value. - A request to
/duties?sort=name&order=descshould return the duties sorted by name in descending order. - A request to
/justice-board?sort=score&order=descshould return the justice board sorted by score in descending order.
- A request to
-
Filtering
Extend the
/justice-board/soldiersand/dutiesfunctionality to accept filtering queries.For example:
- A request to
/justice-board?filter=score>=20should return the justice board with soldiers with score >= 20.
- A request to
-
Pagination
Extend the
/justice-board/soldiersand/dutiesfunctionality to accept pagination.For example:
- A request to
/justice-board?page=2&limit=10should return the justice board with the second page of 10 soldiers.
- A request to
-
Projection
Extend the
/justice-board/soldiersand/dutiesfunctionality to accept projection by fields.For example:
- A request to
/soldiers?select=nameshould return the soldiers with only thenameproperty. - A request to
/duties?select=name,valueshould return the duties with only thenameandvalueproperties.
- A request to
-
Population
Extend the
/justice-board/soldiersand/dutiesfunctionality to accept population of fields.For example:
- A request to
/duties?populate=soldiersshould return the duties with thesoldiersproperty populated with the soldiers data.
- A request to
-
Geo Queries
Extend the
/dutiesfunctionality to accept geo queries.For example:
- A request to
/duties?near=32.0853,34.7818&radius=1000should return the duties that are near Tel Aviv (32.0853, 34.7818) with a max distance of 1000 meters.
- A request to
-
What will happen if you add more soldiers and duties?
You may use this concepts:
- Indexing
- Pagination
- Cache
- Job Queue
-
Who can modified the soldiers and duties?
You may use this concepts:
- Authentication
- Authorization
-
What is the performance of your app?
Make a load test to your app. Profile your app and find the bottlenecks.
Let's return to a type safe space