{"id":17651754,"url":"https://github.com/diegovictor/rentx","last_synced_at":"2025-05-07T07:28:46.485Z","repository":{"id":49306085,"uuid":"353498328","full_name":"DiegoVictor/rentx","owner":"DiegoVictor","description":"Application built during the Rocketseat Ignite Bootcamp ","archived":false,"fork":false,"pushed_at":"2025-03-10T12:14:00.000Z","size":890,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-31T07:41:19.919Z","etag":null,"topics":["api","coverage-report","editorconfig","eslint","ignite","jest","node","nodejs","postgres","redis","rentx","rocketseat","swagger","ts","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DiegoVictor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-03-31T21:47:06.000Z","updated_at":"2025-03-10T12:14:04.000Z","dependencies_parsed_at":"2024-02-20T14:54:30.291Z","dependency_job_id":"b97c0829-2e28-4b8b-bff4-9279c5fcfa44","html_url":"https://github.com/DiegoVictor/rentx","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DiegoVictor%2Frentx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DiegoVictor%2Frentx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DiegoVictor%2Frentx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DiegoVictor%2Frentx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DiegoVictor","download_url":"https://codeload.github.com/DiegoVictor/rentx/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252833032,"owners_count":21811100,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","coverage-report","editorconfig","eslint","ignite","jest","node","nodejs","postgres","redis","rentx","rocketseat","swagger","ts","typescript"],"created_at":"2024-10-23T11:43:29.768Z","updated_at":"2025-05-07T07:28:46.463Z","avatar_url":"https://github.com/DiegoVictor.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Rentx\n[![CircleCI](https://img.shields.io/circleci/build/github/DiegoVictor/rentx?style=flat-square\u0026logo=circleci)](https://app.circleci.com/pipelines/github/DiegoVictor/rentx?branch=main)\n[![postgres](https://img.shields.io/badge/postgres-8.6.0-326690?style=flat-square\u0026logo=postgresql\u0026logoColor=white)](https://www.postgresql.org/)\n[![redis](https://img.shields.io/badge/redis-3.1.2-d92b21?style=flat-square\u0026logo=redis\u0026logoColor=white)](https://redis.io/)\n[![typescript](https://img.shields.io/badge/typescript-4.3.5-3178c6?style=flat-square\u0026logo=typescript)](https://www.typescriptlang.org/)\n[![eslint](https://img.shields.io/badge/eslint-7.31.0-4b32c3?style=flat-square\u0026logo=eslint)](https://eslint.org/)\n[![airbnb-style](https://flat.badgen.net/badge/style-guide/airbnb/ff5a5f?icon=airbnb)](https://github.com/airbnb/javascript)\n[![jest](https://img.shields.io/badge/jest-27.0.6-brightgreen?style=flat-square\u0026logo=jest)](https://jestjs.io/)\n[![coverage](https://img.shields.io/codecov/c/gh/DiegoVictor/rentx?logo=codecov\u0026style=flat-square)](https://codecov.io/gh/DiegoVictor/rentx)\n[![MIT License](https://img.shields.io/badge/license-MIT-green?style=flat-square)](https://raw.githubusercontent.com/DiegoVictor/rentx/main/LICENSE)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)\u003cbr\u003e\n[![Run in Insomnia}](https://insomnia.rest/images/run.svg)](https://insomnia.rest/run/?label=Rentx\u0026uri=https%3A%2F%2Fraw.githubusercontent.com%2FDiegoVictor%2Frentx%2Fmain%2FInsomnia_2024-12-02.json)\n\nAllows 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.\n\n## Table of Contents\n* [Installing](#installing)\n  * [Configuring](#configuring)\n    * [Redis](#redis)\n    * [Postgres](#postgres)\n      * [Migrations](#migrations)\n    * [.env](#env)\n    * [Rate Limit (Optional)](#rate-limit-optional)\n* [Usage](#usage)\n  * [Error Handling](#error-handling)\n    * [Errors Reference](#errors-reference)\n  * [Bearer Token](#bearer-token)\n  * [Versioning](#versioning)\n  * [Routes](#routes)\n    * [Requests](#requests)\n* [Running the tests](#running-the-tests)\n  * [Coverage report](#coverage-report)\n\n# Installing\nEasy peasy lemon squeezy:\n```\n$ yarn\n```\nOr:\n```\n$ npm install\n```\n\u003e Was installed and configured the [`eslint`](https://eslint.org/) and [`prettier`](https://prettier.io/) to keep the code clean and patterned.\n\n## Configuring\nThe application uses two databases: [Postgres](https://www.postgresql.org/) and [Redis](https://redis.io/). For the fastest setup is recommended to use [docker-compose](https://docs.docker.com/compose/), you just need to up all services:\n```\n$ docker-compose up -d\n```\n### Redis\nResponsible 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:\n```\n$ docker run --name rentx-redis -d -p 6379:6379 redis:alpine\n```\n\n### Postgres\nResponsible 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:\n```\n$ docker run --name rentx-postgres -e POSTGRES_PASSWORD=docker -p 5432:5432 -d postgres\n```\n\u003e Then create two databases: `rentx` and `test` (in case you would like to run the tests).\n\n#### Migrations\nRemember to run the database migrations:\n```\n$ yarn ts-node-dev ./node_modules/typeorm/cli.js migration:run\n```\nOr:\n```\n$ yarn typeorm migration:run\n```\n\u003e See more information on [TypeORM Migrations](https://typeorm.io/#/migrations).\n\n### .env\nIn 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](#error-handling)). Rename the `.env.example` in the root directory to `.env` then just update with your settings.\n\n|key|description|default\n|---|---|---\n|API_URL|Used to mount avatars' urls.|`http://localhost:3333`\n|PORT|Port number where the app will run.|`3333`\n|RESET_PASSWORD_URL|Url where the user will be able to change the password|`http://localhost:3333/v1/password/reset?token=`\n|JWT_SECRET|A alphanumeric random string. Used to create signed tokens.| -\n|JWT_EXPIRATION_TIME|How long time will be the token valid. See [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken#usage) repo for more information.|`15m`\n|REFRESH_TOKEN_SECRET|A alphanumeric random string. Used to create signed refresh tokens.| -\n|REFRESH_TOKEN_EXPIRATION_DAYS|How many days long will be the refresh token valid. See [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken#usage) repo for more information.|30\n|DB_HOST|Postgres host.|`pg`\n|DB_PORT|Postgres port.|`5432`\n|DB_USER|Postgres user.| -\n|DB_PASSWORD|Postgres password.| -\n|DB_NAME|Application's database name.| -\n|REDIS_HOST|Redis host.| `redis`\n|REDIS_PORT|Redis port.| `6379`\n|REDIS_PASSWORD|Redis password.| -\n|STORAGE_DRIVER|Set where the files will be stored, the available values are: `local` and `s3`.|`local`\n|MAIL_DRIVER|Set what service to use to send mails, the available values are: `ethereal` and `ses`.|`ses`\n|MAIL_SENDER|The `from` sent in the email.| -\n|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](https://docs.aws.amazon.com/toolkit-for-eclipse/v1/user-guide/setup-credentials.html).| -\n|AWS_BUCKET|Amazon S3 stores data as objects within buckets. To create a bucket see [Creating a bucket](https://docs.aws.amazon.com/AmazonS3/latest/gsg/CreatingABucket.html).| -\n|AWS_BUCKET_URL|Utilized to mount avatars' urls when using `s3` as `STORAGE_DRIVER`. Can also be found while creating the bucket.| -\n|AWS_REGION|You can see your default region in the navigation bar at the top right after login in the [AWS Management Console](https://sa-east-1.console.aws.amazon.com/console/home). Read [AWS service endpoints](https://docs.aws.amazon.com/general/latest/gr/rande.html) to know more about regions.| -\n|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`\n\n### Rate Limit (Optional)\nThe project comes pre-configured, but you can adjust it as your needs.\n\n* `src/config/rateLimit.ts`\n\n|key|description|default\n|---|---|---\n|duration|Number of seconds before consumed points are reset.|`300`\n|points|Maximum number of points can be consumed over duration.|`10`\n\n\u003e The lib [`rate-limiter-flexible`](https://github.com/animir/node-rate-limiter-flexible) was used to rate the api's limits, for more configuration information go to [Options](https://github.com/animir/node-rate-limiter-flexible/wiki/Options#options) page.\n\n# Usage\nTo start up the app run:\n```\n$ yarn dev:server\n```\nOr:\n```\nnpm run dev:server\n```\n\n## Error Handling\nInstead of only throw a simple message and HTTP Status Code this API return friendly errors:\n```json\n{\n  \"statusCode\": 429,\n  \"error\": \"Too Many Requests\",\n  \"message\": \"Too Many Requests\",\n  \"code\": 749,\n  \"docs\": \"https://github.com/DiegoVictor/rentx#errors-reference\"\n}\n```\n\u003e As you can see a url to error docs are returned too. To configure this url update the `DOCS_URL` key from `.env` file.\n\u003e In the next sub section ([Errors Reference](#errors-reference)) you can see the errors `code` description.\n\n### Errors Reference\n|code|message|description\n|---|---|---\n|140|Email or password incorrect.|Password did not match.\n|141|Invalid token.|The reset password refresh token not references an existing one in the database.\n|142|Token expired.|The provided reset password refresh token has already expired.\n|144|Refresh Token does not exists.|The provided authentication refresh token was not found in the database.\n|240|User already exists.|Already exists an user with the same `email`.\n|244|User does not exists.|Was not found the user by the provided `email` to reset the password.\n|245|User does not exists.|The provided `id` does not reference an user in the database.\n|340|Car already exists.|You are trying to create a car with a `license_plate` already in use.\n|341|Car is unavailable.|Is not possible rent a car that is already rent.\n|344|Car does not exists.|The provided `id` does not reference a car in the database.\n|440|Category already exists.|You are trying to create a category with a `name` already in use.\n|540|Specification already exists.|You are trying to create a specification with a `name` already in use.\n|640|There's a rental in progress for this user.|Is not possible to make more than one rent at time.\n|641|A rental must have at least 24 hours of duration.|Is not allowed to rent a car by less than 24 hours.\n|644|Rental does not exists.|The provided `id` does not references a previous rental.\n|741|User is not authorized.|You don't have enough permission to do this action.\n|742|Missing authorization token.|The Bearer Token was not sent.\n|743|Invalid token.|The Bearer Token provided is invalid or expired.\n|749|Too many requests.|You reached at the requests limit.\n\n## Bearer Token\nA few routes expect a Bearer Token in an `Authorization` header.\n\u003e You can see these routes in the [routes](#routes) section.\n```\nGET http://localhost:3333/v1/rentals Authorization: Bearer \u003ctoken\u003e\n```\n\u003e To achieve this token you just need authenticate through the `/sessions` route and it will return the `token` key with a valid Bearer Token.\n\n## Versioning\nA simple versioning was made. Just remember to set after the `host` the `/v1/` string to your requests.\n```\nGET http://localhost:3333/v1/rentals\n```\n\n## Routes\n|route|HTTP Method|params|description|auth method\n|:---|:---:|:---:|:---:|:---:\n|`/sessions`|POST|Body with user's `email` and `password`.|Authenticates user, return a Bearer Token and user's name, email, token and refresh token.|:x:\n|`/refresh_token`|POST|Body with `refresh_token`.|Exchange an new token and refresh token|:x:\n|`/cars`|POST|Body with cars' `name`, `description`, `daily_rate`, `license_plate`, `fine_amount`, `brand`, `category_id`.|Create a new car.|Bearer\n|`/cars/:id/specifications`|POST|`:id` of the car and body with an array of specifications `ids`.|Add specification to a car.|Bearer\n|`/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\n|`/cars/availables`|GET|`brand`, `name` or `category_id` query parameters.|Retrieve cars available for rent.|:x:\n|`/categories`|GET| - |Retrieve a list of categories.|:x:\n|`/categories`|POST|Body with user's `name` and `description`.|Create a new category.|Bearer\n|`/categories/import`|POST|Multipart payload with a `file` field with a image (See insomnia file for good example).|Import a bulk of categories.|Bearer\n|`/password/forgot`|POST|Body with user's `email`.|Send reset password email.|:x:\n|`/password/reset`|POST|`token` query parameter and body with user's new `password`.|Update user's password.|:x:\n|`/rentals/user`|GET| - |Retrieve user's rentals.|Bearer\n|`/rentals`|POST|Body with rental's `expected_return_date` and `car_id`.|Create a car rent.|Bearer\n|`/rentals/:id/devolution`|POST|`id` query parameter.|Close rental.|Bearer\n|`/specifications`|POST|Body with user's `name` and `description`.|Create a new specification.|Bearer\n|`/users`|GET| - |Return user's profile.|Bearer\n|`/users`|POST|Body with user's.|Return user's `name`, `email`, `password` and `driver_license`.|:x:\n|`/users/avatar`|PATCH|Multipart payload with a atavar field with a image (See insomnia file for good example).|Update user avatar.|Bearer\n\n\u003e Routes with `Bearer` as auth method expect an `Authorization` header. See [Bearer Token](#bearer-token) section for more information.\n\n### Requests\n* `POST /session`\n\nRequest body:\n```json\n{\n  \"email\": \"johndoe@example.com\",\n  \"password\": \"123456\"\n}\n```\n\n* `POST /refresh_token`\n\nRequest body:\n```json\n{\n  \"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c\",\n}\n```\n\n* `POST /cars`\n\nRequest body:\n```json\n{\n  \"name\": \"Car name\",\n  \"description\": \"Car description\",\n  \"daily_rate\": 140.0,\n  \"license_plate\": \"XYZ1234\",\n  \"fine_amount\": 100,\n  \"brand\": \"Car brand\",\n  \"category_id\": \"6878f9b2-eb7c-4ad6-ac72-66d958f117c2\"\n}\n```\n\n* `POST /cars/:id/specifications`\n\nRequest body:\n```json\n{\n  \"specifications_id\": [\"b3267e11-eec4-46a4-accc-9e8b4fad0af7\"]\n}\n```\n\n* `POST /cars/:id/images`\u003cbr\u003e\nImage file(s)\n\n* `POST /categories`\n\nRequest body:\n```json\n{\n  \"name\": \"Category name\",\n  \"description\": \"Category description\"\n}\n```\n\n* `POST /categories/import`\u003cbr\u003e\nCSV file\n\n* `POST /password/forgot`\n\nRequest body:\n```json\n{\n  \"email\": \"johndoe@example.com\"\n}\n```\n\n* `POST /password/reset`\n\nRequest body:\n```json\n{\n  \"password\": \"123456\"\n}\n```\n\n* `POST /rentals`\n\nRequest body:\n```json\n{\n  \"expected_return_date\": \"2021-04-08T01:31:15.328Z\",\n  \"car_id\": \"01931fee-32d4-4af7-b4e9-12159c5d703e\"\n}\n```\n\n* `POST /specifications`\n\nRequest body:\n```json\n{\n  \"name\": \"Specification name\",\n  \"description\": \"Specification description\"\n}\n```\n\n* `POST /users`\n\nRequest body:\n```json\n{\n  \"name\": \"John\",\n  \"email\": \"johndoe@example.com\",\n  \"password\": \"123456\",\n  \"driver_license\": \"782378234923\"\n}\n```\n\n* `PATCH /users/avatar`\u003cbr\u003e\nImage file\n\n# Running the tests\n[Jest](https://jestjs.io/) was the choice to test the app, to run:\n```\n$ yarn test\n```\nOr:\n```\n$ npm run test\n```\n\n## Coverage report\nYou can see the coverage report inside `tests/coverage`. They are automatically created after the tests run.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdiegovictor%2Frentx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdiegovictor%2Frentx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdiegovictor%2Frentx/lists"}