Skip to content

flaviojrdev/grid-motors-api

Repository files navigation

πŸš— Grid Motors API

Welcome to my programming challenge project! πŸ‘‹

I created this project as the final task requested by Compass UOL for their scholarship program. The main goal of this project is to simulate a client request for a REST API in a leasing vehicles application. This includes handling authentication, security, validations, and CRUD operations for users, cars, and reserves.

Through this project, I have honed my skills in programming and web development, and I am excited to showcase my ability to create efficient and effective solutions to real-world problems.

Thank you for taking the time to check out my project!

divBar

πŸ”– Table of Contents

divBar

πŸš€ Mandatory requirements

  • Readability
  • Small commits
  • README documentation
  • Swagger documentation (Conflicts with deploy, so it need to be open locally)
  • ESLint
  • API deploy
  • JWT
  • Authentication route
  • Protected routes (token bearer with 12h deadline)
  • Unit tests (70% test coverage) - Achieved 60%

divBar

🌟 Business Logic

Users

  • The CEP must be sent as a request to an external API (viacep) in order to get the full adress.
  • The user must be at least 18 years old.
  • CPF must be unique in the base.
  • Must have password with at least 6 digits.
  • Must have a valid Email.

Cars

  • The car's fabrication year must be between 1950 and 2023.
  • Can't have repeated accessories.
  • Cars must be listed with pagination and can be searched by query params (like model or color).
  • Cars can be searched individually by id.

Reserves

  • User must have a driver's license in order to make a reservation.
  • The same car cannot be reserved in the same period of time.
  • The user cannot have more than one reservation in the same period of time.
  • Start date cannot be greater than the end date.

divBar

πŸ’» Usage

Test it right away

You can test the API without running locally, using the application deployment using Postman or Insomnia..

Local hosting

Pre-requisites

You will need to have Node.JS installed on your machine, as well as a MongoDB Atlas database collection.

Step by step

  1. Check if you have the listed pre-requisites.
  2. Clone the repository to your local machine with the command:
$ git clone https://github.com/flaviojrdev/grid-motors-api.git
  1. Install project dependencies with the command:
$ npm install
  1. Create a .env file at the project root (use the .env.example file as a template).
  2. Start the server with the command:
$ npm run dev
  1. The server will be running at localhost:port.

How to run with Docker

  1. Make sure you have Docker and Docker Compose installed on your machine.
  2. Clone the repository to your local machine with the command:
$ git clone https://github.com/flaviojrdev/grid-motors-api.git
  1. Navigate to the project root directory.
  2. Create a .env file at the project root (use the .env.example file as a template).
  3. Start the application using Docker Compose with the command:
docker-compose up -d
  1. Docker Compose will download the necessary images and start the services defined in the docker-compose.yml file. The API will be accessible at http://localhost:, where is the port defined in the .env file.

divBar

🧰 Technologies

My Skills

Main Stack

  • Node.JS: JavaScript development platform.
  • Express: Web application framework.
  • TypeScript: Typed superset of JavaScript.
  • MongoDB: Document-oriented NoSQL database.

Security and authentication

  • Cors: Middleware to enable CORS (Cross-Origin Resource Sharing) on a Node.js server.
  • Helmet: Middleware to improve Node.js security with HTTP headers.
  • JsonWebToken: Library for generating JSON web tokens for authentication and authorization.
  • Bcrypt: Hashing library for password encryption.

Tests

  • Jest: JavaScript testing framework.
  • Supertest: HTTP endpoint testing library.

Code formatting

Loggers

  • Morgan: A logger middleware for Node.js that logs HTTP requests to the console or a file.
  • Winston: Logging library for Node.js.

Utilitary and Middleware

  • Mongoose: MongoDB library with high-level API for defining and querying data models.
  • Dotenv: Library to load env variables from .env file for easier environment configuration.
  • Nodemon: Utility that monitors code changes and auto-restarts the server.
  • Moment: Library for working with dates and times with easy parsing, manipulation, and display.
  • Joi: Library for validating and sanitizing data with simple yet powerful API.
  • Axios: Library for making HTTP requests with easy-to-use API for data transfer.
  • Swagger: Tool for documenting and testing RESTful APIs with machine-readable format.

divBar

🧩 Project Architecture

The project follows the SOLID principles. The src/api folder contains all the application's source code and is organized as follows:

  • πŸ“‚ entities: Contains the domain entities used by the application, such as Car, User and Reserve.
  • πŸ“‚ errors: Contains custom error classes used by the application.
  • πŸ“‚ interfaces: Contains interfaces used by the application.
  • πŸ“‚ middlewares: Contains middleware functions used by the application, such as authentication.
  • πŸ“‚ routes: Contains the API routes and their respective controllers.
  • πŸ“‚ useCases: Contains the business logic of the application, organized by domain entities.
  • πŸ“‚ utils: Contains utility functions used by the application, such as formatting and validations.
  • πŸ“„ app.ts: Main file that configures the server, sets up middleware and connects to the database.
  • πŸ“„ server.ts: Instantiates the server and listens for HTTP requests.

Aswell as a πŸ“‚ data folder in src/data which contains the database class file.

divBar

πŸ›£ Endpoints

  • Main Route: /api/v1

Authentication Routes

Method Endpoint Protected Description
POST /auth false Authenticate a user

Note This route is for generating or renewing the authentication token.

Example

Generate Token (POST)

Request

{
  "email": "joazinho@email.com",
  "password": "123456"
}

Response

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0MzMxMjk2MDM5ZDk2MDg1NmQxZDZkYSIsImlhdCI6MTY4MTA2ODkwMiwiZXhwIjoxNjgxMTEyMTAyfQ.D5zEzXoHSvQZNKdW48Ip_HVq0jlVo9Wxuf_Rf4AhFdg"
}

User Routes

Method Endpoint Protected Description
GET /user true List all users
POST /user false Register a new user
DELETE /user/:id true Remove a user by ID
PUT /user/:id true Update a user by ID

Examples

Create (POST)

Request

{
  "name": "JoΓ£ozinho Ciclano",
  "cpf": "131.147.860-49",
  "birth": "03/03/2000",
  "email": "joazinho@email.com",
  "password": "123456",
  "cep": "01001000",
  "qualified": "yes"
}

Response

{
  "_id": "64331296039d960856d1d6da",
  "name": "JoΓ£ozinho Ciclano",
  "cpf": "131.147.860-49",
  "birth": "03/03/2000",
  "email": "joazinho@email.com",
  "password": "$2b$10$LatAA.HtTongQlvUbHBEkuEYmGOlzdH01WwHwzNscPxcUrwlrosIS",
  "cep": "01001000",
  "qualified": "yes",
  "patio": "PraΓ§a da SΓ©",
  "complement": "lado Γ­mpar",
  "neighborhood": "SΓ©",
  "locality": "SΓ£o Paulo",
  "uf": "SP"
}

Read (GET)

Response

{
  "users": [
      {
          "_id": "64331296039d960856d1d6da",
          "name": "JoΓ£ozinho Ciclano",
          "cpf": "131.147.860-49",
          "birth": "03/03/2000",
          "email": "joazinho@email.com",
          "password": "$2b$10$LatAA.HtTongQlvUbHBEkuEYmGOlzdH01WwHwzNscPxcUrwlrosIS",
          "cep": "01001000",
          "qualified": "yes",
          "patio": "PraΓ§a da SΓ©",
          "complement": "lado Γ­mpar",
          "neighborhood": "SΓ©",
          "locality": "SΓ£o Paulo",
          "uf": "SP"
      },
      {
          "_id": "64332347bf729514ccb4d316",
          "name": "Julio Cesar",
          "cpf": "390.442.050-05",
          "birth": "03/03/2000",
          "email": "Jcesar@email.com",
          "password": "$2b$10$bB2PwqH55JHPA4UEAbsXPeu8c6XinCrgDeWMWK1ndcFWQwQF8HJd6",
          "cep": "67020695",
          "qualified": "no",
          "patio": "Passagem SΓ£o Pedro",
          "complement": "not provided",
          "neighborhood": "Águas Lindas",
          "locality": "Ananindeua",
          "uf": "PA"
      },
      {
          "_id": "643323e36d85f20f99bbac18",
          "name": "Bruce Wayne",
          "cpf": "234.980.290-61",
          "birth": "03/03/2000",
          "email": "batman@email.com",
          "password": "$2b$10$tBMtW1EIWmK/CLCnUSnKcetaFIjp9/fIdWPY8k8S8sn8/JbgVXT7C",
          "cep": "58058260",
          "qualified": "yes",
          "patio": "Rua Sargento Adahylton Pontes de Lima",
          "complement": "not provided",
          "neighborhood": "Mangabeira",
          "locality": "JoΓ£o Pessoa",
          "uf": "PB"
      }
  ],
  "total": 3,
  "limit": 100,
  "offset": 1,
  "offsets": 100
}

Update (PUT)

Response

{
  "name": "JoΓ£ozinho Ciclano",
  "cpf": "131.147.860-49",
  "birth": "03/03/2000",
  "email": "joazinho@email.com",
  "password": "1234567",
  "cep": "01001000",
  "qualified": "yes",
  "patio": "PraΓ§a da SΓ©",
  "complement": "lado Γ­mpar",
  "neighborhood": "SΓ©",
  "locality": "SΓ£o Paulo",
  "uf": "SP"
}

Response

{
  "_id": "64331296039d960856d1d6da",
  "name": "JoΓ£ozinho Ciclano",
  "cpf": "131.147.860-49",
  "birth": "03/03/2000",
  "email": "joazinho@email.com",
  "password": "$2b$10$o/GOIcUvTTwD33Mlc20tIeXPOc7jxkbGJqoqyKeDQ453z6pBpcX2W",
  "cep": "01001000",
  "qualified": "yes",
  "patio": "PraΓ§a da SΓ©",
  "complement": "lado Γ­mpar",
  "neighborhood": "SΓ©",
  "locality": "SΓ£o Paulo",
  "uf": "SP"
}

Delete (DELETE)

Response

Note Deletion returns an empty response with status 204.


Car Routes

Method Endpoint Protected Description
GET /car true List all cars
GET /car/:id true List a car by ID
POST /car true Register a new car
DELETE /car/:id true Remove a car by ID
PUT /car/:id true Update a car by ID
PATCH /car/:carId/accessories/:accessoryId true Update or remove an accessory by ID

Note If the accessory descriptions matches, it will be treated as a removal, otherwise its an update.

Examples

Create (POST)

Request

{
  "model": "GM S10 2.8",
  "color": "White",
  "year": 2021,
  "values_per_day": 50,
  "accessories": [
      {
          "description": "air conditioner"
      },
      {
          "description": "4x4 traction"
      },
      {
          "description": "4 ports"
      }
  ],
  "number_of_passengers": 5
}

Response

{
  "_id": "64331385039d960856d1d6df",
  "model": "GM S10 2.8",
  "color": "White",
  "year": 2021,
  "values_per_day": 50,
  "accessories": [
      {
          "description": "air conditioner",
          "_id": "64331385039d960856d1d6e0"
      },
      {
          "description": "4x4 traction",
          "_id": "64331385039d960856d1d6e1"
      },
      {
          "description": "4 ports",
          "_id": "64331385039d960856d1d6e2"
      }
  ],
  "number_of_passengers": 5
}

Read (GET)

Response

{
  "cars": [
      {
          "_id": "64331385039d960856d1d6df",
          "model": "GM S10 2.8",
          "color": "White",
          "year": 2021,
          "values_per_day": 50,
          "accessories": [
              {
                  "description": "air conditioner",
                  "_id": "64331385039d960856d1d6e0"
              },
              {
                  "description": "4x4 traction",
                  "_id": "64331385039d960856d1d6e1"
              },
              {
                  "description": "4 ports",
                  "_id": "64331385039d960856d1d6e2"
              }
          ],
          "number_of_passengers": 5
      },
      {
          "_id": "643324406d85f20f99bbac23",
          "model": "Ferrari",
          "color": "Red",
          "year": 2017,
          "values_per_day": 1500,
          "accessories": [
              {
                  "description": "leather seats",
                  "_id": "643324406d85f20f99bbac24"
              }
          ],
          "number_of_passengers": 2
      },
      {
          "_id": "6433249a6d85f20f99bbac27",
          "model": "Batmobile",
          "color": "Black",
          "year": 1966,
          "values_per_day": 200,
          "accessories": [
              {
                  "description": "rocket engine",
                  "_id": "6433249a6d85f20f99bbac28"
              }
          ],
          "number_of_passengers": 1
      }
  ],
  "total": 3,
  "limit": 100,
  "offset": 1,
  "offsets": 100
}

Update (PUT / PATCH)

Request (PUT car)

{
  "model": "Ferrari",
  "color": "Red",
  "year": 2017,
  "values_per_day": 2000,
  "accessories": [
      {
          "description": "leather seats"
      }
  ],
  "number_of_passengers": 2
}

Response (PUT car)

{
  "_id": "643324406d85f20f99bbac23",
  "model": "Ferrari",
  "color": "Red",
  "year": 2017,
  "values_per_day": 2000,
  "accessories": [
      {
          "description": "leather seats",
          "_id": "64338fc5db2e9b24b0e63d97"
      }
  ],
  "number_of_passengers": 2
}

Request (PATCH accessory)

{
  "description": "back camera"
}

Response (PATCH accesory)

{
  "_id": "64331385039d960856d1d6df",
  "model": "GM S10 2.8",
  "color": "White",
  "year": 2021,
  "values_per_day": 50,
  "accessories": [
      {
          "description": "back camera",
          "_id": "6433906adb2e9b24b0e63da6"
      },
      {
          "description": "4x4 traction",
          "_id": "64331385039d960856d1d6e1"
      },
      {
          "description": "4 ports",
          "_id": "64331385039d960856d1d6e2"
      }
  ],
  "number_of_passengers": 5
}

Delete (DELETE / PATCH)

Response (DELETE car)

Note Deletion returns an empty response with status 204.

Response (PATCH accessory)

{
  "description": "back camera"
}

Response (PATCH accessory)

{
  "message": "Accessory deleted",
  "car": {
      "_id": "64331385039d960856d1d6df",
      "model": "GM S10 2.8",
      "color": "White",
      "year": 2021,
      "values_per_day": 50,
      "accessories": [
          {
              "description": "4x4 traction",
              "_id": "64331385039d960856d1d6e1"
          },
          {
              "description": "4 ports",
              "_id": "64331385039d960856d1d6e2"
          }
      ],
      "number_of_passengers": 5
  }
}

Reservation Routes

Method Endpoint Protected Description
POST /reserve true Register a new reservation
GET /reserve true List all reservations
DELETE /reserve/:id true Remove a reservation by ID
PUT /reserve/:id true Update a reservation by ID

Examples

Create (POST)

Request

{
  "start_date": "20/05/2023",
  "end_date": "30/05/2023",
  "id_car": "643324406d85f20f99bbac23"
}

Response

{
  "_id": "64332be36d85f20f99bbac6a",
  "start_date": "20/05/2023",
  "end_date": "30/05/2023",
  "final_value": 15000,
  "id_car": "643324406d85f20f99bbac23",
  "id_user": "643323e36d85f20f99bbac18"
}

Read (GET)

Response

{
  "reserves": [
      {
          "_id": "643326f16d85f20f99bbac4e",
          "start_date": "20/04/2023",
          "end_date": "30/04/2023",
          "final_value": 500,
          "id_car": "64331385039d960856d1d6df",
          "id_user": "64331296039d960856d1d6da"
      },
      {
          "_id": "64332bc26d85f20f99bbac63",
          "start_date": "10/05/2023",
          "end_date": "15/05/2023",
          "final_value": 1000,
          "id_car": "6433249a6d85f20f99bbac27",
          "id_user": "643323e36d85f20f99bbac18"
      },
      {
          "_id": "64332be36d85f20f99bbac6a",
          "start_date": "20/05/2023",
          "end_date": "30/05/2023",
          "final_value": 15000,
          "id_car": "643324406d85f20f99bbac23",
          "id_user": "643323e36d85f20f99bbac18"
      }
  ],
  "total": 3,
  "limit": 100,
  "offset": 1,
  "offsets": 100
}

Update (PUT)

Request

{
  "start_date": "21/04/2023",
  "end_date": "30/04/2023",
  "final_value": 1000,
  "id_car": "64331385039d960856d1d6df",
  "id_user": "64331296039d960856d1d6da"
}

Response

{
  "_id": "643326f16d85f20f99bbac4e",
  "start_date": "21/04/2023",
  "end_date": "30/04/2023",
  "final_value": 1000,
  "id_car": "64331385039d960856d1d6df",
  "id_user": "64331296039d960856d1d6da"
}

Delete (DELETE)

Response

Note Deletion returns an empty response with status 204.

divBar

πŸ“‹ Schemas

User

Field Type Required Unique
name String Yes No
cpf String Yes Yes
birth String Yes No
email String Yes Yes
password String Yes No
cep String Yes No
qualified String Yes No
patio String Yes No
complement String Yes No
neighborhood String Yes No
locality String Yes No
uf String Yes No

Note patio, complement, neighborhood, locality and uf are not passed in the POST request (only in PUT requests). They are filled in the payload via a external API requisition using the cep.


Accessory

Field Type Required Unique
description String Yes No

Car

Field Type Required Unique
model String Yes No
color String Yes No
year Number Yes No
values_per_day Number Yes No
accessories [AccessorySchema] Yes No
number_of_passengers Number Yes No

Reserve

Field Type Required Unique
start_date String Yes No
end_date String Yes No
final_value Number Yes No
id_car ObjectId (Car) Yes No
id_user ObjectId (User) Yes No

divBar

πŸ‘€ Author

Flavio Henrique

About

🧭 This REST API was made as the final challenge for the Compass scholarship program.

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages