main 0be2195024a3 cached
77 files
308.1 KB
79.6k tokens
191 symbols
1 requests
Download .txt
Showing preview only (329K chars total). Download the full file or copy to clipboard to get everything.
Repository: indrayyana/go-fiber-boilerplate
Branch: main
Commit: 0be2195024a3
Files: 77
Total size: 308.1 KB

Directory structure:
gitextract_mzrohxsq/

├── .air.toml
├── .env.example
├── .github/
│   └── workflows/
│       ├── build.yml
│       ├── linter.yml
│       └── test.yml
├── .gitignore
├── .golangci.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── docker-compose.yml
├── go.mod
├── go.sum
├── src/
│   ├── config/
│   │   ├── config.go
│   │   ├── fiber.go
│   │   ├── oauth2.go
│   │   ├── roles.go
│   │   └── tokens.go
│   ├── controller/
│   │   ├── auth_controller.go
│   │   ├── health_check_controller.go
│   │   └── user_controller.go
│   ├── database/
│   │   ├── database.go
│   │   ├── init/
│   │   │   └── init.sql
│   │   └── migrations/
│   │       ├── 20240929085103_create-table-users.down.sql
│   │       ├── 20240929085103_create-table-users.up.sql
│   │       ├── 20240929085107_create-table-tokens.down.sql
│   │       └── 20240929085107_create-table-tokens.up.sql
│   ├── docs/
│   │   ├── docs.go
│   │   ├── swagger.json
│   │   └── swagger.yaml
│   ├── main.go
│   ├── middleware/
│   │   ├── auth.go
│   │   ├── jwt.go
│   │   ├── limiter.go
│   │   ├── logger.go
│   │   └── recover.go
│   ├── model/
│   │   ├── token_model.go
│   │   └── user_model.go
│   ├── response/
│   │   ├── auth_response.go
│   │   ├── error_response.go
│   │   ├── example/
│   │   │   ├── error_example.go
│   │   │   ├── example.go
│   │   │   ├── health_check_example.go
│   │   │   ├── token_example.go
│   │   │   └── user_example.go
│   │   ├── health_check_response.go
│   │   ├── response.go
│   │   └── user_response.go
│   ├── router/
│   │   ├── auth_route.go
│   │   ├── docs_route.go
│   │   ├── health_check_route.go
│   │   ├── router.go
│   │   └── user_route.go
│   ├── service/
│   │   ├── auth_service.go
│   │   ├── email_service.go
│   │   ├── health_check_service.go
│   │   ├── token_service.go
│   │   └── user_service.go
│   ├── utils/
│   │   ├── bcrypt.go
│   │   ├── error.go
│   │   ├── logrus.go
│   │   └── verify.go
│   └── validation/
│       ├── auth_validation.go
│       ├── custom_validation.go
│       ├── user_validation.go
│       └── validation.go
└── test/
    ├── fixture/
    │   ├── token_fixture.go
    │   └── user_fixture.go
    ├── helper/
    │   └── helper.go
    ├── init.go
    ├── integration/
    │   ├── auth_test.go
    │   ├── health_check_test.go
    │   └── user_test.go
    └── unit/
        └── model/
            └── user_model_test.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .air.toml
================================================
# Config file for [Air](https://github.com/air-verse/air) in TOML format

# Working directory
# . or absolute path, please note that the directories following must be under root.
root = "."
tmp_dir = "tmp"

[build]
# Add additional arguments when running binary (bin/full_bin).
args_bin = []
# Binary file yields from `cmd`. change binary to `main.exe` if you using windows
bin = "./tmp/main"
# Just plain old shell command. You could use `make` as well. change binary to `main.exe` if you using windows
cmd = "go build -race -o ./tmp/main ./src"
# It's not necessary to trigger build each time file changes if it's too frequent.
delay = 1000 # ms
# Ignore these filename extensions or directories.
exclude_dir = ["assets", "tmp", "vendor"]
# Exclude files.
exclude_file = []
# Exclude specific regular expressions.
exclude_regex = ["_test\\.go"]
# Exclude unchanged files.
exclude_unchanged = false
# Follow symlink for directories
follow_symlink = false
# Customize binary, can setup environment variables when run your app.
full_bin = ""
# Watch these directories if you specified.
include_dir = []
# Watch these filename extensions.
include_ext = ["go", "tpl", "tmpl", "env"]
# Watch these files.
include_file = []
# Delay after sending Interrupt signal
kill_delay = "0s"
# This log file places in your tmp_dir.
log = "build-errors.log"
# Poll files for changes instead of using fsnotify.
poll = false
# Poll interval (defaults to the minimum interval of 500ms).
poll_interval = 0 # ms
# Array of commands to run after ^C
post_cmd = []
# Array of commands to run before each build
pre_cmd = []
# Rerun binary or not
rerun = false
# Delay after each execution
rerun_delay = 500
# Send Interrupt signal before killing process (windows does not support this feature)
send_interrupt = false
# Stop running old binary when build errors occur.
stop_on_error = false

[color]
# Customize each part's color. If no color found, use the raw app log.
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"

[log]
# Only show main log (silences watcher, build, runner)
main_only = false
# Show log time
time = false

[misc]
# Delete tmp directory on exit
clean_on_exit = true

# Enable live-reloading on the browser.
[proxy]
app_port = 0
enabled = false
proxy_port = 0

[screen]
clear_on_rebuild = true
keep_scroll = true


================================================
FILE: .env.example
================================================
# server configuration
# Env value : prod || dev
APP_ENV=dev
APP_HOST=0.0.0.0
APP_PORT=3000
APP_URL=http://localhost:3000

# database configuration
DB_HOST=postgresdb
DB_USER=postgres
DB_PASSWORD=thisisasamplepassword
DB_NAME=fiberdb
DB_PORT=5432

# JWT
# JWT secret key
JWT_SECRET=thisisasamplesecret
# Number of minutes after which an access token expires
JWT_ACCESS_EXP_MINUTES=30
# Number of days after which a refresh token expires
JWT_REFRESH_EXP_DAYS=30
# Number of minutes after which a reset password token expires
JWT_RESET_PASSWORD_EXP_MINUTES=10
# Number of minutes after which a verify email token expires
JWT_VERIFY_EMAIL_EXP_MINUTES=10

# SMTP configuration options for the email service
SMTP_HOST=email-server
SMTP_PORT=587
SMTP_USERNAME=email-server-username
SMTP_PASSWORD=email-server-password
EMAIL_FROM=support@yourapp.com

# OAuth2 configuration
GOOGLE_CLIENT_ID=yourapps.googleusercontent.com
GOOGLE_CLIENT_SECRET=thisisasamplesecret
REDIRECT_URL=http://localhost:3000/v1/auth/google-callback


================================================
FILE: .github/workflows/build.yml
================================================
name: Build

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

env:
  APP_ENV: dev
  APP_HOST: 0.0.0.0
  APP_PORT: 3000
  DB_HOST: localhost
  DB_USER: postgres
  DB_PASSWORD: thisisasamplepassword
  DB_NAME: fiberdb
  DB_PORT: 5432

jobs:
  GoFiber:
    runs-on: ubuntu-latest

    services:
      postgresdb:
        image: postgres:alpine
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: thisisasamplepassword
          POSTGRES_DB: fiberdb
        ports:
          - 5432:5432
        options: >-
          --health-cmd "pg_isready -U postgres"
          --health-interval 10s
          --health-timeout 20s
          --health-retries 10

    steps:
      - uses: actions/checkout@v4

      - name: Install Go
        uses: actions/setup-go@v4
        with:
          go-version: "1.25"

      - name: Wait for PostgreSQL to be ready
        run: |
          until pg_isready -h localhost -U postgres -d fiberdb; do
            echo "Waiting for PostgreSQL..."
            sleep 5
          done

      - name: Install dependencies
        run: go mod tidy

      - name: Build Go application
        run: CGO_ENABLED=0 GOOS=linux go build src/main.go
        env:
          APP_ENV: ${{ env.APP_ENV }}
          APP_HOST: ${{ env.APP_HOST }}
          APP_PORT: ${{ env.APP_PORT }}
          DB_HOST: ${{ env.DB_HOST }}
          DB_USER: ${{ env.DB_USER }}
          DB_PASSWORD: ${{ env.DB_PASSWORD }}
          DB_NAME: ${{ env.DB_NAME }}
          DB_PORT: ${{ env.DB_PORT }}


================================================
FILE: .github/workflows/linter.yml
================================================
name: Linter

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

jobs:
  Golint:
    runs-on: ubuntu-latest
    steps:
      - name: Fetch Repository
        uses: actions/checkout@v4

      - name: Run Golint
        uses: reviewdog/action-golangci-lint@v2
        with:
          golangci_lint_flags: "--config=.golangci.yml --tests=false"


================================================
FILE: .github/workflows/test.yml
================================================
name: Test

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

jobs:
  Tests:
    runs-on: ubuntu-latest
    steps:
      - name: Fetch Repository
        uses: actions/checkout@v4

      - name: Install Go
        uses: actions/setup-go@v4
        with:
          go-version: "1.25"

      - name: Install dependencies
        run: go mod tidy

      - name: Run Unit Test
        run: go test ./test/unit/... -v -race


================================================
FILE: .gitignore
================================================
# Environment varibales
.env*
!.env*.example

# Temporary
tmp/

lint.txt
main
bin/golangci-lint

================================================
FILE: .golangci.yml
================================================
version: "2"
linters:
  default: none
  enable:
    - asasalint
    - asciicheck
    - bidichk
    - bodyclose
    - canonicalheader
    - cyclop
    - dupl
    - durationcheck
    - errcheck
    - errname
    - errorlint
    - exhaustive
    - fatcontext
    - forbidigo
    - funlen
    - gocheckcompilerdirectives
    - gochecksumtype
    - gocognit
    - goconst
    - gocritic
    - gocyclo
    - gomoddirectives
    - gomodguard
    - goprintffuncname
    - gosec
    - govet
    - ineffassign
    - intrange
    - lll
    - loggercheck
    - makezero
    - mirror
    - musttag
    - nakedret
    - nestif
    - nilerr
    - nilnil
    - noctx
    - nolintlint
    - nonamedreturns
    - nosprintfhostport
    - perfsprint
    - predeclared
    - promlinter
    - protogetter
    - reassign
    - revive
    - rowserrcheck
    - sloglint
    - spancheck
    - sqlclosecheck
    - staticcheck
    - testableexamples
    - testpackage
    - tparallel
    - unconvert
    - unparam
    - unused
    - usestdlibvars
    - wastedassign
    - whitespace
  settings:
    cyclop:
      max-complexity: 30
      package-average: 10
    errcheck:
      check-type-assertions: true
    exhaustive:
      check:
        - switch
        - map
    exhaustruct:
      exclude:
        - ^net/http.Client$
        - ^net/http.Cookie$
        - ^net/http.Request$
        - ^net/http.Response$
        - ^net/http.Server$
        - ^net/http.Transport$
        - ^net/url.URL$
        - ^os/exec.Cmd$
        - ^reflect.StructField$
        - ^github.com/Shopify/sarama.Config$
        - ^github.com/Shopify/sarama.ProducerMessage$
        - ^github.com/mitchellh/mapstructure.DecoderConfig$
        - ^github.com/prometheus/client_golang/.+Opts$
        - ^github.com/spf13/cobra.Command$
        - ^github.com/spf13/cobra.CompletionOptions$
        - ^github.com/stretchr/testify/mock.Mock$
        - ^github.com/testcontainers/testcontainers-go.+Request$
        - ^github.com/testcontainers/testcontainers-go.FromDockerfile$
        - ^golang.org/x/tools/go/analysis.Analyzer$
        - ^google.golang.org/protobuf/.+Options$
        - ^gopkg.in/yaml.v3.Node$
    funlen:
      lines: 100
      statements: 50
      ignore-comments: true
    gocognit:
      min-complexity: 20
    gocritic:
      settings:
        captLocal:
          paramsOnly: false
        underef:
          skipRecvDeref: false
    gomodguard:
      blocked:
        modules:
          - github.com/golang/protobuf:
              recommendations:
                - google.golang.org/protobuf
              reason: see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules
          - github.com/satori/go.uuid:
              recommendations:
                - github.com/google/uuid
              reason: satori's package is not maintained
          - github.com/gofrs/uuid:
              recommendations:
                - github.com/gofrs/uuid/v5
              reason: gofrs' package was not go module before v5
    govet:
      disable:
        - fieldalignment
      enable-all: true
      settings:
        shadow:
          strict: true
    inamedparam:
      skip-single-param: true
    mnd:
      ignored-functions:
        - args.Error
        - flag.Arg
        - flag.Duration.*
        - flag.Float.*
        - flag.Int.*
        - flag.Uint.*
        - os.Chmod
        - os.Mkdir.*
        - os.OpenFile
        - os.WriteFile
        - prometheus.ExponentialBuckets.*
        - prometheus.LinearBuckets
    nakedret:
      max-func-lines: 0
    nolintlint:
      require-explanation: true
      require-specific: true
      allow-no-explanation:
        - funlen
        - gocognit
        - lll
    perfsprint:
      strconcat: false
    rowserrcheck:
      packages:
        - github.com/jmoiron/sqlx
    sloglint:
      no-global: all
      context: scope
  exclusions:
    generated: lax
    presets:
      - comments
      - common-false-positives
      - legacy
      - std-error-handling
    rules:
      - linters:
          - godot
        source: (noinspection|TODO)
      - linters:
          - gocritic
        source: //noinspection
      - linters:
          - lll
        path: example\.go
      - linters:
          - bodyclose
          - dupl
          - funlen
          - goconst
          - gosec
          - lll
          - noctx
          - testpackage
          - wrapcheck
        path: _test\.go
    paths:
      - third_party$
      - builtin$
      - examples$
issues:
  max-same-issues: 50
formatters:
  enable:
    - goimports
  exclusions:
    generated: lax
    paths:
      - third_party$
      - builtin$
      - examples$


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
  and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
  overall community

Examples of unacceptable behavior include:

* The use of sexualized language or imagery, and sexual attention or
  advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
  address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
gdindra13@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series
of actions.

**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior,  harassment of an
individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within
the community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.

Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing

First off, thank you so much for taking the time to contribute. All contributions are more than welcome!

## How can I contribute?

If you have an awesome new feature that you want to implement or you found a bug that you would like to fix, here are some instructions to guide you through the process:

- **Fork the repo**
- **Clone the repo** and set it up (check out the [manual installation](https://github.com/indrayyana/go-fiber-boilerplate#manual-installation) section in README.md)
- **Implement** the necessary changes
- **Create tests** to keep the code coverage high
- **Send a pull request**

## Guidelines

### Git commit messages

Follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification for clear and structured commit messages, here are the key guidelines:

- Limit the subject line to 80 characters
- Capitalize the first letter of the subject line
- Use the present tense ("Add feature" instead of "Added feature")
- Separate the subject from the body with a blank line

### Coding style guide

We are using [golangci-lint](https://golangci-lint.run) to ensure consistent coding standards in this project.

Please make sure that the code you are pushing conforms to the style guides mentioned above.


================================================
FILE: Dockerfile
================================================
FROM golang:1.25 AS build

WORKDIR /app
COPY . .
RUN go clean --modcache
RUN go mod tidy
RUN CGO_ENABLED=0 GOOS=linux go build src/main.go

FROM alpine:latest

RUN apk add --no-cache curl tzdata

WORKDIR /root
COPY --from=build /app/main .
COPY --from=build /app/.env .

EXPOSE 3000
CMD ["./main"]


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2024 I Gede Indra Adnyana

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

================================================
FILE: Makefile
================================================
include .env
export $(shell sed 's/=.*//' .env)

start:
	@go run src/main.go
lint:
	@golangci-lint run
tests:
	@go test -v ./test/...
tests-%:
	@go test -v ./test/... -run=$(shell echo $* | sed 's/_/./g')
testsum:
	@cd test && gotestsum --format testname
swagger:
	@cd src && swag init
migration-%:
	@migrate create -ext sql -dir src/database/migrations create-table-$(subst :,_,$*)
migrate-up:
	@migrate -database "postgres://$(DB_USER):$(DB_PASSWORD)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)?sslmode=disable" -path src/database/migrations up
migrate-down:
	@migrate -database "postgres://$(DB_USER):$(DB_PASSWORD)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)?sslmode=disable" -path src/database/migrations down
migrate-docker-up:
	@docker run -v ./src/database/migrations:/migrations --network go-fiber-boilerplate_go-network migrate/migrate -path=/migrations/ -database postgres://$(DB_USER):$(DB_PASSWORD)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)?sslmode=disable up
migrate-docker-down:
	@docker run -v ./src/database/migrations:/migrations --network go-fiber-boilerplate_go-network migrate/migrate -path=/migrations/ -database postgres://$(DB_USER):$(DB_PASSWORD)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)?sslmode=disable down -all
docker:
	@chmod -R 755 ./src/database/init
	@docker compose up --build
docker-test:
	@docker compose up -d && make tests
docker-down:
	@docker compose down --rmi all --volumes --remove-orphans
docker-cache:
	@docker builder prune -f

================================================
FILE: README.md
================================================
# RESTful API Go Fiber Boilerplate

![Go Version](https://img.shields.io/badge/Go-1.22+-00ADD8?style=flat&logo=go)
[![Go Report Card](https://goreportcard.com/badge/github.com/indravscode/go-fiber-boilerplate)](https://goreportcard.com/report/github.com/indravscode/go-fiber-boilerplate)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
![Repository size](https://img.shields.io/github/repo-size/indravscode/go-fiber-boilerplate?color=56BEB8)
![Build](https://github.com/indravscode/go-fiber-boilerplate/workflows/Build/badge.svg)
![Test](https://github.com/indravscode/go-fiber-boilerplate/workflows/Test/badge.svg)
![Linter](https://github.com/indravscode/go-fiber-boilerplate/workflows/Linter/badge.svg)

A boilerplate/starter project for quickly building RESTful APIs using Go, Fiber, and PostgreSQL. Inspired by the Express boilerplate.

The app comes with many built-in features, such as authentication using JWT and Google OAuth2, request validation, unit and integration tests, docker support, API documentation, pagination, etc. For more details, check the features list below.

## Quick Start

To create a project, simply run:

```bash
go mod init <project-name>
```

## Manual Installation

If you would still prefer to do the installation manually, follow these steps:

Clone the repo:

```bash
git clone --depth 1 https://github.com/indravscode/go-fiber-boilerplate.git
cd go-fiber-boilerplate
rm -rf ./.git
```

Install the dependencies:

```bash
go mod tidy
```

Set the environment variables:

```bash
cp .env.example .env

# open .env and modify the environment variables (if needed)
```

## Table of Contents

- [Features](#features)
- [Commands](#commands)
- [Environment Variables](#environment-variables)
- [Project Structure](#project-structure)
- [API Documentation](#api-documentation)
- [Error Handling](#error-handling)
- [Validation](#validation)
- [Authentication](#authentication)
- [Authorization](#authorization)
- [Logging](#logging)
- [Linting](#linting)
- [Contributing](#contributing)

## Features

- **SQL database**: [PostgreSQL](https://www.postgresql.org) Object Relation Mapping using [Gorm](https://gorm.io)
- **Database migrations**: with [golang-migrate](https://github.com/golang-migrate/migrate)
- **Validation**: request data validation using [Package validator](https://github.com/go-playground/validator)
- **Logging**: using [Logrus](https://github.com/sirupsen/logrus) and [Fiber-Logger](https://docs.gofiber.io/api/middleware/logger)
- **Testing**: unit and integration tests using [Testify](https://github.com/stretchr/testify) and formatted test output using [gotestsum](https://github.com/gotestyourself/gotestsum)
- **Error handling**: centralized error handling mechanism
- **API documentation**: with [Swag](https://github.com/swaggo/swag) and [Swagger](https://github.com/gofiber/swagger)
- **Sending email**: using [Gomail](https://github.com/go-gomail/gomail)
- **Environment variables**: using [Viper](https://github.com/spf13/viper)
- **Security**: set security HTTP headers using [Fiber-Helmet](https://docs.gofiber.io/api/middleware/helmet)
- **CORS**: Cross-Origin Resource-Sharing enabled using [Fiber-CORS](https://docs.gofiber.io/api/middleware/cors)
- **Compression**: gzip compression with [Fiber-Compress](https://docs.gofiber.io/api/middleware/compress)
- **Docker support**
- **Linting**: with [golangci-lint](https://golangci-lint.run)

## Commands

Running locally:

```bash
make start
```

Or running with live reload:

```bash
air
```

> [!NOTE]
> Make sure you have `Air` installed.\
> See 👉 [How to install Air](https://github.com/air-verse/air)

Testing:

```bash
# run all tests
make tests

# run all tests with gotestsum format
make testsum

# run test for the selected function name
make tests-TestUserModel
```

> [!IMPORTANT]
> Tests use a **separate test database**.
>
> By default, the test database name is defined in:
> `test/init.go`
>
> ```go
> DB = database.Connect("localhost", "testdb")
> ```
>
> Make sure the test database (`testdb`) **already exists** and all required
> tables (`users`, `tokens`, etc.) have been migrated before running the test commands.

Docker:

```bash
# run docker container
make docker

# run all tests in a docker container
make docker-test
```

Linting:

```bash
# run lint
make lint
```

Swagger:

```bash
# generate the swagger documentation
make swagger
```

Migration:

```bash
# Create migration
make migration-<table-name>

# Example for table users
make migration-users
```

```bash
# run migration up in local
make migrate-up

# run migration down in local
make migrate-down

# run migration up in docker container
make migrate-docker-up

# run migration down all in docker container
make migrate-docker-down
```

## Environment Variables

The environment variables can be found and modified in the `.env` file. They come with these default values:

```bash
# server configuration
# Env value : prod || dev
APP_ENV=dev
APP_HOST=0.0.0.0
APP_PORT=3000

# database configuration
DB_HOST=postgresdb
DB_USER=postgres
DB_PASSWORD=thisisasamplepassword
DB_NAME=fiberdb
DB_PORT=5432

# JWT
# JWT secret key
JWT_SECRET=thisisasamplesecret
# Number of minutes after which an access token expires
JWT_ACCESS_EXP_MINUTES=30
# Number of days after which a refresh token expires
JWT_REFRESH_EXP_DAYS=30
# Number of minutes after which a reset password token expires
JWT_RESET_PASSWORD_EXP_MINUTES=10
# Number of minutes after which a verify email token expires
JWT_VERIFY_EMAIL_EXP_MINUTES=10

# SMTP configuration options for the email service
SMTP_HOST=email-server
SMTP_PORT=587
SMTP_USERNAME=email-server-username
SMTP_PASSWORD=email-server-password
EMAIL_FROM=support@yourapp.com

# OAuth2 configuration
GOOGLE_CLIENT_ID=yourapps.googleusercontent.com
GOOGLE_CLIENT_SECRET=thisisasamplesecret
REDIRECT_URL=http://localhost:3000/v1/auth/google-callback
```

## Project Structure

```
src\
 |--config\         # Environment variables and configuration related things
 |--controller\     # Route controllers (controller layer)
 |--database\       # Database connection & migrations
 |--docs\           # Swagger files
 |--middleware\     # Custom fiber middlewares
 |--model\          # Postgres models (data layer)
 |--response\       # Response models
 |--router\         # Routes
 |--service\        # Business logic (service layer)
 |--utils\          # Utility classes and functions
 |--validation\     # Request data validation schemas
 |--main.go         # Fiber app
```

## API Documentation

To view the list of available APIs and their specifications, run the server and go to `http://localhost:3000/v1/docs` in your browser.

![Auth](https://indravscode.github.io/assets/images/swagger1.png)
![User](https://indravscode.github.io/assets/images/swagger2.png)

This documentation page is automatically generated using the [Swag](https://github.com/swaggo/swag) definitions written as comments in the controller files.

See 👉 [Declarative Comments Format.](https://github.com/swaggo/swag#declarative-comments-format)

## API Endpoints

List of available routes:

**Auth routes**:\
`POST /v1/auth/register` - register\
`POST /v1/auth/login` - login\
`POST /v1/auth/logout` - logout\
`POST /v1/auth/refresh-tokens` - refresh auth tokens\
`POST /v1/auth/forgot-password` - send reset password email\
`POST /v1/auth/reset-password` - reset password\
`POST /v1/auth/send-verification-email` - send verification email\
`POST /v1/auth/verify-email` - verify email\
`GET /v1/auth/google` - login with google account

**User routes**:\
`POST /v1/users` - create a user\
`GET /v1/users` - get all users\
`GET /v1/users/:userId` - get user\
`PATCH /v1/users/:userId` - update user\
`DELETE /v1/users/:userId` - delete user

## Error Handling

The app includes a custom error handling mechanism, which can be found in the `src/utils/error.go` file.

It also utilizes the `Fiber-Recover` middleware to gracefully recover from any panic that might occur in the handler stack, preventing the app from crashing unexpectedly.

The error handling process sends an error response in the following format:

```json
{
  "code": 404,
  "status": "error",
  "message": "Not found"
}
```

Fiber provides a custom error struct using `fiber.NewError()`, where you can specify a response code and a message. This error can then be returned from any part of your code, and Fiber's `ErrorHandler` will automatically catch it.

For example, if you are trying to retrieve a user from the database but the user is not found, and you want to return a 404 error, the code might look like this:

```go
func (s *userService) GetUserByID(c *fiber.Ctx, id string) {
	user := new(model.User)

	err := s.DB.WithContext(c.Context()).First(user, "id = ?", id).Error

	if errors.Is(err, gorm.ErrRecordNotFound) {
		return fiber.NewError(fiber.StatusNotFound, "User not found")
	}
}
```

## Validation

Request data is validated using [Package validator](https://github.com/go-playground/validator). Check the [documentation](https://pkg.go.dev/github.com/go-playground/validator/v10) for more details on how to write validations.

The validation schemas are defined in the `src/validation` directory and are used within the services by passing them to the validation logic. In this example, the CreateUser method in the userService uses the `validation.CreateUser` schema to validate incoming request data before processing it. The validation is handled by the `Validate.Struct` method, which checks the request data against the schema.

```go
import (
	"app/src/model"
	"app/src/validation"

	"github.com/gofiber/fiber/v2"
)

func (s *userService) CreateUser(c *fiber.Ctx, req validation.CreateUser) (*model.User, error) {
	if err := s.Validate.Struct(&req); err != nil {
		return nil, err
	}
}
```

## Authentication

To require authentication for certain routes, you can use the `Auth` middleware.

```go
import (
	"app/src/controllers"
	m "app/src/middleware"
	"app/src/services"

	"github.com/gofiber/fiber/v2"
)

func SetupRoutes(app *fiber.App, u services.UserService, t services.TokenService) {
  userController := controllers.NewUserController(u, t)
	app.Post("/users", m.Auth(u), userController.CreateUser)
}
```

These routes require a valid JWT access token in the Authorization request header using the Bearer schema. If the request does not contain a valid access token, an Unauthorized (401) error is thrown.

**Generating Access Tokens**:

An access token can be generated by making a successful call to the register (`POST /v1/auth/register`) or login (`POST /v1/auth/login`) endpoints. The response of these endpoints also contains refresh tokens (explained below).

An access token is valid for 30 minutes. You can modify this expiration time by changing the `JWT_ACCESS_EXP_MINUTES` environment variable in the .env file.

**Refreshing Access Tokens**:

After the access token expires, a new access token can be generated, by making a call to the refresh token endpoint (`POST /v1/auth/refresh-tokens`) and sending along a valid refresh token in the request body. This call returns a new access token and a new refresh token.

A refresh token is valid for 30 days. You can modify this expiration time by changing the `JWT_REFRESH_EXP_DAYS` environment variable in the .env file.

## Authorization

The `Auth` middleware can also be used to require certain rights/permissions to access a route.

```go
import (
	"app/src/controllers"
	m "app/src/middleware"
	"app/src/services"

	"github.com/gofiber/fiber/v2"
)

func SetupRoutes(app *fiber.App, u services.UserService, t services.TokenService) {
  userController := controllers.NewUserController(u, t)
	app.Post("/users", m.Auth(u, "manageUsers"), userController.CreateUser)
}
```

In the example above, an authenticated user can access this route only if that user has the `manageUsers` permission.

The permissions are role-based. You can view the permissions/rights of each role in the `src/config/roles.go` file.

If the user making the request does not have the required permissions to access this route, a Forbidden (403) error is thrown.

## Logging

Import the logger from `src/utils/logrus.go`. It is using the [Logrus](https://github.com/sirupsen/logrus) logging library.

Logging should be done according to the following severity levels (ascending order from most important to least important):

```go
import "app/src/utils"

utils.Log.Panic('message') // Calls panic() after logging
utils.Log.Fatal('message'); // Calls os.Exit(1) after logging
utils.Log.Error('message');
utils.Log.Warn('message');
utils.Log.Info('message');
utils.Log.Debug('message');
utils.Log.Trace('message');
```

> [!NOTE]
> API request information (request url, response code, timestamp, etc.) are also automatically logged (using [Fiber-Logger](https://docs.gofiber.io/api/middleware/logger)).

## Linting

Linting is done using [golangci-lint](https://golangci-lint.run)

See 👉 [How to install golangci-lint](https://golangci-lint.run/welcome/install)

To modify the golangci-lint configuration, update the `.golangci.yml` file.

## Contributing

Contributions are more than welcome! Please check out the [contributing guide](CONTRIBUTING.md).

If you find this boilerplate useful, consider giving it a star! ⭐

## Inspirations

- [hagopj13/node-express-boilerplate](https://github.com/hagopj13/node-express-boilerplate)
- [khannedy/golang-clean-architecture](https://github.com/khannedy/golang-clean-architecture)
- [zexoverz/express-prisma-template](https://github.com/zexoverz/express-prisma-template)

## License

[MIT](LICENSE)

## Contributors

[![Contributors](https://contrib.rocks/image?c=6&repo=indravscode/go-fiber-boilerplate)](https://github.com/indravscode/go-fiber-boilerplate/graphs/contributors)


================================================
FILE: docker-compose.yml
================================================
services:
  adminer:
    image: adminer
    restart: always
    ports:
      - 8080:8080
    networks:
      - go-network

  postgresdb:
    image: postgres:alpine
    restart: always
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
      timeout: 20s
      retries: 10
    ports:
      - ${DB_PORT}:5432
    environment:
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=${DB_NAME}
    volumes:
      - dbdata:/var/lib/postgresql/data
      - ./src/database/init:/docker-entrypoint-initdb.d
    networks:
      - go-network

  go-app:
    build: .
    image: go-app
    ports:
      - ${APP_PORT}:3000
    depends_on:
      postgresdb:
        condition: service_healthy
    volumes:
      - .:/usr/src/go-app
    restart: on-failure
    env_file:
      - .env
    networks:
      - go-network
    healthcheck:
      test: ["CMD", "curl", "-f", "${APP_URL}/v1/health-check"]
      interval: 40s
      timeout: 30s
      retries: 3
      start_period: 30s

volumes:
  dbdata:

networks:
  go-network:
    driver: bridge


================================================
FILE: go.mod
================================================
module app

go 1.24.0

require (
	github.com/bytedance/sonic v1.14.2
	github.com/go-playground/validator/v10 v10.29.0
	github.com/gofiber/contrib/jwt v1.1.2
	github.com/gofiber/fiber/v2 v2.52.10
	github.com/gofiber/swagger v1.1.1
	github.com/golang-jwt/jwt/v5 v5.3.0
	github.com/google/uuid v1.6.0
	github.com/sirupsen/logrus v1.9.3
	github.com/spf13/viper v1.21.0
	github.com/stretchr/testify v1.11.1
	github.com/swaggo/swag v1.16.6
	golang.org/x/crypto v0.46.0
	golang.org/x/oauth2 v0.34.0
	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
	gorm.io/driver/postgres v1.6.0
	gorm.io/gorm v1.31.1
)

require (
	cloud.google.com/go/compute/metadata v0.9.0 // indirect
	github.com/KyleBanks/depth v1.2.1 // indirect
	github.com/MicahParks/keyfunc/v2 v2.1.0 // indirect
	github.com/andybalholm/brotli v1.2.0 // indirect
	github.com/bytedance/gopkg v0.1.3 // indirect
	github.com/bytedance/sonic/loader v0.4.0 // indirect
	github.com/clipperhouse/stringish v0.1.1 // indirect
	github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
	github.com/cloudwego/base64x v0.1.6 // indirect
	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
	github.com/fsnotify/fsnotify v1.9.0 // indirect
	github.com/gabriel-vasile/mimetype v1.4.12 // indirect
	github.com/go-openapi/jsonpointer v0.22.4 // indirect
	github.com/go-openapi/jsonreference v0.21.4 // indirect
	github.com/go-openapi/spec v0.22.2 // indirect
	github.com/go-openapi/swag/conv v0.25.4 // indirect
	github.com/go-openapi/swag/jsonname v0.25.4 // indirect
	github.com/go-openapi/swag/jsonutils v0.25.4 // indirect
	github.com/go-openapi/swag/loading v0.25.4 // indirect
	github.com/go-openapi/swag/stringutils v0.25.4 // indirect
	github.com/go-openapi/swag/typeutils v0.25.4 // indirect
	github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
	github.com/go-playground/locales v0.14.1 // indirect
	github.com/go-playground/universal-translator v0.18.1 // indirect
	github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
	github.com/jackc/pgpassfile v1.0.0 // indirect
	github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
	github.com/jackc/pgx/v5 v5.7.6 // indirect
	github.com/jackc/puddle/v2 v2.2.2 // indirect
	github.com/jinzhu/inflection v1.0.0 // indirect
	github.com/jinzhu/now v1.1.5 // indirect
	github.com/klauspost/compress v1.18.2 // indirect
	github.com/klauspost/cpuid/v2 v2.3.0 // indirect
	github.com/leodido/go-urn v1.4.0 // indirect
	github.com/mattn/go-colorable v0.1.14 // indirect
	github.com/mattn/go-isatty v0.0.20 // indirect
	github.com/mattn/go-runewidth v0.0.19 // indirect
	github.com/pelletier/go-toml/v2 v2.2.4 // indirect
	github.com/philhofer/fwd v1.2.0 // indirect
	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
	github.com/sagikazarmark/locafero v0.12.0 // indirect
	github.com/spf13/afero v1.15.0 // indirect
	github.com/spf13/cast v1.10.0 // indirect
	github.com/spf13/pflag v1.0.10 // indirect
	github.com/subosito/gotenv v1.6.0 // indirect
	github.com/swaggo/files/v2 v2.0.2 // indirect
	github.com/tinylib/msgp v1.6.1 // indirect
	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
	github.com/valyala/bytebufferpool v1.0.0 // indirect
	github.com/valyala/fasthttp v1.68.0 // indirect
	go.yaml.in/yaml/v3 v3.0.4 // indirect
	golang.org/x/arch v0.23.0 // indirect
	golang.org/x/mod v0.31.0 // indirect
	golang.org/x/sync v0.19.0 // indirect
	golang.org/x/sys v0.39.0 // indirect
	golang.org/x/text v0.32.0 // indirect
	golang.org/x/tools v0.40.0 // indirect
	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
)


================================================
FILE: go.sum
================================================
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/MicahParks/keyfunc/v2 v2.1.0 h1:6ZXKb9Rp6qp1bDbJefnG7cTH8yMN1IC/4nf+GVjO99k=
github.com/MicahParks/keyfunc/v2 v2.1.0/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4OeE/yHVMteCkw9k=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80=
github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8=
github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4=
github.com/go-openapi/spec v0.22.2 h1:KEU4Fb+Lp1qg0V4MxrSCPv403ZjBl8Lx1a83gIPU8Qc=
github.com/go-openapi/spec v0.22.2/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4=
github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU=
github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=
github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA=
github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM=
github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s=
github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE=
github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8=
github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=
github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw=
github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=
github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw=
github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc=
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4=
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.29.0 h1:lQlF5VNJWNlRbRZNeOIkWElR+1LL/OuHcc0Kp14w1xk=
github.com/go-playground/validator/v10 v10.29.0/go.mod h1:D6QxqeMlgIPuT02L66f2ccrZ7AGgHkzKmmTMZhk/Kc4=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gofiber/contrib/jwt v1.1.2 h1:GmWnOqT4A15EkA8IPXwSpvNUXZR4u5SMj+geBmyLAjs=
github.com/gofiber/contrib/jwt v1.1.2/go.mod h1:CpIwrkUQ3Q6IP8y9n3f0wP9bOnSKx39EDp2fBVgMFVk=
github.com/gofiber/fiber/v2 v2.52.10 h1:jRHROi2BuNti6NYXmZ6gbNSfT3zj/8c0xy94GOU5elY=
github.com/gofiber/fiber/v2 v2.52.10/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/gofiber/swagger v1.1.1 h1:FZVhVQQ9s1ZKLHL/O0loLh49bYB5l1HEAgxDlcTtkRA=
github.com/gofiber/swagger v1.1.1/go.mod h1:vtvY/sQAMc/lGTUCg0lqmBL7Ht9O7uzChpbvJeJQINw=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/swaggo/files/v2 v2.0.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU=
github.com/swaggo/files/v2 v2.0.2/go.mod h1:TVqetIzZsO9OhHX1Am9sRf9LdrFZqoK49N37KON/jr0=
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY=
github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.68.0 h1:v12Nx16iepr8r9ySOwqI+5RBJ/DqTxhOy1HrHoDFnok=
github.com/valyala/fasthttp v1.68.0/go.mod h1:5EXiRfYQAoiO/khu4oU9VISC/eVY6JqmSpPJoHCKsz4=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=


================================================
FILE: src/config/config.go
================================================
package config

import (
	"app/src/utils"

	"github.com/spf13/viper"
)

var (
	IsProd              bool
	AppHost             string
	AppPort             int
	DBHost              string
	DBUser              string
	DBPassword          string
	DBName              string
	DBPort              int
	JWTSecret           string
	JWTAccessExp        int
	JWTRefreshExp       int
	JWTResetPasswordExp int
	JWTVerifyEmailExp   int
	SMTPHost            string
	SMTPPort            int
	SMTPUsername        string
	SMTPPassword        string
	EmailFrom           string
	GoogleClientID      string
	GoogleClientSecret  string
	RedirectURL         string
)

func init() {
	loadConfig()

	// server configuration
	IsProd = viper.GetString("APP_ENV") == "prod"
	AppHost = viper.GetString("APP_HOST")
	AppPort = viper.GetInt("APP_PORT")

	// database configuration
	DBHost = viper.GetString("DB_HOST")
	DBUser = viper.GetString("DB_USER")
	DBPassword = viper.GetString("DB_PASSWORD")
	DBName = viper.GetString("DB_NAME")
	DBPort = viper.GetInt("DB_PORT")

	// jwt configuration
	JWTSecret = viper.GetString("JWT_SECRET")
	JWTAccessExp = viper.GetInt("JWT_ACCESS_EXP_MINUTES")
	JWTRefreshExp = viper.GetInt("JWT_REFRESH_EXP_DAYS")
	JWTResetPasswordExp = viper.GetInt("JWT_RESET_PASSWORD_EXP_MINUTES")
	JWTVerifyEmailExp = viper.GetInt("JWT_VERIFY_EMAIL_EXP_MINUTES")

	// SMTP configuration
	SMTPHost = viper.GetString("SMTP_HOST")
	SMTPPort = viper.GetInt("SMTP_PORT")
	SMTPUsername = viper.GetString("SMTP_USERNAME")
	SMTPPassword = viper.GetString("SMTP_PASSWORD")
	EmailFrom = viper.GetString("EMAIL_FROM")

	// oauth2 configuration
	GoogleClientID = viper.GetString("GOOGLE_CLIENT_ID")
	GoogleClientSecret = viper.GetString("GOOGLE_CLIENT_SECRET")
	RedirectURL = viper.GetString("REDIRECT_URL")
}

func loadConfig() {
	configPaths := []string{
		"./",     // For app
		"../../", // For test folder
	}

	for _, path := range configPaths {
		viper.SetConfigFile(path + ".env")

		if err := viper.ReadInConfig(); err == nil {
			utils.Log.Infof("Config file loaded from %s", path)
			return
		}
	}

	utils.Log.Error("Failed to load any config file")
}


================================================
FILE: src/config/fiber.go
================================================
package config

import (
	"app/src/utils"

	"github.com/bytedance/sonic"
	"github.com/gofiber/fiber/v2"
)

func FiberConfig() fiber.Config {
	return fiber.Config{
		Prefork:       IsProd,
		CaseSensitive: true,
		ServerHeader:  "Fiber",
		AppName:       "Fiber API",
		ErrorHandler:  utils.ErrorHandler,
		JSONEncoder:   sonic.Marshal,
		JSONDecoder:   sonic.Unmarshal,
	}
}


================================================
FILE: src/config/oauth2.go
================================================
package config

import (
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
)

type Config struct {
	GoogleLoginConfig oauth2.Config
}

var AppConfig Config

func GoogleConfig() oauth2.Config {
	AppConfig.GoogleLoginConfig = oauth2.Config{
		RedirectURL:  RedirectURL,
		ClientID:     GoogleClientID,
		ClientSecret: GoogleClientSecret,
		Scopes: []string{
			"https://www.googleapis.com/auth/userinfo.email",
			"https://www.googleapis.com/auth/userinfo.profile",
		},
		Endpoint: google.Endpoint,
	}

	return AppConfig.GoogleLoginConfig
}


================================================
FILE: src/config/roles.go
================================================
package config

var allRoles = map[string][]string{
	"user":  {},
	"admin": {"getUsers", "manageUsers"},
}

var Roles = getKeys(allRoles)
var RoleRights = allRoles

func getKeys(m map[string][]string) []string {
	keys := make([]string, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	return keys
}


================================================
FILE: src/config/tokens.go
================================================
package config

const (
	TokenTypeAccess        = "access"
	TokenTypeRefresh       = "refresh"
	TokenTypeResetPassword = "resetPassword"
	TokenTypeVerifyEmail   = "verifyEmail"
)


================================================
FILE: src/controller/auth_controller.go
================================================
package controller

import (
	"app/src/config"
	"app/src/model"
	"app/src/response"
	"app/src/service"
	"app/src/validation"
	"context"
	"encoding/json"
	"io"
	"net/http"

	"github.com/gofiber/fiber/v2"
	"github.com/google/uuid"
)

type AuthController struct {
	AuthService  service.AuthService
	UserService  service.UserService
	TokenService service.TokenService
	EmailService service.EmailService
}

func NewAuthController(
	authService service.AuthService, userService service.UserService,
	tokenService service.TokenService, emailService service.EmailService,
) *AuthController {
	return &AuthController{
		AuthService:  authService,
		UserService:  userService,
		TokenService: tokenService,
		EmailService: emailService,
	}
}

// @Tags         Auth
// @Summary      Register as user
// @Accept       json
// @Produce      json
// @Param        request  body  validation.Register  true  "Request body"
// @Router       /auth/register [post]
// @Success      201  {object}  example.RegisterResponse
// @Failure      409  {object}  example.DuplicateEmail  "Email already taken"
func (a *AuthController) Register(c *fiber.Ctx) error {
	req := new(validation.Register)

	if err := c.BodyParser(req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}

	user, err := a.AuthService.Register(c, req)
	if err != nil {
		return err
	}

	tokens, err := a.TokenService.GenerateAuthTokens(c, user)
	if err != nil {
		return err
	}

	return c.Status(fiber.StatusCreated).
		JSON(response.SuccessWithTokens{
			Code:    fiber.StatusCreated,
			Status:  "success",
			Message: "Register successfully",
			User:    *user,
			Tokens:  *tokens,
		})
}

// @Tags         Auth
// @Summary      Login
// @Accept       json
// @Produce      json
// @Param        request  body  validation.Login  true  "Request body"
// @Router       /auth/login [post]
// @Success      200  {object}  example.LoginResponse
// @Failure      401  {object}  example.FailedLogin  "Invalid email or password"
func (a *AuthController) Login(c *fiber.Ctx) error {
	req := new(validation.Login)

	if err := c.BodyParser(req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}

	user, err := a.AuthService.Login(c, req)
	if err != nil {
		return err
	}

	tokens, err := a.TokenService.GenerateAuthTokens(c, user)
	if err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.SuccessWithTokens{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Login successfully",
			User:    *user,
			Tokens:  *tokens,
		})
}

// @Tags         Auth
// @Summary      Logout
// @Accept       json
// @Produce      json
// @Param        request  body  example.RefreshToken  true  "Request body"
// @Router       /auth/logout [post]
// @Success      200  {object}  example.LogoutResponse
// @Failure      404  {object}  example.NotFound  "Not found"
func (a *AuthController) Logout(c *fiber.Ctx) error {
	req := new(validation.Logout)

	if err := c.BodyParser(req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}

	if err := a.AuthService.Logout(c, req); err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.Common{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Logout successfully",
		})
}

// @Tags         Auth
// @Summary      Refresh auth tokens
// @Accept       json
// @Produce      json
// @Param        request  body  example.RefreshToken  true  "Request body"
// @Router       /auth/refresh-tokens [post]
// @Success      200  {object}  example.RefreshTokenResponse
// @Failure      401  {object}  example.Unauthorized  "Unauthorized"
func (a *AuthController) RefreshTokens(c *fiber.Ctx) error {
	req := new(validation.RefreshToken)

	if err := c.BodyParser(req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}

	tokens, err := a.AuthService.RefreshAuth(c, req)
	if err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.RefreshToken{
			Code:   fiber.StatusOK,
			Status: "success",
			Tokens: *tokens,
		})
}

// @Tags         Auth
// @Summary      Forgot password
// @Description  An email will be sent to reset password.
// @Accept       json
// @Produce      json
// @Param        request  body  validation.ForgotPassword  true  "Request body"
// @Router       /auth/forgot-password [post]
// @Success      200  {object}  example.ForgotPasswordResponse
// @Failure      404  {object}  example.NotFound  "Not found"
func (a *AuthController) ForgotPassword(c *fiber.Ctx) error {
	req := new(validation.ForgotPassword)

	if err := c.BodyParser(req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}

	resetPasswordToken, err := a.TokenService.GenerateResetPasswordToken(c, req)
	if err != nil {
		return err
	}

	if errEmail := a.EmailService.SendResetPasswordEmail(req.Email, resetPasswordToken); errEmail != nil {
		return errEmail
	}

	return c.Status(fiber.StatusOK).
		JSON(response.Common{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "A password reset link has been sent to your email address.",
		})
}

// @Tags         Auth
// @Summary      Reset password
// @Accept       json
// @Produce      json
// @Param        token   query  string  true  "The reset password token"
// @Param        request  body  validation.UpdatePassOrVerify  true  "Request body"
// @Router       /auth/reset-password [post]
// @Success      200  {object}  example.ResetPasswordResponse
// @Failure      401  {object}  example.FailedResetPassword  "Password reset failed"
func (a *AuthController) ResetPassword(c *fiber.Ctx) error {
	req := new(validation.UpdatePassOrVerify)
	query := &validation.Token{
		Token: c.Query("token"),
	}

	if err := c.BodyParser(req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}

	if err := a.AuthService.ResetPassword(c, query, req); err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.Common{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Update password successfully",
		})
}

// @Tags         Auth
// @Summary      Send verification email
// @Description  An email will be sent to verify email.
// @Security BearerAuth
// @Produce      json
// @Router       /auth/send-verification-email [post]
// @Success      200  {object}  example.SendVerificationEmailResponse
// @Failure      401  {object}  example.Unauthorized  "Unauthorized"
func (a *AuthController) SendVerificationEmail(c *fiber.Ctx) error {
	user, _ := c.Locals("user").(*model.User)

	verifyEmailToken, err := a.TokenService.GenerateVerifyEmailToken(c, user)
	if err != nil {
		return err
	}

	if errEmail := a.EmailService.SendVerificationEmail(user.Email, *verifyEmailToken); errEmail != nil {
		return errEmail
	}

	return c.Status(fiber.StatusOK).
		JSON(response.Common{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Please check your email for a link to verify your account",
		})
}

// @Tags         Auth
// @Summary      Verify email
// @Produce      json
// @Param        token   query  string  true  "The verify email token"
// @Router       /auth/verify-email [post]
// @Success      200  {object}  example.VerifyEmailResponse
// @Failure      401  {object}  example.FailedVerifyEmail  "Verify email failed"
func (a *AuthController) VerifyEmail(c *fiber.Ctx) error {
	query := &validation.Token{
		Token: c.Query("token"),
	}

	if err := a.AuthService.VerifyEmail(c, query); err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.Common{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Verify email successfully",
		})
}

// @Tags         Auth
// @Summary      Login with google
// @Description  This route initiates the Google OAuth2 login flow. Please try this in your browser.
// @Router       /auth/google [get]
// @Success      200  {object}  example.GoogleLoginResponse
func (a *AuthController) GoogleLogin(c *fiber.Ctx) error {
	// Generate a random state
	state := uuid.New().String()

	c.Cookie(&fiber.Cookie{
		Name:   "oauth_state",
		Value:  state,
		MaxAge: 30,
	})

	url := config.AppConfig.GoogleLoginConfig.AuthCodeURL(state)

	return c.Status(fiber.StatusSeeOther).Redirect(url)
}

func (a *AuthController) GoogleCallback(c *fiber.Ctx) error {
	state := c.Query("state")
	storedState := c.Cookies("oauth_state")

	if state != storedState {
		return fiber.NewError(fiber.StatusUnauthorized, "States don't Match!")
	}

	code := c.Query("code")
	googlecon := config.GoogleConfig()

	token, err := googlecon.Exchange(context.Background(), code)
	if err != nil {
		return err
	}

	req, err := http.NewRequestWithContext(
		c.Context(), http.MethodGet,
		"https://www.googleapis.com/oauth2/v2/userinfo?access_token="+token.AccessToken,
		nil,
	)
	if err != nil {
		return err
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	userData, err := io.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	googleUser := new(validation.GoogleLogin)
	if errJSON := json.Unmarshal(userData, googleUser); errJSON != nil {
		return errJSON
	}

	user, err := a.UserService.CreateGoogleUser(c, googleUser)
	if err != nil {
		return err
	}

	tokens, err := a.TokenService.GenerateAuthTokens(c, user)
	if err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.SuccessWithTokens{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Login successfully",
			User:    *user,
			Tokens:  *tokens,
		})

	// TODO: replace this url with the link to the oauth google success page of your front-end app
	// googleLoginURL := fmt.Sprintf("http://link-to-app/google/success?access_token=%s&refresh_token=%s",
	// 	tokens.Access.Token, tokens.Refresh.Token)

	// return c.Status(fiber.StatusSeeOther).Redirect(googleLoginURL)
}


================================================
FILE: src/controller/health_check_controller.go
================================================
package controller

import (
	"app/src/response"
	"app/src/service"

	"github.com/gofiber/fiber/v2"
)

type HealthCheckController struct {
	HealthCheckService service.HealthCheckService
}

func NewHealthCheckController(healthCheckService service.HealthCheckService) *HealthCheckController {
	return &HealthCheckController{
		HealthCheckService: healthCheckService,
	}
}

func (h *HealthCheckController) addServiceStatus(
	serviceList *[]response.HealthCheck, name string, isUp bool, message *string,
) {
	status := "Up"

	if !isUp {
		status = "Down"
	}

	*serviceList = append(*serviceList, response.HealthCheck{
		Name:    name,
		Status:  status,
		IsUp:    isUp,
		Message: message,
	})
}

// @Tags Health
// @Summary Health Check
// @Description Check the status of services and database connections
// @Accept json
// @Produce json
// @Success 200 {object} example.HealthCheckResponse
// @Failure 500 {object} example.HealthCheckResponseError
// @Router /health-check [get]
func (h *HealthCheckController) Check(c *fiber.Ctx) error {
	isHealthy := true
	var serviceList []response.HealthCheck

	// Check the database connection
	if err := h.HealthCheckService.GormCheck(); err != nil {
		isHealthy = false
		errMsg := err.Error()
		h.addServiceStatus(&serviceList, "Postgre", false, &errMsg)
	} else {
		h.addServiceStatus(&serviceList, "Postgre", true, nil)
	}

	if err := h.HealthCheckService.MemoryHeapCheck(); err != nil {
		isHealthy = false
		errMsg := err.Error()
		h.addServiceStatus(&serviceList, "Memory", false, &errMsg)
	} else {
		h.addServiceStatus(&serviceList, "Memory", true, nil)
	}

	// Return the response based on health check result
	statusCode := fiber.StatusOK
	status := "success"

	if !isHealthy {
		statusCode = fiber.StatusInternalServerError
		status = "error"
	}

	return c.Status(statusCode).JSON(response.HealthCheckResponse{
		Status:    status,
		Message:   "Health check completed",
		Code:      statusCode,
		IsHealthy: isHealthy,
		Result:    serviceList,
	})
}


================================================
FILE: src/controller/user_controller.go
================================================
package controller

import (
	"app/src/model"
	"app/src/response"
	"app/src/service"
	"app/src/validation"
	"math"

	"github.com/gofiber/fiber/v2"
	"github.com/google/uuid"
)

type UserController struct {
	UserService  service.UserService
	TokenService service.TokenService
}

func NewUserController(userService service.UserService, tokenService service.TokenService) *UserController {
	return &UserController{
		UserService:  userService,
		TokenService: tokenService,
	}
}

// @Tags         Users
// @Summary      Get all users
// @Description  Only admins can retrieve all users.
// @Security BearerAuth
// @Produce      json
// @Param        page     query     int     false   "Page number"  default(1)
// @Param        limit    query     int     false   "Maximum number of users"    default(10)
// @Param        search   query     string  false  "Search by name or email or role"
// @Router       /users [get]
// @Success      200  {object}  example.GetAllUserResponse
// @Failure      401  {object}  example.Unauthorized  "Unauthorized"
// @Failure      403  {object}  example.Forbidden  "Forbidden"
func (u *UserController) GetUsers(c *fiber.Ctx) error {
	query := &validation.QueryUser{
		Page:   c.QueryInt("page", 1),
		Limit:  c.QueryInt("limit", 10),
		Search: c.Query("search", ""),
	}

	users, totalResults, err := u.UserService.GetUsers(c, query)
	if err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.SuccessWithPaginate[model.User]{
			Code:         fiber.StatusOK,
			Status:       "success",
			Message:      "Get all users successfully",
			Results:      users,
			Page:         query.Page,
			Limit:        query.Limit,
			TotalPages:   int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
			TotalResults: totalResults,
		})
}

// @Tags         Users
// @Summary      Get a user
// @Description  Logged in users can fetch only their own user information. Only admins can fetch other users.
// @Security BearerAuth
// @Produce      json
// @Param        id  path  string  true  "User id"
// @Router       /users/{id} [get]
// @Success      200  {object}  example.GetUserResponse
// @Failure      401  {object}  example.Unauthorized  "Unauthorized"
// @Failure      403  {object}  example.Forbidden  "Forbidden"
// @Failure      404  {object}  example.NotFound  "Not found"
func (u *UserController) GetUserByID(c *fiber.Ctx) error {
	userID := c.Params("userId")

	if _, err := uuid.Parse(userID); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid user ID")
	}

	user, err := u.UserService.GetUserByID(c, userID)
	if err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.SuccessWithUser{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Get user successfully",
			User:    *user,
		})
}

// @Tags         Users
// @Summary      Create a user
// @Description  Only admins can create other users.
// @Security BearerAuth
// @Produce      json
// @Param        request  body  validation.CreateUser  true  "Request body"
// @Router       /users [post]
// @Success      201  {object}  example.CreateUserResponse
// @Failure      401  {object}  example.Unauthorized  "Unauthorized"
// @Failure      403  {object}  example.Forbidden  "Forbidden"
// @Failure      409  {object}  example.DuplicateEmail  "Email already taken"
func (u *UserController) CreateUser(c *fiber.Ctx) error {
	req := new(validation.CreateUser)

	if err := c.BodyParser(req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}

	user, err := u.UserService.CreateUser(c, req)
	if err != nil {
		return err
	}

	return c.Status(fiber.StatusCreated).
		JSON(response.SuccessWithUser{
			Code:    fiber.StatusCreated,
			Status:  "success",
			Message: "Create user successfully",
			User:    *user,
		})
}

// @Tags         Users
// @Summary      Update a user
// @Description  Logged in users can only update their own information. Only admins can update other users.
// @Security BearerAuth
// @Produce      json
// @Param        id  path  string  true  "User id"
// @Param        request  body  validation.UpdateUser  true  "Request body"
// @Router       /users/{id} [patch]
// @Success      200  {object}  example.UpdateUserResponse
// @Failure      401  {object}  example.Unauthorized  "Unauthorized"
// @Failure      403  {object}  example.Forbidden  "Forbidden"
// @Failure      404  {object}  example.NotFound  "Not found"
// @Failure      409  {object}  example.DuplicateEmail  "Email already taken"
func (u *UserController) UpdateUser(c *fiber.Ctx) error {
	req := new(validation.UpdateUser)
	userID := c.Params("userId")

	if _, err := uuid.Parse(userID); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid user ID")
	}

	if err := c.BodyParser(req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}

	user, err := u.UserService.UpdateUser(c, req, userID)
	if err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.SuccessWithUser{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Update user successfully",
			User:    *user,
		})
}

// @Tags         Users
// @Summary      Delete a user
// @Description  Logged in users can delete only themselves. Only admins can delete other users.
// @Security BearerAuth
// @Produce      json
// @Param        id  path  string  true  "User id"
// @Router       /users/{id} [delete]
// @Success      200  {object}  example.DeleteUserResponse
// @Failure      401  {object}  example.Unauthorized  "Unauthorized"
// @Failure      403  {object}  example.Forbidden  "Forbidden"
// @Failure      404  {object}  example.NotFound  "Not found"
func (u *UserController) DeleteUser(c *fiber.Ctx) error {
	userID := c.Params("userId")

	if _, err := uuid.Parse(userID); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid user ID")
	}

	if err := u.TokenService.DeleteAllToken(c, userID); err != nil {
		return err
	}

	if err := u.UserService.DeleteUser(c, userID); err != nil {
		return err
	}

	return c.Status(fiber.StatusOK).
		JSON(response.Common{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Delete user successfully",
		})
}


================================================
FILE: src/database/database.go
================================================
package database

import (
	"app/src/config"
	"app/src/utils"
	"fmt"
	"time"

	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

func Connect(dbHost, dbName string) *gorm.DB {
	dsn := fmt.Sprintf(
		"host=%s user=%s password=%s dbname=%s port=%d sslmode=disable TimeZone=Asia/Shanghai",
		dbHost, config.DBUser, config.DBPassword, dbName, config.DBPort,
	)

	db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
		Logger:                 logger.Default.LogMode(logger.Info),
		SkipDefaultTransaction: true,
		PrepareStmt:            true,
		TranslateError:         true,
	})
	if err != nil {
		utils.Log.Errorf("Failed to connect to database: %+v", err)
	}

	sqlDB, errDB := db.DB()
	if errDB != nil {
		utils.Log.Errorf("Failed to connect to database: %+v", errDB)
	}

	// Config connection pooling
	sqlDB.SetMaxIdleConns(10)
	sqlDB.SetMaxOpenConns(100)
	sqlDB.SetConnMaxLifetime(60 * time.Minute)

	return db
}


================================================
FILE: src/database/init/init.sql
================================================
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE DATABASE testdb;


================================================
FILE: src/database/migrations/20240929085103_create-table-users.down.sql
================================================
DROP TABLE IF EXISTS users;

================================================
FILE: src/database/migrations/20240929085103_create-table-users.up.sql
================================================
CREATE TABLE users(
    id              UUID            PRIMARY KEY DEFAULT uuid_generate_v4(),
    name            VARCHAR(255)    NOT NULL,
    email           VARCHAR(255)    NOT NULL UNIQUE,
    password        VARCHAR(255)    NOT NULL,
    role            VARCHAR(255)    NOT NULL,
    verified_email  BOOLEAN         DEFAULT FALSE  NOT NULL,
    created_at      TIMESTAMP       DEFAULT CURRENT_TIMESTAMP  NOT NULL,
    updated_at      TIMESTAMP       DEFAULT CURRENT_TIMESTAMP  NOT NULL
);


================================================
FILE: src/database/migrations/20240929085107_create-table-tokens.down.sql
================================================
DROP TABLE IF EXISTS tokens;

================================================
FILE: src/database/migrations/20240929085107_create-table-tokens.up.sql
================================================
CREATE TABLE tokens(
    id              UUID            PRIMARY KEY DEFAULT uuid_generate_v4(),
    token           VARCHAR(255)    NOT NULL,
    user_id         UUID            NOT NULL,
    type            VARCHAR(255)    NOT NULL,
    expires         TIMESTAMP       DEFAULT CURRENT_TIMESTAMP  NOT NULL,
    created_at      TIMESTAMP       DEFAULT CURRENT_TIMESTAMP  NOT NULL,
    updated_at      TIMESTAMP       DEFAULT CURRENT_TIMESTAMP  NOT NULL,
    CONSTRAINT fk_user
        FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);


================================================
FILE: src/docs/docs.go
================================================
// Package docs Code generated by swaggo/swag. DO NOT EDIT
package docs

import "github.com/swaggo/swag"

const docTemplate = `{
    "schemes": {{ marshal .Schemes }},
    "swagger": "2.0",
    "info": {
        "description": "{{escape .Description}}",
        "title": "{{.Title}}",
        "contact": {},
        "license": {
            "name": "MIT",
            "url": "https://github.com/indrayyana/go-fiber-boilerplate/blob/main/LICENSE"
        },
        "version": "{{.Version}}"
    },
    "host": "{{.Host}}",
    "basePath": "{{.BasePath}}",
    "paths": {
        "/auth/forgot-password": {
            "post": {
                "description": "An email will be sent to reset password.",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Auth"
                ],
                "summary": "Forgot password",
                "parameters": [
                    {
                        "description": "Request body",
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/validation.ForgotPassword"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.ForgotPasswordResponse"
                        }
                    },
                    "404": {
                        "description": "Not found",
                        "schema": {
                            "$ref": "#/definitions/example.NotFound"
                        }
                    }
                }
            }
        },
        "/auth/google": {
            "get": {
                "description": "This route initiates the Google OAuth2 login flow. Please try this in your browser.",
                "tags": [
                    "Auth"
                ],
                "summary": "Login with google",
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.GoogleLoginResponse"
                        }
                    }
                }
            }
        },
        "/auth/login": {
            "post": {
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Auth"
                ],
                "summary": "Login",
                "parameters": [
                    {
                        "description": "Request body",
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/validation.Login"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.LoginResponse"
                        }
                    },
                    "401": {
                        "description": "Invalid email or password",
                        "schema": {
                            "$ref": "#/definitions/example.FailedLogin"
                        }
                    }
                }
            }
        },
        "/auth/logout": {
            "post": {
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Auth"
                ],
                "summary": "Logout",
                "parameters": [
                    {
                        "description": "Request body",
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/example.RefreshToken"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.LogoutResponse"
                        }
                    },
                    "404": {
                        "description": "Not found",
                        "schema": {
                            "$ref": "#/definitions/example.NotFound"
                        }
                    }
                }
            }
        },
        "/auth/refresh-tokens": {
            "post": {
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Auth"
                ],
                "summary": "Refresh auth tokens",
                "parameters": [
                    {
                        "description": "Request body",
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/example.RefreshToken"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.RefreshTokenResponse"
                        }
                    },
                    "401": {
                        "description": "Unauthorized",
                        "schema": {
                            "$ref": "#/definitions/example.Unauthorized"
                        }
                    }
                }
            }
        },
        "/auth/register": {
            "post": {
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Auth"
                ],
                "summary": "Register as user",
                "parameters": [
                    {
                        "description": "Request body",
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/validation.Register"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Created",
                        "schema": {
                            "$ref": "#/definitions/example.RegisterResponse"
                        }
                    },
                    "409": {
                        "description": "Email already taken",
                        "schema": {
                            "$ref": "#/definitions/example.DuplicateEmail"
                        }
                    }
                }
            }
        },
        "/auth/reset-password": {
            "post": {
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Auth"
                ],
                "summary": "Reset password",
                "parameters": [
                    {
                        "type": "string",
                        "description": "The reset password token",
                        "name": "token",
                        "in": "query",
                        "required": true
                    },
                    {
                        "description": "Request body",
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/validation.UpdatePassOrVerify"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.ResetPasswordResponse"
                        }
                    },
                    "401": {
                        "description": "Password reset failed",
                        "schema": {
                            "$ref": "#/definitions/example.FailedResetPassword"
                        }
                    }
                }
            }
        },
        "/auth/send-verification-email": {
            "post": {
                "security": [
                    {
                        "BearerAuth": []
                    }
                ],
                "description": "An email will be sent to verify email.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Auth"
                ],
                "summary": "Send verification email",
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.SendVerificationEmailResponse"
                        }
                    },
                    "401": {
                        "description": "Unauthorized",
                        "schema": {
                            "$ref": "#/definitions/example.Unauthorized"
                        }
                    }
                }
            }
        },
        "/auth/verify-email": {
            "post": {
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Auth"
                ],
                "summary": "Verify email",
                "parameters": [
                    {
                        "type": "string",
                        "description": "The verify email token",
                        "name": "token",
                        "in": "query",
                        "required": true
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.VerifyEmailResponse"
                        }
                    },
                    "401": {
                        "description": "Verify email failed",
                        "schema": {
                            "$ref": "#/definitions/example.FailedVerifyEmail"
                        }
                    }
                }
            }
        },
        "/health-check": {
            "get": {
                "description": "Check the status of services and database connections",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Health"
                ],
                "summary": "Health Check",
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.HealthCheckResponse"
                        }
                    },
                    "500": {
                        "description": "Internal Server Error",
                        "schema": {
                            "$ref": "#/definitions/example.HealthCheckResponseError"
                        }
                    }
                }
            }
        },
        "/users": {
            "get": {
                "security": [
                    {
                        "BearerAuth": []
                    }
                ],
                "description": "Only admins can retrieve all users.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Users"
                ],
                "summary": "Get all users",
                "parameters": [
                    {
                        "type": "integer",
                        "default": 1,
                        "description": "Page number",
                        "name": "page",
                        "in": "query"
                    },
                    {
                        "type": "integer",
                        "default": 10,
                        "description": "Maximum number of users",
                        "name": "limit",
                        "in": "query"
                    },
                    {
                        "type": "string",
                        "description": "Search by name or email or role",
                        "name": "search",
                        "in": "query"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.GetAllUserResponse"
                        }
                    },
                    "401": {
                        "description": "Unauthorized",
                        "schema": {
                            "$ref": "#/definitions/example.Unauthorized"
                        }
                    },
                    "403": {
                        "description": "Forbidden",
                        "schema": {
                            "$ref": "#/definitions/example.Forbidden"
                        }
                    }
                }
            },
            "post": {
                "security": [
                    {
                        "BearerAuth": []
                    }
                ],
                "description": "Only admins can create other users.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Users"
                ],
                "summary": "Create a user",
                "parameters": [
                    {
                        "description": "Request body",
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/validation.CreateUser"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Created",
                        "schema": {
                            "$ref": "#/definitions/example.CreateUserResponse"
                        }
                    },
                    "401": {
                        "description": "Unauthorized",
                        "schema": {
                            "$ref": "#/definitions/example.Unauthorized"
                        }
                    },
                    "403": {
                        "description": "Forbidden",
                        "schema": {
                            "$ref": "#/definitions/example.Forbidden"
                        }
                    },
                    "409": {
                        "description": "Email already taken",
                        "schema": {
                            "$ref": "#/definitions/example.DuplicateEmail"
                        }
                    }
                }
            }
        },
        "/users/{id}": {
            "get": {
                "security": [
                    {
                        "BearerAuth": []
                    }
                ],
                "description": "Logged in users can fetch only their own user information. Only admins can fetch other users.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Users"
                ],
                "summary": "Get a user",
                "parameters": [
                    {
                        "type": "string",
                        "description": "User id",
                        "name": "id",
                        "in": "path",
                        "required": true
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.GetUserResponse"
                        }
                    },
                    "401": {
                        "description": "Unauthorized",
                        "schema": {
                            "$ref": "#/definitions/example.Unauthorized"
                        }
                    },
                    "403": {
                        "description": "Forbidden",
                        "schema": {
                            "$ref": "#/definitions/example.Forbidden"
                        }
                    },
                    "404": {
                        "description": "Not found",
                        "schema": {
                            "$ref": "#/definitions/example.NotFound"
                        }
                    }
                }
            },
            "delete": {
                "security": [
                    {
                        "BearerAuth": []
                    }
                ],
                "description": "Logged in users can delete only themselves. Only admins can delete other users.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Users"
                ],
                "summary": "Delete a user",
                "parameters": [
                    {
                        "type": "string",
                        "description": "User id",
                        "name": "id",
                        "in": "path",
                        "required": true
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.DeleteUserResponse"
                        }
                    },
                    "401": {
                        "description": "Unauthorized",
                        "schema": {
                            "$ref": "#/definitions/example.Unauthorized"
                        }
                    },
                    "403": {
                        "description": "Forbidden",
                        "schema": {
                            "$ref": "#/definitions/example.Forbidden"
                        }
                    },
                    "404": {
                        "description": "Not found",
                        "schema": {
                            "$ref": "#/definitions/example.NotFound"
                        }
                    }
                }
            },
            "patch": {
                "security": [
                    {
                        "BearerAuth": []
                    }
                ],
                "description": "Logged in users can only update their own information. Only admins can update other users.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Users"
                ],
                "summary": "Update a user",
                "parameters": [
                    {
                        "type": "string",
                        "description": "User id",
                        "name": "id",
                        "in": "path",
                        "required": true
                    },
                    {
                        "description": "Request body",
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/validation.UpdateUser"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.UpdateUserResponse"
                        }
                    },
                    "401": {
                        "description": "Unauthorized",
                        "schema": {
                            "$ref": "#/definitions/example.Unauthorized"
                        }
                    },
                    "403": {
                        "description": "Forbidden",
                        "schema": {
                            "$ref": "#/definitions/example.Forbidden"
                        }
                    },
                    "404": {
                        "description": "Not found",
                        "schema": {
                            "$ref": "#/definitions/example.NotFound"
                        }
                    },
                    "409": {
                        "description": "Email already taken",
                        "schema": {
                            "$ref": "#/definitions/example.DuplicateEmail"
                        }
                    }
                }
            }
        }
    },
    "definitions": {
        "example.CreateUserResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 201
                },
                "message": {
                    "type": "string",
                    "example": "Create user successfully"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                },
                "user": {
                    "$ref": "#/definitions/example.User"
                }
            }
        },
        "example.DeleteUserResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "message": {
                    "type": "string",
                    "example": "Delete user successfully"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                }
            }
        },
        "example.DuplicateEmail": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 409
                },
                "message": {
                    "type": "string",
                    "example": "Email already taken"
                },
                "status": {
                    "type": "string",
                    "example": "error"
                }
            }
        },
        "example.FailedLogin": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 401
                },
                "message": {
                    "type": "string",
                    "example": "Invalid email or password"
                },
                "status": {
                    "type": "string",
                    "example": "error"
                }
            }
        },
        "example.FailedResetPassword": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 401
                },
                "message": {
                    "type": "string",
                    "example": "Password reset failed"
                },
                "status": {
                    "type": "string",
                    "example": "error"
                }
            }
        },
        "example.FailedVerifyEmail": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 401
                },
                "message": {
                    "type": "string",
                    "example": "Verify email failed"
                },
                "status": {
                    "type": "string",
                    "example": "error"
                }
            }
        },
        "example.Forbidden": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 403
                },
                "message": {
                    "type": "string",
                    "example": "You don't have permission to access this resource"
                },
                "status": {
                    "type": "string",
                    "example": "error"
                }
            }
        },
        "example.ForgotPasswordResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "message": {
                    "type": "string",
                    "example": "A password reset link has been sent to your email address."
                },
                "status": {
                    "type": "string",
                    "example": "success"
                }
            }
        },
        "example.GetAllUserResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "limit": {
                    "type": "integer",
                    "example": 10
                },
                "message": {
                    "type": "string",
                    "example": "Get all users successfully"
                },
                "page": {
                    "type": "integer",
                    "example": 1
                },
                "results": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/example.User"
                    }
                },
                "status": {
                    "type": "string",
                    "example": "success"
                },
                "total_pages": {
                    "type": "integer",
                    "example": 1
                },
                "total_results": {
                    "type": "integer",
                    "example": 1
                }
            }
        },
        "example.GetUserResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "message": {
                    "type": "string",
                    "example": "Get user successfully"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                },
                "user": {
                    "$ref": "#/definitions/example.User"
                }
            }
        },
        "example.GoogleLoginResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "message": {
                    "type": "string",
                    "example": "Login successfully"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                },
                "tokens": {
                    "$ref": "#/definitions/example.Tokens"
                },
                "user": {
                    "$ref": "#/definitions/example.GoogleUser"
                }
            }
        },
        "example.GoogleUser": {
            "type": "object",
            "properties": {
                "email": {
                    "type": "string",
                    "example": "fake@example.com"
                },
                "id": {
                    "type": "string",
                    "example": "e088d183-9eea-4a11-8d5d-74d7ec91bdf5"
                },
                "name": {
                    "type": "string",
                    "example": "fake name"
                },
                "role": {
                    "type": "string",
                    "example": "user"
                },
                "verified_email": {
                    "type": "boolean",
                    "example": true
                }
            }
        },
        "example.HealthCheck": {
            "type": "object",
            "properties": {
                "is_up": {
                    "type": "boolean",
                    "example": true
                },
                "name": {
                    "type": "string",
                    "example": "Postgre"
                },
                "status": {
                    "type": "string",
                    "example": "Up"
                }
            }
        },
        "example.HealthCheckError": {
            "type": "object",
            "properties": {
                "is_up": {
                    "type": "boolean",
                    "example": false
                },
                "message": {
                    "type": "string",
                    "example": "failed to connect to 'host=localhost user=postgres database=wrongdb': server error (FATAL: database \"wrongdb\" does not exist (SQLSTATE 3D000))"
                },
                "name": {
                    "type": "string",
                    "example": "Postgre"
                },
                "status": {
                    "type": "string",
                    "example": "Down"
                }
            }
        },
        "example.HealthCheckResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "is_healthy": {
                    "type": "boolean",
                    "example": true
                },
                "message": {
                    "type": "string",
                    "example": "Health check completed"
                },
                "result": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/example.HealthCheck"
                    }
                },
                "status": {
                    "type": "string",
                    "example": "success"
                }
            }
        },
        "example.HealthCheckResponseError": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 500
                },
                "is_healthy": {
                    "type": "boolean",
                    "example": false
                },
                "message": {
                    "type": "string",
                    "example": "Health check completed"
                },
                "result": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/example.HealthCheckError"
                    }
                },
                "status": {
                    "type": "string",
                    "example": "error"
                }
            }
        },
        "example.LoginResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "message": {
                    "type": "string",
                    "example": "Login successfully"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                },
                "tokens": {
                    "$ref": "#/definitions/example.Tokens"
                },
                "user": {
                    "$ref": "#/definitions/example.User"
                }
            }
        },
        "example.LogoutResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "message": {
                    "type": "string",
                    "example": "Logout successfully"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                }
            }
        },
        "example.NotFound": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 404
                },
                "message": {
                    "type": "string",
                    "example": "Not found"
                },
                "status": {
                    "type": "string",
                    "example": "error"
                }
            }
        },
        "example.RefreshToken": {
            "type": "object",
            "properties": {
                "refresh_token": {
                    "type": "string",
                    "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZWJhYzUzNDk1NGI1NDEzOTgwNmMxMTIiLCJpYXQiOjE1ODkyOTg0ODQsImV4cCI6MTU4OTMwMDI4NH0.m1U63blB0MLej_WfB7yC2FTMnCziif9X8yzwDEfJXAg"
                }
            }
        },
        "example.RefreshTokenResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "status": {
                    "type": "string",
                    "example": "success"
                },
                "tokens": {
                    "$ref": "#/definitions/example.Tokens"
                }
            }
        },
        "example.RegisterResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 201
                },
                "message": {
                    "type": "string",
                    "example": "Register successfully"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                },
                "tokens": {
                    "$ref": "#/definitions/example.Tokens"
                },
                "user": {
                    "$ref": "#/definitions/example.User"
                }
            }
        },
        "example.ResetPasswordResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "message": {
                    "type": "string",
                    "example": "Update password successfully"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                }
            }
        },
        "example.SendVerificationEmailResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "message": {
                    "type": "string",
                    "example": "Please check your email for a link to verify your account"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                }
            }
        },
        "example.TokenExpires": {
            "type": "object",
            "properties": {
                "expires": {
                    "type": "string",
                    "example": "2024-10-07T11:56:46.618180553Z"
                },
                "token": {
                    "type": "string",
                    "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZWJhYzUzNDk1NGI1NDEzOTgwNmMxMTIiLCJpYXQiOjE1ODkyOTg0ODQsImV4cCI6MTU4OTMwMDI4NH0.m1U63blB0MLej_WfB7yC2FTMnCziif9X8yzwDEfJXAg"
                }
            }
        },
        "example.Tokens": {
            "type": "object",
            "properties": {
                "access": {
                    "$ref": "#/definitions/example.TokenExpires"
                },
                "refresh": {
                    "$ref": "#/definitions/example.TokenExpires"
                }
            }
        },
        "example.Unauthorized": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 401
                },
                "message": {
                    "type": "string",
                    "example": "Please authenticate"
                },
                "status": {
                    "type": "string",
                    "example": "error"
                }
            }
        },
        "example.UpdateUserResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "message": {
                    "type": "string",
                    "example": "Update user successfully"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                },
                "user": {
                    "$ref": "#/definitions/example.User"
                }
            }
        },
        "example.User": {
            "type": "object",
            "properties": {
                "email": {
                    "type": "string",
                    "example": "fake@example.com"
                },
                "id": {
                    "type": "string",
                    "example": "e088d183-9eea-4a11-8d5d-74d7ec91bdf5"
                },
                "name": {
                    "type": "string",
                    "example": "fake name"
                },
                "role": {
                    "type": "string",
                    "example": "user"
                },
                "verified_email": {
                    "type": "boolean",
                    "example": false
                }
            }
        },
        "example.VerifyEmailResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "message": {
                    "type": "string",
                    "example": "Verify email successfully"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                }
            }
        },
        "validation.CreateUser": {
            "type": "object",
            "required": [
                "email",
                "name",
                "password",
                "role"
            ],
            "properties": {
                "email": {
                    "type": "string",
                    "maxLength": 50,
                    "example": "fake@example.com"
                },
                "name": {
                    "type": "string",
                    "maxLength": 50,
                    "example": "fake name"
                },
                "password": {
                    "type": "string",
                    "maxLength": 20,
                    "minLength": 8,
                    "example": "password1"
                },
                "role": {
                    "type": "string",
                    "maxLength": 50,
                    "enum": [
                        "user",
                        "admin"
                    ],
                    "example": "user"
                }
            }
        },
        "validation.ForgotPassword": {
            "type": "object",
            "required": [
                "email"
            ],
            "properties": {
                "email": {
                    "type": "string",
                    "maxLength": 50,
                    "example": "fake@example.com"
                }
            }
        },
        "validation.Login": {
            "type": "object",
            "required": [
                "email",
                "password"
            ],
            "properties": {
                "email": {
                    "type": "string",
                    "maxLength": 50,
                    "example": "fake@example.com"
                },
                "password": {
                    "type": "string",
                    "maxLength": 20,
                    "minLength": 8,
                    "example": "password1"
                }
            }
        },
        "validation.Register": {
            "type": "object",
            "required": [
                "email",
                "name",
                "password"
            ],
            "properties": {
                "email": {
                    "type": "string",
                    "maxLength": 50,
                    "example": "fake@example.com"
                },
                "name": {
                    "type": "string",
                    "maxLength": 50,
                    "example": "fake name"
                },
                "password": {
                    "type": "string",
                    "maxLength": 20,
                    "minLength": 8,
                    "example": "password1"
                }
            }
        },
        "validation.UpdatePassOrVerify": {
            "type": "object",
            "properties": {
                "password": {
                    "type": "string",
                    "maxLength": 20,
                    "minLength": 8,
                    "example": "password1"
                }
            }
        },
        "validation.UpdateUser": {
            "type": "object",
            "properties": {
                "email": {
                    "type": "string",
                    "maxLength": 50,
                    "example": "fake@example.com"
                },
                "name": {
                    "type": "string",
                    "maxLength": 50,
                    "example": "fake name"
                },
                "password": {
                    "type": "string",
                    "maxLength": 20,
                    "minLength": 8,
                    "example": "password1"
                }
            }
        }
    },
    "securityDefinitions": {
        "BearerAuth": {
            "description": "Example Value: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
            "type": "apiKey",
            "name": "Authorization",
            "in": "header"
        }
    }
}`

// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
	Version:          "1.3.1",
	Host:             "localhost:3000",
	BasePath:         "/v1",
	Schemes:          []string{},
	Title:            "go-fiber-boilerplate API documentation",
	Description:      "",
	InfoInstanceName: "swagger",
	SwaggerTemplate:  docTemplate,
	LeftDelim:        "{{",
	RightDelim:       "}}",
}

func init() {
	swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}


================================================
FILE: src/docs/swagger.json
================================================
{
    "swagger": "2.0",
    "info": {
        "title": "go-fiber-boilerplate API documentation",
        "contact": {},
        "license": {
            "name": "MIT",
            "url": "https://github.com/indrayyana/go-fiber-boilerplate/blob/main/LICENSE"
        },
        "version": "1.3.1"
    },
    "host": "localhost:3000",
    "basePath": "/v1",
    "paths": {
        "/auth/forgot-password": {
            "post": {
                "description": "An email will be sent to reset password.",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Auth"
                ],
                "summary": "Forgot password",
                "parameters": [
                    {
                        "description": "Request body",
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/validation.ForgotPassword"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.ForgotPasswordResponse"
                        }
                    },
                    "404": {
                        "description": "Not found",
                        "schema": {
                            "$ref": "#/definitions/example.NotFound"
                        }
                    }
                }
            }
        },
        "/auth/google": {
            "get": {
                "description": "This route initiates the Google OAuth2 login flow. Please try this in your browser.",
                "tags": [
                    "Auth"
                ],
                "summary": "Login with google",
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.GoogleLoginResponse"
                        }
                    }
                }
            }
        },
        "/auth/login": {
            "post": {
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Auth"
                ],
                "summary": "Login",
                "parameters": [
                    {
                        "description": "Request body",
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/validation.Login"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.LoginResponse"
                        }
                    },
                    "401": {
                        "description": "Invalid email or password",
                        "schema": {
                            "$ref": "#/definitions/example.FailedLogin"
                        }
                    }
                }
            }
        },
        "/auth/logout": {
            "post": {
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Auth"
                ],
                "summary": "Logout",
                "parameters": [
                    {
                        "description": "Request body",
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/example.RefreshToken"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.LogoutResponse"
                        }
                    },
                    "404": {
                        "description": "Not found",
                        "schema": {
                            "$ref": "#/definitions/example.NotFound"
                        }
                    }
                }
            }
        },
        "/auth/refresh-tokens": {
            "post": {
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Auth"
                ],
                "summary": "Refresh auth tokens",
                "parameters": [
                    {
                        "description": "Request body",
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/example.RefreshToken"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.RefreshTokenResponse"
                        }
                    },
                    "401": {
                        "description": "Unauthorized",
                        "schema": {
                            "$ref": "#/definitions/example.Unauthorized"
                        }
                    }
                }
            }
        },
        "/auth/register": {
            "post": {
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Auth"
                ],
                "summary": "Register as user",
                "parameters": [
                    {
                        "description": "Request body",
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/validation.Register"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Created",
                        "schema": {
                            "$ref": "#/definitions/example.RegisterResponse"
                        }
                    },
                    "409": {
                        "description": "Email already taken",
                        "schema": {
                            "$ref": "#/definitions/example.DuplicateEmail"
                        }
                    }
                }
            }
        },
        "/auth/reset-password": {
            "post": {
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Auth"
                ],
                "summary": "Reset password",
                "parameters": [
                    {
                        "type": "string",
                        "description": "The reset password token",
                        "name": "token",
                        "in": "query",
                        "required": true
                    },
                    {
                        "description": "Request body",
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/validation.UpdatePassOrVerify"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.ResetPasswordResponse"
                        }
                    },
                    "401": {
                        "description": "Password reset failed",
                        "schema": {
                            "$ref": "#/definitions/example.FailedResetPassword"
                        }
                    }
                }
            }
        },
        "/auth/send-verification-email": {
            "post": {
                "security": [
                    {
                        "BearerAuth": []
                    }
                ],
                "description": "An email will be sent to verify email.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Auth"
                ],
                "summary": "Send verification email",
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.SendVerificationEmailResponse"
                        }
                    },
                    "401": {
                        "description": "Unauthorized",
                        "schema": {
                            "$ref": "#/definitions/example.Unauthorized"
                        }
                    }
                }
            }
        },
        "/auth/verify-email": {
            "post": {
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Auth"
                ],
                "summary": "Verify email",
                "parameters": [
                    {
                        "type": "string",
                        "description": "The verify email token",
                        "name": "token",
                        "in": "query",
                        "required": true
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.VerifyEmailResponse"
                        }
                    },
                    "401": {
                        "description": "Verify email failed",
                        "schema": {
                            "$ref": "#/definitions/example.FailedVerifyEmail"
                        }
                    }
                }
            }
        },
        "/health-check": {
            "get": {
                "description": "Check the status of services and database connections",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Health"
                ],
                "summary": "Health Check",
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.HealthCheckResponse"
                        }
                    },
                    "500": {
                        "description": "Internal Server Error",
                        "schema": {
                            "$ref": "#/definitions/example.HealthCheckResponseError"
                        }
                    }
                }
            }
        },
        "/users": {
            "get": {
                "security": [
                    {
                        "BearerAuth": []
                    }
                ],
                "description": "Only admins can retrieve all users.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Users"
                ],
                "summary": "Get all users",
                "parameters": [
                    {
                        "type": "integer",
                        "default": 1,
                        "description": "Page number",
                        "name": "page",
                        "in": "query"
                    },
                    {
                        "type": "integer",
                        "default": 10,
                        "description": "Maximum number of users",
                        "name": "limit",
                        "in": "query"
                    },
                    {
                        "type": "string",
                        "description": "Search by name or email or role",
                        "name": "search",
                        "in": "query"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.GetAllUserResponse"
                        }
                    },
                    "401": {
                        "description": "Unauthorized",
                        "schema": {
                            "$ref": "#/definitions/example.Unauthorized"
                        }
                    },
                    "403": {
                        "description": "Forbidden",
                        "schema": {
                            "$ref": "#/definitions/example.Forbidden"
                        }
                    }
                }
            },
            "post": {
                "security": [
                    {
                        "BearerAuth": []
                    }
                ],
                "description": "Only admins can create other users.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Users"
                ],
                "summary": "Create a user",
                "parameters": [
                    {
                        "description": "Request body",
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/validation.CreateUser"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Created",
                        "schema": {
                            "$ref": "#/definitions/example.CreateUserResponse"
                        }
                    },
                    "401": {
                        "description": "Unauthorized",
                        "schema": {
                            "$ref": "#/definitions/example.Unauthorized"
                        }
                    },
                    "403": {
                        "description": "Forbidden",
                        "schema": {
                            "$ref": "#/definitions/example.Forbidden"
                        }
                    },
                    "409": {
                        "description": "Email already taken",
                        "schema": {
                            "$ref": "#/definitions/example.DuplicateEmail"
                        }
                    }
                }
            }
        },
        "/users/{id}": {
            "get": {
                "security": [
                    {
                        "BearerAuth": []
                    }
                ],
                "description": "Logged in users can fetch only their own user information. Only admins can fetch other users.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Users"
                ],
                "summary": "Get a user",
                "parameters": [
                    {
                        "type": "string",
                        "description": "User id",
                        "name": "id",
                        "in": "path",
                        "required": true
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.GetUserResponse"
                        }
                    },
                    "401": {
                        "description": "Unauthorized",
                        "schema": {
                            "$ref": "#/definitions/example.Unauthorized"
                        }
                    },
                    "403": {
                        "description": "Forbidden",
                        "schema": {
                            "$ref": "#/definitions/example.Forbidden"
                        }
                    },
                    "404": {
                        "description": "Not found",
                        "schema": {
                            "$ref": "#/definitions/example.NotFound"
                        }
                    }
                }
            },
            "delete": {
                "security": [
                    {
                        "BearerAuth": []
                    }
                ],
                "description": "Logged in users can delete only themselves. Only admins can delete other users.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Users"
                ],
                "summary": "Delete a user",
                "parameters": [
                    {
                        "type": "string",
                        "description": "User id",
                        "name": "id",
                        "in": "path",
                        "required": true
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.DeleteUserResponse"
                        }
                    },
                    "401": {
                        "description": "Unauthorized",
                        "schema": {
                            "$ref": "#/definitions/example.Unauthorized"
                        }
                    },
                    "403": {
                        "description": "Forbidden",
                        "schema": {
                            "$ref": "#/definitions/example.Forbidden"
                        }
                    },
                    "404": {
                        "description": "Not found",
                        "schema": {
                            "$ref": "#/definitions/example.NotFound"
                        }
                    }
                }
            },
            "patch": {
                "security": [
                    {
                        "BearerAuth": []
                    }
                ],
                "description": "Logged in users can only update their own information. Only admins can update other users.",
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "Users"
                ],
                "summary": "Update a user",
                "parameters": [
                    {
                        "type": "string",
                        "description": "User id",
                        "name": "id",
                        "in": "path",
                        "required": true
                    },
                    {
                        "description": "Request body",
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/validation.UpdateUser"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/example.UpdateUserResponse"
                        }
                    },
                    "401": {
                        "description": "Unauthorized",
                        "schema": {
                            "$ref": "#/definitions/example.Unauthorized"
                        }
                    },
                    "403": {
                        "description": "Forbidden",
                        "schema": {
                            "$ref": "#/definitions/example.Forbidden"
                        }
                    },
                    "404": {
                        "description": "Not found",
                        "schema": {
                            "$ref": "#/definitions/example.NotFound"
                        }
                    },
                    "409": {
                        "description": "Email already taken",
                        "schema": {
                            "$ref": "#/definitions/example.DuplicateEmail"
                        }
                    }
                }
            }
        }
    },
    "definitions": {
        "example.CreateUserResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 201
                },
                "message": {
                    "type": "string",
                    "example": "Create user successfully"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                },
                "user": {
                    "$ref": "#/definitions/example.User"
                }
            }
        },
        "example.DeleteUserResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "message": {
                    "type": "string",
                    "example": "Delete user successfully"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                }
            }
        },
        "example.DuplicateEmail": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 409
                },
                "message": {
                    "type": "string",
                    "example": "Email already taken"
                },
                "status": {
                    "type": "string",
                    "example": "error"
                }
            }
        },
        "example.FailedLogin": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 401
                },
                "message": {
                    "type": "string",
                    "example": "Invalid email or password"
                },
                "status": {
                    "type": "string",
                    "example": "error"
                }
            }
        },
        "example.FailedResetPassword": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 401
                },
                "message": {
                    "type": "string",
                    "example": "Password reset failed"
                },
                "status": {
                    "type": "string",
                    "example": "error"
                }
            }
        },
        "example.FailedVerifyEmail": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 401
                },
                "message": {
                    "type": "string",
                    "example": "Verify email failed"
                },
                "status": {
                    "type": "string",
                    "example": "error"
                }
            }
        },
        "example.Forbidden": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 403
                },
                "message": {
                    "type": "string",
                    "example": "You don't have permission to access this resource"
                },
                "status": {
                    "type": "string",
                    "example": "error"
                }
            }
        },
        "example.ForgotPasswordResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "message": {
                    "type": "string",
                    "example": "A password reset link has been sent to your email address."
                },
                "status": {
                    "type": "string",
                    "example": "success"
                }
            }
        },
        "example.GetAllUserResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "limit": {
                    "type": "integer",
                    "example": 10
                },
                "message": {
                    "type": "string",
                    "example": "Get all users successfully"
                },
                "page": {
                    "type": "integer",
                    "example": 1
                },
                "results": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/example.User"
                    }
                },
                "status": {
                    "type": "string",
                    "example": "success"
                },
                "total_pages": {
                    "type": "integer",
                    "example": 1
                },
                "total_results": {
                    "type": "integer",
                    "example": 1
                }
            }
        },
        "example.GetUserResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "message": {
                    "type": "string",
                    "example": "Get user successfully"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                },
                "user": {
                    "$ref": "#/definitions/example.User"
                }
            }
        },
        "example.GoogleLoginResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "message": {
                    "type": "string",
                    "example": "Login successfully"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                },
                "tokens": {
                    "$ref": "#/definitions/example.Tokens"
                },
                "user": {
                    "$ref": "#/definitions/example.GoogleUser"
                }
            }
        },
        "example.GoogleUser": {
            "type": "object",
            "properties": {
                "email": {
                    "type": "string",
                    "example": "fake@example.com"
                },
                "id": {
                    "type": "string",
                    "example": "e088d183-9eea-4a11-8d5d-74d7ec91bdf5"
                },
                "name": {
                    "type": "string",
                    "example": "fake name"
                },
                "role": {
                    "type": "string",
                    "example": "user"
                },
                "verified_email": {
                    "type": "boolean",
                    "example": true
                }
            }
        },
        "example.HealthCheck": {
            "type": "object",
            "properties": {
                "is_up": {
                    "type": "boolean",
                    "example": true
                },
                "name": {
                    "type": "string",
                    "example": "Postgre"
                },
                "status": {
                    "type": "string",
                    "example": "Up"
                }
            }
        },
        "example.HealthCheckError": {
            "type": "object",
            "properties": {
                "is_up": {
                    "type": "boolean",
                    "example": false
                },
                "message": {
                    "type": "string",
                    "example": "failed to connect to 'host=localhost user=postgres database=wrongdb': server error (FATAL: database \"wrongdb\" does not exist (SQLSTATE 3D000))"
                },
                "name": {
                    "type": "string",
                    "example": "Postgre"
                },
                "status": {
                    "type": "string",
                    "example": "Down"
                }
            }
        },
        "example.HealthCheckResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "is_healthy": {
                    "type": "boolean",
                    "example": true
                },
                "message": {
                    "type": "string",
                    "example": "Health check completed"
                },
                "result": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/example.HealthCheck"
                    }
                },
                "status": {
                    "type": "string",
                    "example": "success"
                }
            }
        },
        "example.HealthCheckResponseError": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 500
                },
                "is_healthy": {
                    "type": "boolean",
                    "example": false
                },
                "message": {
                    "type": "string",
                    "example": "Health check completed"
                },
                "result": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/example.HealthCheckError"
                    }
                },
                "status": {
                    "type": "string",
                    "example": "error"
                }
            }
        },
        "example.LoginResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "message": {
                    "type": "string",
                    "example": "Login successfully"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                },
                "tokens": {
                    "$ref": "#/definitions/example.Tokens"
                },
                "user": {
                    "$ref": "#/definitions/example.User"
                }
            }
        },
        "example.LogoutResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "message": {
                    "type": "string",
                    "example": "Logout successfully"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                }
            }
        },
        "example.NotFound": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 404
                },
                "message": {
                    "type": "string",
                    "example": "Not found"
                },
                "status": {
                    "type": "string",
                    "example": "error"
                }
            }
        },
        "example.RefreshToken": {
            "type": "object",
            "properties": {
                "refresh_token": {
                    "type": "string",
                    "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZWJhYzUzNDk1NGI1NDEzOTgwNmMxMTIiLCJpYXQiOjE1ODkyOTg0ODQsImV4cCI6MTU4OTMwMDI4NH0.m1U63blB0MLej_WfB7yC2FTMnCziif9X8yzwDEfJXAg"
                }
            }
        },
        "example.RefreshTokenResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "status": {
                    "type": "string",
                    "example": "success"
                },
                "tokens": {
                    "$ref": "#/definitions/example.Tokens"
                }
            }
        },
        "example.RegisterResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 201
                },
                "message": {
                    "type": "string",
                    "example": "Register successfully"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                },
                "tokens": {
                    "$ref": "#/definitions/example.Tokens"
                },
                "user": {
                    "$ref": "#/definitions/example.User"
                }
            }
        },
        "example.ResetPasswordResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "message": {
                    "type": "string",
                    "example": "Update password successfully"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                }
            }
        },
        "example.SendVerificationEmailResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "message": {
                    "type": "string",
                    "example": "Please check your email for a link to verify your account"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                }
            }
        },
        "example.TokenExpires": {
            "type": "object",
            "properties": {
                "expires": {
                    "type": "string",
                    "example": "2024-10-07T11:56:46.618180553Z"
                },
                "token": {
                    "type": "string",
                    "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZWJhYzUzNDk1NGI1NDEzOTgwNmMxMTIiLCJpYXQiOjE1ODkyOTg0ODQsImV4cCI6MTU4OTMwMDI4NH0.m1U63blB0MLej_WfB7yC2FTMnCziif9X8yzwDEfJXAg"
                }
            }
        },
        "example.Tokens": {
            "type": "object",
            "properties": {
                "access": {
                    "$ref": "#/definitions/example.TokenExpires"
                },
                "refresh": {
                    "$ref": "#/definitions/example.TokenExpires"
                }
            }
        },
        "example.Unauthorized": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 401
                },
                "message": {
                    "type": "string",
                    "example": "Please authenticate"
                },
                "status": {
                    "type": "string",
                    "example": "error"
                }
            }
        },
        "example.UpdateUserResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "message": {
                    "type": "string",
                    "example": "Update user successfully"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                },
                "user": {
                    "$ref": "#/definitions/example.User"
                }
            }
        },
        "example.User": {
            "type": "object",
            "properties": {
                "email": {
                    "type": "string",
                    "example": "fake@example.com"
                },
                "id": {
                    "type": "string",
                    "example": "e088d183-9eea-4a11-8d5d-74d7ec91bdf5"
                },
                "name": {
                    "type": "string",
                    "example": "fake name"
                },
                "role": {
                    "type": "string",
                    "example": "user"
                },
                "verified_email": {
                    "type": "boolean",
                    "example": false
                }
            }
        },
        "example.VerifyEmailResponse": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "integer",
                    "example": 200
                },
                "message": {
                    "type": "string",
                    "example": "Verify email successfully"
                },
                "status": {
                    "type": "string",
                    "example": "success"
                }
            }
        },
        "validation.CreateUser": {
            "type": "object",
            "required": [
                "email",
                "name",
                "password",
                "role"
            ],
            "properties": {
                "email": {
                    "type": "string",
                    "maxLength": 50,
                    "example": "fake@example.com"
                },
                "name": {
                    "type": "string",
                    "maxLength": 50,
                    "example": "fake name"
                },
                "password": {
                    "type": "string",
                    "maxLength": 20,
                    "minLength": 8,
                    "example": "password1"
                },
                "role": {
                    "type": "string",
                    "maxLength": 50,
                    "enum": [
                        "user",
                        "admin"
                    ],
                    "example": "user"
                }
            }
        },
        "validation.ForgotPassword": {
            "type": "object",
            "required": [
                "email"
            ],
            "properties": {
                "email": {
                    "type": "string",
                    "maxLength": 50,
                    "example": "fake@example.com"
                }
            }
        },
        "validation.Login": {
            "type": "object",
            "required": [
                "email",
                "password"
            ],
            "properties": {
                "email": {
                    "type": "string",
                    "maxLength": 50,
                    "example": "fake@example.com"
                },
                "password": {
                    "type": "string",
                    "maxLength": 20,
                    "minLength": 8,
                    "example": "password1"
                }
            }
        },
        "validation.Register": {
            "type": "object",
            "required": [
                "email",
                "name",
                "password"
            ],
            "properties": {
                "email": {
                    "type": "string",
                    "maxLength": 50,
                    "example": "fake@example.com"
                },
                "name": {
                    "type": "string",
                    "maxLength": 50,
                    "example": "fake name"
                },
                "password": {
                    "type": "string",
                    "maxLength": 20,
                    "minLength": 8,
                    "example": "password1"
                }
            }
        },
        "validation.UpdatePassOrVerify": {
            "type": "object",
            "properties": {
                "password": {
                    "type": "string",
                    "maxLength": 20,
                    "minLength": 8,
                    "example": "password1"
                }
            }
        },
        "validation.UpdateUser": {
            "type": "object",
            "properties": {
                "email": {
                    "type": "string",
                    "maxLength": 50,
                    "example": "fake@example.com"
                },
                "name": {
                    "type": "string",
                    "maxLength": 50,
                    "example": "fake name"
                },
                "password": {
                    "type": "string",
                    "maxLength": 20,
                    "minLength": 8,
                    "example": "password1"
                }
            }
        }
    },
    "securityDefinitions": {
        "BearerAuth": {
            "description": "Example Value: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
            "type": "apiKey",
            "name": "Authorization",
            "in": "header"
        }
    }
}

================================================
FILE: src/docs/swagger.yaml
================================================
basePath: /v1
definitions:
  example.CreateUserResponse:
    properties:
      code:
        example: 201
        type: integer
      message:
        example: Create user successfully
        type: string
      status:
        example: success
        type: string
      user:
        $ref: '#/definitions/example.User'
    type: object
  example.DeleteUserResponse:
    properties:
      code:
        example: 200
        type: integer
      message:
        example: Delete user successfully
        type: string
      status:
        example: success
        type: string
    type: object
  example.DuplicateEmail:
    properties:
      code:
        example: 409
        type: integer
      message:
        example: Email already taken
        type: string
      status:
        example: error
        type: string
    type: object
  example.FailedLogin:
    properties:
      code:
        example: 401
        type: integer
      message:
        example: Invalid email or password
        type: string
      status:
        example: error
        type: string
    type: object
  example.FailedResetPassword:
    properties:
      code:
        example: 401
        type: integer
      message:
        example: Password reset failed
        type: string
      status:
        example: error
        type: string
    type: object
  example.FailedVerifyEmail:
    properties:
      code:
        example: 401
        type: integer
      message:
        example: Verify email failed
        type: string
      status:
        example: error
        type: string
    type: object
  example.Forbidden:
    properties:
      code:
        example: 403
        type: integer
      message:
        example: You don't have permission to access this resource
        type: string
      status:
        example: error
        type: string
    type: object
  example.ForgotPasswordResponse:
    properties:
      code:
        example: 200
        type: integer
      message:
        example: A password reset link has been sent to your email address.
        type: string
      status:
        example: success
        type: string
    type: object
  example.GetAllUserResponse:
    properties:
      code:
        example: 200
        type: integer
      limit:
        example: 10
        type: integer
      message:
        example: Get all users successfully
        type: string
      page:
        example: 1
        type: integer
      results:
        items:
          $ref: '#/definitions/example.User'
        type: array
      status:
        example: success
        type: string
      total_pages:
        example: 1
        type: integer
      total_results:
        example: 1
        type: integer
    type: object
  example.GetUserResponse:
    properties:
      code:
        example: 200
        type: integer
      message:
        example: Get user successfully
        type: string
      status:
        example: success
        type: string
      user:
        $ref: '#/definitions/example.User'
    type: object
  example.GoogleLoginResponse:
    properties:
      code:
        example: 200
        type: integer
      message:
        example: Login successfully
        type: string
      status:
        example: success
        type: string
      tokens:
        $ref: '#/definitions/example.Tokens'
      user:
        $ref: '#/definitions/example.GoogleUser'
    type: object
  example.GoogleUser:
    properties:
      email:
        example: fake@example.com
        type: string
      id:
        example: e088d183-9eea-4a11-8d5d-74d7ec91bdf5
        type: string
      name:
        example: fake name
        type: string
      role:
        example: user
        type: string
      verified_email:
        example: true
        type: boolean
    type: object
  example.HealthCheck:
    properties:
      is_up:
        example: true
        type: boolean
      name:
        example: Postgre
        type: string
      status:
        example: Up
        type: string
    type: object
  example.HealthCheckError:
    properties:
      is_up:
        example: false
        type: boolean
      message:
        example: 'failed to connect to ''host=localhost user=postgres database=wrongdb'':
          server error (FATAL: database "wrongdb" does not exist (SQLSTATE 3D000))'
        type: string
      name:
        example: Postgre
        type: string
      status:
        example: Down
        type: string
    type: object
  example.HealthCheckResponse:
    properties:
      code:
        example: 200
        type: integer
      is_healthy:
        example: true
        type: boolean
      message:
        example: Health check completed
        type: string
      result:
        items:
          $ref: '#/definitions/example.HealthCheck'
        type: array
      status:
        example: success
        type: string
    type: object
  example.HealthCheckResponseError:
    properties:
      code:
        example: 500
        type: integer
      is_healthy:
        example: false
        type: boolean
      message:
        example: Health check completed
        type: string
      result:
        items:
          $ref: '#/definitions/example.HealthCheckError'
        type: array
      status:
        example: error
        type: string
    type: object
  example.LoginResponse:
    properties:
      code:
        example: 200
        type: integer
      message:
        example: Login successfully
        type: string
      status:
        example: success
        type: string
      tokens:
        $ref: '#/definitions/example.Tokens'
      user:
        $ref: '#/definitions/example.User'
    type: object
  example.LogoutResponse:
    properties:
      code:
        example: 200
        type: integer
      message:
        example: Logout successfully
        type: string
      status:
        example: success
        type: string
    type: object
  example.NotFound:
    properties:
      code:
        example: 404
        type: integer
      message:
        example: Not found
        type: string
      status:
        example: error
        type: string
    type: object
  example.RefreshToken:
    properties:
      refresh_token:
        example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZWJhYzUzNDk1NGI1NDEzOTgwNmMxMTIiLCJpYXQiOjE1ODkyOTg0ODQsImV4cCI6MTU4OTMwMDI4NH0.m1U63blB0MLej_WfB7yC2FTMnCziif9X8yzwDEfJXAg
        type: string
    type: object
  example.RefreshTokenResponse:
    properties:
      code:
        example: 200
        type: integer
      status:
        example: success
        type: string
      tokens:
        $ref: '#/definitions/example.Tokens'
    type: object
  example.RegisterResponse:
    properties:
      code:
        example: 201
        type: integer
      message:
        example: Register successfully
        type: string
      status:
        example: success
        type: string
      tokens:
        $ref: '#/definitions/example.Tokens'
      user:
        $ref: '#/definitions/example.User'
    type: object
  example.ResetPasswordResponse:
    properties:
      code:
        example: 200
        type: integer
      message:
        example: Update password successfully
        type: string
      status:
        example: success
        type: string
    type: object
  example.SendVerificationEmailResponse:
    properties:
      code:
        example: 200
        type: integer
      message:
        example: Please check your email for a link to verify your account
        type: string
      status:
        example: success
        type: string
    type: object
  example.TokenExpires:
    properties:
      expires:
        example: "2024-10-07T11:56:46.618180553Z"
        type: string
      token:
        example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZWJhYzUzNDk1NGI1NDEzOTgwNmMxMTIiLCJpYXQiOjE1ODkyOTg0ODQsImV4cCI6MTU4OTMwMDI4NH0.m1U63blB0MLej_WfB7yC2FTMnCziif9X8yzwDEfJXAg
        type: string
    type: object
  example.Tokens:
    properties:
      access:
        $ref: '#/definitions/example.TokenExpires'
      refresh:
        $ref: '#/definitions/example.TokenExpires'
    type: object
  example.Unauthorized:
    properties:
      code:
        example: 401
        type: integer
      message:
        example: Please authenticate
        type: string
      status:
        example: error
        type: string
    type: object
  example.UpdateUserResponse:
    properties:
      code:
        example: 200
        type: integer
      message:
        example: Update user successfully
        type: string
      status:
        example: success
        type: string
      user:
        $ref: '#/definitions/example.User'
    type: object
  example.User:
    properties:
      email:
        example: fake@example.com
        type: string
      id:
        example: e088d183-9eea-4a11-8d5d-74d7ec91bdf5
        type: string
      name:
        example: fake name
        type: string
      role:
        example: user
        type: string
      verified_email:
        example: false
        type: boolean
    type: object
  example.VerifyEmailResponse:
    properties:
      code:
        example: 200
        type: integer
      message:
        example: Verify email successfully
        type: string
      status:
        example: success
        type: string
    type: object
  validation.CreateUser:
    properties:
      email:
        example: fake@example.com
        maxLength: 50
        type: string
      name:
        example: fake name
        maxLength: 50
        type: string
      password:
        example: password1
        maxLength: 20
        minLength: 8
        type: string
      role:
        enum:
        - user
        - admin
        example: user
        maxLength: 50
        type: string
    required:
    - email
    - name
    - password
    - role
    type: object
  validation.ForgotPassword:
    properties:
      email:
        example: fake@example.com
        maxLength: 50
        type: string
    required:
    - email
    type: object
  validation.Login:
    properties:
      email:
        example: fake@example.com
        maxLength: 50
        type: string
      password:
        example: password1
        maxLength: 20
        minLength: 8
        type: string
    required:
    - email
    - password
    type: object
  validation.Register:
    properties:
      email:
        example: fake@example.com
        maxLength: 50
        type: string
      name:
        example: fake name
        maxLength: 50
        type: string
      password:
        example: password1
        maxLength: 20
        minLength: 8
        type: string
    required:
    - email
    - name
    - password
    type: object
  validation.UpdatePassOrVerify:
    properties:
      password:
        example: password1
        maxLength: 20
        minLength: 8
        type: string
    type: object
  validation.UpdateUser:
    properties:
      email:
        example: fake@example.com
        maxLength: 50
        type: string
      name:
        example: fake name
        maxLength: 50
        type: string
      password:
        example: password1
        maxLength: 20
        minLength: 8
        type: string
    type: object
host: localhost:3000
info:
  contact: {}
  license:
    name: MIT
    url: https://github.com/indrayyana/go-fiber-boilerplate/blob/main/LICENSE
  title: go-fiber-boilerplate API documentation
  version: 1.3.1
paths:
  /auth/forgot-password:
    post:
      consumes:
      - application/json
      description: An email will be sent to reset password.
      parameters:
      - description: Request body
        in: body
        name: request
        required: true
        schema:
          $ref: '#/definitions/validation.ForgotPassword'
      produces:
      - application/json
      responses:
        "200":
          description: OK
          schema:
            $ref: '#/definitions/example.ForgotPasswordResponse'
        "404":
          description: Not found
          schema:
            $ref: '#/definitions/example.NotFound'
      summary: Forgot password
      tags:
      - Auth
  /auth/google:
    get:
      description: This route initiates the Google OAuth2 login flow. Please try this
        in your browser.
      responses:
        "200":
          description: OK
          schema:
            $ref: '#/definitions/example.GoogleLoginResponse'
      summary: Login with google
      tags:
      - Auth
  /auth/login:
    post:
      consumes:
      - application/json
      parameters:
      - description: Request body
        in: body
        name: request
        required: true
        schema:
          $ref: '#/definitions/validation.Login'
      produces:
      - application/json
      responses:
        "200":
          description: OK
          schema:
            $ref: '#/definitions/example.LoginResponse'
        "401":
          description: Invalid email or password
          schema:
            $ref: '#/definitions/example.FailedLogin'
      summary: Login
      tags:
      - Auth
  /auth/logout:
    post:
      consumes:
      - application/json
      parameters:
      - description: Request body
        in: body
        name: request
        required: true
        schema:
          $ref: '#/definitions/example.RefreshToken'
      produces:
      - application/json
      responses:
        "200":
          description: OK
          schema:
            $ref: '#/definitions/example.LogoutResponse'
        "404":
          description: Not found
          schema:
            $ref: '#/definitions/example.NotFound'
      summary: Logout
      tags:
      - Auth
  /auth/refresh-tokens:
    post:
      consumes:
      - application/json
      parameters:
      - description: Request body
        in: body
        name: request
        required: true
        schema:
          $ref: '#/definitions/example.RefreshToken'
      produces:
      - application/json
      responses:
        "200":
          description: OK
          schema:
            $ref: '#/definitions/example.RefreshTokenResponse'
        "401":
          description: Unauthorized
          schema:
            $ref: '#/definitions/example.Unauthorized'
      summary: Refresh auth tokens
      tags:
      - Auth
  /auth/register:
    post:
      consumes:
      - application/json
      parameters:
      - description: Request body
        in: body
        name: request
        required: true
        schema:
          $ref: '#/definitions/validation.Register'
      produces:
      - application/json
      responses:
        "201":
          description: Created
          schema:
            $ref: '#/definitions/example.RegisterResponse'
        "409":
          description: Email already taken
          schema:
            $ref: '#/definitions/example.DuplicateEmail'
      summary: Register as user
      tags:
      - Auth
  /auth/reset-password:
    post:
      consumes:
      - application/json
      parameters:
      - description: The reset password token
        in: query
        name: token
        required: true
        type: string
      - description: Request body
        in: body
        name: request
        required: true
        schema:
          $ref: '#/definitions/validation.UpdatePassOrVerify'
      produces:
      - application/json
      responses:
        "200":
          description: OK
          schema:
            $ref: '#/definitions/example.ResetPasswordResponse'
        "401":
          description: Password reset failed
          schema:
            $ref: '#/definitions/example.FailedResetPassword'
      summary: Reset password
      tags:
      - Auth
  /auth/send-verification-email:
    post:
      description: An email will be sent to verify email.
      produces:
      - application/json
      responses:
        "200":
          description: OK
          schema:
            $ref: '#/definitions/example.SendVerificationEmailResponse'
        "401":
          description: Unauthorized
          schema:
            $ref: '#/definitions/example.Unauthorized'
      security:
      - BearerAuth: []
      summary: Send verification email
      tags:
      - Auth
  /auth/verify-email:
    post:
      parameters:
      - description: The verify email token
        in: query
        name: token
        required: true
        type: string
      produces:
      - application/json
      responses:
        "200":
          description: OK
          schema:
            $ref: '#/definitions/example.VerifyEmailResponse'
        "401":
          description: Verify email failed
          schema:
            $ref: '#/definitions/example.FailedVerifyEmail'
      summary: Verify email
      tags:
      - Auth
  /health-check:
    get:
      consumes:
      - application/json
      description: Check the status of services and database connections
      produces:
      - application/json
      responses:
        "200":
          description: OK
          schema:
            $ref: '#/definitions/example.HealthCheckResponse'
        "500":
          description: Internal Server Error
          schema:
            $ref: '#/definitions/example.HealthCheckResponseError'
      summary: Health Check
      tags:
      - Health
  /users:
    get:
      description: Only admins can retrieve all users.
      parameters:
      - default: 1
        description: Page number
        in: query
        name: page
        type: integer
      - default: 10
        description: Maximum number of users
        in: query
        name: limit
        type: integer
      - description: Search by name or email or role
        in: query
        name: search
        type: string
      produces:
      - application/json
      responses:
        "200":
          description: OK
          schema:
            $ref: '#/definitions/example.GetAllUserResponse'
        "401":
          description: Unauthorized
          schema:
            $ref: '#/definitions/example.Unauthorized'
        "403":
          description: Forbidden
          schema:
            $ref: '#/definitions/example.Forbidden'
      security:
      - BearerAuth: []
      summary: Get all users
      tags:
      - Users
    post:
      description: Only admins can create other users.
      parameters:
      - description: Request body
        in: body
        name: request
        required: true
        schema:
          $ref: '#/definitions/validation.CreateUser'
      produces:
      - application/json
      responses:
        "201":
          description: Created
          schema:
            $ref: '#/definitions/example.CreateUserResponse'
        "401":
          description: Unauthorized
          schema:
            $ref: '#/definitions/example.Unauthorized'
        "403":
          description: Forbidden
          schema:
            $ref: '#/definitions/example.Forbidden'
        "409":
          description: Email already taken
          schema:
            $ref: '#/definitions/example.DuplicateEmail'
      security:
      - BearerAuth: []
      summary: Create a user
      tags:
      - Users
  /users/{id}:
    delete:
      description: Logged in users can delete only themselves. Only admins can delete
        other users.
      parameters:
      - description: User id
        in: path
        name: id
        required: true
        type: string
      produces:
      - application/json
      responses:
        "200":
          description: OK
          schema:
            $ref: '#/definitions/example.DeleteUserResponse'
        "401":
          description: Unauthorized
          schema:
            $ref: '#/definitions/example.Unauthorized'
        "403":
          description: Forbidden
          schema:
            $ref: '#/definitions/example.Forbidden'
        "404":
          description: Not found
          schema:
            $ref: '#/definitions/example.NotFound'
      security:
      - BearerAuth: []
      summary: Delete a user
      tags:
      - Users
    get:
      description: Logged in users can fetch only their own user information. Only
        admins can fetch other users.
      parameters:
      - description: User id
        in: path
        name: id
        required: true
        type: string
      produces:
      - application/json
      responses:
        "200":
          description: OK
          schema:
            $ref: '#/definitions/example.GetUserResponse'
        "401":
          description: Unauthorized
          schema:
            $ref: '#/definitions/example.Unauthorized'
        "403":
          description: Forbidden
          schema:
            $ref: '#/definitions/example.Forbidden'
        "404":
          description: Not found
          schema:
            $ref: '#/definitions/example.NotFound'
      security:
      - BearerAuth: []
      summary: Get a user
      tags:
      - Users
    patch:
      description: Logged in users can only update their own information. Only admins
        can update other users.
      parameters:
      - description: User id
        in: path
        name: id
        required: true
        type: string
      - description: Request body
        in: body
        name: request
        required: true
        schema:
          $ref: '#/definitions/validation.UpdateUser'
      produces:
      - application/json
      responses:
        "200":
          description: OK
          schema:
            $ref: '#/definitions/example.UpdateUserResponse'
        "401":
          description: Unauthorized
          schema:
            $ref: '#/definitions/example.Unauthorized'
        "403":
          description: Forbidden
          schema:
            $ref: '#/definitions/example.Forbidden'
        "404":
          description: Not found
          schema:
            $ref: '#/definitions/example.NotFound'
        "409":
          description: Email already taken
          schema:
            $ref: '#/definitions/example.DuplicateEmail'
      security:
      - BearerAuth: []
      summary: Update a user
      tags:
      - Users
securityDefinitions:
  BearerAuth:
    description: 'Example Value: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
    in: header
    name: Authorization
    type: apiKey
swagger: "2.0"


================================================
FILE: src/main.go
================================================
package main

import (
	"app/src/config"
	"app/src/database"
	"app/src/middleware"
	"app/src/router"
	"app/src/utils"
	"context"
	"fmt"
	"os"
	"os/signal"
	"syscall"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/compress"
	"github.com/gofiber/fiber/v2/middleware/cors"
	"github.com/gofiber/fiber/v2/middleware/helmet"
	"gorm.io/gorm"
)

// @title go-fiber-boilerplate API documentation
// @version 1.3.1
// @license.name MIT
// @license.url https://github.com/indrayyana/go-fiber-boilerplate/blob/main/LICENSE
// @host localhost:3000
// @BasePath /v1
// @securityDefinitions.apikey BearerAuth
// @in header
// @name Authorization
// @description Example Value: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	app := setupFiberApp()
	db := setupDatabase()
	defer closeDatabase(db)
	setupRoutes(app, db)

	address := fmt.Sprintf("%s:%d", config.AppHost, config.AppPort)

	// Start server and handle graceful shutdown
	serverErrors := make(chan error, 1)
	go startServer(app, address, serverErrors)
	handleGracefulShutdown(ctx, app, serverErrors)
}

func setupFiberApp() *fiber.App {
	app := fiber.New(config.FiberConfig())

	// Middleware setup
	app.Use("/v1/auth", middleware.LimiterConfig())
	app.Use(middleware.LoggerConfig())
	app.Use(helmet.New())
	app.Use(compress.New())
	app.Use(cors.New())
	app.Use(middleware.RecoverConfig())

	return app
}

func setupDatabase() *gorm.DB {
	db := database.Connect(config.DBHost, config.DBName)
	// Add any additional database setup if needed
	return db
}

func setupRoutes(app *fiber.App, db *gorm.DB) {
	router.Routes(app, db)
	app.Use(utils.NotFoundHandler)
}

func startServer(app *fiber.App, address string, errs chan<- error) {
	if err := app.Listen(address); err != nil {
		errs <- fmt.Errorf("error starting server: %w", err)
	}
}

func closeDatabase(db *gorm.DB) {
	sqlDB, errDB := db.DB()
	if errDB != nil {
		utils.Log.Errorf("Error getting database instance: %v", errDB)
		return
	}

	if err := sqlDB.Close(); err != nil {
		utils.Log.Errorf("Error closing database connection: %v", err)
	} else {
		utils.Log.Info("Database connection closed successfully")
	}
}

func handleGracefulShutdown(ctx context.Context, app *fiber.App, serverErrors <-chan error) {
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, os.Interrupt, syscall.SIGTERM)

	select {
	case err := <-serverErrors:
		utils.Log.Fatalf("Server error: %v", err)
	case <-quit:
		utils.Log.Info("Shutting down server...")
		if err := app.Shutdown(); err != nil {
			utils.Log.Fatalf("Error during server shutdown: %v", err)
		}
	case <-ctx.Done():
		utils.Log.Info("Server exiting due to context cancellation")
	}

	utils.Log.Info("Server exited")
}


================================================
FILE: src/middleware/auth.go
================================================
package middleware

import (
	"app/src/config"
	"app/src/service"
	"app/src/utils"
	"strings"

	"github.com/gofiber/fiber/v2"
)

func Auth(userService service.UserService, requiredRights ...string) fiber.Handler {
	return func(c *fiber.Ctx) error {
		authHeader := c.Get("Authorization")
		token := strings.TrimSpace(strings.TrimPrefix(authHeader, "Bearer "))

		if token == "" {
			return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
		}

		userID, err := utils.VerifyToken(token, config.JWTSecret, config.TokenTypeAccess)
		if err != nil {
			return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
		}

		user, err := userService.GetUserByID(c, userID)
		if err != nil || user == nil {
			return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
		}

		c.Locals("user", user)

		if len(requiredRights) > 0 {
			userRights, hasRights := config.RoleRights[user.Role]
			if (!hasRights || !hasAllRights(userRights, requiredRights)) && c.Params("userId") != userID {
				return fiber.NewError(fiber.StatusForbidden, "You don't have permission to access this resource")
			}
		}

		return c.Next()
	}
}

func hasAllRights(userRights, requiredRights []string) bool {
	rightSet := make(map[string]struct{}, len(userRights))
	for _, right := range userRights {
		rightSet[right] = struct{}{}
	}

	for _, right := range requiredRights {
		if _, exists := ri
Download .txt
gitextract_mzrohxsq/

├── .air.toml
├── .env.example
├── .github/
│   └── workflows/
│       ├── build.yml
│       ├── linter.yml
│       └── test.yml
├── .gitignore
├── .golangci.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── docker-compose.yml
├── go.mod
├── go.sum
├── src/
│   ├── config/
│   │   ├── config.go
│   │   ├── fiber.go
│   │   ├── oauth2.go
│   │   ├── roles.go
│   │   └── tokens.go
│   ├── controller/
│   │   ├── auth_controller.go
│   │   ├── health_check_controller.go
│   │   └── user_controller.go
│   ├── database/
│   │   ├── database.go
│   │   ├── init/
│   │   │   └── init.sql
│   │   └── migrations/
│   │       ├── 20240929085103_create-table-users.down.sql
│   │       ├── 20240929085103_create-table-users.up.sql
│   │       ├── 20240929085107_create-table-tokens.down.sql
│   │       └── 20240929085107_create-table-tokens.up.sql
│   ├── docs/
│   │   ├── docs.go
│   │   ├── swagger.json
│   │   └── swagger.yaml
│   ├── main.go
│   ├── middleware/
│   │   ├── auth.go
│   │   ├── jwt.go
│   │   ├── limiter.go
│   │   ├── logger.go
│   │   └── recover.go
│   ├── model/
│   │   ├── token_model.go
│   │   └── user_model.go
│   ├── response/
│   │   ├── auth_response.go
│   │   ├── error_response.go
│   │   ├── example/
│   │   │   ├── error_example.go
│   │   │   ├── example.go
│   │   │   ├── health_check_example.go
│   │   │   ├── token_example.go
│   │   │   └── user_example.go
│   │   ├── health_check_response.go
│   │   ├── response.go
│   │   └── user_response.go
│   ├── router/
│   │   ├── auth_route.go
│   │   ├── docs_route.go
│   │   ├── health_check_route.go
│   │   ├── router.go
│   │   └── user_route.go
│   ├── service/
│   │   ├── auth_service.go
│   │   ├── email_service.go
│   │   ├── health_check_service.go
│   │   ├── token_service.go
│   │   └── user_service.go
│   ├── utils/
│   │   ├── bcrypt.go
│   │   ├── error.go
│   │   ├── logrus.go
│   │   └── verify.go
│   └── validation/
│       ├── auth_validation.go
│       ├── custom_validation.go
│       ├── user_validation.go
│       └── validation.go
└── test/
    ├── fixture/
    │   ├── token_fixture.go
    │   └── user_fixture.go
    ├── helper/
    │   └── helper.go
    ├── init.go
    ├── integration/
    │   ├── auth_test.go
    │   ├── health_check_test.go
    │   └── user_test.go
    └── unit/
        └── model/
            └── user_model_test.go
Download .txt
SYMBOL INDEX (191 symbols across 55 files)

FILE: src/config/config.go
  function init (line 33) | func init() {
  function loadConfig (line 68) | func loadConfig() {

FILE: src/config/fiber.go
  function FiberConfig (line 10) | func FiberConfig() fiber.Config {

FILE: src/config/oauth2.go
  type Config (line 8) | type Config struct
  function GoogleConfig (line 14) | func GoogleConfig() oauth2.Config {

FILE: src/config/roles.go
  function getKeys (line 11) | func getKeys(m map[string][]string) []string {

FILE: src/config/tokens.go
  constant TokenTypeAccess (line 4) | TokenTypeAccess        = "access"
  constant TokenTypeRefresh (line 5) | TokenTypeRefresh       = "refresh"
  constant TokenTypeResetPassword (line 6) | TokenTypeResetPassword = "resetPassword"
  constant TokenTypeVerifyEmail (line 7) | TokenTypeVerifyEmail   = "verifyEmail"

FILE: src/controller/auth_controller.go
  type AuthController (line 18) | type AuthController struct
    method Register (line 45) | func (a *AuthController) Register(c *fiber.Ctx) error {
    method Login (line 80) | func (a *AuthController) Login(c *fiber.Ctx) error {
    method Logout (line 115) | func (a *AuthController) Logout(c *fiber.Ctx) error {
    method RefreshTokens (line 142) | func (a *AuthController) RefreshTokens(c *fiber.Ctx) error {
    method ForgotPassword (line 171) | func (a *AuthController) ForgotPassword(c *fiber.Ctx) error {
    method ResetPassword (line 204) | func (a *AuthController) ResetPassword(c *fiber.Ctx) error {
    method SendVerificationEmail (line 234) | func (a *AuthController) SendVerificationEmail(c *fiber.Ctx) error {
    method VerifyEmail (line 261) | func (a *AuthController) VerifyEmail(c *fiber.Ctx) error {
    method GoogleLogin (line 283) | func (a *AuthController) GoogleLogin(c *fiber.Ctx) error {
    method GoogleCallback (line 298) | func (a *AuthController) GoogleCallback(c *fiber.Ctx) error {
  function NewAuthController (line 25) | func NewAuthController(

FILE: src/controller/health_check_controller.go
  type HealthCheckController (line 10) | type HealthCheckController struct
    method addServiceStatus (line 20) | func (h *HealthCheckController) addServiceStatus(
    method Check (line 45) | func (h *HealthCheckController) Check(c *fiber.Ctx) error {
  function NewHealthCheckController (line 14) | func NewHealthCheckController(healthCheckService service.HealthCheckServ...

FILE: src/controller/user_controller.go
  type UserController (line 14) | type UserController struct
    method GetUsers (line 38) | func (u *UserController) GetUsers(c *fiber.Ctx) error {
    method GetUserByID (line 74) | func (u *UserController) GetUserByID(c *fiber.Ctx) error {
    method CreateUser (line 106) | func (u *UserController) CreateUser(c *fiber.Ctx) error {
    method UpdateUser (line 140) | func (u *UserController) UpdateUser(c *fiber.Ctx) error {
    method DeleteUser (line 177) | func (u *UserController) DeleteUser(c *fiber.Ctx) error {
  function NewUserController (line 19) | func NewUserController(userService service.UserService, tokenService ser...

FILE: src/database/database.go
  function Connect (line 14) | func Connect(dbHost, dbName string) *gorm.DB {

FILE: src/database/migrations/20240929085103_create-table-users.up.sql
  type users (line 1) | CREATE TABLE users(

FILE: src/database/migrations/20240929085107_create-table-tokens.up.sql
  type tokens (line 1) | CREATE TABLE tokens(

FILE: src/docs/docs.go
  constant docTemplate (line 6) | docTemplate = `{
  function init (line 1387) | func init() {

FILE: src/main.go
  function main (line 32) | func main() {
  function setupFiberApp (line 49) | func setupFiberApp() *fiber.App {
  function setupDatabase (line 63) | func setupDatabase() *gorm.DB {
  function setupRoutes (line 69) | func setupRoutes(app *fiber.App, db *gorm.DB) {
  function startServer (line 74) | func startServer(app *fiber.App, address string, errs chan<- error) {
  function closeDatabase (line 80) | func closeDatabase(db *gorm.DB) {
  function handleGracefulShutdown (line 94) | func handleGracefulShutdown(ctx context.Context, app *fiber.App, serverE...

FILE: src/middleware/auth.go
  function Auth (line 12) | func Auth(userService service.UserService, requiredRights ...string) fib...
  function hasAllRights (line 44) | func hasAllRights(userRights, requiredRights []string) bool {

FILE: src/middleware/jwt.go
  function JwtConfig (line 8) | func JwtConfig() fiber.Handler {

FILE: src/middleware/limiter.go
  function LimiterConfig (line 11) | func LimiterConfig() fiber.Handler {

FILE: src/middleware/logger.go
  function LoggerConfig (line 8) | func LoggerConfig() fiber.Handler {

FILE: src/middleware/recover.go
  function RecoverConfig (line 8) | func RecoverConfig() fiber.Handler {

FILE: src/model/token_model.go
  type Token (line 10) | type Token struct
    method BeforeCreate (line 21) | func (token *Token) BeforeCreate(_ *gorm.DB) error {

FILE: src/model/user_model.go
  type User (line 10) | type User struct
    method BeforeCreate (line 22) | func (user *User) BeforeCreate(_ *gorm.DB) error {

FILE: src/response/auth_response.go
  type Tokens (line 5) | type Tokens struct
  type TokenExpires (line 10) | type TokenExpires struct
  type RefreshToken (line 15) | type RefreshToken struct

FILE: src/response/error_response.go
  function Error (line 8) | func Error(c *fiber.Ctx, statusCode int, message string, details interfa...

FILE: src/response/example/error_example.go
  type Unauthorized (line 3) | type Unauthorized struct
  type FailedLogin (line 9) | type FailedLogin struct
  type FailedResetPassword (line 15) | type FailedResetPassword struct
  type FailedVerifyEmail (line 21) | type FailedVerifyEmail struct
  type Forbidden (line 27) | type Forbidden struct
  type NotFound (line 33) | type NotFound struct
  type DuplicateEmail (line 39) | type DuplicateEmail struct

FILE: src/response/example/example.go
  type RegisterResponse (line 3) | type RegisterResponse struct
  type LoginResponse (line 11) | type LoginResponse struct
  type GoogleLoginResponse (line 19) | type GoogleLoginResponse struct
  type LogoutResponse (line 27) | type LogoutResponse struct
  type RefreshTokenResponse (line 33) | type RefreshTokenResponse struct
  type ForgotPasswordResponse (line 39) | type ForgotPasswordResponse struct
  type ResetPasswordResponse (line 45) | type ResetPasswordResponse struct
  type SendVerificationEmailResponse (line 51) | type SendVerificationEmailResponse struct
  type VerifyEmailResponse (line 57) | type VerifyEmailResponse struct
  type GetAllUserResponse (line 63) | type GetAllUserResponse struct
  type GetUserResponse (line 74) | type GetUserResponse struct
  type CreateUserResponse (line 81) | type CreateUserResponse struct
  type UpdateUserResponse (line 88) | type UpdateUserResponse struct
  type DeleteUserResponse (line 95) | type DeleteUserResponse struct

FILE: src/response/example/health_check_example.go
  type HealthCheck (line 3) | type HealthCheck struct
  type HealthCheckResponse (line 9) | type HealthCheckResponse struct
  type HealthCheckError (line 17) | type HealthCheckError struct
  type HealthCheckResponseError (line 24) | type HealthCheckResponseError struct

FILE: src/response/example/token_example.go
  type Tokens (line 5) | type Tokens struct
  type TokenExpires (line 10) | type TokenExpires struct
  type RefreshToken (line 15) | type RefreshToken struct

FILE: src/response/example/user_example.go
  type User (line 5) | type User struct
  type GoogleUser (line 13) | type GoogleUser struct

FILE: src/response/health_check_response.go
  type HealthCheck (line 3) | type HealthCheck struct
  type HealthCheckResponse (line 10) | type HealthCheckResponse struct

FILE: src/response/response.go
  type Common (line 5) | type Common struct
  type SuccessWithUser (line 11) | type SuccessWithUser struct
  type SuccessWithTokens (line 18) | type SuccessWithTokens struct
  type SuccessWithPaginate (line 26) | type SuccessWithPaginate struct
  type ErrorDetails (line 37) | type ErrorDetails struct

FILE: src/response/user_response.go
  type CreateUser (line 5) | type CreateUser struct
  type GetUsers (line 12) | type GetUsers struct

FILE: src/router/auth_route.go
  function AuthRoutes (line 12) | func AuthRoutes(

FILE: src/router/docs_route.go
  function DocsRoutes (line 11) | func DocsRoutes(v1 fiber.Router) {

FILE: src/router/health_check_route.go
  function HealthCheckRoutes (line 10) | func HealthCheckRoutes(v1 fiber.Router, h service.HealthCheckService) {

FILE: src/router/router.go
  function Routes (line 12) | func Routes(app *fiber.App, db *gorm.DB) {

FILE: src/router/user_route.go
  function UserRoutes (line 11) | func UserRoutes(v1 fiber.Router, u service.UserService, t service.TokenS...

FILE: src/service/auth_service.go
  type AuthService (line 17) | type AuthService interface
  type authService (line 26) | type authService struct
    method Register (line 46) | func (s *authService) Register(c *fiber.Ctx, req *validation.Register)...
    method Login (line 75) | func (s *authService) Login(c *fiber.Ctx, req *validation.Login) (*mod...
    method Logout (line 92) | func (s *authService) Logout(c *fiber.Ctx, req *validation.Logout) err...
    method RefreshAuth (line 107) | func (s *authService) RefreshAuth(c *fiber.Ctx, req *validation.Refres...
    method ResetPassword (line 130) | func (s *authService) ResetPassword(c *fiber.Ctx, query *validation.To...
    method VerifyEmail (line 156) | func (s *authService) VerifyEmail(c *fiber.Ctx, query *validation.Toke...
  function NewAuthService (line 34) | func NewAuthService(

FILE: src/service/email_service.go
  type EmailService (line 12) | type EmailService interface
  type emailService (line 18) | type emailService struct
    method SendEmail (line 35) | func (s *emailService) SendEmail(to, subject, body string) error {
    method SendResetPasswordEmail (line 50) | func (s *emailService) SendResetPasswordEmail(to, token string) error {
    method SendVerificationEmail (line 63) | func (s *emailService) SendVerificationEmail(to, token string) error {
  function NewEmailService (line 23) | func NewEmailService() EmailService {

FILE: src/service/health_check_service.go
  type HealthCheckService (line 12) | type HealthCheckService interface
  type healthCheckService (line 17) | type healthCheckService struct
    method GormCheck (line 29) | func (s *healthCheckService) GormCheck() error {
    method MemoryHeapCheck (line 45) | func (s *healthCheckService) MemoryHeapCheck() error {
  function NewHealthCheckService (line 22) | func NewHealthCheckService(db *gorm.DB) HealthCheckService {

FILE: src/service/token_service.go
  type TokenService (line 19) | type TokenService interface
  type tokenService (line 30) | type tokenService struct
    method GenerateToken (line 46) | func (s *tokenService) GenerateToken(userID string, expires time.Time,...
    method SaveToken (line 58) | func (s *tokenService) SaveToken(c *fiber.Ctx, token, userID, tokenTyp...
    method DeleteToken (line 79) | func (s *tokenService) DeleteToken(c *fiber.Ctx, tokenType string, use...
    method DeleteAllToken (line 93) | func (s *tokenService) DeleteAllToken(c *fiber.Ctx, userID string) err...
    method GetTokenByUserID (line 105) | func (s *tokenService) GetTokenByUserID(c *fiber.Ctx, tokenStr string)...
    method GenerateAuthTokens (line 125) | func (s *tokenService) GenerateAuthTokens(c *fiber.Ctx, user *model.Us...
    method GenerateResetPasswordToken (line 156) | func (s *tokenService) GenerateResetPasswordToken(c *fiber.Ctx, req *v...
    method GenerateVerifyEmailToken (line 180) | func (s *tokenService) GenerateVerifyEmailToken(c *fiber.Ctx, user *mo...
  function NewTokenService (line 37) | func NewTokenService(db *gorm.DB, validate *validator.Validate, userServ...

FILE: src/service/user_service.go
  type UserService (line 15) | type UserService interface
  type userService (line 26) | type userService struct
    method GetUsers (line 40) | func (s *userService) GetUsers(c *fiber.Ctx, params *validation.QueryU...
    method GetUserByID (line 71) | func (s *userService) GetUserByID(c *fiber.Ctx, id string) (*model.Use...
    method GetUserByEmail (line 87) | func (s *userService) GetUserByEmail(c *fiber.Ctx, email string) (*mod...
    method CreateUser (line 103) | func (s *userService) CreateUser(c *fiber.Ctx, req *validation.CreateU...
    method UpdateUser (line 134) | func (s *userService) UpdateUser(c *fiber.Ctx, req *validation.UpdateU...
    method UpdatePassOrVerify (line 179) | func (s *userService) UpdatePassOrVerify(c *fiber.Ctx, req *validation...
    method DeleteUser (line 214) | func (s *userService) DeleteUser(c *fiber.Ctx, id string) error {
    method CreateGoogleUser (line 230) | func (s *userService) CreateGoogleUser(c *fiber.Ctx, req *validation.G...
  function NewUserService (line 32) | func NewUserService(db *gorm.DB, validate *validator.Validate) UserServi...

FILE: src/utils/bcrypt.go
  function HashPassword (line 5) | func HashPassword(password string) (string, error) {
  function CheckPasswordHash (line 10) | func CheckPasswordHash(password, hash string) bool {

FILE: src/utils/error.go
  function ErrorHandler (line 11) | func ErrorHandler(c *fiber.Ctx, err error) error {
  function NotFoundHandler (line 24) | func NotFoundHandler(c *fiber.Ctx) error {

FILE: src/utils/logrus.go
  type CustomFormatter (line 9) | type CustomFormatter struct
  function init (line 15) | func init() {

FILE: src/utils/verify.go
  function VerifyToken (line 10) | func VerifyToken(tokenStr, secret, tokenType string) (string, error) {

FILE: src/validation/auth_validation.go
  type Register (line 3) | type Register struct
  type Login (line 9) | type Login struct
  type GoogleLogin (line 14) | type GoogleLogin struct
  type Logout (line 20) | type Logout struct
  type RefreshToken (line 24) | type RefreshToken struct
  type ForgotPassword (line 28) | type ForgotPassword struct
  type Token (line 32) | type Token struct

FILE: src/validation/custom_validation.go
  function Password (line 9) | func Password(field validator.FieldLevel) bool {

FILE: src/validation/user_validation.go
  type CreateUser (line 3) | type CreateUser struct
  type UpdateUser (line 10) | type UpdateUser struct
  type UpdatePassOrVerify (line 16) | type UpdatePassOrVerify struct
  type QueryUser (line 21) | type QueryUser struct

FILE: src/validation/validation.go
  function CustomErrorMessages (line 23) | func CustomErrorMessages(err error) map[string]string {
  function generateErrorMessages (line 31) | func generateErrorMessages(validationErrors validator.ValidationErrors) ...
  function formatErrorMessage (line 47) | func formatErrorMessage(customMessage string, err validator.FieldError, ...
  function defaultErrorMessage (line 54) | func defaultErrorMessage(err validator.FieldError) string {
  function Validator (line 58) | func Validator() *validator.Validate {

FILE: test/fixture/token_fixture.go
  function AccessToken (line 15) | func AccessToken(user *model.User) (string, error) {
  function RefreshToken (line 23) | func RefreshToken(user *model.User) (string, error) {
  function ResetPasswordToken (line 31) | func ResetPasswordToken(user *model.User) (string, error) {
  function VerifyEmailToken (line 41) | func VerifyEmailToken(user *model.User) (string, error) {

FILE: test/helper/helper.go
  function ClearAll (line 16) | func ClearAll(db *gorm.DB) {
  function ClearUsers (line 21) | func ClearUsers(db *gorm.DB) {
  function ClearToken (line 28) | func ClearToken(db *gorm.DB) {
  function CreateUser (line 35) | func CreateUser(db *gorm.DB, email, password, name string) {
  function InsertUser (line 53) | func InsertUser(db *gorm.DB, users ...*model.User) {
  function SaveToken (line 71) | func SaveToken(db *gorm.DB, token, userID, tokenType string, expires tim...
  function DeleteToken (line 88) | func DeleteToken(db *gorm.DB, tokenType, userID string) error {
  function GenerateToken (line 96) | func GenerateToken(
  function GenerateInvalidToken (line 110) | func GenerateInvalidToken(
  function GetTokenByUserID (line 124) | func GetTokenByUserID(db *gorm.DB, tokenStr string) (*model.Token, error) {
  function GetTokenByType (line 141) | func GetTokenByType(db *gorm.DB, userID string, tokenType string) (*mode...
  function GetUserByID (line 153) | func GetUserByID(db *gorm.DB, id string) (*model.User, error) {

FILE: test/init.go
  function init (line 19) | func init() {

FILE: test/integration/auth_test.go
  function TestAuthRoutes (line 23) | func TestAuthRoutes(t *testing.T) {
  function TestAuthMiddleware (line 736) | func TestAuthMiddleware(t *testing.T) {

FILE: test/integration/health_check_test.go
  function TestHealthCheckRoutes (line 15) | func TestHealthCheckRoutes(t *testing.T) {

FILE: test/integration/user_test.go
  function TestUserRoutes (line 20) | func TestUserRoutes(t *testing.T) {

FILE: test/unit/model/user_model_test.go
  function TestUserModel (line 14) | func TestUserModel(t *testing.T) {
Condensed preview — 77 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (345K chars).
[
  {
    "path": ".air.toml",
    "chars": 2336,
    "preview": "# Config file for [Air](https://github.com/air-verse/air) in TOML format\n\n# Working directory\n# . or absolute path, plea"
  },
  {
    "path": ".env.example",
    "chars": 1015,
    "preview": "# server configuration\n# Env value : prod || dev\nAPP_ENV=dev\nAPP_HOST=0.0.0.0\nAPP_PORT=3000\nAPP_URL=http://localhost:300"
  },
  {
    "path": ".github/workflows/build.yml",
    "chars": 1537,
    "preview": "name: Build\n\non:\n  push:\n    branches: [\"main\"]\n  pull_request:\n    branches: [\"main\"]\n\nenv:\n  APP_ENV: dev\n  APP_HOST: "
  },
  {
    "path": ".github/workflows/linter.yml",
    "chars": 366,
    "preview": "name: Linter\n\non:\n  push:\n    branches: [\"main\"]\n  pull_request:\n    branches: [\"main\"]\n\njobs:\n  Golint:\n    runs-on: ub"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 444,
    "preview": "name: Test\n\non:\n  push:\n    branches: [\"main\"]\n  pull_request:\n    branches: [\"main\"]\n\njobs:\n  Tests:\n    runs-on: ubunt"
  },
  {
    "path": ".gitignore",
    "chars": 95,
    "preview": "# Environment varibales\n.env*\n!.env*.example\n\n# Temporary\ntmp/\n\nlint.txt\nmain\nbin/golangci-lint"
  },
  {
    "path": ".golangci.yml",
    "chars": 4664,
    "preview": "version: \"2\"\nlinters:\n  default: none\n  enable:\n    - asasalint\n    - asciicheck\n    - bidichk\n    - bodyclose\n    - can"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 5221,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1272,
    "preview": "# Contributing\n\nFirst off, thank you so much for taking the time to contribute. All contributions are more than welcome!"
  },
  {
    "path": "Dockerfile",
    "chars": 298,
    "preview": "FROM golang:1.25 AS build\n\nWORKDIR /app\nCOPY . .\nRUN go clean --modcache\nRUN go mod tidy\nRUN CGO_ENABLED=0 GOOS=linux go"
  },
  {
    "path": "LICENSE",
    "chars": 1076,
    "preview": "MIT License\n\nCopyright (c) 2024 I Gede Indra Adnyana\n\nPermission is hereby granted, free of charge, to any person obtain"
  },
  {
    "path": "Makefile",
    "chars": 1432,
    "preview": "include .env\nexport $(shell sed 's/=.*//' .env)\n\nstart:\n\t@go run src/main.go\nlint:\n\t@golangci-lint run\ntests:\n\t@go test "
  },
  {
    "path": "README.md",
    "chars": 13937,
    "preview": "# RESTful API Go Fiber Boilerplate\n\n![Go Version](https://img.shields.io/badge/Go-1.22+-00ADD8?style=flat&logo=go)\n[![Go"
  },
  {
    "path": "docker-compose.yml",
    "chars": 1086,
    "preview": "services:\n  adminer:\n    image: adminer\n    restart: always\n    ports:\n      - 8080:8080\n    networks:\n      - go-networ"
  },
  {
    "path": "go.mod",
    "chars": 3664,
    "preview": "module app\n\ngo 1.24.0\n\nrequire (\n\tgithub.com/bytedance/sonic v1.14.2\n\tgithub.com/go-playground/validator/v10 v10.29.0\n\tg"
  },
  {
    "path": "go.sum",
    "chars": 17064,
    "preview": "cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=\ncloud.google.com/go/compute/"
  },
  {
    "path": "src/config/config.go",
    "chars": 2138,
    "preview": "package config\n\nimport (\n\t\"app/src/utils\"\n\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tIsProd              bool\n\tAppHost        "
  },
  {
    "path": "src/config/fiber.go",
    "chars": 375,
    "preview": "package config\n\nimport (\n\t\"app/src/utils\"\n\n\t\"github.com/bytedance/sonic\"\n\t\"github.com/gofiber/fiber/v2\"\n)\n\nfunc FiberCon"
  },
  {
    "path": "src/config/oauth2.go",
    "chars": 543,
    "preview": "package config\n\nimport (\n\t\"golang.org/x/oauth2\"\n\t\"golang.org/x/oauth2/google\"\n)\n\ntype Config struct {\n\tGoogleLoginConfig"
  },
  {
    "path": "src/config/roles.go",
    "chars": 310,
    "preview": "package config\n\nvar allRoles = map[string][]string{\n\t\"user\":  {},\n\t\"admin\": {\"getUsers\", \"manageUsers\"},\n}\n\nvar Roles = "
  },
  {
    "path": "src/config/tokens.go",
    "chars": 179,
    "preview": "package config\n\nconst (\n\tTokenTypeAccess        = \"access\"\n\tTokenTypeRefresh       = \"refresh\"\n\tTokenTypeResetPassword ="
  },
  {
    "path": "src/controller/auth_controller.go",
    "chars": 9920,
    "preview": "package controller\n\nimport (\n\t\"app/src/config\"\n\t\"app/src/model\"\n\t\"app/src/response\"\n\t\"app/src/service\"\n\t\"app/src/validat"
  },
  {
    "path": "src/controller/health_check_controller.go",
    "chars": 2005,
    "preview": "package controller\n\nimport (\n\t\"app/src/response\"\n\t\"app/src/service\"\n\n\t\"github.com/gofiber/fiber/v2\"\n)\n\ntype HealthCheckC"
  },
  {
    "path": "src/controller/user_controller.go",
    "chars": 6228,
    "preview": "package controller\n\nimport (\n\t\"app/src/model\"\n\t\"app/src/response\"\n\t\"app/src/service\"\n\t\"app/src/validation\"\n\t\"math\"\n\n\t\"gi"
  },
  {
    "path": "src/database/database.go",
    "chars": 933,
    "preview": "package database\n\nimport (\n\t\"app/src/config\"\n\t\"app/src/utils\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"gorm.io/driver/postgres\"\n\t\"gorm.io/gorm\""
  },
  {
    "path": "src/database/init/init.sql",
    "chars": 68,
    "preview": "CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";\nCREATE DATABASE testdb;\n"
  },
  {
    "path": "src/database/migrations/20240929085103_create-table-users.down.sql",
    "chars": 27,
    "preview": "DROP TABLE IF EXISTS users;"
  },
  {
    "path": "src/database/migrations/20240929085103_create-table-users.up.sql",
    "chars": 496,
    "preview": "CREATE TABLE users(\n    id              UUID            PRIMARY KEY DEFAULT uuid_generate_v4(),\n    name            VARC"
  },
  {
    "path": "src/database/migrations/20240929085107_create-table-tokens.down.sql",
    "chars": 28,
    "preview": "DROP TABLE IF EXISTS tokens;"
  },
  {
    "path": "src/database/migrations/20240929085107_create-table-tokens.up.sql",
    "chars": 549,
    "preview": "CREATE TABLE tokens(\n    id              UUID            PRIMARY KEY DEFAULT uuid_generate_v4(),\n    token           VAR"
  },
  {
    "path": "src/docs/docs.go",
    "chars": 45195,
    "preview": "// Package docs Code generated by swaggo/swag. DO NOT EDIT\npackage docs\n\nimport \"github.com/swaggo/swag\"\n\nconst docTempl"
  },
  {
    "path": "src/docs/swagger.json",
    "chars": 44501,
    "preview": "{\n    \"swagger\": \"2.0\",\n    \"info\": {\n        \"title\": \"go-fiber-boilerplate API documentation\",\n        \"contact\": {},\n"
  },
  {
    "path": "src/docs/swagger.yaml",
    "chars": 22245,
    "preview": "basePath: /v1\ndefinitions:\n  example.CreateUserResponse:\n    properties:\n      code:\n        example: 201\n        type: "
  },
  {
    "path": "src/main.go",
    "chars": 2781,
    "preview": "package main\n\nimport (\n\t\"app/src/config\"\n\t\"app/src/database\"\n\t\"app/src/middleware\"\n\t\"app/src/router\"\n\t\"app/src/utils\"\n\t\""
  },
  {
    "path": "src/middleware/auth.go",
    "chars": 1462,
    "preview": "package middleware\n\nimport (\n\t\"app/src/config\"\n\t\"app/src/service\"\n\t\"app/src/utils\"\n\t\"strings\"\n\n\t\"github.com/gofiber/fibe"
  },
  {
    "path": "src/middleware/jwt.go",
    "chars": 237,
    "preview": "package middleware\n\nimport (\n\tjwtware \"github.com/gofiber/contrib/jwt\"\n\t\"github.com/gofiber/fiber/v2\"\n)\n\nfunc JwtConfig("
  },
  {
    "path": "src/middleware/limiter.go",
    "chars": 558,
    "preview": "package middleware\n\nimport (\n\t\"app/src/response\"\n\t\"time\"\n\n\t\"github.com/gofiber/fiber/v2\"\n\t\"github.com/gofiber/fiber/v2/m"
  },
  {
    "path": "src/middleware/logger.go",
    "chars": 286,
    "preview": "package middleware\n\nimport (\n\t\"github.com/gofiber/fiber/v2\"\n\t\"github.com/gofiber/fiber/v2/middleware/logger\"\n)\n\nfunc Log"
  },
  {
    "path": "src/middleware/recover.go",
    "chars": 218,
    "preview": "package middleware\n\nimport (\n\t\"github.com/gofiber/fiber/v2\"\n\t\"github.com/gofiber/fiber/v2/middleware/recover\"\n)\n\nfunc Re"
  },
  {
    "path": "src/model/token_model.go",
    "chars": 583,
    "preview": "package model\n\nimport (\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n\t\"gorm.io/gorm\"\n)\n\ntype Token struct {\n\tID        uuid.UUID `"
  },
  {
    "path": "src/model/user_model.go",
    "chars": 829,
    "preview": "package model\n\nimport (\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n\t\"gorm.io/gorm\"\n)\n\ntype User struct {\n\tID            uuid.UUI"
  },
  {
    "path": "src/response/auth_response.go",
    "chars": 354,
    "preview": "package response\n\nimport \"time\"\n\ntype Tokens struct {\n\tAccess  TokenExpires `json:\"access\"`\n\tRefresh TokenExpires `json:"
  },
  {
    "path": "src/response/error_response.go",
    "chars": 593,
    "preview": "package response\n\nimport (\n\t\"github.com/gofiber/fiber/v2\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc Error(c *fiber.Ctx, stat"
  },
  {
    "path": "src/response/example/error_example.go",
    "chars": 1345,
    "preview": "package example\n\ntype Unauthorized struct {\n\tCode    int    `json:\"code\" example:\"401\"`\n\tStatus  string `json:\"status\" e"
  },
  {
    "path": "src/response/example/example.go",
    "chars": 3363,
    "preview": "package example\n\ntype RegisterResponse struct {\n\tCode    int    `json:\"code\" example:\"201\"`\n\tStatus  string `json:\"statu"
  },
  {
    "path": "src/response/example/health_check_example.go",
    "chars": 1238,
    "preview": "package example\n\ntype HealthCheck struct {\n\tName   string `json:\"name\" example:\"Postgre\"`\n\tStatus string `json:\"status\" "
  },
  {
    "path": "src/response/example/token_example.go",
    "chars": 713,
    "preview": "package example\n\nimport \"time\"\n\ntype Tokens struct {\n\tAccess  TokenExpires `json:\"access\"`\n\tRefresh TokenExpires `json:\""
  },
  {
    "path": "src/response/example/user_example.go",
    "chars": 756,
    "preview": "package example\n\nimport \"github.com/google/uuid\"\n\ntype User struct {\n\tID            uuid.UUID `json:\"id\" example:\"e088d1"
  },
  {
    "path": "src/response/health_check_response.go",
    "chars": 431,
    "preview": "package response\n\ntype HealthCheck struct {\n\tName    string  `json:\"name\"`\n\tStatus  string  `json:\"status\"`\n\tIsUp    boo"
  },
  {
    "path": "src/response/response.go",
    "chars": 1072,
    "preview": "package response\n\nimport \"app/src/model\"\n\ntype Common struct {\n\tCode    int    `json:\"code\"`\n\tStatus  string `json:\"stat"
  },
  {
    "path": "src/response/user_response.go",
    "chars": 487,
    "preview": "package response\n\nimport \"github.com/google/uuid\"\n\ntype CreateUser struct {\n\tName            string `json:\"name\"`\n\tEmail"
  },
  {
    "path": "src/router/auth_route.go",
    "chars": 964,
    "preview": "package router\n\nimport (\n\t\"app/src/config\"\n\t\"app/src/controller\"\n\tm \"app/src/middleware\"\n\t\"app/src/service\"\n\n\t\"github.co"
  },
  {
    "path": "src/router/docs_route.go",
    "chars": 254,
    "preview": "package router\n\nimport (\n\t// initialize the Swagger documentation\n\t_ \"app/src/docs\"\n\n\t\"github.com/gofiber/fiber/v2\"\n\t\"gi"
  },
  {
    "path": "src/router/health_check_route.go",
    "chars": 334,
    "preview": "package router\n\nimport (\n\t\"app/src/controller\"\n\t\"app/src/service\"\n\n\t\"github.com/gofiber/fiber/v2\"\n)\n\nfunc HealthCheckRou"
  },
  {
    "path": "src/router/router.go",
    "chars": 779,
    "preview": "package router\n\nimport (\n\t\"app/src/config\"\n\t\"app/src/service\"\n\t\"app/src/validation\"\n\n\t\"github.com/gofiber/fiber/v2\"\n\t\"go"
  },
  {
    "path": "src/router/user_route.go",
    "chars": 653,
    "preview": "package router\n\nimport (\n\t\"app/src/controller\"\n\tm \"app/src/middleware\"\n\t\"app/src/service\"\n\n\t\"github.com/gofiber/fiber/v2"
  },
  {
    "path": "src/service/auth_service.go",
    "chars": 5034,
    "preview": "package service\n\nimport (\n\t\"app/src/config\"\n\t\"app/src/model\"\n\t\"app/src/response\"\n\t\"app/src/utils\"\n\t\"app/src/validation\"\n"
  },
  {
    "path": "src/service/email_service.go",
    "chars": 1936,
    "preview": "package service\n\nimport (\n\t\"app/src/config\"\n\t\"app/src/utils\"\n\t\"fmt\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"gopkg.in/gomail.v2\""
  },
  {
    "path": "src/service/health_check_service.go",
    "chars": 1377,
    "preview": "package service\n\nimport (\n\t\"app/src/utils\"\n\t\"errors\"\n\t\"runtime\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"gorm.io/gorm\"\n)\n\ntype H"
  },
  {
    "path": "src/service/token_service.go",
    "chars": 5601,
    "preview": "package service\n\nimport (\n\t\"app/src/config\"\n\t\"app/src/model\"\n\tres \"app/src/response\"\n\t\"app/src/utils\"\n\t\"app/src/validati"
  },
  {
    "path": "src/service/user_service.go",
    "chars": 6826,
    "preview": "package service\n\nimport (\n\t\"app/src/model\"\n\t\"app/src/utils\"\n\t\"app/src/validation\"\n\t\"errors\"\n\n\t\"github.com/go-playground/"
  },
  {
    "path": "src/utils/bcrypt.go",
    "chars": 360,
    "preview": "package utils\n\nimport \"golang.org/x/crypto/bcrypt\"\n\nfunc HashPassword(password string) (string, error) {\n\tbytes, err := "
  },
  {
    "path": "src/utils/error.go",
    "chars": 658,
    "preview": "package utils\n\nimport (\n\t\"app/src/response\"\n\t\"app/src/validation\"\n\t\"errors\"\n\n\t\"github.com/gofiber/fiber/v2\"\n)\n\nfunc Erro"
  },
  {
    "path": "src/utils/logrus.go",
    "chars": 427,
    "preview": "package utils\n\nimport (\n\t\"os\"\n\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype CustomFormatter struct {\n\tlogrus.TextFormatter\n}\n\nv"
  },
  {
    "path": "src/utils/verify.go",
    "chars": 683,
    "preview": "//revive:disable:var-naming\npackage utils\n\nimport (\n\t\"errors\"\n\n\t\"github.com/golang-jwt/jwt/v5\"\n)\n\nfunc VerifyToken(token"
  },
  {
    "path": "src/validation/auth_validation.go",
    "chars": 1151,
    "preview": "package validation\n\ntype Register struct {\n\tName     string `json:\"name\" validate:\"required,max=50\" example:\"fake name\"`"
  },
  {
    "path": "src/validation/custom_validation.go",
    "chars": 389,
    "preview": "package validation\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/go-playground/validator/v10\"\n)\n\nfunc Password(field validator.Field"
  },
  {
    "path": "src/validation/user_validation.go",
    "chars": 1143,
    "preview": "package validation\n\ntype CreateUser struct {\n\tName     string `json:\"name\" validate:\"required,max=50\" example:\"fake name"
  },
  {
    "path": "src/validation/validation.go",
    "chars": 1960,
    "preview": "package validation\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/go-playground/validator/v10\"\n)\n\nvar customMessages = map[str"
  },
  {
    "path": "test/fixture/token_fixture.go",
    "chars": 1512,
    "preview": "package fixture\n\nimport (\n\t\"app/src/config\"\n\t\"app/src/model\"\n\t\"app/test/helper\"\n\t\"time\"\n)\n\nvar ExpiresAccessToken = time"
  },
  {
    "path": "test/fixture/user_fixture.go",
    "chars": 653,
    "preview": "package fixture\n\nimport (\n\t\"app/src/model\"\n\n\t\"github.com/google/uuid\"\n)\n\nvar UserOne = &model.User{\n\tID:            uuid"
  },
  {
    "path": "test/helper/helper.go",
    "chars": 3696,
    "preview": "package helper\n\nimport (\n\t\"app/src/config\"\n\t\"app/src/model\"\n\t\"app/src/utils\"\n\t\"errors\"\n\t\"time\"\n\n\t\"github.com/golang-jwt/"
  },
  {
    "path": "test/init.go",
    "chars": 446,
    "preview": "package test\n\nimport (\n\t\"app/src/database\"\n\t\"app/src/router\"\n\t\"app/src/utils\"\n\n\t\"github.com/gofiber/fiber/v2\"\n\t\"gorm.io/"
  },
  {
    "path": "test/integration/auth_test.go",
    "chars": 31802,
    "preview": "package integration\n\nimport (\n\t\"app/src/config\"\n\t\"app/src/response\"\n\t\"app/src/utils\"\n\t\"app/src/validation\"\n\t\"app/test\"\n\t"
  },
  {
    "path": "test/integration/health_check_test.go",
    "chars": 2357,
    "preview": "package integration\n\nimport (\n\t\"app/src/response\"\n\t\"app/test\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\""
  },
  {
    "path": "test/integration/user_test.go",
    "chars": 32029,
    "preview": "package integration\n\nimport (\n\t\"app/src/model\"\n\t\"app/src/response\"\n\t\"app/src/validation\"\n\t\"app/test\"\n\t\"app/test/fixture\""
  },
  {
    "path": "test/unit/model/user_model_test.go",
    "chars": 3959,
    "preview": "package model_test\n\nimport (\n\t\"app/src/model\"\n\t\"app/src/validation\"\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/stretchr/t"
  }
]

About this extraction

This page contains the full source code of the indrayyana/go-fiber-boilerplate GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 77 files (308.1 KB), approximately 79.6k tokens, and a symbol index with 191 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!