Allows users to register yourself, manage his token, reset passwords, see his own profile, update his avatar, create cars and set specification and categories to it, import a bulk of categories at once, see available cars for rent, attach cars'images, rent a car, see previous rents and make a car devolution. The app has rate limit, friendly errors, use JWT to logins, validation, also a simple versioning was made.
Easy peasy lemon squeezy:
$ yarn
Or:
$ npm install
Was installed and configured the
eslintandprettierto keep the code clean and patterned.
The application uses two databases: Postgres and Redis. For the fastest setup is recommended to use docker-compose, you just need to up all services:
$ docker-compose up -d
In this file you may configure your Redis and Postgres database connection, JWT settings, the environment, app's port, mail and storage driver, aws settings (case be necessary) and a url to documentation (this will be returned with error responses, see error section). Rename the .env.example in the root directory to .env then just update with your settings.
| key | description | default |
|---|---|---|
| API_URL | Used to mount avatars' urls. | http://localhost:3333 |
| PORT | Port number where the app will run. | 3333 |
| RESET_PASSWORD_URL | Url where the user will be able to change the password | http://localhost:3333/v1/password/reset?token= |
| JWT_SECRET | A alphanumeric random string. Used to create signed tokens. | - |
| JWT_EXPIRATION_TIME | How long time will be the token valid. See jsonwebtoken repo for more information. | 15m |
| REFRESH_TOKEN_SECRET | A alphanumeric random string. Used to create signed refresh tokens. | - |
| REFRESH_TOKEN_EXPIRATION_DAYS | How many days long will be the refresh token valid. See jsonwebtoken repo for more information. | 30 |
| DB_HOST | Postgres host. | pg |
| DB_PORT | Postgres port. | 5432 |
| DB_USER | Postgres user. | - |
| DB_PASSWORD | Postgres password. | - |
| DB_NAME | Application's database name. | - |
| REDIS_HOST | Redis host. | redis |
| REDIS_PORT | Redis port. | 6379 |
| REDIS_PASSWORD | Redis password. | - |
| STORAGE_DRIVER | Set where the files will be stored, the available values are: local and s3. |
local |
| MAIL_DRIVER | Set what service to use to send mails, the available values are: ethereal and ses. |
ses |
| MAIL_SENDER | The from sent in the email. |
- |
| AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY | These keys are necessary to AWS allow the application to use the S3 and SES services throught API. See how to get yours keys here: Set up AWS Credentials. | - |
| AWS_BUCKET | Amazon S3 stores data as objects within buckets. To create a bucket see Creating a bucket. | - |
| AWS_BUCKET_URL | Utilized to mount avatars' urls when using s3 as STORAGE_DRIVER. Can also be found while creating the bucket. |
- |
| AWS_REGION | You can see your default region in the navigation bar at the top right after login in the AWS Management Console. Read AWS service endpoints to know more about regions. | - |
| DOCS_URL | An url to docs where users can find more information about the app's internal code errors. | https://github.com/DiegoVictor/rentx#errors-reference |
Responsible to store data utilized by the rate limit middleware. If for any reason you would like to create a Redis container instead of use docker-compose, you can do it by running the following command:
$ docker run --name rentx-redis -d -p 6379:6379 redis:alpine
Responsible to store all application data. If for any reason you would like to create a MongoDB container instead of use docker-compose, you can do it by running the following command:
$ docker run --name rentx-postgres -e POSTGRES_PASSWORD=docker -p 5432:5432 -d postgres
Then create two databases:
rentxandtest(in case you would like to run the tests).
Remember to run the database migrations:
$ yarn ts-node-dev ./node_modules/typeorm/cli.js migration:run
Or:
$ yarn typeorm migration:run
See more information on TypeORM Migrations.
The project comes pre-configured, but you can adjust it as your needs.
src/config/rateLimit.ts
| key | description | default |
|---|---|---|
| duration | Number of seconds before consumed points are reset. | 300 |
| points | Maximum number of points can be consumed over duration. | 10 |
The lib
rate-limiter-flexiblewas used to rate the api's limits, for more configuration information go to Options page.
To start up the app run:
$ yarn dev:server
Or:
npm run dev:server
Instead of only throw a simple message and HTTP Status Code this API return friendly errors:
{
"statusCode": 429,
"error": "Too Many Requests",
"message": "Too Many Requests",
"code": 749,
"docs": "https://github.com/DiegoVictor/rentx#errors-reference"
}As you can see a url to error docs are returned too. To configure this url update the
DOCS_URLkey from.envfile. In the next sub section (Errors Reference) you can see the errorscodedescription.
| code | message | description |
|---|---|---|
| 140 | Email or password incorrect. | Password did not match. |
| 141 | Invalid token. | The reset password refresh token not references an existing one in the database. |
| 142 | Token expired. | The provided reset password refresh token has already expired. |
| 144 | Refresh Token does not exists. | The provided authentication refresh token was not found in the database. |
| 240 | User already exists. | Already exists an user with the same email. |
| 244 | User does not exists. | Was not found the user by the provided email to reset the password. |
| 245 | User does not exists. | The provided id does not reference an user in the database. |
| 340 | Car already exists. | You are trying to create a car with a license_plate already in use. |
| 341 | Car is unavailable. | Is not possible rent a car that is already rent. |
| 344 | Car does not exists. | The provided id does not reference a car in the database. |
| 440 | Category already exists. | You are trying to create a category with a name already in use. |
| 540 | Specification already exists. | You are trying to create a specification with a name already in use. |
| 640 | There's a rental in progress for this user. | Is not possible to make more than one rent at time. |
| 641 | A rental must have at least 24 hours of duration. | Is not allowed to rent a car by less than 24 hours. |
| 644 | Rental does not exists. | The provided id does not references a previous rental. |
| 741 | User is not authorized. | You don't have enough permission to do this action. |
| 742 | Missing authorization token. | The Bearer Token was not sent. |
| 743 | Invalid token. | The Bearer Token provided is invalid or expired. |
| 749 | Too many requests. | You reached at the requests limit. |
A few routes expect a Bearer Token in an Authorization header.
You can see these routes in the routes section.
GET http://localhost:3333/v1/rentals Authorization: Bearer <token>
To achieve this token you just need authenticate through the
/sessionsroute and it will return thetokenkey with a valid Bearer Token.
A simple versioning was made. Just remember to set after the host the /v1/ string to your requests.
GET http://localhost:3333/v1/rentals
| route | HTTP Method | params | description | auth method |
|---|---|---|---|---|
/sessions |
POST | Body with user's email and password. |
Authenticates user, return a Bearer Token and user's name, email, token and refresh token. | ❌ |
/refresh_token |
POST | Body with refresh_token. |
Exchange an new token and refresh token | ❌ |
/cars |
POST | Body with cars' name, description, daily_rate, license_plate, fine_amount, brand, category_id. |
Create a new car. | Bearer |
/cars/:id/specifications |
POST | :id of the car and body with an array of specifications ids. |
Add specification to a car. | Bearer |
/cars/:id/images |
POST | :id of the car and multipart body with car_images fields with a image (See insomnia file for good example). |
Add images to a car. | Bearer |
/cars/availables |
GET | brand, name or category_id query parameters. |
Retrieve cars available for rent. | ❌ |
/categories |
GET | - | Retrieve a list of categories. | ❌ |
/categories |
POST | Body with user's name and description. |
Create a new category. | Bearer |
/categories/import |
POST | Multipart payload with a file field with a image (See insomnia file for good example). |
Import a bulk of categories. | Bearer |
/password/forgot |
POST | Body with user's email. |
Send reset password email. | ❌ |
/password/reset |
POST | token query parameter and body with user's new password. |
Update user's password. | ❌ |
/rentals/user |
GET | - | Retrieve user's rentals. | Bearer |
/rentals |
POST | Body with rental's expected_return_date and car_id. |
Create a car rent. | Bearer |
/rentals/:id/devolution |
POST | id query parameter. |
Close rental. | Bearer |
/specifications |
POST | Body with user's name and description. |
Create a new specification. | Bearer |
/users |
GET | - | Return user's profile. | Bearer |
/users |
POST | Body with user's. | Return user's name, email, password and driver_license. |
❌ |
/users/avatar |
PATCH | Multipart payload with a atavar field with a image (See insomnia file for good example). | Update user avatar. | Bearer |
Routes with
Beareras auth method expect anAuthorizationheader. See Bearer Token section for more information.
POST /session
Request body:
{
"email": "johndoe@example.com",
"password": "123456"
}POST /refresh_token
Request body:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
}POST /cars
Request body:
{
"name": "Car name",
"description": "Car description",
"daily_rate": 140.0,
"license_plate": "XYZ1234",
"fine_amount": 100,
"brand": "Car brand",
"category_id": "6878f9b2-eb7c-4ad6-ac72-66d958f117c2"
}POST /cars/:id/specifications
Request body:
{
"specifications_id": ["b3267e11-eec4-46a4-accc-9e8b4fad0af7"]
}-
POST /cars/:id/images
Image file(s) -
POST /categories
Request body:
{
"name": "Category name",
"description": "Category description"
}-
POST /categories/import
CSV file -
POST /password/forgot
Request body:
{
"email": "johndoe@example.com"
}POST /password/reset
Request body:
{
"password": "123456"
}POST /rentals
Request body:
{
"expected_return_date": "2021-04-08T01:31:15.328Z",
"car_id": "01931fee-32d4-4af7-b4e9-12159c5d703e"
}POST /specifications
Request body:
{
"name": "Specification name",
"description": "Specification description"
}POST /users
Request body:
{
"name": "John",
"email": "johndoe@example.com",
"password": "123456",
"driver_license": "782378234923"
}PATCH /users/avatar
Image file
Jest was the choice to test the app, to run:
$ yarn test
Or:
$ npm run test
You can see the coverage report inside tests/coverage. They are automatically created after the tests run.