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
eslint
andprettier
to 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
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:
rentx
andtest
(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.
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 |
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-flexible
was 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_URL
key from.env
file. In the next sub section (Errors Reference) you can see the errorscode
description.
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
/sessions
route and it will return thetoken
key 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
Bearer
as auth method expect anAuthorization
header. 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.