{"id":21589555,"url":"https://github.com/pedrohenrikle/gympass-api","last_synced_at":"2026-05-20T06:04:25.462Z","repository":{"id":224634765,"uuid":"763798931","full_name":"pedrohenrikle/gympass-api","owner":"pedrohenrikle","description":"Node.js \u0026 Typescript based, developed with concepts like SOLID, DDD, TDD, Repository Pattern, Factory Method and RBAC system. Has unit test for especific use-cases and test E2E for every HTTP controller.","archived":false,"fork":false,"pushed_at":"2024-03-08T20:29:25.000Z","size":572,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-24T16:14:26.091Z","etag":null,"topics":["api","ddd","e2e-tests","factory-pattern","nodejs","repository-pattern","solid","tdd","typescript","unit-testing"],"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/pedrohenrikle.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}},"created_at":"2024-02-26T23:54:36.000Z","updated_at":"2024-03-10T00:35:02.000Z","dependencies_parsed_at":"2024-03-08T21:22:51.010Z","dependency_job_id":null,"html_url":"https://github.com/pedrohenrikle/gympass-api","commit_stats":null,"previous_names":["pedrohenrikle/gympass-api"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pedrohenrikle%2Fgympass-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pedrohenrikle%2Fgympass-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pedrohenrikle%2Fgympass-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pedrohenrikle%2Fgympass-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pedrohenrikle","download_url":"https://codeload.github.com/pedrohenrikle/gympass-api/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244198189,"owners_count":20414439,"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","ddd","e2e-tests","factory-pattern","nodejs","repository-pattern","solid","tdd","typescript","unit-testing"],"created_at":"2024-11-24T16:14:52.891Z","updated_at":"2026-05-20T06:04:20.428Z","avatar_url":"https://github.com/pedrohenrikle.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg width=\"150px\" src=\"./.github/images/logo.png\"/\u003e\n\n# **GympassAPI**\n\n✨ A copy (or something like) of the Gympass API. Node.js \u0026 Typescript based, developed with concepts like SOLID, DDD, TDD, Repository Pattern, Factory Method and RBAC system. Has unit test for especific use-cases and test E2E for every HTTP controller. ✨\n\n[![GitHub Repo stars](https://img.shields.io/github/stars/pedrohenrikle/gympass-api)](https://github.com/pedrohenrikle/gympass-api)\n[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)\n\n\u003c/div\u003e\n\n## Table of contents\n\n- [Why GympassAPI?](#why-gympassapi)\n- [Getting Started](#getting-started)\n- [Features](#🛠️-features)\n  - [Functional requirements](#frs-functional-requirements)\n  - [Business rules](#brs-business-rules)\n  - [Non-functional requirements](#nfrs-non-functional-requirements)\n- [Endpoints](#🛑-endpoints)\n  - [Users](#users)\n  - [Gyms](#gyms)\n  - [Check-Ins](#check-ins)\n- [Technologies](#🚀-technologies)\n- [License](#📝-license)\n- [Author](#✍-author)\n\n## Why GympassAPI?\n\nGympassAPI was a project to better learn the concepts involved in the backend, such as SOLID concepts, the principles of DDD and TDD. I also learned about the RBAC authorization system, the use of the JWT token, authentication and validation systems and much more.\n\n\n## Getting Started\n\nTo get started with GympassAPI, follow these simple steps:\n\n### 1. Installation\n\nFirst of all, clone the repository and install de dependencies.\n\n```shell\nnpm install\n```\n\n### 2. Setting up your environment\n\nNow we must setup our environment variables. Create a file on root as ***.env***. Inside, put all data like is on .env.example\n\n```shell\nNODE_ENV=dev\n\n# Auth\nJWT_SECRET=YOUR_SECRET_HERE\n\n# Database\nDATABASE_URL=\"url_from_your_database\"\n```\n\n### 3. Create a Database\n\nThere is a `docker-compose.yml` file inside the project, so if you want to create the container as I thot, you can run the following command\n\n```shell\ndocker compose up -d\n```\n\nand if you want to stop the container\n\n```shell\ndocker compose stop\n```\n\n## 🛠️ Features\n\n### FRs (Functional requirements)\n\n- [x] It must be possible to register;\n- [x] It must be possible to authenticate;\n- [x] It must be possible to obtain the profile of a logged-in user;\n- [x] It must be possible to obtain the number of check-ins carried out by the logged-in user;\n- [x] It must be possible for the user to obtain their check-in history;\n- [x] It must be possible for the user to search for nearby gyms (up to 10km);\n- [x] It must be possible for the user to search for gyms by name;\n- [x] It must be possible for the user to check-in at a gym;\n- [x] It must be possible to validate a user's check-in;\n- [x] It must be possible to register a gym;\n\n### BRs (Business Rules)\n\n- [x] The user must not be able to register with a duplicate e-mail address;\n- [x] The user cannot make 2 check-ins on the same day;\n- [x] The user cannot check in if they are not close (100m) to the gym;\n- [x] The check-in can only be validated up to 20 minutes after it has been created;\n- [x] The check-in can only be validated by administrators;\n- [x] The gym can only be registered by administrators;\n\n### NFRs (Non-functional requirements)\n\n- [x] The user's password must be encrypted;\n- [x] The application data must be persisted in a PostgreSQL database;\n- [x] All data lists must be paginated with 20 items per page;\n- [x] The user must be identified by a JWT (JSON Web Token);\n\n## 🛑 Endpoints\n\nHere you can see all the endpoints of the application\n\n### Users\n\n- ```POST - '/users'```\n  - This is the user's entry route, where the user will register with on the application. You must send the request with a ***data*** equal a some user's informations on the body of the requisition.\n  \u003cbr\u003e\u003cbr\u003e\n\n    | Params       | Type        | Default    |\n    | :---         | :---        | :---       |\n    | `name`       | **string**  | -          |\n    | `email`      | **string**  | -          |\n    | `password`   | **string**  | -          |\n    | `role`       | **string**  | `'MEMBER'` |\n\n\n\u003cbr\u003e\u003cbr\u003e\n\n- `POST - '/sessions'`\n  - This is the authenticate route. Here, the user can log-in on our application. Send this data as the body of the request as a JSON. After that, we will generate a access token that contains the **user ID** and about his **role**.\n  \u003cbr\u003e\u003cbr\u003e\n\n    | Params       | Type       | Default  |\n    | :---         | :---       | :---     |\n    | `email`      | **string** | -        |\n    | `password`   | **string** | -        |\n\n\u003cbr\u003e\u003cbr\u003e\n\n- ```PATCH - '/token/refresh'```\n  - This route will check if the user has an access token. If not, it will try to create another one based on the refresh token located in cookies. You must send the requisition with the access token as authorization (if you have one).\n  \u003cbr\u003e\u003cbr\u003e\n\n    | Header           | Type       | \n    | :---             | :---       | \n    | `Authorization`  | **Bearer** |\n\n\u003cbr\u003e\u003cbr\u003e\n\n- ```GET - '/me'```\n  - This route requires that you already be logged-in. Here, you send your requisition with a JWT Token as authorization.\n  \u003cbr\u003e\u003cbr\u003e\n\n    | Header           | Type       | \n    | :---             | :---       | \n    | `Authorization`  | **Bearer** |\n\n  will be something like this\n\n  ```ts\n  fetch(url, {\n    method: 'GET',\n    headers: {\n      'Authorization': `Bearer ${token}`,\n      'Content-Type': 'application/json' \n    }\n  })\n  ```\n\n### Gyms\n\nOn ***Gyms***, every route needs to be authenticated, that means every request must have the following header: `Authorization Bearer ${token}`. Some routes also require `role = 'ADMIN'`.\n\n- ```GET - '/gyms/search'```\n  - Here you can search gyms by their name. You must send the request with a ***query*** as **q** on the body of the requisition as a JSON.\n  \u003cbr\u003e\u003cbr\u003e\n\n    | Params       | Type       | Default  |\n    | :---         | :---       | :---     |\n    | `q`          | **string** | -        |\n    | `page`       | **number** | 1        |\n\n\u003cbr\u003e\u003cbr\u003e\n\n- ```GET - '/gyms/nearby'```\n  - Here you can find nearby gyms based on latitude and longitude of the user. You must send the request with a ***latitude*** and ***longitude*** of the user on the body of the requisitionas as a JSON.\n\n    | Params         | Type       | Default  |\n    | :---           | :---       | :---     |\n    | `latitude`     | **number** | -        |\n    | `longitude`    | **number** | -        |\n\n\u003cbr\u003e\u003cbr\u003e\n\n- ```POST - '/gyms'```\n  - Here we create a new Gym on database. You must do this as a `'ADMIN'` user so your access token must have this proprerty. You can pass the informations of the gym on the body of the requisition as a JSON.\n\n    | Params         | Type       | Default  |\n    | :---           | :---       | :---     |\n    | `title`        | **string** | -        |\n    | `description`  | **string** | -        |\n    | `phone`        | **number** | -        |\n    | `latitude`     | **number** | -        |\n    | `longitude`    | **number** | -        |\n\n### Check Ins\n\nOn ***Check Ins***, every route needs to be authenticated.\n\n- ```GET - '/check-ins/history'```\n  - Here we can take the history of all check-ins that the user made it. You must just provide your JWT Token for the authorization.\n\n    | Header           | Type       | \n    | :---             | :---       | \n    | `Authorization`  | **Bearer** |\n\n  will be something like this\n\n    ```ts\n    fetch(url, {\n      method: 'GET',\n      headers: {\n        'Authorization': `Bearer ${token}`,\n        'Content-Type': 'application/json' \n      }\n    })\n    ```\n\u003cbr\u003e\u003cbr\u003e\n\n- ```GET - '/check-ins/metrics'```\n  - Here we can take the metrics of the user. You must just provide your JWT Token for the authorization.\n\n    | Header           | Type       | \n    | :---             | :---       | \n    | `Authorization`  | **Bearer** |\n\n  will be something like this\n\n  ```ts\n  fetch(url, {\n    method: 'GET',\n    headers: {\n      'Authorization': `Bearer ${token}`,\n      'Content-Type': 'application/json' \n    }\n  })\n  ```\n\n\u003cbr\u003e\u003cbr\u003e\n\n- ```POST - '/gyms/:gymId/check-ins'```\n  - Here we can create a check-in on a gym. You must provide on the URL param the id of the gym as **gymID** and on the body of the request the **latitude** and **longitude** of the user as a JSON.\n\n    | Params         | Type       | Default  |\n    | :---           | :---       | :---     |\n    | `gymId`        | **string** | -        |\n    | `latitude`     | **number** | -        |\n    | `longitude`    | **number** | -        |\n\n\u003cbr\u003e\u003cbr\u003e\n\n- ```POST - '/check-ins/:checkInId/validate'```\n  - Here we validate the check-in of the user. You must do this as a `'ADMIN'` user so your access token must have this proprerty. You also must provide on the URL param the **checkInId** as the check-in that will be validated.\n\n    | Params         | Type       | Default  |\n    | :---           | :---       | :---     |\n    | `checkInId`    | **string** | -        |\n\n\n## 🚀 Technologies\n  The technologies used to develop this application was:\n  - [nodejs](https://nodejs.org/en)\n  - [typescript](https://www.typescriptlang.org/)\n  - [fastify](https://fastify.dev/)\n  - [@fastify/cookie](https://github.com/fastify/fastify-cookie)\n  - [@fastify/jwt](https://github.com/fastify/fastify-jwt)\n  - [prisma](https://www.prisma.io/)\n  - [zod](https://zod.dev/)\n  - [vitest](vitest.dev)\n  - [supertest](https://github.com/ladjs/supertest)\n  - [dayjs](https://day.js.org/en/)\n  - [tsup](https://github.com/egoist/tsup)\n  - [dotenv](https://github.com/motdotla/dotenv)\n  - [bcryptjs](https://github.com/dcodeIO/bcrypt.js)\n  - [eslint](https://eslint.org/)\n\n## 📝 License\nGympassAPI is released under the MIT License.\n\n\n## ✍ Author\n\u003cbr\u003e\n\n\u003cdiv style=\"display: flex; flex-direction: column; gap: 0.5rem\"\u003e\n  \u003cimg alt=\"Pedro Henrique Klein\" title=\"Pedro Henrique Klein\" src=\"https://github.com/pedrohenrikle.png\" width=\"150\"\u003e\n  \u003cp\u003e\n      Made with 💜 by Pedro Henrique Klein\n  \u003c/p\u003e\n  \u003cdiv style=\"display: flex; align-item: center; gap: 1rem\"\u003e\n      \u003ca href=\"https://www.linkedin.com/in/pedro-klein/\" target=\"_blank\"\u003e\n          \u003cimg align=\"center\" src=\"https://img.shields.io/badge/LinkedIn-%230077B5?style=for-the-badge\u0026logo=linkedin\u0026logoColor=white\" alt=\"LinkedIn: pedro-klein\" /\u003e\n      \u003c/a\u003e\n      \u003ca href=\"mailto:pedro.klein.sl@gmail.com\" target=\"_blank\"\u003e\n          \u003cimg align=\"center\" src=\"https://img.shields.io/badge/Gmail-FF0000?style=for-the-badge\u0026logo=gmail\u0026logoColor=white\" alt=\"pedro.klein.sl@gmail.com\" /\u003e\n      \u003c/a\u003e\n  \u003c/div\u003e\n\u003c/div\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpedrohenrikle%2Fgympass-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpedrohenrikle%2Fgympass-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpedrohenrikle%2Fgympass-api/lists"}