{"id":14974018,"url":"https://github.com/fontseca/noda-tasks-management-tool-api","last_synced_at":"2025-10-27T05:31:34.608Z","repository":{"id":194180036,"uuid":"690141655","full_name":"fontseca/noda-tasks-management-tool-api","owner":"fontseca","description":"A robust task management RESTful API developed with Go, PostgreSQL, JWT for authentication, and Docker for containerization. It features secure user authentication, task creation and management.","archived":false,"fork":false,"pushed_at":"2024-11-29T22:50:54.000Z","size":1414,"stargazers_count":9,"open_issues_count":15,"forks_count":0,"subscribers_count":1,"default_branch":"development","last_synced_at":"2025-02-01T02:41:23.609Z","etag":null,"topics":["api","backend","docker","go","golang","jwt","plpgsql","postgresql","restful-api","sql"],"latest_commit_sha":null,"homepage":"","language":"Go","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/fontseca.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":"2023-09-11T15:56:14.000Z","updated_at":"2024-12-10T16:32:45.000Z","dependencies_parsed_at":"2024-01-01T23:51:13.190Z","dependency_job_id":"f5722bce-37af-4b40-a1a2-64d0157db3a8","html_url":"https://github.com/fontseca/noda-tasks-management-tool-api","commit_stats":{"total_commits":447,"total_committers":1,"mean_commits":447.0,"dds":0.0,"last_synced_commit":"1f6db3fa0fe23045fd8140a40914f2c8a7e48ea0"},"previous_names":["fontseca/noda","fontseca/tasks-management-tool"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fontseca%2Fnoda-tasks-management-tool-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fontseca%2Fnoda-tasks-management-tool-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fontseca%2Fnoda-tasks-management-tool-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fontseca%2Fnoda-tasks-management-tool-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fontseca","download_url":"https://codeload.github.com/fontseca/noda-tasks-management-tool-api/tar.gz/refs/heads/development","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238445843,"owners_count":19473822,"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","backend","docker","go","golang","jwt","plpgsql","postgresql","restful-api","sql"],"created_at":"2024-09-24T13:49:49.893Z","updated_at":"2025-10-27T05:31:34.099Z","avatar_url":"https://github.com/fontseca.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"./logo.svg\" alt=\"drawing\" style=\"width:400px;\"/\u003e\n\n---\n\n  \u003ca href=\"https://golang.org/doc/go1.20\"\u003e\n    \u003cimg alt=\"golang version\" src=\"https://img.shields.io/badge/Go-1.22.5-blue.svg\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://opensource.org/licenses/MIT\"\u003e\n    \u003cimg alt=\"project license\" src=\"https://img.shields.io/badge/license-MIT-brightgreen.svg\" /\u003e\n  \u003c/a\u003e\n  \u003cimg alt=\"last commit\" src=\"https://img.shields.io/github/last-commit/fontseca/noda?color=61dfc6\u0026label=last%20commit\" /\u003e\n\u003c/div\u003e\n\n**NODA** is a RESTful task management web API built with Go and designed to simplify the process of managing tasks,\nlists, and user interactions. It uses PostgreSQL for data storage, JSON Web Tokens (JWT) for authorization and security,\nand Docker Compose for easy development.\n\n## Table of contents\n\n\u003c!-- TOC --\u003e\n  * [Table of contents](#table-of-contents)\n  * [Source code structure](#source-code-structure)\n    * [Overall structure](#overall-structure)\n    * [Detailed explanation](#detailed-explanation)\n  * [Installation](#installation)\n    * [Prerequisites](#prerequisites)\n    * [Step-by-step setup](#step-by-step-setup)\n  * [Debugging](#debugging)\n  * [Running the tests](#running-the-tests)\n  * [Screenshots](#screenshots)\n    * [Database](#database)\n      * [Some database objects](#some-database-objects)\n      * [Some database tables](#some-database-tables)\n      * [Searching for users and making lists for a specific user](#searching-for-users-and-making-lists-for-a-specific-user)\n      * [Making and blocking a user with a reason](#making-and-blocking-a-user-with-a-reason)\n  * [API endpoints](#api-endpoints)\n    * [Authentication](#authentication)\n    * [Users management](#users-management)\n    * [Groups management](#groups-management)\n    * [Lists management](#lists-management)\n    * [Tasks management](#tasks-management)\n    * [Steps management](#steps-management)\n    * [Tags management](#tags-management)\n    * [Attachments management](#attachments-management)\n  * [Recommendations](#recommendations)\n\u003c!-- TOC --\u003e\n\n## Source code structure\n\nFor structuring the project source code I'm following a simple and pragmatic approach that has work very well for me.\nPersonally, I prefer to wire up everything in the [`main.go`](./main.go) file and avoid having separate files for\ndatabase configuration, server configuration and API routing, as I believe this is a more elegant and cleaner\napproach compared to having several dispersed packages/files.\n\nWhile I have a preference for wiring everything in [`main.go`](./main.go), it might be a wiser practice for very large\ncodebases to implement separation of concerns.\n\n### Overall structure\n\n```text\n.\n├── assets\n├── client\n├── data\n│  ├── model\n│  ├── transfer\n│  └── types\n├── database\n├── docs\n├── failure\n├── global\n├── handler\n├── mocks\n├── repository\n└── service\n```\n\n\u003e **NOTE:** Another approach would be to move the `model`, `transfer` and `types` directories (packages) to the root\n\u003e directory; this approach, however, provides the opportunity to refer these packages in a more natural way as in “data\n\u003e types”.\n\n### Detailed explanation\n\n* **assets**: Contains images and other similar assets.\n* **client**: Contains the implementation of a client for this web API (not implemented yet.)\n* **[data](./data)**\n    * **[model](./data/model)**: Contains the data models representing the core entities of the application.\n    * **[transfer](./data/transfer)**: Contains data transfer objects (DTOs) for communication with clients.\n    * **[types](./data/types)**: Contains custom types used throughout the application.\n* **[database](./database)**: Contains the database source code.\n* **[docs](./docs)**: Holds detailed API documentation and usage (out of date).\n* **[failure](./failure)**: Manages error handling and custom error definitions to standardize responses. (\n  See [Recommendations](#recommendations).)\n* **[global](./global)**: Contains globally accessible constants, especially if they're coming from environment\n  variable.\n* **[handler](./handler)**: Implements the HTTP request handlers.\n* **[mocks](./mocks)**: Contains mock implementations for unit testing.\n* **[repository](./repository)**: Defines the data access layer for interactions with the database.\n* **[service](./service)**: Contains the business logic layer for core functionalities and validations.\n\nI decided to move the database scripts to a different repository as I think this it's easier to maintain, version\ncontrol, scale and reuse. However, since I still need it, I make it accessible from this repository as a Git submodule.\n\n## Installation\n\nTo run the project you require the database to be running. Once you've finished all the following steps, you must end up\nhaving something like:\n\n```shell\ndocker ps\nCONTAINER ID   IMAGE                       COMMAND                  CREATED         STATUS                            PORTS                                       NAMES\n5d7ce9611cfc   postgres:16rc1-alpine3.18   \"docker-entrypoint.s…\"   3 seconds ago   Up 2 seconds (health: starting)   0.0.0.0:7891-\u003e5432/tcp, :::7891-\u003e5432/tcp   noda_database\nd8f3bcf2c1ae   golang:1.22.5-alpine3.20    \"sh -c ' cd /src \u0026\u0026 …\"   3 seconds ago   Up 2 seconds                      0.0.0.0:7890-\u003e7890/tcp, :::7890-\u003e7890/tcp   noda_backend\n```\n\n### Prerequisites\n\n* Docker\n* Docker Compose\n* Git\n\n### Step-by-step setup\n\n1. Clone this repository and its submodules:\n\n    ```shell\n    git clone --recurse-submodules git@github.com:fontseca/noda-tasks-management-tool-api.git\n    cd noda-tasks-management-tool-api\n    ```\n\n2. Run the Docker containers:\n\n    ```shell\n    docker compose up --detach\n    ```\n\n   In the backend service, wait for the project packages to be installed before running the server.\n\n    ```shell\n    docker logs noda_backend --follow\n    Installing Go dependencies...\n    Go dependencies installed.\n    ^C\n    ```\n\n3. Bootstrap the database objects:\n\n    ```shell\n    docker exec --interactive --tty noda_database sh -c '$PROJECT_DIR/bootstrap.sh'\n    ```\n\n4. If the database objects are created successfully, then access the backend container and from there you can either\n   build or run the web API server:\n\n    ```shell\n    docker exec --interactive --tty noda_backend sh\n    cd /src\n    go run .\n    ```\n\nNow you can start making requests to the web API at `http://0.0.0.0:7890`.\n\n## Debugging\n\nFirst, install [Delve](https://github.com/go-delve/delve) inside the container, if not installed yet:\n\n```shell\ndocker exec --interactive --tty noda_backend go install github.com/go-delve/delve/cmd/dlv@latest\n```\n\nOnce installed, start the debugging server:\n\n```shell\ndocker exec --interactive --tty noda_backend sh -c 'cd /src \u0026\u0026 dlv debug --listen=:$SERVER_PORT --headless=true --api-version=2'\n```\n\n\u003e **Note:** The debugging server uses the same port as the actual application.\n\n## Running the tests\n\nYou run the tests of at leas one of the packages `repository`, `service` and `handler` with the command:\n\n````shell\ngo test ./repository/ ./service/ ./handler/\nok      noda/repository 0.051s\nok      noda/service    0.745s\nok      noda/handler    0.029s\n````\n\nOr, if you prefer using [gotestsum](https://github.com/gotestyourself/gotestsum):\n\n````shell\ngotestsum ./service/ ./repository ./handler\n✓  handler (29ms)\n✓  repository (48ms)\n✓  service (643ms)\n\nDONE 699 tests in 6.567s\n````\n\n## Screenshots\n\n### Database\n\n#### Some database objects\n\n![img.png](assets/img_2.png)\n\n#### Some database tables\n\n![img_1.png](assets/img_3.png)\n\n#### Searching for users and making lists for a specific user\n\n![img_3.png](assets/img_4.png)\n\n#### Making and blocking a user with a reason\n\n![img_4.png](assets/img_5.png)\n\n## API endpoints\n\n### Authentication\n\n| Actor | HTTP Method | Endpoint              | Description                                |\n|-------|-------------|-----------------------|--------------------------------------------|\n| Any   | `POST`      | `/signup`             | Create a new user.                         |\n| User  | `POST`      | `/signin`             | Log in an existent user.                   |\n| User  | `POST`      | `/me/logout`          | Log out the current user.                  |\n| User  | `POST`      | `/me/change_password` | Change the password of the logged in user. |\n\n### Users management\n\n| Actor | HTTP Verb | Endpoint                   | Description                                           |\n|-------|-----------|----------------------------|-------------------------------------------------------|\n| Admin | `GET`     | `/users`                   | Retrieve all users.                                   |\n| Admin | `GET`     | `/users/search`            | Search for users.                                     |\n| Admin | `GET`     | `/users/{user_uuid}`       | Retrieve a user.                                      |\n| Admin | `DELETE`  | `/users/{user_uuid}`       | Permanently remove a user and all its related data.   |\n| Admin | `PUT`     | `/users/{user_uuid}/block` | Block one user.                                       |\n| Admin | `DELETE`  | `/users/{user_uuid}/block` | Unblock one user.                                     |\n| Admin | `GET`     | `/users/blocked`           | Retrieve all blocked users.                           |\n| User  | `GET`     | `/me`                      | Get the logged in user.                               |\n| User  | `PUT`     | `/me`                      | Partially update the account of the logged in user.   |\n| User  | `DELETE`  | `/me`                      | Permanently remove the account of the logged in user. |\n| User  | `GET`     | `/me/settings`             | Retrieve all the settings of the logged in user.      |\n\n### Groups management\n\n| Actor | HTTP Method | Endpoint                 | Description                                     |\n|-------|-------------|--------------------------|-------------------------------------------------|\n| User  | `GET`       | `/me/groups`             | Retrieve all the groups.                        |\n| User  | `POST`      | `/me/groups`             | Create a new group.                             |\n| User  | `GET`       | `/me/groups/{groups_id}` | Retrieve a group.                               |\n| User  | `PATCH`     | `/me/groups/{groups_id}` | Partially update a list.                        |\n| User  | `DELETE`    | `/me/groups/{groups_id}` | Permanently remove a list and all related data. |\n| User  | `GET`       | `/me/groups/{groups_id}` | Retrieve a list.                                |\n\n### Lists management\n\n| Actor | HTTP Method | Endpoint                                    | Description                                                |\n|:-----:|:-----------:|---------------------------------------------|------------------------------------------------------------|\n| User  |    `GET`    | `/me/lists`                                 | Retrieve all the ungrouped lists.                          |\n| User  |   `POST`    | `/me/lists`                                 | Create a new ungrouped list.                               |\n| User  |    `GET`    | `/me/lists/{list_uuid}`                     | Retrieve a ungrouped list.                                 |\n| User  |   `PATCH`   | `/me/lists/{list_uuid}`                     | Partially update a ungrouped list.                         |\n| User  |  `DELETE`   | `/me/lists/{list_uuid}`                     | Permanently remove an ungrouped list and all related data. |\n| User  |    `GET`    | `/me/groups/{group_uuid}/lists`             | Retrieve all the lists of a group.                         |\n| User  |   `POST`    | `/me/groups/{group_uuid}/lists`             | Create a new list for a group.                             |\n| User  |    `GET`    | `/me/groups/{group_uuid}/lists/{list_uuid}` | Retrieve a list of a group.                                |\n| User  |   `PATCH`   | `/me/groups/{group_uuid}/lists/{list_uuid}` | Partially update a list of a group.                        |\n| User  |  `DELETE`   | `/me/groups/{group_uuid}/lists/{list_uuid}` | Permanently remove a list of a group and all related data. |\n\n### Tasks management\n\n| Actor | HTTP Method | Endpoint                                          | Description                                         |\n|-------|-------------|---------------------------------------------------|-----------------------------------------------------|\n| User  | `GET`       | `/me/today`                                       | Retrieve all the tasks from the Today list.         |\n| User  | `GET`       | `/me/tomorrow`                                    | Retrieve all the tasks for tomorrow.                |\n| User  | `GET`       | `/me/tasks`                                       | Retrieve all the tasks.                             |\n| User  | `POST`      | `/me/tasks`                                       | Create a new task and store in the Today list.      |\n| User  | `GET`       | `/me/tasks/search`                                | Search for tasks.                                   |\n| User  | `GET`       | `/me/tasks/completed`                             | Retrieve all the completed tasks.                   |\n| User  | `GET`       | `/me/tasks/archived`                              | Retrieve archived tasks.                            |\n| User  | `GET`       | `/me/tasks/trashed`                               | Retrieve trashed tasks.                             |\n| User  | `GET`       | `/me/tasks/{task_uuid}`                           | Retrieve a task.                                    |\n| User  | `PATCH`     | `/me/tasks/{task_uuid}`                           | Partially update a task.                            |\n| User  | `DELETE`    | `/me/tasks/{task_uuid}`                           | Permanently remove a task and all related data.     |\n| User  | `PUT`       | `/me/tasks/{task_uuid}/trash`                     | Move a task to trash.                               |\n| User  | `DELETE`    | `/me/tasks/{task_uuid}/trash`                     | Recover a task from trash.                          |\n| User  | `PUT`       | `/me/tasks/{task_uuid}/reorder`                   | Rearrange a task in its list.                       |\n| User  | `GET`       | `/me/lists/{list_uuid}/tasks`                     | Retrieve all the tasks of an ungrouped list.        |\n| User  | `POST`      | `/me/lists/{list_uuid}/tasks`                     | Create a task and save it in an ungrouped list.     |\n| User  | `GET`       | `/me/groups/{group_uuid}/lists/{list_uuid}/tasks` | Retrieve all the tasks of a list in a group.        |\n| User  | `POST`      | `/me/groups/{group_uuid}/lists/{list_uuid}/tasks` | Create a task and save it in a list within a group. |\n\n### Steps management\n\n| Actor | HTTP Method | Endpoint                                             | Description                            |\n|-------|-------------|------------------------------------------------------|----------------------------------------|\n| User  | `GET`       | `/me/tasks/{task_uuid}/steps`                        | Retrieve the steps to achieve a task.  |\n| User  | `POST`      | `/me/tasks/{task_uuid}/steps`                        | Add a new step to achieve a task.      |\n| User  | `PATCH`     | `/me/tasks/{task_uuid}/steps/{step_uuid}`            | Partially update a step.               |\n| User  | `PUT`       | `/me/tasks/{task_uuid}/steps/{step_uuid}/accomplish` | Mark a step as accomplished.           |\n| User  | `DELETE`    | `/me/tasks/{task_uuid}/steps/{step_uuid}/accomplish` | Unmark a step as accomplished.         |\n| User  | `DELETE`    | `/me/tasks/{task_uuid}/steps/{step_uuid}`            | Permanently remove a step from a task. |\n| User  | `POST`      | `/me/tasks/{task_uuid}/steps/{step_uuid}/reorder`    | Rearrange a step in a task.            |\n\n### Tags management\n\n| Actor | HTTP Method | Endpoint              | Description                           |\n|-------|-------------|-----------------------|---------------------------------------|\n| User  | `GET`       | `/me/tags`            | Retrieve the steps to achieve a task. |\n| User  | `POST`      | `/me/tags`            | Create a new tag.                     |\n| User  | `PATCH`     | `/me/tags/{tag_uuid}` | Partially update a tag.               |\n| User  | `DELETE`    | `/me/tags/{tag_uuid}` | Permanently remove a tag.             |\n\n### Attachments management\n\n| Actor | HTTP Method | Endpoint                                              | Description                                      |\n|-------|-------------|-------------------------------------------------------|--------------------------------------------------|\n| User  | `GET`       | `/me/tasks/{task_uuid}/attachments`                   | Retrieve all the attachments in a task (if any). |\n| User  | `GET`       | `/me/tasks/{task_uuid}/attachments/{attachment_uuid}` | Get an attachments in a task.                    |\n| User  | `DELETE`    | `/me/tasks/{task_uuid}/attachments/{attachment_uuid}` | Permanently remove an attachments in a task.     |\n\n## Recommendations\n\nIf in doubt about how to transmit error messages to the clients of your web API, use\nthe [RFC 9457: Problem Details for HTTP APIs](https://www.rfc-editor.org/rfc/rfc9457) specification. I didn't know about\nit by the time I started this project and instead used a similar approach inspired by the PostgreSQL style.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffontseca%2Fnoda-tasks-management-tool-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffontseca%2Fnoda-tasks-management-tool-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffontseca%2Fnoda-tasks-management-tool-api/lists"}