lifedebugger commited on
Commit
92bfa2a
·
1 Parent(s): a389a2e

Deploy files from GitHub repository

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .github/workflows/deploy-development.yml +32 -0
  2. .github/workflows/deploy-production.yml +32 -0
  3. .github/workflows/deploy-test.yml +49 -0
  4. .github/workflows/go-build-test.yml +28 -0
  5. .github/workflows/uptime-check.yml +44 -0
  6. .gitignore +8 -0
  7. Dockerfile +28 -0
  8. README.md +216 -10
  9. cmd/compile_swagger.ps1 +1 -0
  10. cmd/do_inject_config.ps1 +435 -0
  11. cmd/do_inject_controllers.ps1 +310 -0
  12. cmd/do_inject_middleware.ps1 +312 -0
  13. cmd/do_inject_repository.ps1 +327 -0
  14. cmd/do_inject_services.ps1 +383 -0
  15. config/config.go +1 -0
  16. config/database_config.go +55 -0
  17. config/env_config.go +112 -0
  18. config/jwt_config.go +24 -0
  19. config/supabase_config.go +25 -0
  20. config/upload_config.go +46 -0
  21. config/xendit_config.go +31 -0
  22. controllers/authentication_controller.go +59 -0
  23. controllers/controller.go +53 -0
  24. controllers/home_controller.go +113 -0
  25. controllers/otp_verification_controller.go +42 -0
  26. controllers/payment_callback_controller.go +79 -0
  27. docs/docs.go +705 -0
  28. docs/swagger.json +676 -0
  29. docs/swagger.yaml +440 -0
  30. err.txt +17 -0
  31. err2.txt +12 -0
  32. err3.txt +7 -0
  33. err4.txt +0 -0
  34. err_swag.txt +0 -0
  35. err_swag2.txt +3 -0
  36. go.mod +75 -0
  37. go.sum +215 -0
  38. go.work +3 -0
  39. go.work.sum +91 -0
  40. logs/error_log.txt +0 -0
  41. logs/security_log.txt +0 -0
  42. main.go +11 -0
  43. middleware/authentication_middleware.go +48 -0
  44. middleware/middleware.go +1 -0
  45. models/dto/authentication_dto.go +55 -0
  46. models/dto/email_verification_dto.go +5 -0
  47. models/dto/home_dto.go +92 -0
  48. models/dto/home_swagger_wrapper.go +18 -0
  49. models/dto/http_response_dto.go +15 -0
  50. models/dto/jwt_dto.go +15 -0
.github/workflows/deploy-development.yml ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ name: Deploy to Development
3
+ on:
4
+ push:
5
+ branches:
6
+ - development
7
+
8
+ jobs:
9
+ deploy:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Checkout
14
+ uses: actions/checkout@v4
15
+ with:
16
+ fetch-depth: 0
17
+
18
+ - name: Setup SSH
19
+ uses: webfactory/ssh-agent@v0.9.0
20
+ with:
21
+ ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
22
+
23
+ - name: Add VPS to known_hosts
24
+ run: |
25
+ mkdir -p ~/.ssh
26
+ ssh-keyscan -H ${{ secrets.VPS_HOST }} >> ~/.ssh/known_hosts
27
+
28
+ - name: Push to VPS
29
+ run: |
30
+ git remote remove vps || true
31
+ git remote add vps git@${{ secrets.VPS_HOST }}:/home/git/repos/backend-development.git
32
+ git push vps HEAD:development --force
.github/workflows/deploy-production.yml ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Deploy to Production
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - production
7
+
8
+ jobs:
9
+ deploy:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Checkout
14
+ uses: actions/checkout@v4
15
+ with:
16
+ fetch-depth: 0
17
+
18
+ - name: Setup SSH
19
+ uses: webfactory/ssh-agent@v0.9.0
20
+ with:
21
+ ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
22
+
23
+ - name: Add VPS to known_hosts
24
+ run: |
25
+ mkdir -p ~/.ssh
26
+ ssh-keyscan -H ${{ secrets.VPS_HOST }} >> ~/.ssh/known_hosts
27
+
28
+ - name: Push to VPS
29
+ run: |
30
+ git remote remove vps || true
31
+ git remote add vps git@${{ secrets.VPS_HOST }}:/home/git/repos/backend-production.git
32
+ git push vps HEAD:production --force
.github/workflows/deploy-test.yml ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Deploy to Huggingface - tests
2
+ on:
3
+ push:
4
+ branches:
5
+ - test
6
+ jobs:
7
+ deploy-to-huggingface:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ # Checkout repository
11
+ - name: Checkout Repository
12
+ uses: actions/checkout@v3
13
+ # Setup Git
14
+ - name: Setup Git for Huggingface
15
+ run: |
16
+ git config --global user.email "abdan.hafidz@gmail.com"
17
+ git config --global user.name "abdanhafidz"
18
+ # Clone Huggingface Space Repository
19
+ - name: Clone Huggingface Space
20
+ env:
21
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
22
+ run: |
23
+ git clone https://huggingface.co/spaces/lifedebugger/DanzApp-BE-Test space
24
+ # Update Git Remote URL and Pull Latest Changes
25
+ - name: Update Remote and Pull Changes
26
+ env:
27
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
28
+ run: |
29
+ cd space
30
+ git remote set-url origin https://lifedebugger:$HF_TOKEN@huggingface.co/spaces/lifedebugger/DanzApp-BE-Test
31
+ git pull origin main || echo "No changes to pull"
32
+ # Clean Space Directory - Delete all files except .git
33
+ - name: Clean Space Directory
34
+ run: |
35
+ cd space
36
+ find . -mindepth 1 -not -path "./.git*" -delete
37
+ # Copy Files to Huggingface Space
38
+ - name: Copy Files to Space
39
+ run: |
40
+ rsync -av --exclude='.git' ./ space/
41
+ # Commit and Push to Huggingface Space
42
+ - name: Commit and Push to Huggingface
43
+ env:
44
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
45
+ run: |
46
+ cd space
47
+ git add .
48
+ git commit -m "Deploy files from GitHub repository" || echo "No changes to commit"
49
+ git push origin main || echo "No changes to push"
.github/workflows/go-build-test.yml ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This workflow will build a golang project
2
+ # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
3
+
4
+ name: Go
5
+
6
+ on:
7
+ push:
8
+ branches: [ "test" ]
9
+ pull_request:
10
+ branches: [ "development" ]
11
+
12
+ jobs:
13
+
14
+ build:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Go
20
+ uses: actions/setup-go@v4
21
+ with:
22
+ go-version: '1.20'
23
+
24
+ - name: Build
25
+ run: go build -v ./...
26
+
27
+ - name: Test
28
+ run: go test -v ./...
.github/workflows/uptime-check.yml ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ name: Uptime Check Backend API
3
+
4
+ on:
5
+ push:
6
+ branches:
7
+ - tests
8
+
9
+ jobs:
10
+ check-uptime:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - name: Wait 5 Minutes Before Checking Uptime
15
+ run: |
16
+ echo "⏳ Waiting 5 minutes before running uptime checks..."
17
+ sleep 300
18
+
19
+ - name: Check Dev API Uptime
20
+ run: |
21
+ echo "🔍 Checking Development API..."
22
+ RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" {YOUR_DEV_ENDPOINT})
23
+
24
+ echo "Development API HTTP Status: $RESPONSE"
25
+
26
+ if [ "$RESPONSE" -ne 200 ]; then
27
+ echo "❌ Development API DOWN! Expected 200 but got $RESPONSE"
28
+ exit 1
29
+ fi
30
+
31
+ - name: Check Test API Uptime
32
+ run: |
33
+ echo "🔍 Checking Test API..."
34
+ RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" {YOUR_TEST_ENDPOINT})
35
+
36
+ echo "Test API HTTP Status: $RESPONSE"
37
+
38
+ if [ "$RESPONSE" -ne 200 ]; then
39
+ echo "❌ Test API DOWN! Expected 200 but got $RESPONSE"
40
+ exit 1
41
+ fi
42
+
43
+ - name: Success Message
44
+ run: echo "✔ All API endpoints are UP!"
.gitignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ *.env
2
+ go-boilerplate.exe
3
+ vendor/
4
+ *.log
5
+ *.exe
6
+ *.dll
7
+ *.so
8
+ *.dylib
Dockerfile ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # =====================
2
+ # BUILD STAGE
3
+ # =====================
4
+ FROM golang:1.25.4 AS builder
5
+
6
+ WORKDIR /app
7
+
8
+ COPY go.mod go.sum ./
9
+ RUN go mod download
10
+
11
+ COPY . .
12
+ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app
13
+
14
+ # =====================
15
+ # RUNTIME STAGE
16
+ # =====================
17
+ FROM gcr.io/distroless/base-debian12
18
+
19
+ WORKDIR /app
20
+
21
+ COPY --from=builder /app/app /app/app
22
+
23
+ # Non-root user
24
+ USER nonroot:nonroot
25
+
26
+ EXPOSE 8080
27
+
28
+ CMD ["/app/app"]
README.md CHANGED
@@ -1,10 +1,216 @@
1
- ---
2
- title: DanzApp BE Test
3
- emoji: 🏆
4
- colorFrom: yellow
5
- colorTo: red
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🚀 Go Starter Boilerplate - Clean Layered Architecture
2
+
3
+ [![Go Version](https://img.shields.io/badge/Go-1.25.4+-00ADD8?style=flat&logo=go)](https://golang.org/)
4
+ [![Gin Framework](https://img.shields.io/badge/Framework-Gin-008080?style=flat)](https://gin-gonic.com/)
5
+ [![GORM](https://img.shields.io/badge/ORM-GORM-blue?style=flat)](https://gorm.io/)
6
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
7
+
8
+ A high-performance, scalable, and professional Go boilerplate built with **Clean Layered Architecture**. This starter kit is designed to provide a solid foundation for enterprise-grade backend applications, featuring dependency injection, modularity, and robust CI/CD pipelines.
9
+
10
+ ---
11
+
12
+ ## 📑 Table of Contents
13
+ - [Core Architecture & Theory](#-core-architecture--theory)
14
+ - [Tech Stack](#-tech-stack)
15
+ - [Key Features](#-key-features)
16
+ - [Project Structure](#-project-structure)
17
+ - [CLI Tools & Automated DI](#-cli-tools--automated-di)
18
+ - [Setup & Installation](#-setup--installation)
19
+ - [Environment Variables](#-environment-variables)
20
+ - [Documentation (Swagger)](#-documentation-swagger)
21
+ - [CI/CD & Deployment](#-cicd--deployment)
22
+ - [SOLID Principles](#-solid-principles)
23
+
24
+ ---
25
+
26
+ ## 🏗️ Core Architecture & Theory
27
+
28
+ This project follows the **Clean Layered Architecture** pattern, ensuring high maintainability and testability by decoupling business logic from external dependencies (DB, API, Frameworks).
29
+
30
+ ### The Four Layers:
31
+ 1. **Transport / Router**: Defines API endpoints and handles HTTP protocol specifics using Gin.
32
+ 2. **Controller**: Entry point for requests. Validates input, calls services, and formats JSON responses.
33
+ 3. **Service (Usecase)**: The heart of the application. Contains business logic and orchestrates data flow between repositories.
34
+ 4. **Repository (Data Access)**: Handles database interactions using GORM. Isolated from business logic.
35
+
36
+ ### 🔌 Dependency Injection (Provider Pattern)
37
+ We use a **Provider Pattern** for Dependency Injection, managed through the `provider/` directory. While the wiring is handled in Go, this project features an **Automated DI Engine** that generates this wiring for you.
38
+
39
+ Benefits:
40
+ - **Singleton Management**: Ensures single instances of services/repositories.
41
+ - **Automated Wiring**: No more manual `NewService(repo1, repo2, ...)` maintenance.
42
+ - **Cycle Detection**: The induction engine automatically detects and prevents circular dependencies.
43
+ - **Decoupling**: Layers remain isolated through interfaces.
44
+
45
+ ---
46
+
47
+ ## 🛠️ Tech Stack
48
+
49
+ - **Language**: [Go (Golang)](https://golang.org/)
50
+ - **Web Framework**: [Gin Gonic](https://github.com/gin-gonic/gin)
51
+ - **ORM**: [GORM](https://gorm.io/) with PostgreSQL
52
+ - **Database**: [PostgreSQL](https://www.postgresql.org/)
53
+ - **Authentication**: [JWT (JSON Web Token)](https://jwt.io/)
54
+ - **Payment Gateway**: [Xendit](https://www.xendit.co/)
55
+ - **Object Storage**: [Supabase Storage](https://supabase.com/storage)
56
+ - **Documentation**: [Swagger (swaggo)](https://github.com/swaggo/swag)
57
+ - **Containerization**: [Docker](https://www.docker.com/)
58
+
59
+ ---
60
+
61
+ ## ✨ Key Features
62
+
63
+ - **Lock-tight Auth**: Complete Authentication flow (Login, Register, Email Verification, Forgot Password).
64
+ - **Role-Based Access**: Specialized routes for Admin and User roles.
65
+ - **Payment Ready**: Integrated with Xendit for seamless payment processing and webhooks.
66
+ - **Cloud Storage**: Native support for Supabase storage for file uploads.
67
+ - **Automated Migrations**: Database schema automatically synchronizes on startup.
68
+ - **Standardized Responses**: Unified JSON response structure for success and error handling.
69
+ - **Modular Routing**: Cleanly separated route definitions per module.
70
+
71
+ ---
72
+
73
+ ## 📁 Project Structure
74
+
75
+ ```text
76
+ ├── cmd/ # Custom CLI tools (e.g., Swagger compiler)
77
+ ├── config/ # Configuration loaders & schema definitions
78
+ ├── controllers/ # Request handlers (Input validation, Response formatting)
79
+ ├── middleware/ # Gin middlewares (Auth, Logging, Gzip)
80
+ ├── models/
81
+ │ ├── dto/ # Data Transfer Objects (Request/Response structs)
82
+ │ ├── entity/ # GORM database models
83
+ │ └── error/ # Custom error definitions
84
+ ├── provider/ # Dependency Injection & Bootstrapping
85
+ ├── repositories/ # Database access layer (SQL logic)
86
+ ├── router/ # Route definitions & grouping
87
+ ├── services/ # Business logic layer
88
+ ├── swagger/ # Automatically generated Swagger UI files
89
+ ├── utils/ # Cross-cutting helpers (Response helpers, string utils)
90
+ ├── main.go # Application entry point
91
+ ├── Dockerfile # Multi-stage production container
92
+ └── .github/workflows/ # CI/CD Pipelines
93
+ ```
94
+
95
+ ---
96
+
97
+ ## 🛠️ CLI Tools & Automated DI
98
+
99
+ This boilerplate features a unique **PowerShell-based Dependency Injection Engine** located in the `cmd/` directory. These scripts scan your code, resolve dependencies, and automatically generate the necessary boilerplate code in the `provider/` package.
100
+
101
+ ### 💉 Automated Injection Scripts
102
+ | Script | Purpose |
103
+ | :--- | :--- |
104
+ | `do_inject_config.ps1` | Scans configuration files and wires them into the Config Provider. |
105
+ | `do_inject_repository.ps1` | Discovers GORM repositories and updates `repositories_provider.go`. |
106
+ | `do_inject_services.ps1` | Performs **topological sorting** on services to resolve their dependencies in the correct order. |
107
+ | `do_inject_controllers.ps1` | Wires services into controllers and updates the Controller Provider. |
108
+ | `do_inject_middleware.ps1` | Manages registration of custom Gin middlewares. |
109
+
110
+ ### 🧠 How the DI Engine Works:
111
+ 1. **Scanning**: The scripts use regex to find constructor functions (e.g., `NewAccountService`).
112
+ 2. **Resolution**: It identifies the required parameters (Repositories, other Services, or Configs).
113
+ 3. **Topological Sort**: (For Services) It builds a dependency graph and sorts them so dependencies are initialized before they are needed.
114
+ 4. **Codegen**: It writes a clean, standardized `provider/*.go` file with all the wiring logic.
115
+
116
+ ### 📝 Swagger Compilation
117
+ - **`compile_swagger.ps1`**: This script wraps the `swag init` command with optimized flags (`--parseDependency`, `--parseInternal`) to ensure your Swagger documentation is always complete and up-to-date.
118
+
119
+ ---
120
+
121
+ ## 🚀 Setup & Installation
122
+
123
+ ### Prerequisites
124
+ - Go 1.25.4 or higher
125
+ - PostgreSQL
126
+ - Docker (Optional, for containerization)
127
+
128
+ ### Step 1: Clone the Repository
129
+ ```bash
130
+ git clone <your-repo-url>
131
+ cd <your-directory-name>
132
+ ```
133
+
134
+ ### Step 2: Environment Setup
135
+ Copy the example environment file and fill in your credentials:
136
+ ```bash
137
+ cp .env.example .env
138
+ ```
139
+
140
+ ### Step 3: Install Dependencies
141
+ ```bash
142
+ go mod download
143
+ ```
144
+
145
+ ### Step 4: Run the Application
146
+ ```bash
147
+ go run main.go
148
+ ```
149
+
150
+ ---
151
+
152
+ ## � Environment Variables
153
+
154
+ | Variable | Description |
155
+ | :--- | :--- |
156
+ | `DB_HOST` | PostgreSQL Host address |
157
+ | `DB_USER` | Database username |
158
+ | `DB_PASSWORD` | Database password |
159
+ | `DB_PORT` | Database port (default 5432) |
160
+ | `DB_NAME` | Name of the database |
161
+ | `JWT_SECRET_KEY` | Secret key for signing JWT tokens |
162
+ | `XENDIT_API_KEY` | Your Xendit Secret Key |
163
+ | `HOST_PORT` | Port for the Go server to listen on |
164
+
165
+ ---
166
+
167
+ ## 📖 Documentation (Swagger)
168
+
169
+ This project uses `swaggo` to generate interactive API documentation.
170
+
171
+ ### How to Compile Swagger:
172
+ Run the provided script to synchronize your code comments with the documentation:
173
+ ```bash
174
+ ./cmd/compile_swagger
175
+ ```
176
+
177
+ ### Accessing Swagger UI:
178
+ Once the app is running, visit:
179
+ `http://localhost:<PORT>/swagger/index.html`
180
+
181
+ ---
182
+
183
+ ## 🚢 CI/CD & Deployment
184
+
185
+ ### GitHub Workflows
186
+ The project includes automated pipelines in `.github/workflows/`:
187
+ - **`go-build-test.yml`**: Automatically runs tests and builds the binary on every push.
188
+ - **`deploy-production.yml` / `deploy-development.yml`**: CD pipelines for automated shipping to target environments.
189
+ - **`uptime-check.yml`**: Specialized workflow to monitor service health.
190
+
191
+ ### Docker
192
+ We use a **Multi-Stage Dockerfile** to ensure the production image is as small and secure as possible.
193
+
194
+ **Build the image:**
195
+ ```bash
196
+ docker build -t go-starter .
197
+ ```
198
+
199
+ **Run the container:**
200
+ ```bash
201
+ docker run -p 8080:8080 --env-file .env go-starter
202
+ ```
203
+
204
+ ---
205
+
206
+ ## 💎 SOLID Principles
207
+
208
+ - **S**: Single Responsibility. Each layer (Controller, Service, Repo) does exactly one thing.
209
+ - **O**: Open/Closed. Services are open for extension via interfaces but closed for modification.
210
+ - **L**: Liskov Substitution. Implementations are interchangeable via Provider interfaces.
211
+ - **I**: Interface Segregation. Slim, specific interfaces for each capability.
212
+ - **D**: Dependency Inversion. High-level modules don't depend on low-level modules; both depend on abstractions.
213
+
214
+ ---
215
+
216
+ Developed with ❤️ by Abdan Hafidz.
cmd/compile_swagger.ps1 ADDED
@@ -0,0 +1 @@
 
 
1
+ swag init -g swagger/swagger_info.go -o swagger/docs --parseDependency --parseInternal
cmd/do_inject_config.ps1 ADDED
@@ -0,0 +1,435 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #Requires -Version 5.1
2
+ <#
3
+ .SYNOPSIS
4
+ Automatic Dependency Injection Generator for Go Configuration
5
+
6
+ .DESCRIPTION
7
+ Scans ./config/ directory, discovers all config constructors, infers their dependencies,
8
+ and generates provider/config_provider.go with full DI wiring. Special handling for
9
+ chained dependencies where configs depend on other configs (e.g., DatabaseConfig depends on EnvConfig).
10
+
11
+ .EXAMPLE
12
+ .\config_injector.ps1
13
+
14
+ .NOTES
15
+ - Works with PowerShell 5.1+ and PowerShell 7+
16
+ - No external dependencies required
17
+ - Supports multi-line constructor signatures
18
+ - Handles config-to-config dependencies with topological sorting
19
+ - Special handling for method calls like envConfig.GetDatabaseHost()
20
+ #>
21
+
22
+ [CmdletBinding()]
23
+ param()
24
+
25
+ # Configuration
26
+ $ConfigDir = "./config"
27
+ $OutputFile = "provider/config_provider.go"
28
+ $ModulePath = "abdanhafidz.com/go-boilerplate/config"
29
+
30
+ # ANSI colors for better output
31
+ $script:UseColors = $Host.UI.SupportsVirtualTerminal
32
+ function Write-ColorOutput {
33
+ param([string]$Message, [string]$Color = "White")
34
+ if ($script:UseColors) {
35
+ $colors = @{
36
+ "Green" = "`e[32m"; "Yellow" = "`e[33m"; "Red" = "`e[31m"
37
+ "Cyan" = "`e[36m"; "Blue" = "`e[34m"; "Reset" = "`e[0m"
38
+ }
39
+ Write-Host "$($colors[$Color])$Message$($colors['Reset'])"
40
+ } else {
41
+ Write-Host $Message
42
+ }
43
+ }
44
+
45
+ # Data structures
46
+ class ConfigInfo {
47
+ [string]$ConstructorName # NewDatabaseConfig
48
+ [string]$Domain # DatabaseConfig
49
+ [string]$VarName # databaseConfig
50
+ [System.Collections.Generic.List[Parameter]]$Parameters
51
+ [System.Collections.Generic.List[string]]$ConfigDependencies
52
+
53
+ ConfigInfo() {
54
+ $this.Parameters = [System.Collections.Generic.List[Parameter]]::new()
55
+ $this.ConfigDependencies = [System.Collections.Generic.List[string]]::new()
56
+ }
57
+ }
58
+
59
+ class Parameter {
60
+ [string]$Name
61
+ [string]$RawType
62
+ [string]$NormalizedType
63
+ }
64
+
65
+ function Get-LowerCamelCase {
66
+ param([string]$Text)
67
+ if ($Text.Length -eq 0) { return $Text }
68
+ return $Text.Substring(0, 1).ToLower() + $Text.Substring(1)
69
+ }
70
+
71
+ function Normalize-TypeName {
72
+ param([string]$TypeStr)
73
+
74
+ # Remove leading pointer
75
+ $cleaned = $TypeStr -replace '^\*+', ''
76
+
77
+ # Remove package prefix (everything before last dot)
78
+ if ($cleaned -match '\.([^.]+)$') {
79
+ $cleaned = $matches[1]
80
+ }
81
+
82
+ return $cleaned.Trim()
83
+ }
84
+
85
+ function Parse-GoFiles {
86
+ param([string]$Directory)
87
+
88
+ Write-ColorOutput "Scanning for config constructors in $Directory..." "Cyan"
89
+
90
+ if (-not (Test-Path $Directory)) {
91
+ Write-ColorOutput "ERROR: Directory '$Directory' not found!" "Red"
92
+ exit 1
93
+ }
94
+
95
+ $goFiles = Get-ChildItem -Path $Directory -Filter "*.go" -Recurse -File
96
+ $configs = [System.Collections.Generic.List[ConfigInfo]]::new()
97
+
98
+ foreach ($file in $goFiles) {
99
+ $content = Get-Content $file.FullName -Raw
100
+
101
+ # Match function signatures (support multi-line)
102
+ # Pattern: func NewXxxConfig(...) XxxConfig
103
+ $pattern = '(?ms)func\s+(New[a-zA-Z0-9]+Config)\s*\(([^)]*)\)\s+([a-zA-Z0-9*_.]+Config)'
104
+ $matches = [regex]::Matches($content, $pattern)
105
+
106
+ foreach ($match in $matches) {
107
+ $constructorName = $match.Groups[1].Value
108
+ $paramsStr = $match.Groups[2].Value
109
+ $returnType = $match.Groups[3].Value
110
+
111
+ # Extract domain name (XxxConfig)
112
+ $domain = Normalize-TypeName $returnType
113
+ $varName = Get-LowerCamelCase $domain
114
+
115
+ $config = [ConfigInfo]::new()
116
+ $config.ConstructorName = $constructorName
117
+ $config.Domain = $domain
118
+ $config.VarName = $varName
119
+
120
+ # Parse parameters
121
+ if ($paramsStr.Trim() -ne "") {
122
+ # Split by comma, but be careful with nested types
123
+ $paramList = $paramsStr -split ',\s*(?![^<>]*>)'
124
+
125
+ foreach ($param in $paramList) {
126
+ $param = $param.Trim()
127
+ if ($param -eq "") { continue }
128
+
129
+ # Split into name and type
130
+ $parts = $param -split '\s+', 2
131
+
132
+ $p = [Parameter]::new()
133
+ if ($parts.Count -eq 2) {
134
+ $p.Name = $parts[0]
135
+ $p.RawType = $parts[1]
136
+ } elseif ($parts.Count -eq 1) {
137
+ # Anonymous parameter - synthesize name
138
+ $p.Name = "param$($config.Parameters.Count)"
139
+ $p.RawType = $parts[0]
140
+ } else {
141
+ continue
142
+ }
143
+
144
+ $p.NormalizedType = Normalize-TypeName $p.RawType
145
+ $config.Parameters.Add($p)
146
+
147
+ # Track config dependencies (configs that depend on other configs)
148
+ if ($p.NormalizedType -match 'Config$') {
149
+ $config.ConfigDependencies.Add($p.NormalizedType)
150
+ }
151
+ }
152
+ }
153
+
154
+ $configs.Add($config)
155
+ Write-ColorOutput " Found: $constructorName" "Green"
156
+ }
157
+ }
158
+
159
+ if ($configs.Count -eq 0) {
160
+ Write-ColorOutput "No config constructors found matching pattern 'NewXxxConfig'!" "Red"
161
+ exit 1
162
+ }
163
+
164
+ Write-ColorOutput "`nTotal configs discovered: $($configs.Count)" "Blue"
165
+ return $configs
166
+ }
167
+
168
+ function Get-TopologicalOrder {
169
+ param([System.Collections.Generic.List[ConfigInfo]]$Configs)
170
+
171
+ Write-ColorOutput "`nBuilding dependency graph..." "Cyan"
172
+
173
+ # Build adjacency list
174
+ $graph = @{}
175
+ $inDegree = @{}
176
+ $domainToConfig = @{}
177
+
178
+ foreach ($cfg in $Configs) {
179
+ $graph[$cfg.Domain] = [System.Collections.Generic.List[string]]::new()
180
+ $inDegree[$cfg.Domain] = 0
181
+ $domainToConfig[$cfg.Domain] = $cfg
182
+ }
183
+
184
+ # Build edges (dependencies)
185
+ foreach ($cfg in $Configs) {
186
+ foreach ($dep in $cfg.ConfigDependencies) {
187
+ if ($graph.ContainsKey($dep)) {
188
+ $graph[$dep].Add($cfg.Domain)
189
+ $inDegree[$cfg.Domain]++
190
+ }
191
+ }
192
+ }
193
+
194
+ # Kahn's algorithm for topological sort
195
+ $queue = [System.Collections.Generic.Queue[string]]::new()
196
+ foreach ($domain in $inDegree.Keys) {
197
+ if ($inDegree[$domain] -eq 0) {
198
+ $queue.Enqueue($domain)
199
+ }
200
+ }
201
+
202
+ $sorted = [System.Collections.Generic.List[string]]::new()
203
+
204
+ while ($queue.Count -gt 0) {
205
+ $current = $queue.Dequeue()
206
+ $sorted.Add($current)
207
+
208
+ foreach ($neighbor in $graph[$current]) {
209
+ $inDegree[$neighbor]--
210
+ if ($inDegree[$neighbor] -eq 0) {
211
+ $queue.Enqueue($neighbor)
212
+ }
213
+ }
214
+ }
215
+
216
+ # Check for cycles
217
+ if ($sorted.Count -ne $Configs.Count) {
218
+ $remaining = $inDegree.Keys | Where-Object { $inDegree[$_] -gt 0 }
219
+ Write-ColorOutput "`nERROR: Circular dependency detected in configs!" "Red"
220
+ Write-ColorOutput "Configs involved in cycle: $($remaining -join ', ')" "Yellow"
221
+ exit 1
222
+ }
223
+
224
+ Write-ColorOutput " Dependency graph validated (no cycles)" "Green"
225
+ Write-ColorOutput " Topological order: $($sorted -join ' -> ')" "Blue"
226
+
227
+ # Return configs in topological order
228
+ return $sorted | ForEach-Object { $domainToConfig[$_] }
229
+ }
230
+
231
+ function Resolve-ConfigArgument {
232
+ param(
233
+ [Parameter]$Param,
234
+ [string]$DependentConfigVar
235
+ )
236
+
237
+ $type = $Param.NormalizedType
238
+ $paramName = $Param.Name
239
+
240
+ # SPECIAL CASE MAPPINGS FOR CONFIG
241
+ # ============================================
242
+
243
+ # 1. Config pattern: XxxConfig -> already instantiated config variable
244
+ if ($type -match '^(.+)Config$') {
245
+ $configVarName = Get-LowerCamelCase $type
246
+ return $configVarName
247
+ }
248
+
249
+ # 2. String parameters - try to infer from parameter name and match with config getter methods
250
+ if ($type -eq "string") {
251
+ # Common patterns for EnvConfig getters
252
+ $getterMappings = @{
253
+ "host" = "GetDatabaseHost()"
254
+ "databaseHost" = "GetDatabaseHost()"
255
+ "user" = "GetDatabaseUser()"
256
+ "databaseUser" = "GetDatabaseUser()"
257
+ "password" = "GetDatabasePassword()"
258
+ "databasePassword" = "GetDatabasePassword()"
259
+ "name" = "GetDatabaseName()"
260
+ "databaseName" = "GetDatabaseName()"
261
+ "dbName" = "GetDatabaseName()"
262
+ "port" = "GetDatabasePort()"
263
+ "databasePort" = "GetDatabasePort()"
264
+ "salt" = "GetSalt()"
265
+ "secret" = "GetSecretKey()"
266
+ "secretKey" = "GetSecretKey()"
267
+ "jwtSecret" = "GetSecretKey()"
268
+ "apiKey" = "GetAPIKey()"
269
+ "timezone" = "GetTimezone()"
270
+ }
271
+
272
+ # Try to find matching getter
273
+ foreach ($key in $getterMappings.Keys) {
274
+ if ($paramName -like "*$key*") {
275
+ return "envConfig.$($getterMappings[$key])"
276
+ }
277
+ }
278
+
279
+ # If dependent on a config, try to construct getter name from param name
280
+ if ($DependentConfigVar) {
281
+ # Convert paramName to PascalCase for getter
282
+ $getterName = (Get-Culture).TextInfo.ToTitleCase($paramName)
283
+ $getterName = $getterName -replace '\s', ''
284
+ return "${DependentConfigVar}.Get${getterName}()"
285
+ }
286
+ }
287
+
288
+ # 3. Int/port parameters
289
+ if ($type -eq "int" -or $type -eq "int32" -or $type -eq "int64") {
290
+ if ($paramName -match "port") {
291
+ return "envConfig.GetDatabasePort()"
292
+ }
293
+ }
294
+
295
+ # ADD MORE SPECIAL CASES HERE:
296
+ # --------------------------------------------
297
+ # Example: Redis config
298
+ # if ($paramName -match "redis") {
299
+ # return "envConfig.GetRedisURL()"
300
+ # }
301
+ #
302
+ # Example: Mail config
303
+ # if ($paramName -match "smtp") {
304
+ # return "envConfig.GetSMTPHost()"
305
+ # }
306
+ # --------------------------------------------
307
+
308
+ # 4. Hardcoded constants (timezone example)
309
+ if ($type -eq "string" -and $paramName -match "timezone|location") {
310
+ return "`"Asia/Jakarta`""
311
+ }
312
+
313
+ # 5. Fallback: unresolved type
314
+ return "/* TODO: provide $($Param.RawType) for $paramName */"
315
+ }
316
+
317
+ function Generate-ProviderCode {
318
+ param([System.Collections.Generic.List[ConfigInfo]]$ConfigsInOrder)
319
+
320
+ Write-ColorOutput "`nGenerating config provider code..." "Cyan"
321
+
322
+ $sb = [System.Text.StringBuilder]::new()
323
+ [void]$sb.AppendLine("package provider")
324
+ [void]$sb.AppendLine()
325
+ [void]$sb.AppendLine("import `"$ModulePath`"")
326
+ [void]$sb.AppendLine()
327
+
328
+ # Interface
329
+ [void]$sb.AppendLine("type ConfigProvider interface {")
330
+ foreach ($cfg in $ConfigsInOrder) {
331
+ $line = "`tProvide$($cfg.Domain)() config.$($cfg.Domain)"
332
+ [void]$sb.AppendLine($line)
333
+ }
334
+ [void]$sb.AppendLine("}")
335
+ [void]$sb.AppendLine()
336
+
337
+ # Struct
338
+ [void]$sb.AppendLine("type configProvider struct {")
339
+ foreach ($cfg in $ConfigsInOrder) {
340
+ $line = "`t$($cfg.VarName) config.$($cfg.Domain)"
341
+ [void]$sb.AppendLine($line)
342
+ }
343
+ [void]$sb.AppendLine("}")
344
+ [void]$sb.AppendLine()
345
+
346
+ # Constructor
347
+ [void]$sb.AppendLine("func NewConfigProvider() ConfigProvider {")
348
+
349
+ # Initialize configs in topological order
350
+ foreach ($cfg in $ConfigsInOrder) {
351
+ # Check if this config depends on another config
352
+ $dependentConfigVar = $null
353
+ if ($cfg.ConfigDependencies.Count -gt 0) {
354
+ $dependentConfigVar = Get-LowerCamelCase $cfg.ConfigDependencies[0]
355
+ }
356
+
357
+ $args = @()
358
+ foreach ($param in $cfg.Parameters) {
359
+ $args += Resolve-ConfigArgument -Param $param -DependentConfigVar $dependentConfigVar
360
+ }
361
+ $argsStr = $args -join ", "
362
+ $line = "`t$($cfg.VarName) := config.$($cfg.ConstructorName)($argsStr)"
363
+ [void]$sb.AppendLine($line)
364
+ }
365
+
366
+ [void]$sb.AppendLine("`treturn &configProvider{")
367
+ foreach ($cfg in $ConfigsInOrder) {
368
+ $line = "`t`t$($cfg.VarName): $($cfg.VarName),"
369
+ [void]$sb.AppendLine($line)
370
+ }
371
+ [void]$sb.AppendLine("`t}")
372
+ [void]$sb.AppendLine("}")
373
+ [void]$sb.AppendLine()
374
+
375
+ # Getter methods
376
+ foreach ($cfg in $ConfigsInOrder) {
377
+ [void]$sb.AppendLine("func (c *configProvider) Provide$($cfg.Domain)() config.$($cfg.Domain) {")
378
+ [void]$sb.AppendLine("`treturn c.$($cfg.VarName)")
379
+ [void]$sb.AppendLine("}")
380
+ [void]$sb.AppendLine()
381
+ }
382
+
383
+ return $sb.ToString()
384
+ }
385
+
386
+ function Write-ProviderFile {
387
+ param([string]$Code, [string]$OutputPath)
388
+
389
+ Write-ColorOutput "Writing to $OutputPath..." "Cyan"
390
+
391
+ # Ensure directory exists
392
+ $dir = Split-Path $OutputPath -Parent
393
+ if ($dir -and -not (Test-Path $dir)) {
394
+ New-Item -ItemType Directory -Path $dir -Force | Out-Null
395
+ }
396
+
397
+ # Write file as UTF-8 without BOM
398
+ $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
399
+ [System.IO.File]::WriteAllText($OutputPath, $Code, $utf8NoBom)
400
+
401
+ Write-ColorOutput " Successfully generated $OutputPath" "Green"
402
+ }
403
+
404
+ # ============================================
405
+ # MAIN EXECUTION
406
+ # ============================================
407
+
408
+ try {
409
+ Write-ColorOutput "`n=========================================" "Blue"
410
+ Write-ColorOutput " Go Config Provider Generator v1.0" "Blue"
411
+ Write-ColorOutput "=========================================`n" "Blue"
412
+
413
+ # Step 1: Parse all config constructors
414
+ $configs = Parse-GoFiles -Directory $ConfigDir
415
+
416
+ # Step 2: Perform topological sort (configs can depend on other configs)
417
+ $sortedConfigs = Get-TopologicalOrder -Configs $configs
418
+
419
+ # Step 3: Generate provider code
420
+ $code = Generate-ProviderCode -ConfigsInOrder $sortedConfigs
421
+
422
+ # Step 4: Write to file
423
+ Write-ProviderFile -Code $code -OutputPath $OutputFile
424
+
425
+ Write-ColorOutput "`nSUCCESS! Config provider generated successfully.`n" "Green"
426
+ Write-ColorOutput "Next steps:" "Cyan"
427
+ Write-ColorOutput " 1. Review $OutputFile" "White"
428
+ Write-ColorOutput " 2. Fill any /* TODO: provide ... */ placeholders" "White"
429
+ Write-ColorOutput " 3. Run: go build ./provider" "White"
430
+
431
+ } catch {
432
+ Write-ColorOutput "`nERROR: $($_.Exception.Message)" "Red"
433
+ Write-ColorOutput "Stack trace: $($_.ScriptStackTrace)" "Yellow"
434
+ exit 1
435
+ }
cmd/do_inject_controllers.ps1 ADDED
@@ -0,0 +1,310 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #Requires -Version 5.1
2
+ <#
3
+ .SYNOPSIS
4
+ Automatic Dependency Injection Generator for Go Controllers
5
+
6
+ .DESCRIPTION
7
+ Scans ./controllers/ directory, discovers all controller constructors, infers their dependencies,
8
+ and generates provider/controller_provider.go with full DI wiring.
9
+
10
+ .EXAMPLE
11
+ .\controller_injector.ps1
12
+
13
+ .NOTES
14
+ - Works with PowerShell 5.1+ and PowerShell 7+
15
+ - No external dependencies required
16
+ - Supports multi-line constructor signatures
17
+ - Controllers depend on services from ServicesProvider
18
+ #>
19
+
20
+ [CmdletBinding()]
21
+ param()
22
+
23
+ # Configuration
24
+ $ControllersDir = "./controllers"
25
+ $OutputFile = "provider/controller_provider.go"
26
+ $ModulePath = "abdanhafidz.com/go-boilerplate/controllers"
27
+
28
+ # ANSI colors for better output
29
+ $script:UseColors = $Host.UI.SupportsVirtualTerminal
30
+ function Write-ColorOutput {
31
+ param([string]$Message, [string]$Color = "White")
32
+ if ($script:UseColors) {
33
+ $colors = @{
34
+ "Green" = "`e[32m"; "Yellow" = "`e[33m"; "Red" = "`e[31m"
35
+ "Cyan" = "`e[36m"; "Blue" = "`e[34m"; "Reset" = "`e[0m"
36
+ }
37
+ Write-Host "$($colors[$Color])$Message$($colors['Reset'])"
38
+ } else {
39
+ Write-Host $Message
40
+ }
41
+ }
42
+
43
+ # Data structures
44
+ class ControllerInfo {
45
+ [string]$ConstructorName # NewAccountController
46
+ [string]$Domain # AccountController
47
+ [string]$VarName # accountController
48
+ [System.Collections.Generic.List[Parameter]]$Parameters
49
+
50
+ ControllerInfo() {
51
+ $this.Parameters = [System.Collections.Generic.List[Parameter]]::new()
52
+ }
53
+ }
54
+
55
+ class Parameter {
56
+ [string]$Name
57
+ [string]$RawType
58
+ [string]$NormalizedType
59
+ }
60
+
61
+ function Get-LowerCamelCase {
62
+ param([string]$Text)
63
+ if ($Text.Length -eq 0) { return $Text }
64
+ return $Text.Substring(0, 1).ToLower() + $Text.Substring(1)
65
+ }
66
+
67
+ function Normalize-TypeName {
68
+ param([string]$TypeStr)
69
+
70
+ # Remove leading pointer
71
+ $cleaned = $TypeStr -replace '^\*+', ''
72
+
73
+ # Remove package prefix (everything before last dot)
74
+ if ($cleaned -match '\.([^.]+)$') {
75
+ $cleaned = $matches[1]
76
+ }
77
+
78
+ return $cleaned.Trim()
79
+ }
80
+
81
+ function Parse-GoFiles {
82
+ param([string]$Directory)
83
+
84
+ Write-ColorOutput "Scanning for controller constructors in $Directory..." "Cyan"
85
+
86
+ if (-not (Test-Path $Directory)) {
87
+ Write-ColorOutput "ERROR: Directory '$Directory' not found!" "Red"
88
+ exit 1
89
+ }
90
+
91
+ $goFiles = Get-ChildItem -Path $Directory -Filter "*.go" -Recurse -File
92
+ $controllers = [System.Collections.Generic.List[ControllerInfo]]::new()
93
+
94
+ foreach ($file in $goFiles) {
95
+ $content = Get-Content $file.FullName -Raw
96
+
97
+ # Match function signatures (support multi-line)
98
+ # Pattern: func NewXxxController(...) XxxController
99
+ $pattern = '(?ms)func\s+(New[a-zA-Z0-9]+Controller)\s*\(([^)]*)\)\s+([a-zA-Z0-9*_.]+Controller)'
100
+ $matches = [regex]::Matches($content, $pattern)
101
+
102
+ foreach ($match in $matches) {
103
+ $constructorName = $match.Groups[1].Value
104
+ $paramsStr = $match.Groups[2].Value
105
+ $returnType = $match.Groups[3].Value
106
+
107
+ # Extract domain name (XxxController)
108
+ $domain = Normalize-TypeName $returnType
109
+ $varName = Get-LowerCamelCase $domain
110
+
111
+ $controller = [ControllerInfo]::new()
112
+ $controller.ConstructorName = $constructorName
113
+ $controller.Domain = $domain
114
+ $controller.VarName = $varName
115
+
116
+ # Parse parameters
117
+ if ($paramsStr.Trim() -ne "") {
118
+ # Split by comma, but be careful with nested types
119
+ $paramList = $paramsStr -split ',\s*(?![^<>]*>)'
120
+
121
+ foreach ($param in $paramList) {
122
+ $param = $param.Trim()
123
+ if ($param -eq "") { continue }
124
+
125
+ # Split into name and type
126
+ $parts = $param -split '\s+', 2
127
+
128
+ $p = [Parameter]::new()
129
+ if ($parts.Count -eq 2) {
130
+ $p.Name = $parts[0]
131
+ $p.RawType = $parts[1]
132
+ } elseif ($parts.Count -eq 1) {
133
+ # Anonymous parameter - synthesize name
134
+ $p.Name = "param$($controller.Parameters.Count)"
135
+ $p.RawType = $parts[0]
136
+ } else {
137
+ continue
138
+ }
139
+
140
+ $p.NormalizedType = Normalize-TypeName $p.RawType
141
+ $controller.Parameters.Add($p)
142
+ }
143
+ }
144
+
145
+ $controllers.Add($controller)
146
+ Write-ColorOutput " Found: $constructorName" "Green"
147
+ }
148
+ }
149
+
150
+ if ($controllers.Count -eq 0) {
151
+ Write-ColorOutput "No controller constructors found matching pattern 'NewXxxController'!" "Red"
152
+ exit 1
153
+ }
154
+
155
+ Write-ColorOutput "`nTotal controllers discovered: $($controllers.Count)" "Blue"
156
+ return $controllers
157
+ }
158
+
159
+ function Resolve-ControllerArgument {
160
+ param([Parameter]$Param)
161
+
162
+ $type = $Param.NormalizedType
163
+
164
+ # DEPENDENCY RESOLUTION RULES
165
+ # ============================================
166
+
167
+ # 1. Service pattern: XxxxService -> servicesProvider.ProvideXxxxService()
168
+ if ($type -match '^(.+)Service$') {
169
+ $serviceName = $type
170
+ return "servicesProvider.Provide${serviceName}()"
171
+ }
172
+
173
+ # ADD MORE SPECIAL CASES HERE:
174
+ # --------------------------------------------
175
+ # Example: Config dependency
176
+ # if ($type -eq "Config") {
177
+ # return "configProvider.ProvideConfig()"
178
+ # }
179
+ #
180
+ # Example: Logger
181
+ # if ($type -eq "Logger") {
182
+ # return "loggerProvider.ProvideLogger()"
183
+ # }
184
+ #
185
+ # Example: Validator
186
+ # if ($type -eq "Validator") {
187
+ # return "validatorProvider.ProvideValidator()"
188
+ # }
189
+ # --------------------------------------------
190
+
191
+ # 2. Fallback: unresolved type
192
+ return "/* TODO: provide $($Param.RawType) */"
193
+ }
194
+
195
+ function Generate-ProviderCode {
196
+ param([System.Collections.Generic.List[ControllerInfo]]$Controllers)
197
+
198
+ Write-ColorOutput "`nGenerating controller provider code..." "Cyan"
199
+
200
+ # Sort controllers alphabetically for consistent output
201
+ $sortedControllers = $Controllers | Sort-Object -Property Domain
202
+
203
+ $sb = [System.Text.StringBuilder]::new()
204
+ [void]$sb.AppendLine("package provider")
205
+ [void]$sb.AppendLine()
206
+ [void]$sb.AppendLine("import `"$ModulePath`"")
207
+ [void]$sb.AppendLine()
208
+
209
+ # Interface
210
+ [void]$sb.AppendLine("type ControllerProvider interface {")
211
+ foreach ($ctrl in $sortedControllers) {
212
+ $line = "`tProvide$($ctrl.Domain)() controllers.$($ctrl.Domain)"
213
+ [void]$sb.AppendLine($line)
214
+ }
215
+ [void]$sb.AppendLine("}")
216
+ [void]$sb.AppendLine()
217
+
218
+ # Struct
219
+ [void]$sb.AppendLine("type controllerProvider struct {")
220
+ foreach ($ctrl in $sortedControllers) {
221
+ $line = "`t$($ctrl.VarName) controllers.$($ctrl.Domain)"
222
+ [void]$sb.AppendLine($line)
223
+ }
224
+ [void]$sb.AppendLine("}")
225
+ [void]$sb.AppendLine()
226
+
227
+ # Constructor
228
+ [void]$sb.AppendLine("func NewControllerProvider(servicesProvider ServicesProvider) ControllerProvider {")
229
+ [void]$sb.AppendLine()
230
+
231
+ # Initialize controllers
232
+ foreach ($ctrl in $sortedControllers) {
233
+ $args = @()
234
+ foreach ($param in $ctrl.Parameters) {
235
+ $args += Resolve-ControllerArgument $param
236
+ }
237
+ $argsStr = $args -join ", "
238
+ $line = "`t$($ctrl.VarName) := controllers.$($ctrl.ConstructorName)($argsStr)"
239
+ [void]$sb.AppendLine($line)
240
+ }
241
+
242
+ [void]$sb.AppendLine("`treturn &controllerProvider{")
243
+ foreach ($ctrl in $sortedControllers) {
244
+ $line = "`t`t$($ctrl.VarName): $($ctrl.VarName),"
245
+ [void]$sb.AppendLine($line)
246
+ }
247
+ [void]$sb.AppendLine("`t}")
248
+ [void]$sb.AppendLine("}")
249
+ [void]$sb.AppendLine()
250
+
251
+ # Getter methods
252
+ [void]$sb.AppendLine("// --- Getter Methods ---")
253
+ [void]$sb.AppendLine()
254
+ foreach ($ctrl in $sortedControllers) {
255
+ [void]$sb.AppendLine("func (c *controllerProvider) Provide$($ctrl.Domain)() controllers.$($ctrl.Domain) {")
256
+ [void]$sb.AppendLine("`treturn c.$($ctrl.VarName)")
257
+ [void]$sb.AppendLine("}")
258
+ [void]$sb.AppendLine()
259
+ }
260
+
261
+ return $sb.ToString()
262
+ }
263
+
264
+ function Write-ProviderFile {
265
+ param([string]$Code, [string]$OutputPath)
266
+
267
+ Write-ColorOutput "Writing to $OutputPath..." "Cyan"
268
+
269
+ # Ensure directory exists
270
+ $dir = Split-Path $OutputPath -Parent
271
+ if ($dir -and -not (Test-Path $dir)) {
272
+ New-Item -ItemType Directory -Path $dir -Force | Out-Null
273
+ }
274
+
275
+ # Write file as UTF-8 without BOM
276
+ $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
277
+ [System.IO.File]::WriteAllText($OutputPath, $Code, $utf8NoBom)
278
+
279
+ Write-ColorOutput " Successfully generated $OutputPath" "Green"
280
+ }
281
+
282
+ # ============================================
283
+ # MAIN EXECUTION
284
+ # ============================================
285
+
286
+ try {
287
+ Write-ColorOutput "`n=========================================" "Blue"
288
+ Write-ColorOutput " Go Controller Provider Generator v1.0" "Blue"
289
+ Write-ColorOutput "=========================================`n" "Blue"
290
+
291
+ # Step 1: Parse all controller constructors
292
+ $controllers = Parse-GoFiles -Directory $ControllersDir
293
+
294
+ # Step 2: Generate provider code
295
+ $code = Generate-ProviderCode -Controllers $controllers
296
+
297
+ # Step 3: Write to file
298
+ Write-ProviderFile -Code $code -OutputPath $OutputFile
299
+
300
+ Write-ColorOutput "`nSUCCESS! Controller provider generated successfully.`n" "Green"
301
+ Write-ColorOutput "Next steps:" "Cyan"
302
+ Write-ColorOutput " 1. Review $OutputFile" "White"
303
+ Write-ColorOutput " 2. Fill any /* TODO: provide ... */ placeholders" "White"
304
+ Write-ColorOutput " 3. Run: go build ./provider" "White"
305
+
306
+ } catch {
307
+ Write-ColorOutput "`nERROR: $($_.Exception.Message)" "Red"
308
+ Write-ColorOutput "Stack trace: $($_.ScriptStackTrace)" "Yellow"
309
+ exit 1
310
+ }
cmd/do_inject_middleware.ps1 ADDED
@@ -0,0 +1,312 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #Requires -Version 5.1
2
+ <#
3
+ .SYNOPSIS
4
+ Automatic Dependency Injection Generator for Go Middleware
5
+
6
+ .DESCRIPTION
7
+ Scans ./middleware/ directory, discovers all middleware constructors, infers their dependencies,
8
+ and generates provider/middleware_provider.go with full DI wiring.
9
+
10
+ .EXAMPLE
11
+ .\middleware_injector.ps1
12
+
13
+ .NOTES
14
+ - Works with PowerShell 5.1+ and PowerShell 7+
15
+ - No external dependencies required
16
+ - Supports multi-line constructor signatures
17
+ - Middleware depend on services from ServicesProvider
18
+ #>
19
+
20
+ [CmdletBinding()]
21
+ param()
22
+
23
+ # Configuration
24
+ $MiddlewareDir = "./middleware"
25
+ $OutputFile = "provider/middleware_provider.go"
26
+ $ModulePath = "abdanhafidz.com/go-boilerplate/middleware"
27
+
28
+ # ANSI colors for better output
29
+ $script:UseColors = $Host.UI.SupportsVirtualTerminal
30
+ function Write-ColorOutput {
31
+ param([string]$Message, [string]$Color = "White")
32
+ if ($script:UseColors) {
33
+ $colors = @{
34
+ "Green" = "`e[32m"; "Yellow" = "`e[33m"; "Red" = "`e[31m"
35
+ "Cyan" = "`e[36m"; "Blue" = "`e[34m"; "Reset" = "`e[0m"
36
+ }
37
+ Write-Host "$($colors[$Color])$Message$($colors['Reset'])"
38
+ } else {
39
+ Write-Host $Message
40
+ }
41
+ }
42
+
43
+ # Data structures
44
+ class MiddlewareInfo {
45
+ [string]$ConstructorName # NewAuthenticationMiddleware
46
+ [string]$Domain # AuthenticationMiddleware
47
+ [string]$VarName # authenticationMiddleware
48
+ [System.Collections.Generic.List[Parameter]]$Parameters
49
+
50
+ MiddlewareInfo() {
51
+ $this.Parameters = [System.Collections.Generic.List[Parameter]]::new()
52
+ }
53
+ }
54
+
55
+ class Parameter {
56
+ [string]$Name
57
+ [string]$RawType
58
+ [string]$NormalizedType
59
+ }
60
+
61
+ function Get-LowerCamelCase {
62
+ param([string]$Text)
63
+ if ($Text.Length -eq 0) { return $Text }
64
+ return $Text.Substring(0, 1).ToLower() + $Text.Substring(1)
65
+ }
66
+
67
+ function Normalize-TypeName {
68
+ param([string]$TypeStr)
69
+
70
+ # Remove leading pointer
71
+ $cleaned = $TypeStr -replace '^\*+', ''
72
+
73
+ # Remove package prefix (everything before last dot)
74
+ if ($cleaned -match '\.([^.]+)$') {
75
+ $cleaned = $matches[1]
76
+ }
77
+
78
+ return $cleaned.Trim()
79
+ }
80
+
81
+ function Parse-GoFiles {
82
+ param([string]$Directory)
83
+
84
+ Write-ColorOutput "Scanning for middleware constructors in $Directory..." "Cyan"
85
+
86
+ if (-not (Test-Path $Directory)) {
87
+ Write-ColorOutput "ERROR: Directory '$Directory' not found!" "Red"
88
+ exit 1
89
+ }
90
+
91
+ $goFiles = Get-ChildItem -Path $Directory -Filter "*.go" -Recurse -File
92
+ $middlewares = [System.Collections.Generic.List[MiddlewareInfo]]::new()
93
+
94
+ foreach ($file in $goFiles) {
95
+ $content = Get-Content $file.FullName -Raw
96
+
97
+ # Match function signatures (support multi-line)
98
+ # Pattern: func NewXxxMiddleware(...) XxxMiddleware
99
+ $pattern = '(?ms)func\s+(New[a-zA-Z0-9]+Middleware)\s*\(([^)]*)\)\s+([a-zA-Z0-9*_.]+Middleware)'
100
+ $matches = [regex]::Matches($content, $pattern)
101
+
102
+ foreach ($match in $matches) {
103
+ $constructorName = $match.Groups[1].Value
104
+ $paramsStr = $match.Groups[2].Value
105
+ $returnType = $match.Groups[3].Value
106
+
107
+ # Extract domain name (XxxMiddleware)
108
+ $domain = Normalize-TypeName $returnType
109
+ $varName = Get-LowerCamelCase $domain
110
+
111
+ $middleware = [MiddlewareInfo]::new()
112
+ $middleware.ConstructorName = $constructorName
113
+ $middleware.Domain = $domain
114
+ $middleware.VarName = $varName
115
+
116
+ # Parse parameters
117
+ if ($paramsStr.Trim() -ne "") {
118
+ # Split by comma, but be careful with nested types
119
+ $paramList = $paramsStr -split ',\s*(?![^<>]*>)'
120
+
121
+ foreach ($param in $paramList) {
122
+ $param = $param.Trim()
123
+ if ($param -eq "") { continue }
124
+
125
+ # Split into name and type
126
+ $parts = $param -split '\s+', 2
127
+
128
+ $p = [Parameter]::new()
129
+ if ($parts.Count -eq 2) {
130
+ $p.Name = $parts[0]
131
+ $p.RawType = $parts[1]
132
+ } elseif ($parts.Count -eq 1) {
133
+ # Anonymous parameter - synthesize name
134
+ $p.Name = "param$($middleware.Parameters.Count)"
135
+ $p.RawType = $parts[0]
136
+ } else {
137
+ continue
138
+ }
139
+
140
+ $p.NormalizedType = Normalize-TypeName $p.RawType
141
+ $middleware.Parameters.Add($p)
142
+ }
143
+ }
144
+
145
+ $middlewares.Add($middleware)
146
+ Write-ColorOutput " Found: $constructorName" "Green"
147
+ }
148
+ }
149
+
150
+ if ($middlewares.Count -eq 0) {
151
+ Write-ColorOutput "No middleware constructors found matching pattern 'NewXxxMiddleware'!" "Red"
152
+ exit 1
153
+ }
154
+
155
+ Write-ColorOutput "`nTotal middleware discovered: $($middlewares.Count)" "Blue"
156
+ return $middlewares
157
+ }
158
+
159
+ function Resolve-MiddlewareArgument {
160
+ param([Parameter]$Param)
161
+
162
+ $type = $Param.NormalizedType
163
+
164
+ # DEPENDENCY RESOLUTION RULES
165
+ # ============================================
166
+
167
+ # 1. Service pattern: XxxxService -> servicesProvider.ProvideXxxxService()
168
+ if ($type -match '^(.+)Service$') {
169
+ $serviceName = $type
170
+ return "servicesProvider.Provide${serviceName}()"
171
+ }
172
+
173
+ # ADD MORE SPECIAL CASES HERE:
174
+ # --------------------------------------------
175
+ # Example: Config dependency
176
+ # if ($type -eq "Config") {
177
+ # return "configProvider.ProvideConfig()"
178
+ # }
179
+ #
180
+ # Example: Logger
181
+ # if ($type -eq "Logger") {
182
+ # return "loggerProvider.ProvideLogger()"
183
+ # }
184
+ #
185
+ # Example: JWT Config
186
+ # if ($type -eq "JWTConfig") {
187
+ # return "configProvider.ProvideJWTConfig()"
188
+ # }
189
+ #
190
+ # Example: Database
191
+ # if ($type -eq "DB" -or $type -eq "Database") {
192
+ # return "dbProvider.ProvideDatabase()"
193
+ # }
194
+ # --------------------------------------------
195
+
196
+ # 2. Fallback: unresolved type
197
+ return "/* TODO: provide $($Param.RawType) */"
198
+ }
199
+
200
+ function Generate-ProviderCode {
201
+ param([System.Collections.Generic.List[MiddlewareInfo]]$Middlewares)
202
+
203
+ Write-ColorOutput "`nGenerating middleware provider code..." "Cyan"
204
+
205
+ # Sort middleware alphabetically for consistent output
206
+ $sortedMiddlewares = $Middlewares | Sort-Object -Property Domain
207
+
208
+ $sb = [System.Text.StringBuilder]::new()
209
+ [void]$sb.AppendLine("package provider")
210
+ [void]$sb.AppendLine()
211
+ [void]$sb.AppendLine("import `"$ModulePath`"")
212
+ [void]$sb.AppendLine()
213
+
214
+ # Interface
215
+ [void]$sb.AppendLine("type MiddlewareProvider interface {")
216
+ foreach ($mw in $sortedMiddlewares) {
217
+ $line = "`tProvide$($mw.Domain)() middleware.$($mw.Domain)"
218
+ [void]$sb.AppendLine($line)
219
+ }
220
+ [void]$sb.AppendLine("}")
221
+ [void]$sb.AppendLine()
222
+
223
+ # Struct
224
+ [void]$sb.AppendLine("type middlewareProvider struct {")
225
+ foreach ($mw in $sortedMiddlewares) {
226
+ $line = "`t$($mw.VarName) middleware.$($mw.Domain)"
227
+ [void]$sb.AppendLine($line)
228
+ }
229
+ [void]$sb.AppendLine("}")
230
+ [void]$sb.AppendLine()
231
+
232
+ # Constructor
233
+ [void]$sb.AppendLine("func NewMiddlewareProvider(servicesProvider ServicesProvider) MiddlewareProvider {")
234
+
235
+ # Initialize middleware
236
+ foreach ($mw in $sortedMiddlewares) {
237
+ $args = @()
238
+ foreach ($param in $mw.Parameters) {
239
+ $args += Resolve-MiddlewareArgument $param
240
+ }
241
+ $argsStr = $args -join ", "
242
+ $line = "`t$($mw.VarName) := middleware.$($mw.ConstructorName)($argsStr)"
243
+ [void]$sb.AppendLine($line)
244
+ }
245
+
246
+ [void]$sb.AppendLine("`treturn &middlewareProvider{")
247
+ foreach ($mw in $sortedMiddlewares) {
248
+ $line = "`t`t$($mw.VarName): $($mw.VarName),"
249
+ [void]$sb.AppendLine($line)
250
+ }
251
+ [void]$sb.AppendLine("`t}")
252
+ [void]$sb.AppendLine("}")
253
+ [void]$sb.AppendLine()
254
+
255
+ # Getter methods
256
+ foreach ($mw in $sortedMiddlewares) {
257
+ [void]$sb.AppendLine("func (p *middlewareProvider) Provide$($mw.Domain)() middleware.$($mw.Domain) {")
258
+ [void]$sb.AppendLine("`treturn p.$($mw.VarName)")
259
+ [void]$sb.AppendLine("}")
260
+ [void]$sb.AppendLine()
261
+ }
262
+
263
+ return $sb.ToString()
264
+ }
265
+
266
+ function Write-ProviderFile {
267
+ param([string]$Code, [string]$OutputPath)
268
+
269
+ Write-ColorOutput "Writing to $OutputPath..." "Cyan"
270
+
271
+ # Ensure directory exists
272
+ $dir = Split-Path $OutputPath -Parent
273
+ if ($dir -and -not (Test-Path $dir)) {
274
+ New-Item -ItemType Directory -Path $dir -Force | Out-Null
275
+ }
276
+
277
+ # Write file as UTF-8 without BOM
278
+ $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
279
+ [System.IO.File]::WriteAllText($OutputPath, $Code, $utf8NoBom)
280
+
281
+ Write-ColorOutput " Successfully generated $OutputPath" "Green"
282
+ }
283
+
284
+ # ============================================
285
+ # MAIN EXECUTION
286
+ # ============================================
287
+
288
+ try {
289
+ Write-ColorOutput "`n=========================================" "Blue"
290
+ Write-ColorOutput " Go Middleware Provider Generator v1.0" "Blue"
291
+ Write-ColorOutput "=========================================`n" "Blue"
292
+
293
+ # Step 1: Parse all middleware constructors
294
+ $middlewares = Parse-GoFiles -Directory $MiddlewareDir
295
+
296
+ # Step 2: Generate provider code
297
+ $code = Generate-ProviderCode -Middlewares $middlewares
298
+
299
+ # Step 3: Write to file
300
+ Write-ProviderFile -Code $code -OutputPath $OutputFile
301
+
302
+ Write-ColorOutput "`nSUCCESS! Middleware provider generated successfully.`n" "Green"
303
+ Write-ColorOutput "Next steps:" "Cyan"
304
+ Write-ColorOutput " 1. Review $OutputFile" "White"
305
+ Write-ColorOutput " 2. Fill any /* TODO: provide ... */ placeholders" "White"
306
+ Write-ColorOutput " 3. Run: go build ./provider" "White"
307
+
308
+ } catch {
309
+ Write-ColorOutput "`nERROR: $($_.Exception.Message)" "Red"
310
+ Write-ColorOutput "Stack trace: $($_.ScriptStackTrace)" "Yellow"
311
+ exit 1
312
+ }
cmd/do_inject_repository.ps1 ADDED
@@ -0,0 +1,327 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #Requires -Version 5.1
2
+ <#
3
+ .SYNOPSIS
4
+ Automatic Dependency Injection Generator for Go Repositories
5
+
6
+ .DESCRIPTION
7
+ Scans ./repositories/ directory, discovers all repository constructors,
8
+ and generates provider/repositories_provider.go with full DI wiring.
9
+ Repositories typically depend on database connection from ConfigProvider.
10
+
11
+ .EXAMPLE
12
+ .\repository_injector.ps1
13
+
14
+ .NOTES
15
+ - Works with PowerShell 5.1+ and PowerShell 7+
16
+ - No external dependencies required
17
+ - Supports multi-line constructor signatures
18
+ - Repositories depend on database instance from ConfigProvider
19
+ #>
20
+
21
+ [CmdletBinding()]
22
+ param()
23
+
24
+ # Configuration
25
+ $RepositoriesDir = "./repositories"
26
+ $OutputFile = "provider/repositories_provider.go"
27
+ $ModulePath = "abdanhafidz.com/go-boilerplate/repositories"
28
+
29
+ # ANSI colors for better output
30
+ $script:UseColors = $Host.UI.SupportsVirtualTerminal
31
+ function Write-ColorOutput {
32
+ param([string]$Message, [string]$Color = "White")
33
+ if ($script:UseColors) {
34
+ $colors = @{
35
+ "Green" = "`e[32m"; "Yellow" = "`e[33m"; "Red" = "`e[31m"
36
+ "Cyan" = "`e[36m"; "Blue" = "`e[34m"; "Reset" = "`e[0m"
37
+ }
38
+ Write-Host "$($colors[$Color])$Message$($colors['Reset'])"
39
+ } else {
40
+ Write-Host $Message
41
+ }
42
+ }
43
+
44
+ # Data structures
45
+ class RepositoryInfo {
46
+ [string]$ConstructorName # NewAccountRepository
47
+ [string]$Domain # AccountRepository
48
+ [string]$VarName # accountRepository
49
+ [System.Collections.Generic.List[Parameter]]$Parameters
50
+
51
+ RepositoryInfo() {
52
+ $this.Parameters = [System.Collections.Generic.List[Parameter]]::new()
53
+ }
54
+ }
55
+
56
+ class Parameter {
57
+ [string]$Name
58
+ [string]$RawType
59
+ [string]$NormalizedType
60
+ }
61
+
62
+ function Get-LowerCamelCase {
63
+ param([string]$Text)
64
+ if ($Text.Length -eq 0) { return $Text }
65
+ return $Text.Substring(0, 1).ToLower() + $Text.Substring(1)
66
+ }
67
+
68
+ function Normalize-TypeName {
69
+ param([string]$TypeStr)
70
+
71
+ # Remove leading pointer
72
+ $cleaned = $TypeStr -replace '^\*+', ''
73
+
74
+ # Remove package prefix (everything before last dot)
75
+ if ($cleaned -match '\.([^.]+)$') {
76
+ $cleaned = $matches[1]
77
+ }
78
+
79
+ return $cleaned.Trim()
80
+ }
81
+
82
+ function Parse-GoFiles {
83
+ param([string]$Directory)
84
+
85
+ Write-ColorOutput "Scanning for repository constructors in $Directory..." "Cyan"
86
+
87
+ if (-not (Test-Path $Directory)) {
88
+ Write-ColorOutput "ERROR: Directory '$Directory' not found!" "Red"
89
+ exit 1
90
+ }
91
+
92
+ $goFiles = Get-ChildItem -Path $Directory -Filter "*.go" -Recurse -File
93
+ $repositories = [System.Collections.Generic.List[RepositoryInfo]]::new()
94
+
95
+ foreach ($file in $goFiles) {
96
+ $content = Get-Content $file.FullName -Raw
97
+
98
+ # Match function signatures (support multi-line)
99
+ # Pattern: func NewXxxRepository(...) XxxRepository
100
+ $pattern = '(?ms)func\s+(New[a-zA-Z0-9]+Repository)\s*\(([^)]*)\)\s+([a-zA-Z0-9*_.]+Repository)'
101
+ $matches = [regex]::Matches($content, $pattern)
102
+
103
+ foreach ($match in $matches) {
104
+ $constructorName = $match.Groups[1].Value
105
+ $paramsStr = $match.Groups[2].Value
106
+ $returnType = $match.Groups[3].Value
107
+
108
+ # Extract domain name (XxxRepository)
109
+ $domain = Normalize-TypeName $returnType
110
+ $varName = Get-LowerCamelCase $domain
111
+
112
+ $repo = [RepositoryInfo]::new()
113
+ $repo.ConstructorName = $constructorName
114
+ $repo.Domain = $domain
115
+ $repo.VarName = $varName
116
+
117
+ # Parse parameters
118
+ if ($paramsStr.Trim() -ne "") {
119
+ # Split by comma, but be careful with nested types
120
+ $paramList = $paramsStr -split ',\s*(?![^<>]*>)'
121
+
122
+ foreach ($param in $paramList) {
123
+ $param = $param.Trim()
124
+ if ($param -eq "") { continue }
125
+
126
+ # Split into name and type
127
+ $parts = $param -split '\s+', 2
128
+
129
+ $p = [Parameter]::new()
130
+ if ($parts.Count -eq 2) {
131
+ $p.Name = $parts[0]
132
+ $p.RawType = $parts[1]
133
+ } elseif ($parts.Count -eq 1) {
134
+ # Anonymous parameter - synthesize name
135
+ $p.Name = "param$($repo.Parameters.Count)"
136
+ $p.RawType = $parts[0]
137
+ } else {
138
+ continue
139
+ }
140
+
141
+ $p.NormalizedType = Normalize-TypeName $p.RawType
142
+ $repo.Parameters.Add($p)
143
+ }
144
+ }
145
+
146
+ $repositories.Add($repo)
147
+ Write-ColorOutput " Found: $constructorName" "Green"
148
+ }
149
+ }
150
+
151
+ if ($repositories.Count -eq 0) {
152
+ Write-ColorOutput "No repository constructors found matching pattern 'NewXxxRepository'!" "Red"
153
+ exit 1
154
+ }
155
+
156
+ Write-ColorOutput "`nTotal repositories discovered: $($repositories.Count)" "Blue"
157
+ return $repositories
158
+ }
159
+
160
+ function Resolve-RepositoryArgument {
161
+ param([Parameter]$Param)
162
+
163
+ $type = $Param.NormalizedType
164
+ $paramName = $Param.Name
165
+
166
+ # DEPENDENCY RESOLUTION RULES FOR REPOSITORIES
167
+ # ============================================
168
+
169
+ # 1. Database connection patterns
170
+ if ($type -match '^(DB|Database|Gorm|SqlDB|Connection)$' -or $paramName -match '^(db|database|conn|connection)$') {
171
+ return "db"
172
+ }
173
+
174
+ # 2. *gorm.DB (most common in Go GORM projects)
175
+ if ($Param.RawType -match 'gorm\.DB' -or $type -eq "DB") {
176
+ return "db"
177
+ }
178
+
179
+ # 3. *sql.DB (standard library)
180
+ if ($Param.RawType -match 'sql\.DB') {
181
+ return "db"
182
+ }
183
+
184
+ # ADD MORE SPECIAL CASES HERE:
185
+ # --------------------------------------------
186
+ # Example: Redis connection
187
+ # if ($type -eq "RedisClient" -or $paramName -match "redis") {
188
+ # return "redisClient"
189
+ # }
190
+ #
191
+ # Example: MongoDB connection
192
+ # if ($type -eq "MongoClient" -or $paramName -match "mongo") {
193
+ # return "mongoClient"
194
+ # }
195
+ #
196
+ # Example: Cache
197
+ # if ($type -eq "Cache" -or $paramName -match "cache") {
198
+ # return "cache"
199
+ # }
200
+ #
201
+ # Example: Logger
202
+ # if ($type -eq "Logger" -or $paramName -match "logger") {
203
+ # return "logger"
204
+ # }
205
+ # --------------------------------------------
206
+
207
+ # 4. Fallback: unresolved type
208
+ return "/* TODO: provide $($Param.RawType) */"
209
+ }
210
+
211
+ function Generate-ProviderCode {
212
+ param([System.Collections.Generic.List[RepositoryInfo]]$Repositories)
213
+
214
+ Write-ColorOutput "`nGenerating repositories provider code..." "Cyan"
215
+
216
+ # Sort repositories alphabetically for consistent output
217
+ $sortedRepos = $Repositories | Sort-Object -Property Domain
218
+
219
+ $sb = [System.Text.StringBuilder]::new()
220
+ [void]$sb.AppendLine("package provider")
221
+ [void]$sb.AppendLine()
222
+ [void]$sb.AppendLine("import `"$ModulePath`"")
223
+ [void]$sb.AppendLine()
224
+
225
+ # Interface
226
+ [void]$sb.AppendLine("type RepositoriesProvider interface {")
227
+ foreach ($repo in $sortedRepos) {
228
+ $line = "`tProvide$($repo.Domain)() repositories.$($repo.Domain)"
229
+ [void]$sb.AppendLine($line)
230
+ }
231
+ [void]$sb.AppendLine("}")
232
+ [void]$sb.AppendLine()
233
+
234
+ # Struct
235
+ [void]$sb.AppendLine("type repositoriesProvider struct {")
236
+ foreach ($repo in $sortedRepos) {
237
+ $line = "`t$($repo.VarName) repositories.$($repo.Domain)"
238
+ [void]$sb.AppendLine($line)
239
+ }
240
+ [void]$sb.AppendLine("}")
241
+ [void]$sb.AppendLine()
242
+
243
+ # Constructor
244
+ [void]$sb.AppendLine("func NewRepositoriesProvider(cfg ConfigProvider) RepositoriesProvider {")
245
+ [void]$sb.AppendLine("`tdbConfig := cfg.ProvideDatabaseConfig()")
246
+ [void]$sb.AppendLine("`tdb := dbConfig.GetInstance()")
247
+ [void]$sb.AppendLine()
248
+
249
+ # Initialize repositories
250
+ foreach ($repo in $sortedRepos) {
251
+ $args = @()
252
+ foreach ($param in $repo.Parameters) {
253
+ $args += Resolve-RepositoryArgument $param
254
+ }
255
+ $argsStr = $args -join ", "
256
+ $line = "`t$($repo.VarName) := repositories.$($repo.ConstructorName)($argsStr)"
257
+ [void]$sb.AppendLine($line)
258
+ }
259
+
260
+ [void]$sb.AppendLine()
261
+ [void]$sb.AppendLine("`treturn &repositoriesProvider{")
262
+ foreach ($repo in $sortedRepos) {
263
+ $line = "`t`t$($repo.VarName): $($repo.VarName),"
264
+ [void]$sb.AppendLine($line)
265
+ }
266
+ [void]$sb.AppendLine("`t}")
267
+ [void]$sb.AppendLine("}")
268
+ [void]$sb.AppendLine()
269
+
270
+ # Getter methods
271
+ foreach ($repo in $sortedRepos) {
272
+ [void]$sb.AppendLine("func (r *repositoriesProvider) Provide$($repo.Domain)() repositories.$($repo.Domain) {")
273
+ [void]$sb.AppendLine("`treturn r.$($repo.VarName)")
274
+ [void]$sb.AppendLine("}")
275
+ [void]$sb.AppendLine()
276
+ }
277
+
278
+ return $sb.ToString()
279
+ }
280
+
281
+ function Write-ProviderFile {
282
+ param([string]$Code, [string]$OutputPath)
283
+
284
+ Write-ColorOutput "Writing to $OutputPath..." "Cyan"
285
+
286
+ # Ensure directory exists
287
+ $dir = Split-Path $OutputPath -Parent
288
+ if ($dir -and -not (Test-Path $dir)) {
289
+ New-Item -ItemType Directory -Path $dir -Force | Out-Null
290
+ }
291
+
292
+ # Write file as UTF-8 without BOM
293
+ $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
294
+ [System.IO.File]::WriteAllText($OutputPath, $Code, $utf8NoBom)
295
+
296
+ Write-ColorOutput " Successfully generated $OutputPath" "Green"
297
+ }
298
+
299
+ # ============================================
300
+ # MAIN EXECUTION
301
+ # ============================================
302
+
303
+ try {
304
+ Write-ColorOutput "`n=========================================" "Blue"
305
+ Write-ColorOutput " Go Repository Provider Generator v1.0" "Blue"
306
+ Write-ColorOutput "=========================================`n" "Blue"
307
+
308
+ # Step 1: Parse all repository constructors
309
+ $repositories = Parse-GoFiles -Directory $RepositoriesDir
310
+
311
+ # Step 2: Generate provider code
312
+ $code = Generate-ProviderCode -Repositories $repositories
313
+
314
+ # Step 3: Write to file
315
+ Write-ProviderFile -Code $code -OutputPath $OutputFile
316
+
317
+ Write-ColorOutput "`nSUCCESS! Repositories provider generated successfully.`n" "Green"
318
+ Write-ColorOutput "Next steps:" "Cyan"
319
+ Write-ColorOutput " 1. Review $OutputFile" "White"
320
+ Write-ColorOutput " 2. Fill any /* TODO: provide ... */ placeholders" "White"
321
+ Write-ColorOutput " 3. Run: go build ./provider" "White"
322
+
323
+ } catch {
324
+ Write-ColorOutput "`nERROR: $($_.Exception.Message)" "Red"
325
+ Write-ColorOutput "Stack trace: $($_.ScriptStackTrace)" "Yellow"
326
+ exit 1
327
+ }
cmd/do_inject_services.ps1 ADDED
@@ -0,0 +1,383 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #Requires -Version 5.1
2
+ <#
3
+ .SYNOPSIS
4
+ Automatic Dependency Injection Generator for Go Services
5
+
6
+ .DESCRIPTION
7
+ Scans ./services/ directory, discovers service constructors, resolves dependencies,
8
+ performs topological sorting, and generates provider/services_provider.go with full DI wiring.
9
+
10
+ .EXAMPLE
11
+ .\service_injector.ps1
12
+
13
+ .NOTES
14
+ - Works with PowerShell 5.1+ and PowerShell 7+
15
+ - No external dependencies required
16
+ - Supports multi-line constructor signatures
17
+ - Handles dependency cycles with clear error messages
18
+ #>
19
+
20
+ [CmdletBinding()]
21
+ param()
22
+
23
+ # Configuration
24
+ $ServicesDir = "./services"
25
+ $OutputFile = "provider/services_provider.go"
26
+ $ModulePath = "abdanhafidz.com/go-boilerplate/services"
27
+
28
+ # ANSI colors for better output (fallback to plain text if not supported)
29
+ $script:UseColors = $Host.UI.SupportsVirtualTerminal
30
+ function Write-ColorOutput {
31
+ param([string]$Message, [string]$Color = "White")
32
+ if ($script:UseColors) {
33
+ $colors = @{
34
+ "Green" = "`e[32m"; "Yellow" = "`e[33m"; "Red" = "`e[31m"
35
+ "Cyan" = "`e[36m"; "Blue" = "`e[34m"; "Reset" = "`e[0m"
36
+ }
37
+ Write-Host "$($colors[$Color])$Message$($colors['Reset'])"
38
+ } else {
39
+ Write-Host $Message
40
+ }
41
+ }
42
+
43
+ # Data structures
44
+ class ServiceInfo {
45
+ [string]$ConstructorName # NewAccountService
46
+ [string]$Domain # AccountService
47
+ [string]$VarName # accountService
48
+ [System.Collections.Generic.List[Parameter]]$Parameters
49
+ [System.Collections.Generic.List[string]]$ServiceDependencies
50
+
51
+ ServiceInfo() {
52
+ $this.Parameters = [System.Collections.Generic.List[Parameter]]::new()
53
+ $this.ServiceDependencies = [System.Collections.Generic.List[string]]::new()
54
+ }
55
+ }
56
+
57
+ class Parameter {
58
+ [string]$Name
59
+ [string]$RawType
60
+ [string]$NormalizedType
61
+ }
62
+
63
+ function Get-LowerCamelCase {
64
+ param([string]$Text)
65
+ if ($Text.Length -eq 0) { return $Text }
66
+ return $Text.Substring(0, 1).ToLower() + $Text.Substring(1)
67
+ }
68
+
69
+ function Normalize-TypeName {
70
+ param([string]$TypeStr)
71
+
72
+ # Remove leading pointer
73
+ $cleaned = $TypeStr -replace '^\*+', ''
74
+
75
+ # Remove package prefix (everything before last dot)
76
+ if ($cleaned -match '\.([^.]+)$') {
77
+ $cleaned = $matches[1]
78
+ }
79
+
80
+ return $cleaned.Trim()
81
+ }
82
+
83
+ function Parse-GoFiles {
84
+ param([string]$Directory)
85
+
86
+ Write-ColorOutput "Scanning for service constructors in $Directory..." "Cyan"
87
+
88
+ if (-not (Test-Path $Directory)) {
89
+ Write-ColorOutput "ERROR: Directory '$Directory' not found!" "Red"
90
+ exit 1
91
+ }
92
+
93
+ $goFiles = Get-ChildItem -Path $Directory -Filter "*.go" -Recurse -File
94
+ $services = [System.Collections.Generic.List[ServiceInfo]]::new()
95
+
96
+ foreach ($file in $goFiles) {
97
+ $content = Get-Content $file.FullName -Raw
98
+
99
+ # Match function signatures (support multi-line)
100
+ # Pattern: func NewXxxService(...) XxxService
101
+ $pattern = '(?ms)func\s+(New[a-zA-Z0-9]+Service)\s*\(([^)]*)\)\s+([a-zA-Z0-9*_.]+Service)'
102
+ $matches = [regex]::Matches($content, $pattern)
103
+
104
+ foreach ($match in $matches) {
105
+ $constructorName = $match.Groups[1].Value
106
+ $paramsStr = $match.Groups[2].Value
107
+ $returnType = $match.Groups[3].Value
108
+
109
+ # Extract domain name (XxxService)
110
+ $domain = Normalize-TypeName $returnType
111
+ $varName = Get-LowerCamelCase $domain
112
+
113
+ $service = [ServiceInfo]::new()
114
+ $service.ConstructorName = $constructorName
115
+ $service.Domain = $domain
116
+ $service.VarName = $varName
117
+
118
+ # Parse parameters
119
+ if ($paramsStr.Trim() -ne "") {
120
+ # Split by comma, but be careful with nested types
121
+ $paramList = $paramsStr -split ',\s*(?![^<>]*>)'
122
+
123
+ foreach ($param in $paramList) {
124
+ $param = $param.Trim()
125
+ if ($param -eq "") { continue }
126
+
127
+ # Split into name and type
128
+ $parts = $param -split '\s+', 2
129
+
130
+ $p = [Parameter]::new()
131
+ if ($parts.Count -eq 2) {
132
+ $p.Name = $parts[0]
133
+ $p.RawType = $parts[1]
134
+ } elseif ($parts.Count -eq 1) {
135
+ # Anonymous parameter - synthesize name
136
+ $p.Name = "param$($service.Parameters.Count)"
137
+ $p.RawType = $parts[0]
138
+ } else {
139
+ continue
140
+ }
141
+
142
+ $p.NormalizedType = Normalize-TypeName $p.RawType
143
+ $service.Parameters.Add($p)
144
+
145
+ # Track service dependencies
146
+ if ($p.NormalizedType -match 'Service$') {
147
+ $service.ServiceDependencies.Add($p.NormalizedType)
148
+ }
149
+ }
150
+ }
151
+
152
+ $services.Add($service)
153
+ Write-ColorOutput " Found: $constructorName" "Green"
154
+ }
155
+ }
156
+
157
+ if ($services.Count -eq 0) {
158
+ Write-ColorOutput "No service constructors found matching pattern 'NewXxxService'!" "Red"
159
+ exit 1
160
+ }
161
+
162
+ Write-ColorOutput "`nTotal services discovered: $($services.Count)" "Blue"
163
+ return $services
164
+ }
165
+
166
+ function Get-TopologicalOrder {
167
+ param([System.Collections.Generic.List[ServiceInfo]]$Services)
168
+
169
+ Write-ColorOutput "`nBuilding dependency graph..." "Cyan"
170
+
171
+ # Build adjacency list
172
+ $graph = @{}
173
+ $inDegree = @{}
174
+ $domainToService = @{}
175
+
176
+ foreach ($svc in $Services) {
177
+ $graph[$svc.Domain] = [System.Collections.Generic.List[string]]::new()
178
+ $inDegree[$svc.Domain] = 0
179
+ $domainToService[$svc.Domain] = $svc
180
+ }
181
+
182
+ # Build edges
183
+ foreach ($svc in $Services) {
184
+ foreach ($dep in $svc.ServiceDependencies) {
185
+ if ($graph.ContainsKey($dep)) {
186
+ $graph[$dep].Add($svc.Domain)
187
+ $inDegree[$svc.Domain]++
188
+ }
189
+ }
190
+ }
191
+
192
+ # Kahn's algorithm for topological sort
193
+ $queue = [System.Collections.Generic.Queue[string]]::new()
194
+ foreach ($domain in $inDegree.Keys) {
195
+ if ($inDegree[$domain] -eq 0) {
196
+ $queue.Enqueue($domain)
197
+ }
198
+ }
199
+
200
+ $sorted = [System.Collections.Generic.List[string]]::new()
201
+
202
+ while ($queue.Count -gt 0) {
203
+ $current = $queue.Dequeue()
204
+ $sorted.Add($current)
205
+
206
+ foreach ($neighbor in $graph[$current]) {
207
+ $inDegree[$neighbor]--
208
+ if ($inDegree[$neighbor] -eq 0) {
209
+ $queue.Enqueue($neighbor)
210
+ }
211
+ }
212
+ }
213
+
214
+ # Check for cycles
215
+ if ($sorted.Count -ne $Services.Count) {
216
+ $remaining = $inDegree.Keys | Where-Object { $inDegree[$_] -gt 0 }
217
+ Write-ColorOutput "`nERROR: Circular dependency detected!" "Red"
218
+ Write-ColorOutput "Services involved in cycle: $($remaining -join ', ')" "Yellow"
219
+ exit 1
220
+ }
221
+
222
+ Write-ColorOutput " Dependency graph validated (no cycles)" "Green"
223
+ Write-ColorOutput " Topological order: $($sorted -join ' -> ')" "Blue"
224
+
225
+ # Return services in topological order
226
+ return $sorted | ForEach-Object { $domainToService[$_] }
227
+ }
228
+
229
+ function Resolve-ConstructorArgument {
230
+ param([Parameter]$Param)
231
+
232
+ $type = $Param.NormalizedType
233
+
234
+ # SPECIAL CASE MAPPINGS - Add more here as needed
235
+ # ============================================
236
+
237
+ # 1. JWT secret string
238
+ if ($Param.RawType -eq "string" -and $Param.Name -match "secret|key") {
239
+ return "configProvider.ProvideJWTConfig().GetSecretKey()"
240
+ }
241
+
242
+ # 2. Repository pattern: XxxxRepository -> repoProvider.ProvideXxxxRepository()
243
+ if ($type -match '^(.+)Repository$') {
244
+ $repoName = $matches[1]
245
+ return "repoProvider.Provide${repoName}Repository()"
246
+ }
247
+
248
+ # 3. Service pattern: XxxxService -> use variable (will be constructed before this)
249
+ if ($type -match 'Service$') {
250
+ return (Get-LowerCamelCase $type)
251
+ }
252
+
253
+ # ADD MORE SPECIAL CASES HERE:
254
+ # --------------------------------------------
255
+ # Example: Mail config
256
+ # if ($type -eq "MailConfig") {
257
+ # return "configProvider.ProvideMailConfig()"
258
+ # }
259
+ #
260
+ # Example: Redis client
261
+ # if ($type -eq "RedisClient") {
262
+ # return "configProvider.ProvideRedisClient()"
263
+ # }
264
+ # --------------------------------------------
265
+
266
+ # 4. Fallback: unresolved type
267
+ return "/* TODO: provide $($Param.RawType) */"
268
+ }
269
+
270
+ function Generate-ProviderCode {
271
+ param([System.Collections.Generic.List[ServiceInfo]]$ServicesInOrder)
272
+
273
+ Write-ColorOutput "`nGenerating provider code..." "Cyan"
274
+
275
+ $sb = [System.Text.StringBuilder]::new()
276
+ [void]$sb.AppendLine("package provider")
277
+ [void]$sb.AppendLine()
278
+ [void]$sb.AppendLine("import `"$ModulePath`"")
279
+ [void]$sb.AppendLine()
280
+
281
+ # Interface
282
+ [void]$sb.AppendLine("type ServicesProvider interface {")
283
+ foreach ($svc in $ServicesInOrder) {
284
+ $line = "`tProvide$($svc.Domain)() services.$($svc.Domain)"
285
+ [void]$sb.AppendLine($line)
286
+ }
287
+ [void]$sb.AppendLine("}")
288
+ [void]$sb.AppendLine()
289
+
290
+ # Struct
291
+ [void]$sb.AppendLine("type servicesProvider struct {")
292
+ foreach ($svc in $ServicesInOrder) {
293
+ $line = "`t$($svc.VarName) services.$($svc.Domain)"
294
+ [void]$sb.AppendLine($line)
295
+ }
296
+ [void]$sb.AppendLine("}")
297
+ [void]$sb.AppendLine()
298
+
299
+ # Constructor
300
+ [void]$sb.AppendLine("func NewServicesProvider(repoProvider RepositoriesProvider, configProvider ConfigProvider) ServicesProvider {")
301
+
302
+ # Initialize services in topological order
303
+ foreach ($svc in $ServicesInOrder) {
304
+ $args = @()
305
+ foreach ($param in $svc.Parameters) {
306
+ $args += Resolve-ConstructorArgument $param
307
+ }
308
+ $argsStr = $args -join ", "
309
+ $line = "`t$($svc.VarName) := services.$($svc.ConstructorName)($argsStr)"
310
+ [void]$sb.AppendLine($line)
311
+ }
312
+
313
+ [void]$sb.AppendLine()
314
+ [void]$sb.AppendLine("`treturn &servicesProvider{")
315
+ foreach ($svc in $ServicesInOrder) {
316
+ $line = "`t`t$($svc.VarName): $($svc.VarName),"
317
+ [void]$sb.AppendLine($line)
318
+ }
319
+ [void]$sb.AppendLine("`t}")
320
+ [void]$sb.AppendLine("}")
321
+ [void]$sb.AppendLine()
322
+
323
+ # Getter methods
324
+ foreach ($svc in $ServicesInOrder) {
325
+ [void]$sb.AppendLine("func (s *servicesProvider) Provide$($svc.Domain)() services.$($svc.Domain) {")
326
+ [void]$sb.AppendLine("`treturn s.$($svc.VarName)")
327
+ [void]$sb.AppendLine("}")
328
+ [void]$sb.AppendLine()
329
+ }
330
+
331
+ return $sb.ToString()
332
+ }
333
+
334
+ function Write-ProviderFile {
335
+ param([string]$Code, [string]$OutputPath)
336
+
337
+ Write-ColorOutput "Writing to $OutputPath..." "Cyan"
338
+
339
+ # Ensure directory exists
340
+ $dir = Split-Path $OutputPath -Parent
341
+ if ($dir -and -not (Test-Path $dir)) {
342
+ New-Item -ItemType Directory -Path $dir -Force | Out-Null
343
+ }
344
+
345
+ # Write file as UTF-8 without BOM
346
+ $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
347
+ [System.IO.File]::WriteAllText($OutputPath, $Code, $utf8NoBom)
348
+
349
+ Write-ColorOutput " Successfully generated $OutputPath" "Green"
350
+ }
351
+
352
+ # ============================================
353
+ # MAIN EXECUTION
354
+ # ============================================
355
+
356
+ try {
357
+ Write-ColorOutput "`n=========================================" "Blue"
358
+ Write-ColorOutput " Go Service Dependency Injector v1.0" "Blue"
359
+ Write-ColorOutput "=========================================`n" "Blue"
360
+
361
+ # Step 1: Parse all service constructors
362
+ $services = Parse-GoFiles -Directory $ServicesDir
363
+
364
+ # Step 2: Perform topological sort
365
+ $sortedServices = Get-TopologicalOrder -Services $services
366
+
367
+ # Step 3: Generate provider code
368
+ $code = Generate-ProviderCode -ServicesInOrder $sortedServices
369
+
370
+ # Step 4: Write to file
371
+ Write-ProviderFile -Code $code -OutputPath $OutputFile
372
+
373
+ Write-ColorOutput "`nSUCCESS! Provider generated successfully.`n" "Green"
374
+ Write-ColorOutput "Next steps:" "Cyan"
375
+ Write-ColorOutput " 1. Review $OutputFile" "White"
376
+ Write-ColorOutput " 2. Fill any /* TODO: provide ... */ placeholders" "White"
377
+ Write-ColorOutput " 3. Run: go build ./provider" "White"
378
+
379
+ } catch {
380
+ Write-ColorOutput "`nERROR: $($_.Exception.Message)" "Red"
381
+ Write-ColorOutput "Stack trace: $($_.ScriptStackTrace)" "Yellow"
382
+ exit 1
383
+ }
config/config.go ADDED
@@ -0,0 +1 @@
 
 
1
+ package config
config/database_config.go ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package config
2
+
3
+ import (
4
+ "fmt"
5
+ "log"
6
+
7
+ "gorm.io/driver/postgres"
8
+ "gorm.io/gorm"
9
+ )
10
+
11
+ type DatabaseConfig interface {
12
+ AutoMigrateAll(entities ...interface{}) error
13
+ GetInstance() *gorm.DB
14
+ }
15
+ type databaseConfig struct {
16
+ db *gorm.DB
17
+ }
18
+
19
+ func NewDatabaseConfig(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, DB_PORT string) DatabaseConfig {
20
+ dsn := fmt.Sprintf(
21
+ "host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Jakarta ",
22
+ DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, DB_PORT,
23
+ )
24
+
25
+ db, err := gorm.Open(postgres.New(postgres.Config{
26
+ DSN: dsn,
27
+ PreferSimpleProtocol: true, // required for PgBouncer/Supabase pooled connections
28
+ }), &gorm.Config{
29
+ TranslateError: true,
30
+ })
31
+
32
+ db = db.Session(&gorm.Session{
33
+ PrepareStmt: false,
34
+ })
35
+
36
+ if err != nil {
37
+ log.Fatal("Failed to connect to database:", err)
38
+ }
39
+
40
+ return &databaseConfig{db: db}
41
+ }
42
+
43
+ func (cfg *databaseConfig) AutoMigrateAll(entities ...interface{}) error {
44
+
45
+ err := cfg.db.AutoMigrate(
46
+ entities...,
47
+ )
48
+
49
+ return err
50
+
51
+ }
52
+
53
+ func (cfg *databaseConfig) GetInstance() *gorm.DB {
54
+ return cfg.db
55
+ }
config/env_config.go ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package config
2
+
3
+ import (
4
+ "os"
5
+ "strconv"
6
+ "strings"
7
+ "abdanhafidz.com/go-boilerplate/utils"
8
+ "github.com/joho/godotenv"
9
+ )
10
+
11
+ type EnvConfig interface {
12
+ GetTCPAddress() string
13
+ GetLogPath() string
14
+ GetHostAddress() string
15
+ GetHostPort() string
16
+ GetEmailVerificationDuration() int
17
+ GetDatabaseHost() string
18
+ GetDatabasePort() string
19
+ GetDatabaseUser() string
20
+ GetDatabasePassword() string
21
+ GetDatabaseName() string
22
+ GetSalt() string
23
+ GetSupabaseURL() string
24
+ GetSupabaseKey() string
25
+ GetSupabaseBucket() string
26
+ GetXenditAPIKey() string
27
+ GetXenditCallbackToken() string
28
+ }
29
+
30
+ type envConfig struct {
31
+ timezone string
32
+ }
33
+
34
+ func NewEnvConfig(timezone string) EnvConfig {
35
+ godotenv.Load()
36
+ os.Setenv("TZ", timezone)
37
+ return &envConfig{
38
+ timezone: timezone,
39
+ }
40
+ }
41
+
42
+ func (e *envConfig) GetTCPAddress() string {
43
+ return utils.GetEnv("HOST_ADDRESS") + ":" + utils.GetEnv("HOST_PORT")
44
+ }
45
+
46
+ func (e *envConfig) GetLogPath() string {
47
+ return utils.GetEnv("LOG_PATH")
48
+ }
49
+
50
+ func (e *envConfig) GetHostAddress() string {
51
+ return utils.GetEnv("HOST_ADDRESS")
52
+ }
53
+
54
+ func (e *envConfig) GetHostPort() string {
55
+ return utils.GetEnv("HOST_PORT")
56
+ }
57
+
58
+ func (e *envConfig) GetEmailVerificationDuration() int {
59
+ duration, err := strconv.Atoi(utils.GetEnv("EMAIL_VERIFICATION_DURATION"))
60
+ if err != nil {
61
+ return 0 // Default value if parsing fails
62
+ }
63
+ return duration
64
+ }
65
+
66
+ func (e *envConfig) GetDatabaseHost() string {
67
+ return utils.GetEnv("DB_HOST")
68
+ }
69
+
70
+ func (e *envConfig) GetDatabasePort() string {
71
+ return utils.GetEnv("DB_PORT")
72
+ }
73
+
74
+ func (e *envConfig) GetDatabaseUser() string {
75
+ return utils.GetEnv("DB_USER")
76
+ }
77
+
78
+ func (e *envConfig) GetDatabasePassword() string {
79
+ return utils.GetEnv("DB_PASSWORD")
80
+ }
81
+
82
+ func (e *envConfig) GetDatabaseName() string {
83
+ return utils.GetEnv("DB_NAME")
84
+ }
85
+
86
+ func (e *envConfig) GetSalt() string {
87
+ salt := utils.GetEnv("SALT")
88
+ if salt == "" {
89
+ return "Def4u|7" // Default salt value
90
+ }
91
+ return salt
92
+ }
93
+
94
+ func (e *envConfig) GetSupabaseURL() string {
95
+ return strings.TrimSpace(utils.GetEnv("SUPABASE_URL"))
96
+ }
97
+
98
+ func (e *envConfig) GetSupabaseKey() string {
99
+ return strings.TrimSpace(utils.GetEnv("SUPABASE_SERVICE_KEY"))
100
+ }
101
+
102
+ func (e *envConfig) GetSupabaseBucket() string {
103
+ return strings.TrimSpace(utils.GetEnv("SUPABASE_BUCKET_NAME"))
104
+ }
105
+
106
+ func (e *envConfig) GetXenditAPIKey() string {
107
+ return strings.TrimSpace(utils.GetEnv("XENDIT_API_KEY"))
108
+ }
109
+
110
+ func (e *envConfig) GetXenditCallbackToken() string {
111
+ return strings.TrimSpace(utils.GetEnv("XENDIT_CALLBACK_TOKEN"))
112
+ }
config/jwt_config.go ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package config
2
+
3
+ type JWTConfig interface {
4
+ SetSecretKey(key string)
5
+ GetSecretKey() string
6
+ }
7
+
8
+ type jwtConfig struct {
9
+ secretKey string
10
+ }
11
+
12
+ func NewJWTConfig(secretKey string) JWTConfig {
13
+ return &jwtConfig{
14
+ secretKey: secretKey,
15
+ }
16
+ }
17
+
18
+ func (cfg *jwtConfig) SetSecretKey(key string) {
19
+ cfg.secretKey = key
20
+ }
21
+
22
+ func (cfg *jwtConfig) GetSecretKey() string {
23
+ return cfg.secretKey
24
+ }
config/supabase_config.go ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package config
2
+
3
+ type SupabaseConfig interface {
4
+ GetURL() string
5
+ GetServiceKey() string
6
+ GetBucketName() string
7
+ }
8
+
9
+ type supabaseConfig struct {
10
+ url string
11
+ serviceKey string
12
+ bucketName string
13
+ }
14
+
15
+ func NewSupabaseConfig(url string, key string, bucket string) SupabaseConfig {
16
+ return &supabaseConfig{
17
+ url: url,
18
+ serviceKey: key,
19
+ bucketName: bucket,
20
+ }
21
+ }
22
+
23
+ func (c *supabaseConfig) GetURL() string { return c.url }
24
+ func (c *supabaseConfig) GetServiceKey() string { return c.serviceKey }
25
+ func (c *supabaseConfig) GetBucketName() string { return c.bucketName }
config/upload_config.go ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package config
2
+
3
+ import (
4
+ models "abdanhafidz.com/go-boilerplate/models/entity"
5
+ http_error "abdanhafidz.com/go-boilerplate/models/error"
6
+ )
7
+
8
+ type UploadRule struct {
9
+ MaxBytes int64
10
+ AllowedExts map[string]bool
11
+ PathPrefix string
12
+ MaxCount int
13
+ }
14
+
15
+ type UploadConfig interface {
16
+ Get(contextType string) (UploadRule, error)
17
+ }
18
+
19
+ type uploadConfig struct{}
20
+
21
+ func NewUploadConfig() UploadConfig { return &uploadConfig{} }
22
+
23
+ func (c *uploadConfig) Get(contextType string) (UploadRule, error) {
24
+ codeExts := map[string]bool{".cpp": true, ".c": true, ".py": true, ".java": true, ".go": true, ".js": true, ".txt": true}
25
+ imgExts := map[string]bool{".jpg": true, ".jpeg": true, ".png": true, ".webp": true, ".gif": true}
26
+ docExts := map[string]bool{".pdf": true, ".doc": true, ".docx": true}
27
+
28
+ allExts := make(map[string]bool)
29
+ for k, v := range codeExts { allExts[k] = v }
30
+ for k, v := range imgExts { allExts[k] = v }
31
+ for k, v := range docExts { allExts[k] = v }
32
+
33
+ switch contextType {
34
+ case "image":
35
+ return UploadRule{ MaxBytes: 10 * models.MB, AllowedExts: imgExts, PathPrefix: "images", MaxCount: 5 }, nil
36
+ case "material":
37
+ return UploadRule{ MaxBytes: 10 * models.MB, AllowedExts: docExts, PathPrefix: "materials", MaxCount: 1 }, nil
38
+ case "submission":
39
+ return UploadRule{ MaxBytes: 1 * models.MB, AllowedExts: codeExts, PathPrefix: "submissions", MaxCount: 1 }, nil
40
+ case "general":
41
+ return UploadRule{ MaxBytes: 5 * models.MB, AllowedExts: allExts, PathPrefix: "temp", MaxCount: 5 }, nil
42
+ default:
43
+ return UploadRule{}, http_error.INVALID_UPLOAD_CONTEXT_ERROR
44
+ }
45
+ }
46
+
config/xendit_config.go ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package config
2
+
3
+ import (
4
+ "sync"
5
+
6
+ xendit "github.com/xendit/xendit-go/v7"
7
+ )
8
+
9
+ var (
10
+ xenditOnce sync.Once
11
+ )
12
+
13
+ type XenditConfig interface {
14
+ GetClient() *xendit.APIClient
15
+ }
16
+
17
+ type xenditConfig struct {
18
+ envConfig EnvConfig
19
+ client *xendit.APIClient
20
+ }
21
+
22
+ func NewXenditConfig(envConfig EnvConfig) XenditConfig {
23
+ return &xenditConfig{
24
+ envConfig: envConfig,
25
+ client: xendit.NewClient(envConfig.GetXenditAPIKey()),
26
+ }
27
+ }
28
+
29
+ func (c *xenditConfig) GetClient() *xendit.APIClient {
30
+ return c.client
31
+ }
controllers/authentication_controller.go ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controllers
2
+
3
+ import (
4
+ "abdanhafidz.com/go-boilerplate/models/dto"
5
+ entity "abdanhafidz.com/go-boilerplate/models/entity"
6
+ "abdanhafidz.com/go-boilerplate/services"
7
+ "github.com/gin-gonic/gin"
8
+ )
9
+
10
+ type AuthenticationController interface {
11
+ SignUp(ctx *gin.Context)
12
+ SignIn(ctx *gin.Context)
13
+ ChangePassword(ctx *gin.Context)
14
+ UpdateUserRole(ctx *gin.Context)
15
+ }
16
+
17
+ type authenticationController struct {
18
+ userService services.UserService
19
+ }
20
+
21
+ func NewAuthenticationController(userService services.UserService) AuthenticationController {
22
+ return &authenticationController{
23
+ userService: userService,
24
+ }
25
+ }
26
+
27
+ func (c *authenticationController) SignUp(ctx *gin.Context) {
28
+ req := RequestJSON[dto.SignUpRequest](ctx)
29
+ res, err := c.userService.Create(ctx.Request.Context(), req.Name, req.Email, req.Username, req.Password)
30
+ ResponseJSON(ctx, req, res, err)
31
+ }
32
+
33
+ func (c *authenticationController) SignIn(ctx *gin.Context) {
34
+ req := RequestJSON[dto.SignInRequest](ctx)
35
+ res, err := c.userService.Validate(ctx, req.EmailorUsername, req.Password)
36
+ ResponseJSON(ctx, req, res, err)
37
+ }
38
+
39
+ func (c *authenticationController) ChangePassword(ctx *gin.Context) {
40
+ req := RequestJSON[dto.ChangePasswordRequest](ctx)
41
+ userId := ParseUserId(ctx)
42
+ res, err := c.userService.ChangePassword(ctx.Request.Context(), userId, req.OldPassword, req.NewPassword)
43
+ ResponseJSON(ctx, req, res, err)
44
+ }
45
+
46
+ func (c *authenticationController) UpdateUserRole(ctx *gin.Context) {
47
+ req := RequestJSON[dto.UpdateUserRoleRequest](ctx)
48
+ userId := ctx.Param("userId")
49
+ user, err := c.userService.GetById(ctx.Request.Context(), userId)
50
+ if err != nil {
51
+ ResponseJSON(ctx, req, entity.User{}, err)
52
+ return
53
+ }
54
+
55
+ // For now, don't implement full Role update logic (requires roles association in schema)
56
+ // Just stub it
57
+ res, err := c.userService.Update(ctx.Request.Context(), user)
58
+ ResponseJSON(ctx, req, res, err)
59
+ }
controllers/controller.go ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controllers
2
+
3
+ import (
4
+ http_error "abdanhafidz.com/go-boilerplate/models/error"
5
+ "abdanhafidz.com/go-boilerplate/utils"
6
+ "github.com/gin-gonic/gin"
7
+ uuid "github.com/google/uuid"
8
+ )
9
+
10
+ func ParseUserId(ctx *gin.Context) string {
11
+ guserId, _ := ctx.Get("user_id")
12
+ userId, ok := guserId.(string)
13
+ if !ok {
14
+ ResponseJSON(ctx, gin.H{"user_id": userId}, "", http_error.INVALID_TOKEN)
15
+ return ""
16
+ }
17
+ return userId
18
+ }
19
+
20
+ func ParseUUID(ctx *gin.Context, attrName string) uuid.UUID {
21
+ uuidRaw, _ := ctx.Get(attrName)
22
+ uuidParsed, err := utils.ToUUID(uuidRaw)
23
+
24
+ if err != nil {
25
+ ResponseJSON(ctx, gin.H{"id": uuidParsed}, uuid.UUID{}, http_error.INVALID_TOKEN)
26
+ return uuid.UUID{}
27
+ }
28
+ return uuidParsed
29
+ }
30
+ func RequestJSON[TRequest any](ctx *gin.Context) TRequest {
31
+ var request TRequest
32
+ if err := ctx.ShouldBindJSON(&request); err != nil {
33
+ utils.ResponseFAILED(ctx, request, http_error.BAD_REQUEST_ERROR)
34
+ ctx.Abort()
35
+ return request
36
+ } else {
37
+ return request
38
+ }
39
+ }
40
+
41
+ func RequestForm[TRequest any](ctx *gin.Context) TRequest {
42
+ var request TRequest
43
+ if err := ctx.ShouldBind(&request); err != nil {
44
+ utils.ResponseFAILED(ctx, request, http_error.BAD_REQUEST_ERROR)
45
+ ctx.Abort()
46
+ return request
47
+ }
48
+ return request
49
+ }
50
+
51
+ func ResponseJSON[TResponse any, TMetaData any](ctx *gin.Context, metaData TMetaData, res TResponse, err error) {
52
+ utils.SendResponse(ctx, metaData, res, err)
53
+ }
controllers/home_controller.go ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controllers
2
+
3
+ import (
4
+ "abdanhafidz.com/go-boilerplate/services"
5
+ "github.com/gin-gonic/gin"
6
+ )
7
+
8
+ type HomeController interface {
9
+ GetHomeData(ctx *gin.Context)
10
+ GetSchedules(ctx *gin.Context)
11
+ GetEventCategories(ctx *gin.Context)
12
+ GetClassCategories(ctx *gin.Context)
13
+ }
14
+
15
+ type homeController struct {
16
+ homeService services.HomeService
17
+ }
18
+
19
+ func NewHomeController(homeService services.HomeService) HomeController {
20
+ return &homeController{homeService: homeService}
21
+ }
22
+
23
+ // GetHomeData godoc
24
+ // @Summary Get Home Page Data
25
+ // @Description Return all sections needed by the home page in one aggregated response.
26
+ // @Tags Home Dashboard
27
+ // @Accept json
28
+ // @Produce json
29
+ // @Param date query string false "Selected date for schedule card section (YYYY-MM-DD)"
30
+ // @Param latitude query number false "User latitude"
31
+ // @Param longitude query number false "User longitude"
32
+ // @Param city query string false "User city"
33
+ // @Success 200 {object} dto.HomeResponseWrapper
34
+ // @Failure 400 {object} dto.ErrorResponse
35
+ // @Security BearerAuth
36
+ // @Router /api/v1/home [get]
37
+ func (c *homeController) GetHomeData(ctx *gin.Context) {
38
+ userId := ParseUserId(ctx)
39
+
40
+ params := map[string]interface{}{
41
+ "date": ctx.Query("date"),
42
+ "latitude": ctx.Query("latitude"),
43
+ "longitude": ctx.Query("longitude"),
44
+ "city": ctx.Query("city"),
45
+ }
46
+
47
+ res, err := c.homeService.GetHomeData(ctx.Request.Context(), userId, params)
48
+ ResponseJSON(ctx, "", res, err)
49
+ }
50
+
51
+ // GetSchedules godoc
52
+ // @Summary Get Schedule List
53
+ // @Description Get a list of class or event schedules based on date range, city, filters, etc.
54
+ // @Tags Home Dashboard
55
+ // @Accept json
56
+ // @Produce json
57
+ // @Param date query string false "Exact date (YYYY-MM-DD)"
58
+ // @Param start_date query string false "Start date range (YYYY-MM-DD)"
59
+ // @Param end_date query string false "End date range (YYYY-MM-DD)"
60
+ // @Param city query string false "Filter by city"
61
+ // @Param level query string false "Filter by class level (e.g., beginner)"
62
+ // @Param category query string false "Filter by class category (e.g., salsa)"
63
+ // @Param page query int false "Page number (default: 1)"
64
+ // @Param limit query int false "Items per page (default: 10)"
65
+ // @Success 200 {object} dto.ScheduleListResponseWrapper
66
+ // @Failure 400 {object} dto.ErrorResponse
67
+ // @Security BearerAuth
68
+ // @Router /api/v1/schedules [get]
69
+ func (c *homeController) GetSchedules(ctx *gin.Context) {
70
+ userId := ParseUserId(ctx)
71
+
72
+ params := map[string]interface{}{
73
+ "date": ctx.Query("date"),
74
+ "start_date": ctx.Query("start_date"),
75
+ "end_date": ctx.Query("end_date"),
76
+ "city": ctx.Query("city"),
77
+ "level": ctx.Query("level"),
78
+ "category": ctx.Query("category"),
79
+ "page": ctx.Query("page"),
80
+ "limit": ctx.Query("limit"),
81
+ }
82
+
83
+ res, err := c.homeService.GetSchedules(ctx.Request.Context(), userId, params)
84
+ ResponseJSON(ctx, "", res, err)
85
+ }
86
+
87
+ // GetEventCategories godoc
88
+ // @Summary Get Event Categories
89
+ // @Description Get master list of event categories
90
+ // @Tags Master Data
91
+ // @Accept json
92
+ // @Produce json
93
+ // @Success 200 {object} dto.CategoryResponseWrapper
94
+ // @Failure 400 {object} dto.ErrorResponse
95
+ // @Router /api/v1/event-categories [get]
96
+ func (c *homeController) GetEventCategories(ctx *gin.Context) {
97
+ res, err := c.homeService.GetEventCategories(ctx.Request.Context())
98
+ ResponseJSON(ctx, "", res, err)
99
+ }
100
+
101
+ // GetClassCategories godoc
102
+ // @Summary Get Class Categories
103
+ // @Description Get master list of class categories
104
+ // @Tags Master Data
105
+ // @Accept json
106
+ // @Produce json
107
+ // @Success 200 {object} dto.CategoryResponseWrapper
108
+ // @Failure 400 {object} dto.ErrorResponse
109
+ // @Router /api/v1/class-categories [get]
110
+ func (c *homeController) GetClassCategories(ctx *gin.Context) {
111
+ res, err := c.homeService.GetClassCategories(ctx.Request.Context())
112
+ ResponseJSON(ctx, "", res, err)
113
+ }
controllers/otp_verification_controller.go ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controllers
2
+
3
+ import (
4
+ "abdanhafidz.com/go-boilerplate/services"
5
+ "github.com/gin-gonic/gin"
6
+ )
7
+
8
+ type OTPVerificationController interface {
9
+ RequestOTP(ctx *gin.Context)
10
+ VerifyOTP(ctx *gin.Context)
11
+ }
12
+
13
+ type otpVerificationController struct {
14
+ otpService services.OTPVerificationService
15
+ }
16
+
17
+ func NewOTPVerificationController(otpService services.OTPVerificationService) OTPVerificationController {
18
+ return &otpVerificationController{
19
+ otpService: otpService,
20
+ }
21
+ }
22
+
23
+ type RequestOTPPayload struct {
24
+ PhoneNumber string `json:"phone_number" binding:"required"`
25
+ }
26
+
27
+ type VerifyOTPPayload struct {
28
+ PhoneNumber string `json:"phone_number" binding:"required"`
29
+ Code string `json:"code" binding:"required"`
30
+ }
31
+
32
+ func (c *otpVerificationController) RequestOTP(ctx *gin.Context) {
33
+ req := RequestJSON[RequestOTPPayload](ctx)
34
+ err := c.otpService.RequestOTP(ctx.Request.Context(), req.PhoneNumber)
35
+ ResponseJSON(ctx, req, "", err)
36
+ }
37
+
38
+ func (c *otpVerificationController) VerifyOTP(ctx *gin.Context) {
39
+ req := RequestJSON[VerifyOTPPayload](ctx)
40
+ err := c.otpService.VerifyOTP(ctx.Request.Context(), req.PhoneNumber, req.Code)
41
+ ResponseJSON(ctx, req, "", err)
42
+ }
controllers/payment_callback_controller.go ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controllers
2
+
3
+ import (
4
+ "log"
5
+
6
+ "abdanhafidz.com/go-boilerplate/models/dto"
7
+ http_error "abdanhafidz.com/go-boilerplate/models/error"
8
+ "abdanhafidz.com/go-boilerplate/services"
9
+ "abdanhafidz.com/go-boilerplate/utils"
10
+ "github.com/gin-gonic/gin"
11
+ )
12
+
13
+ type PaymentCallbackController interface {
14
+ HandleCallback(ctx *gin.Context)
15
+ }
16
+
17
+ type paymentCallbackController struct {
18
+ paymentService services.PaymentService
19
+ }
20
+
21
+ func NewPaymentCallbackController(
22
+ paymentService services.PaymentService,
23
+ ) PaymentCallbackController {
24
+ return &paymentCallbackController{
25
+ paymentService: paymentService,
26
+ }
27
+ }
28
+
29
+ // Handle Payment Callback godoc
30
+ // @Summary Handle Xendit Payment Callback
31
+ // @Description Receive and process payment status updates from Xendit
32
+ // @Tags Payment
33
+ // @Accept json
34
+ // @Produce json
35
+ // @Param request body map[string]interface{} true "Xendit Callback Payload"
36
+ // @Success 200 {object} dto.SuccessResponse[any]
37
+ // @Failure 400 {object} dto.ErrorResponse
38
+ // @Router /api/v1/payment/callback [post]
39
+ func (c *paymentCallbackController) HandleCallback(ctx *gin.Context) {
40
+ // Xendit sends JSON payload
41
+ // Basic structure for Invoice Callback:
42
+ // { "id": "...", "external_id": "...", "status": "PAID", ... }
43
+ var callbackData map[string]interface{}
44
+ if err := ctx.ShouldBindJSON(&callbackData); err != nil {
45
+ utils.ResponseFAILED(ctx, gin.H(nil), http_error.BAD_REQUEST_ERROR)
46
+ return
47
+ }
48
+
49
+ log.Printf("Payment Callback Received: %+v", callbackData)
50
+
51
+ status, ok := callbackData["status"].(string)
52
+ if !ok {
53
+ // Not a status update or unknown format
54
+ var _ dto.SuccessResponse[any]
55
+
56
+ ResponseJSON(ctx, gin.H(nil), "Ignored: No status", nil)
57
+ return
58
+ }
59
+
60
+ invoiceId, _ := callbackData["id"].(string)
61
+
62
+ if status == "PAID" || status == "SETTLED" {
63
+ // Handle Event Payment
64
+ // We need a method in Service to handle "ConfirmPayment" by InvoiceID
65
+ // But existing services don't have it.
66
+ // Let's add it to PaymentService? Yes.
67
+ err := c.paymentService.ConfirmPayment(ctx.Request.Context(), invoiceId)
68
+ if err != nil {
69
+ log.Printf("Payment Confirmation Failed: %v", err)
70
+ // Don't return error to Xendit if logic failed, but maybe we should?
71
+ // Xendit expects 200 OK.
72
+ }
73
+ } else if status == "EXPIRED" {
74
+ c.paymentService.ExpirePayment(ctx.Request.Context(), invoiceId)
75
+ }
76
+
77
+ // Always return 200 to Xendit
78
+ ResponseJSON(ctx, gin.H(nil), gin.H{"callback": "Callback Received"}, nil)
79
+ }
docs/docs.go ADDED
@@ -0,0 +1,705 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Package docs Code generated by swaggo/swag. DO NOT EDIT
2
+ package docs
3
+
4
+ import "github.com/swaggo/swag"
5
+
6
+ const docTemplate = `{
7
+ "schemes": {{ marshal .Schemes }},
8
+ "swagger": "2.0",
9
+ "info": {
10
+ "description": "{{escape .Description}}",
11
+ "title": "{{.Title}}",
12
+ "contact": {},
13
+ "version": "{{.Version}}"
14
+ },
15
+ "host": "{{.Host}}",
16
+ "basePath": "{{.BasePath}}",
17
+ "paths": {
18
+ "/api/v1/class-categories": {
19
+ "get": {
20
+ "description": "Get master list of class categories",
21
+ "consumes": [
22
+ "application/json"
23
+ ],
24
+ "produces": [
25
+ "application/json"
26
+ ],
27
+ "tags": [
28
+ "Master Data"
29
+ ],
30
+ "summary": "Get Class Categories",
31
+ "responses": {
32
+ "200": {
33
+ "description": "OK",
34
+ "schema": {
35
+ "$ref": "#/definitions/dto.CategoryResponseWrapper"
36
+ }
37
+ },
38
+ "400": {
39
+ "description": "Bad Request",
40
+ "schema": {
41
+ "$ref": "#/definitions/dto.ErrorResponse"
42
+ }
43
+ }
44
+ }
45
+ }
46
+ },
47
+ "/api/v1/event-categories": {
48
+ "get": {
49
+ "description": "Get master list of event categories",
50
+ "consumes": [
51
+ "application/json"
52
+ ],
53
+ "produces": [
54
+ "application/json"
55
+ ],
56
+ "tags": [
57
+ "Master Data"
58
+ ],
59
+ "summary": "Get Event Categories",
60
+ "responses": {
61
+ "200": {
62
+ "description": "OK",
63
+ "schema": {
64
+ "$ref": "#/definitions/dto.CategoryResponseWrapper"
65
+ }
66
+ },
67
+ "400": {
68
+ "description": "Bad Request",
69
+ "schema": {
70
+ "$ref": "#/definitions/dto.ErrorResponse"
71
+ }
72
+ }
73
+ }
74
+ }
75
+ },
76
+ "/api/v1/home": {
77
+ "get": {
78
+ "security": [
79
+ {
80
+ "BearerAuth": []
81
+ }
82
+ ],
83
+ "description": "Return all sections needed by the home page in one aggregated response.",
84
+ "consumes": [
85
+ "application/json"
86
+ ],
87
+ "produces": [
88
+ "application/json"
89
+ ],
90
+ "tags": [
91
+ "Home Dashboard"
92
+ ],
93
+ "summary": "Get Home Page Data",
94
+ "parameters": [
95
+ {
96
+ "type": "string",
97
+ "description": "Selected date for schedule card section (YYYY-MM-DD)",
98
+ "name": "date",
99
+ "in": "query"
100
+ },
101
+ {
102
+ "type": "number",
103
+ "description": "User latitude",
104
+ "name": "latitude",
105
+ "in": "query"
106
+ },
107
+ {
108
+ "type": "number",
109
+ "description": "User longitude",
110
+ "name": "longitude",
111
+ "in": "query"
112
+ },
113
+ {
114
+ "type": "string",
115
+ "description": "User city",
116
+ "name": "city",
117
+ "in": "query"
118
+ }
119
+ ],
120
+ "responses": {
121
+ "200": {
122
+ "description": "OK",
123
+ "schema": {
124
+ "$ref": "#/definitions/dto.HomeResponseWrapper"
125
+ }
126
+ },
127
+ "400": {
128
+ "description": "Bad Request",
129
+ "schema": {
130
+ "$ref": "#/definitions/dto.ErrorResponse"
131
+ }
132
+ }
133
+ }
134
+ }
135
+ },
136
+ "/api/v1/payment/callback": {
137
+ "post": {
138
+ "description": "Receive and process payment status updates from Xendit",
139
+ "consumes": [
140
+ "application/json"
141
+ ],
142
+ "produces": [
143
+ "application/json"
144
+ ],
145
+ "tags": [
146
+ "Payment"
147
+ ],
148
+ "summary": "Handle Xendit Payment Callback",
149
+ "parameters": [
150
+ {
151
+ "description": "Xendit Callback Payload",
152
+ "name": "request",
153
+ "in": "body",
154
+ "required": true,
155
+ "schema": {
156
+ "type": "object",
157
+ "additionalProperties": true
158
+ }
159
+ }
160
+ ],
161
+ "responses": {
162
+ "200": {
163
+ "description": "OK",
164
+ "schema": {
165
+ "$ref": "#/definitions/dto.SuccessResponse-any"
166
+ }
167
+ },
168
+ "400": {
169
+ "description": "Bad Request",
170
+ "schema": {
171
+ "$ref": "#/definitions/dto.ErrorResponse"
172
+ }
173
+ }
174
+ }
175
+ }
176
+ },
177
+ "/api/v1/schedules": {
178
+ "get": {
179
+ "security": [
180
+ {
181
+ "BearerAuth": []
182
+ }
183
+ ],
184
+ "description": "Get a list of class or event schedules based on date range, city, filters, etc.",
185
+ "consumes": [
186
+ "application/json"
187
+ ],
188
+ "produces": [
189
+ "application/json"
190
+ ],
191
+ "tags": [
192
+ "Home Dashboard"
193
+ ],
194
+ "summary": "Get Schedule List",
195
+ "parameters": [
196
+ {
197
+ "type": "string",
198
+ "description": "Exact date (YYYY-MM-DD)",
199
+ "name": "date",
200
+ "in": "query"
201
+ },
202
+ {
203
+ "type": "string",
204
+ "description": "Start date range (YYYY-MM-DD)",
205
+ "name": "start_date",
206
+ "in": "query"
207
+ },
208
+ {
209
+ "type": "string",
210
+ "description": "End date range (YYYY-MM-DD)",
211
+ "name": "end_date",
212
+ "in": "query"
213
+ },
214
+ {
215
+ "type": "string",
216
+ "description": "Filter by city",
217
+ "name": "city",
218
+ "in": "query"
219
+ },
220
+ {
221
+ "type": "string",
222
+ "description": "Filter by class level (e.g., beginner)",
223
+ "name": "level",
224
+ "in": "query"
225
+ },
226
+ {
227
+ "type": "string",
228
+ "description": "Filter by class category (e.g., salsa)",
229
+ "name": "category",
230
+ "in": "query"
231
+ },
232
+ {
233
+ "type": "integer",
234
+ "description": "Page number (default: 1)",
235
+ "name": "page",
236
+ "in": "query"
237
+ },
238
+ {
239
+ "type": "integer",
240
+ "description": "Items per page (default: 10)",
241
+ "name": "limit",
242
+ "in": "query"
243
+ }
244
+ ],
245
+ "responses": {
246
+ "200": {
247
+ "description": "OK",
248
+ "schema": {
249
+ "$ref": "#/definitions/dto.ScheduleListResponseWrapper"
250
+ }
251
+ },
252
+ "400": {
253
+ "description": "Bad Request",
254
+ "schema": {
255
+ "$ref": "#/definitions/dto.ErrorResponse"
256
+ }
257
+ }
258
+ }
259
+ }
260
+ }
261
+ },
262
+ "definitions": {
263
+ "dto.CategoryResponse": {
264
+ "type": "object",
265
+ "properties": {
266
+ "id": {
267
+ "type": "string"
268
+ },
269
+ "image_url": {
270
+ "type": "string"
271
+ },
272
+ "name": {
273
+ "type": "string"
274
+ }
275
+ }
276
+ },
277
+ "dto.CategoryResponseWrapper": {
278
+ "type": "object",
279
+ "properties": {
280
+ "data": {
281
+ "type": "array",
282
+ "items": {
283
+ "$ref": "#/definitions/dto.CategoryResponse"
284
+ }
285
+ },
286
+ "success": {
287
+ "type": "boolean"
288
+ }
289
+ }
290
+ },
291
+ "dto.ErrorResponse": {
292
+ "type": "object",
293
+ "properties": {
294
+ "errors": {},
295
+ "message": {},
296
+ "meta_data": {},
297
+ "status": {
298
+ "type": "string"
299
+ }
300
+ }
301
+ },
302
+ "dto.HomeBeginnerClassSection": {
303
+ "type": "object",
304
+ "properties": {
305
+ "id": {
306
+ "type": "string"
307
+ },
308
+ "image_url": {
309
+ "type": "string"
310
+ },
311
+ "name": {
312
+ "type": "string"
313
+ },
314
+ "type": {
315
+ "type": "string"
316
+ }
317
+ }
318
+ },
319
+ "dto.HomeDay": {
320
+ "type": "object",
321
+ "properties": {
322
+ "date": {
323
+ "type": "string"
324
+ },
325
+ "day_name_short": {
326
+ "type": "string"
327
+ },
328
+ "day_number": {
329
+ "type": "integer"
330
+ },
331
+ "has_schedule": {
332
+ "type": "boolean"
333
+ },
334
+ "is_selected": {
335
+ "type": "boolean"
336
+ }
337
+ }
338
+ },
339
+ "dto.HomeEventSection": {
340
+ "type": "object",
341
+ "properties": {
342
+ "id": {
343
+ "type": "string"
344
+ },
345
+ "image_url": {
346
+ "type": "string"
347
+ },
348
+ "name": {
349
+ "type": "string"
350
+ },
351
+ "type": {
352
+ "type": "string"
353
+ }
354
+ }
355
+ },
356
+ "dto.HomeHeroBanner": {
357
+ "type": "object",
358
+ "properties": {
359
+ "badge_text": {
360
+ "type": "string"
361
+ },
362
+ "cta_target_id": {
363
+ "type": "string"
364
+ },
365
+ "cta_text": {
366
+ "type": "string"
367
+ },
368
+ "cta_type": {
369
+ "type": "string"
370
+ },
371
+ "id": {
372
+ "type": "string"
373
+ },
374
+ "image_url": {
375
+ "type": "string"
376
+ },
377
+ "subtitle": {
378
+ "type": "string"
379
+ },
380
+ "title": {
381
+ "type": "string"
382
+ }
383
+ }
384
+ },
385
+ "dto.HomeNotifications": {
386
+ "type": "object",
387
+ "properties": {
388
+ "unread_count": {
389
+ "type": "integer"
390
+ }
391
+ }
392
+ },
393
+ "dto.HomePromoStrip": {
394
+ "type": "object",
395
+ "properties": {
396
+ "cta_target_id": {
397
+ "type": "string"
398
+ },
399
+ "cta_text": {
400
+ "type": "string"
401
+ },
402
+ "cta_type": {
403
+ "type": "string"
404
+ },
405
+ "id": {
406
+ "type": "string"
407
+ },
408
+ "text": {
409
+ "type": "string"
410
+ }
411
+ }
412
+ },
413
+ "dto.HomeResponse": {
414
+ "type": "object",
415
+ "properties": {
416
+ "beginner_class_sections": {
417
+ "type": "array",
418
+ "items": {
419
+ "$ref": "#/definitions/dto.HomeBeginnerClassSection"
420
+ }
421
+ },
422
+ "event_sections": {
423
+ "type": "array",
424
+ "items": {
425
+ "$ref": "#/definitions/dto.HomeEventSection"
426
+ }
427
+ },
428
+ "hero_banners": {
429
+ "type": "array",
430
+ "items": {
431
+ "$ref": "#/definitions/dto.HomeHeroBanner"
432
+ }
433
+ },
434
+ "notifications": {
435
+ "$ref": "#/definitions/dto.HomeNotifications"
436
+ },
437
+ "promo_strip": {
438
+ "$ref": "#/definitions/dto.HomePromoStrip"
439
+ },
440
+ "search": {
441
+ "$ref": "#/definitions/dto.HomeSearch"
442
+ },
443
+ "user": {
444
+ "$ref": "#/definitions/dto.HomeUser"
445
+ },
446
+ "week_schedule": {
447
+ "$ref": "#/definitions/dto.HomeWeekSchedule"
448
+ }
449
+ }
450
+ },
451
+ "dto.HomeResponseWrapper": {
452
+ "type": "object",
453
+ "properties": {
454
+ "data": {
455
+ "$ref": "#/definitions/dto.HomeResponse"
456
+ },
457
+ "message": {
458
+ "type": "string"
459
+ },
460
+ "success": {
461
+ "type": "boolean"
462
+ }
463
+ }
464
+ },
465
+ "dto.HomeSearch": {
466
+ "type": "object",
467
+ "properties": {
468
+ "placeholder": {
469
+ "type": "string"
470
+ }
471
+ }
472
+ },
473
+ "dto.HomeUser": {
474
+ "type": "object",
475
+ "properties": {
476
+ "avatar_url": {
477
+ "type": "string"
478
+ },
479
+ "first_name": {
480
+ "type": "string"
481
+ },
482
+ "full_name": {
483
+ "type": "string"
484
+ },
485
+ "id": {
486
+ "type": "string"
487
+ }
488
+ }
489
+ },
490
+ "dto.HomeWeekSchedule": {
491
+ "type": "object",
492
+ "properties": {
493
+ "days": {
494
+ "type": "array",
495
+ "items": {
496
+ "$ref": "#/definitions/dto.HomeDay"
497
+ }
498
+ },
499
+ "end_date": {
500
+ "type": "string"
501
+ },
502
+ "items": {
503
+ "type": "array",
504
+ "items": {
505
+ "$ref": "#/definitions/dto.ScheduleItem"
506
+ }
507
+ },
508
+ "selected_date": {
509
+ "type": "string"
510
+ },
511
+ "start_date": {
512
+ "type": "string"
513
+ },
514
+ "view_all_url": {
515
+ "type": "string"
516
+ },
517
+ "week_label": {
518
+ "type": "string"
519
+ }
520
+ }
521
+ },
522
+ "dto.PaginationMeta": {
523
+ "type": "object",
524
+ "properties": {
525
+ "limit": {
526
+ "type": "integer"
527
+ },
528
+ "page": {
529
+ "type": "integer"
530
+ },
531
+ "total_items": {
532
+ "type": "integer"
533
+ },
534
+ "total_pages": {
535
+ "type": "integer"
536
+ }
537
+ }
538
+ },
539
+ "dto.PriceData": {
540
+ "type": "object",
541
+ "properties": {
542
+ "amount": {
543
+ "type": "integer"
544
+ },
545
+ "currency": {
546
+ "type": "string"
547
+ },
548
+ "display": {
549
+ "type": "string"
550
+ }
551
+ }
552
+ },
553
+ "dto.ScheduleItem": {
554
+ "type": "object",
555
+ "properties": {
556
+ "city": {
557
+ "type": "string"
558
+ },
559
+ "class_id": {
560
+ "type": "string"
561
+ },
562
+ "date": {
563
+ "type": "string"
564
+ },
565
+ "display_time": {
566
+ "type": "string"
567
+ },
568
+ "end_time": {
569
+ "type": "string"
570
+ },
571
+ "is_booked": {
572
+ "type": "boolean"
573
+ },
574
+ "schedule_id": {
575
+ "type": "string"
576
+ },
577
+ "start_time": {
578
+ "type": "string"
579
+ },
580
+ "studio_name": {
581
+ "type": "string"
582
+ },
583
+ "teacher_name": {
584
+ "type": "string"
585
+ },
586
+ "title": {
587
+ "type": "string"
588
+ }
589
+ }
590
+ },
591
+ "dto.ScheduleListItem": {
592
+ "type": "object",
593
+ "properties": {
594
+ "address": {
595
+ "type": "string"
596
+ },
597
+ "available_slots": {
598
+ "type": "integer"
599
+ },
600
+ "category": {
601
+ "type": "string"
602
+ },
603
+ "city": {
604
+ "type": "string"
605
+ },
606
+ "class_id": {
607
+ "type": "string"
608
+ },
609
+ "date": {
610
+ "type": "string"
611
+ },
612
+ "display_time": {
613
+ "type": "string"
614
+ },
615
+ "end_time": {
616
+ "type": "string"
617
+ },
618
+ "is_booked": {
619
+ "type": "boolean"
620
+ },
621
+ "level": {
622
+ "type": "string"
623
+ },
624
+ "price": {
625
+ "$ref": "#/definitions/dto.PriceData"
626
+ },
627
+ "schedule_id": {
628
+ "type": "string"
629
+ },
630
+ "start_time": {
631
+ "type": "string"
632
+ },
633
+ "studio_name": {
634
+ "type": "string"
635
+ },
636
+ "teacher_name": {
637
+ "type": "string"
638
+ },
639
+ "thumbnail_url": {
640
+ "type": "string"
641
+ },
642
+ "title": {
643
+ "type": "string"
644
+ }
645
+ }
646
+ },
647
+ "dto.ScheduleListResponse": {
648
+ "type": "object",
649
+ "properties": {
650
+ "items": {
651
+ "type": "array",
652
+ "items": {
653
+ "$ref": "#/definitions/dto.ScheduleListItem"
654
+ }
655
+ },
656
+ "pagination": {
657
+ "$ref": "#/definitions/dto.PaginationMeta"
658
+ }
659
+ }
660
+ },
661
+ "dto.ScheduleListResponseWrapper": {
662
+ "type": "object",
663
+ "properties": {
664
+ "data": {
665
+ "$ref": "#/definitions/dto.ScheduleListResponse"
666
+ },
667
+ "message": {
668
+ "type": "string"
669
+ },
670
+ "success": {
671
+ "type": "boolean"
672
+ }
673
+ }
674
+ },
675
+ "dto.SuccessResponse-any": {
676
+ "type": "object",
677
+ "properties": {
678
+ "data": {},
679
+ "message": {},
680
+ "meta_data": {},
681
+ "status": {
682
+ "type": "string"
683
+ }
684
+ }
685
+ }
686
+ }
687
+ }`
688
+
689
+ // SwaggerInfo holds exported Swagger Info so clients can modify it
690
+ var SwaggerInfo = &swag.Spec{
691
+ Version: "",
692
+ Host: "",
693
+ BasePath: "",
694
+ Schemes: []string{},
695
+ Title: "",
696
+ Description: "",
697
+ InfoInstanceName: "swagger",
698
+ SwaggerTemplate: docTemplate,
699
+ LeftDelim: "{{",
700
+ RightDelim: "}}",
701
+ }
702
+
703
+ func init() {
704
+ swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
705
+ }
docs/swagger.json ADDED
@@ -0,0 +1,676 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "swagger": "2.0",
3
+ "info": {
4
+ "contact": {}
5
+ },
6
+ "paths": {
7
+ "/api/v1/class-categories": {
8
+ "get": {
9
+ "description": "Get master list of class categories",
10
+ "consumes": [
11
+ "application/json"
12
+ ],
13
+ "produces": [
14
+ "application/json"
15
+ ],
16
+ "tags": [
17
+ "Master Data"
18
+ ],
19
+ "summary": "Get Class Categories",
20
+ "responses": {
21
+ "200": {
22
+ "description": "OK",
23
+ "schema": {
24
+ "$ref": "#/definitions/dto.CategoryResponseWrapper"
25
+ }
26
+ },
27
+ "400": {
28
+ "description": "Bad Request",
29
+ "schema": {
30
+ "$ref": "#/definitions/dto.ErrorResponse"
31
+ }
32
+ }
33
+ }
34
+ }
35
+ },
36
+ "/api/v1/event-categories": {
37
+ "get": {
38
+ "description": "Get master list of event categories",
39
+ "consumes": [
40
+ "application/json"
41
+ ],
42
+ "produces": [
43
+ "application/json"
44
+ ],
45
+ "tags": [
46
+ "Master Data"
47
+ ],
48
+ "summary": "Get Event Categories",
49
+ "responses": {
50
+ "200": {
51
+ "description": "OK",
52
+ "schema": {
53
+ "$ref": "#/definitions/dto.CategoryResponseWrapper"
54
+ }
55
+ },
56
+ "400": {
57
+ "description": "Bad Request",
58
+ "schema": {
59
+ "$ref": "#/definitions/dto.ErrorResponse"
60
+ }
61
+ }
62
+ }
63
+ }
64
+ },
65
+ "/api/v1/home": {
66
+ "get": {
67
+ "security": [
68
+ {
69
+ "BearerAuth": []
70
+ }
71
+ ],
72
+ "description": "Return all sections needed by the home page in one aggregated response.",
73
+ "consumes": [
74
+ "application/json"
75
+ ],
76
+ "produces": [
77
+ "application/json"
78
+ ],
79
+ "tags": [
80
+ "Home Dashboard"
81
+ ],
82
+ "summary": "Get Home Page Data",
83
+ "parameters": [
84
+ {
85
+ "type": "string",
86
+ "description": "Selected date for schedule card section (YYYY-MM-DD)",
87
+ "name": "date",
88
+ "in": "query"
89
+ },
90
+ {
91
+ "type": "number",
92
+ "description": "User latitude",
93
+ "name": "latitude",
94
+ "in": "query"
95
+ },
96
+ {
97
+ "type": "number",
98
+ "description": "User longitude",
99
+ "name": "longitude",
100
+ "in": "query"
101
+ },
102
+ {
103
+ "type": "string",
104
+ "description": "User city",
105
+ "name": "city",
106
+ "in": "query"
107
+ }
108
+ ],
109
+ "responses": {
110
+ "200": {
111
+ "description": "OK",
112
+ "schema": {
113
+ "$ref": "#/definitions/dto.HomeResponseWrapper"
114
+ }
115
+ },
116
+ "400": {
117
+ "description": "Bad Request",
118
+ "schema": {
119
+ "$ref": "#/definitions/dto.ErrorResponse"
120
+ }
121
+ }
122
+ }
123
+ }
124
+ },
125
+ "/api/v1/payment/callback": {
126
+ "post": {
127
+ "description": "Receive and process payment status updates from Xendit",
128
+ "consumes": [
129
+ "application/json"
130
+ ],
131
+ "produces": [
132
+ "application/json"
133
+ ],
134
+ "tags": [
135
+ "Payment"
136
+ ],
137
+ "summary": "Handle Xendit Payment Callback",
138
+ "parameters": [
139
+ {
140
+ "description": "Xendit Callback Payload",
141
+ "name": "request",
142
+ "in": "body",
143
+ "required": true,
144
+ "schema": {
145
+ "type": "object",
146
+ "additionalProperties": true
147
+ }
148
+ }
149
+ ],
150
+ "responses": {
151
+ "200": {
152
+ "description": "OK",
153
+ "schema": {
154
+ "$ref": "#/definitions/dto.SuccessResponse-any"
155
+ }
156
+ },
157
+ "400": {
158
+ "description": "Bad Request",
159
+ "schema": {
160
+ "$ref": "#/definitions/dto.ErrorResponse"
161
+ }
162
+ }
163
+ }
164
+ }
165
+ },
166
+ "/api/v1/schedules": {
167
+ "get": {
168
+ "security": [
169
+ {
170
+ "BearerAuth": []
171
+ }
172
+ ],
173
+ "description": "Get a list of class or event schedules based on date range, city, filters, etc.",
174
+ "consumes": [
175
+ "application/json"
176
+ ],
177
+ "produces": [
178
+ "application/json"
179
+ ],
180
+ "tags": [
181
+ "Home Dashboard"
182
+ ],
183
+ "summary": "Get Schedule List",
184
+ "parameters": [
185
+ {
186
+ "type": "string",
187
+ "description": "Exact date (YYYY-MM-DD)",
188
+ "name": "date",
189
+ "in": "query"
190
+ },
191
+ {
192
+ "type": "string",
193
+ "description": "Start date range (YYYY-MM-DD)",
194
+ "name": "start_date",
195
+ "in": "query"
196
+ },
197
+ {
198
+ "type": "string",
199
+ "description": "End date range (YYYY-MM-DD)",
200
+ "name": "end_date",
201
+ "in": "query"
202
+ },
203
+ {
204
+ "type": "string",
205
+ "description": "Filter by city",
206
+ "name": "city",
207
+ "in": "query"
208
+ },
209
+ {
210
+ "type": "string",
211
+ "description": "Filter by class level (e.g., beginner)",
212
+ "name": "level",
213
+ "in": "query"
214
+ },
215
+ {
216
+ "type": "string",
217
+ "description": "Filter by class category (e.g., salsa)",
218
+ "name": "category",
219
+ "in": "query"
220
+ },
221
+ {
222
+ "type": "integer",
223
+ "description": "Page number (default: 1)",
224
+ "name": "page",
225
+ "in": "query"
226
+ },
227
+ {
228
+ "type": "integer",
229
+ "description": "Items per page (default: 10)",
230
+ "name": "limit",
231
+ "in": "query"
232
+ }
233
+ ],
234
+ "responses": {
235
+ "200": {
236
+ "description": "OK",
237
+ "schema": {
238
+ "$ref": "#/definitions/dto.ScheduleListResponseWrapper"
239
+ }
240
+ },
241
+ "400": {
242
+ "description": "Bad Request",
243
+ "schema": {
244
+ "$ref": "#/definitions/dto.ErrorResponse"
245
+ }
246
+ }
247
+ }
248
+ }
249
+ }
250
+ },
251
+ "definitions": {
252
+ "dto.CategoryResponse": {
253
+ "type": "object",
254
+ "properties": {
255
+ "id": {
256
+ "type": "string"
257
+ },
258
+ "image_url": {
259
+ "type": "string"
260
+ },
261
+ "name": {
262
+ "type": "string"
263
+ }
264
+ }
265
+ },
266
+ "dto.CategoryResponseWrapper": {
267
+ "type": "object",
268
+ "properties": {
269
+ "data": {
270
+ "type": "array",
271
+ "items": {
272
+ "$ref": "#/definitions/dto.CategoryResponse"
273
+ }
274
+ },
275
+ "success": {
276
+ "type": "boolean"
277
+ }
278
+ }
279
+ },
280
+ "dto.ErrorResponse": {
281
+ "type": "object",
282
+ "properties": {
283
+ "errors": {},
284
+ "message": {},
285
+ "meta_data": {},
286
+ "status": {
287
+ "type": "string"
288
+ }
289
+ }
290
+ },
291
+ "dto.HomeBeginnerClassSection": {
292
+ "type": "object",
293
+ "properties": {
294
+ "id": {
295
+ "type": "string"
296
+ },
297
+ "image_url": {
298
+ "type": "string"
299
+ },
300
+ "name": {
301
+ "type": "string"
302
+ },
303
+ "type": {
304
+ "type": "string"
305
+ }
306
+ }
307
+ },
308
+ "dto.HomeDay": {
309
+ "type": "object",
310
+ "properties": {
311
+ "date": {
312
+ "type": "string"
313
+ },
314
+ "day_name_short": {
315
+ "type": "string"
316
+ },
317
+ "day_number": {
318
+ "type": "integer"
319
+ },
320
+ "has_schedule": {
321
+ "type": "boolean"
322
+ },
323
+ "is_selected": {
324
+ "type": "boolean"
325
+ }
326
+ }
327
+ },
328
+ "dto.HomeEventSection": {
329
+ "type": "object",
330
+ "properties": {
331
+ "id": {
332
+ "type": "string"
333
+ },
334
+ "image_url": {
335
+ "type": "string"
336
+ },
337
+ "name": {
338
+ "type": "string"
339
+ },
340
+ "type": {
341
+ "type": "string"
342
+ }
343
+ }
344
+ },
345
+ "dto.HomeHeroBanner": {
346
+ "type": "object",
347
+ "properties": {
348
+ "badge_text": {
349
+ "type": "string"
350
+ },
351
+ "cta_target_id": {
352
+ "type": "string"
353
+ },
354
+ "cta_text": {
355
+ "type": "string"
356
+ },
357
+ "cta_type": {
358
+ "type": "string"
359
+ },
360
+ "id": {
361
+ "type": "string"
362
+ },
363
+ "image_url": {
364
+ "type": "string"
365
+ },
366
+ "subtitle": {
367
+ "type": "string"
368
+ },
369
+ "title": {
370
+ "type": "string"
371
+ }
372
+ }
373
+ },
374
+ "dto.HomeNotifications": {
375
+ "type": "object",
376
+ "properties": {
377
+ "unread_count": {
378
+ "type": "integer"
379
+ }
380
+ }
381
+ },
382
+ "dto.HomePromoStrip": {
383
+ "type": "object",
384
+ "properties": {
385
+ "cta_target_id": {
386
+ "type": "string"
387
+ },
388
+ "cta_text": {
389
+ "type": "string"
390
+ },
391
+ "cta_type": {
392
+ "type": "string"
393
+ },
394
+ "id": {
395
+ "type": "string"
396
+ },
397
+ "text": {
398
+ "type": "string"
399
+ }
400
+ }
401
+ },
402
+ "dto.HomeResponse": {
403
+ "type": "object",
404
+ "properties": {
405
+ "beginner_class_sections": {
406
+ "type": "array",
407
+ "items": {
408
+ "$ref": "#/definitions/dto.HomeBeginnerClassSection"
409
+ }
410
+ },
411
+ "event_sections": {
412
+ "type": "array",
413
+ "items": {
414
+ "$ref": "#/definitions/dto.HomeEventSection"
415
+ }
416
+ },
417
+ "hero_banners": {
418
+ "type": "array",
419
+ "items": {
420
+ "$ref": "#/definitions/dto.HomeHeroBanner"
421
+ }
422
+ },
423
+ "notifications": {
424
+ "$ref": "#/definitions/dto.HomeNotifications"
425
+ },
426
+ "promo_strip": {
427
+ "$ref": "#/definitions/dto.HomePromoStrip"
428
+ },
429
+ "search": {
430
+ "$ref": "#/definitions/dto.HomeSearch"
431
+ },
432
+ "user": {
433
+ "$ref": "#/definitions/dto.HomeUser"
434
+ },
435
+ "week_schedule": {
436
+ "$ref": "#/definitions/dto.HomeWeekSchedule"
437
+ }
438
+ }
439
+ },
440
+ "dto.HomeResponseWrapper": {
441
+ "type": "object",
442
+ "properties": {
443
+ "data": {
444
+ "$ref": "#/definitions/dto.HomeResponse"
445
+ },
446
+ "message": {
447
+ "type": "string"
448
+ },
449
+ "success": {
450
+ "type": "boolean"
451
+ }
452
+ }
453
+ },
454
+ "dto.HomeSearch": {
455
+ "type": "object",
456
+ "properties": {
457
+ "placeholder": {
458
+ "type": "string"
459
+ }
460
+ }
461
+ },
462
+ "dto.HomeUser": {
463
+ "type": "object",
464
+ "properties": {
465
+ "avatar_url": {
466
+ "type": "string"
467
+ },
468
+ "first_name": {
469
+ "type": "string"
470
+ },
471
+ "full_name": {
472
+ "type": "string"
473
+ },
474
+ "id": {
475
+ "type": "string"
476
+ }
477
+ }
478
+ },
479
+ "dto.HomeWeekSchedule": {
480
+ "type": "object",
481
+ "properties": {
482
+ "days": {
483
+ "type": "array",
484
+ "items": {
485
+ "$ref": "#/definitions/dto.HomeDay"
486
+ }
487
+ },
488
+ "end_date": {
489
+ "type": "string"
490
+ },
491
+ "items": {
492
+ "type": "array",
493
+ "items": {
494
+ "$ref": "#/definitions/dto.ScheduleItem"
495
+ }
496
+ },
497
+ "selected_date": {
498
+ "type": "string"
499
+ },
500
+ "start_date": {
501
+ "type": "string"
502
+ },
503
+ "view_all_url": {
504
+ "type": "string"
505
+ },
506
+ "week_label": {
507
+ "type": "string"
508
+ }
509
+ }
510
+ },
511
+ "dto.PaginationMeta": {
512
+ "type": "object",
513
+ "properties": {
514
+ "limit": {
515
+ "type": "integer"
516
+ },
517
+ "page": {
518
+ "type": "integer"
519
+ },
520
+ "total_items": {
521
+ "type": "integer"
522
+ },
523
+ "total_pages": {
524
+ "type": "integer"
525
+ }
526
+ }
527
+ },
528
+ "dto.PriceData": {
529
+ "type": "object",
530
+ "properties": {
531
+ "amount": {
532
+ "type": "integer"
533
+ },
534
+ "currency": {
535
+ "type": "string"
536
+ },
537
+ "display": {
538
+ "type": "string"
539
+ }
540
+ }
541
+ },
542
+ "dto.ScheduleItem": {
543
+ "type": "object",
544
+ "properties": {
545
+ "city": {
546
+ "type": "string"
547
+ },
548
+ "class_id": {
549
+ "type": "string"
550
+ },
551
+ "date": {
552
+ "type": "string"
553
+ },
554
+ "display_time": {
555
+ "type": "string"
556
+ },
557
+ "end_time": {
558
+ "type": "string"
559
+ },
560
+ "is_booked": {
561
+ "type": "boolean"
562
+ },
563
+ "schedule_id": {
564
+ "type": "string"
565
+ },
566
+ "start_time": {
567
+ "type": "string"
568
+ },
569
+ "studio_name": {
570
+ "type": "string"
571
+ },
572
+ "teacher_name": {
573
+ "type": "string"
574
+ },
575
+ "title": {
576
+ "type": "string"
577
+ }
578
+ }
579
+ },
580
+ "dto.ScheduleListItem": {
581
+ "type": "object",
582
+ "properties": {
583
+ "address": {
584
+ "type": "string"
585
+ },
586
+ "available_slots": {
587
+ "type": "integer"
588
+ },
589
+ "category": {
590
+ "type": "string"
591
+ },
592
+ "city": {
593
+ "type": "string"
594
+ },
595
+ "class_id": {
596
+ "type": "string"
597
+ },
598
+ "date": {
599
+ "type": "string"
600
+ },
601
+ "display_time": {
602
+ "type": "string"
603
+ },
604
+ "end_time": {
605
+ "type": "string"
606
+ },
607
+ "is_booked": {
608
+ "type": "boolean"
609
+ },
610
+ "level": {
611
+ "type": "string"
612
+ },
613
+ "price": {
614
+ "$ref": "#/definitions/dto.PriceData"
615
+ },
616
+ "schedule_id": {
617
+ "type": "string"
618
+ },
619
+ "start_time": {
620
+ "type": "string"
621
+ },
622
+ "studio_name": {
623
+ "type": "string"
624
+ },
625
+ "teacher_name": {
626
+ "type": "string"
627
+ },
628
+ "thumbnail_url": {
629
+ "type": "string"
630
+ },
631
+ "title": {
632
+ "type": "string"
633
+ }
634
+ }
635
+ },
636
+ "dto.ScheduleListResponse": {
637
+ "type": "object",
638
+ "properties": {
639
+ "items": {
640
+ "type": "array",
641
+ "items": {
642
+ "$ref": "#/definitions/dto.ScheduleListItem"
643
+ }
644
+ },
645
+ "pagination": {
646
+ "$ref": "#/definitions/dto.PaginationMeta"
647
+ }
648
+ }
649
+ },
650
+ "dto.ScheduleListResponseWrapper": {
651
+ "type": "object",
652
+ "properties": {
653
+ "data": {
654
+ "$ref": "#/definitions/dto.ScheduleListResponse"
655
+ },
656
+ "message": {
657
+ "type": "string"
658
+ },
659
+ "success": {
660
+ "type": "boolean"
661
+ }
662
+ }
663
+ },
664
+ "dto.SuccessResponse-any": {
665
+ "type": "object",
666
+ "properties": {
667
+ "data": {},
668
+ "message": {},
669
+ "meta_data": {},
670
+ "status": {
671
+ "type": "string"
672
+ }
673
+ }
674
+ }
675
+ }
676
+ }
docs/swagger.yaml ADDED
@@ -0,0 +1,440 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ definitions:
2
+ dto.CategoryResponse:
3
+ properties:
4
+ id:
5
+ type: string
6
+ image_url:
7
+ type: string
8
+ name:
9
+ type: string
10
+ type: object
11
+ dto.CategoryResponseWrapper:
12
+ properties:
13
+ data:
14
+ items:
15
+ $ref: '#/definitions/dto.CategoryResponse'
16
+ type: array
17
+ success:
18
+ type: boolean
19
+ type: object
20
+ dto.ErrorResponse:
21
+ properties:
22
+ errors: {}
23
+ message: {}
24
+ meta_data: {}
25
+ status:
26
+ type: string
27
+ type: object
28
+ dto.HomeBeginnerClassSection:
29
+ properties:
30
+ id:
31
+ type: string
32
+ image_url:
33
+ type: string
34
+ name:
35
+ type: string
36
+ type:
37
+ type: string
38
+ type: object
39
+ dto.HomeDay:
40
+ properties:
41
+ date:
42
+ type: string
43
+ day_name_short:
44
+ type: string
45
+ day_number:
46
+ type: integer
47
+ has_schedule:
48
+ type: boolean
49
+ is_selected:
50
+ type: boolean
51
+ type: object
52
+ dto.HomeEventSection:
53
+ properties:
54
+ id:
55
+ type: string
56
+ image_url:
57
+ type: string
58
+ name:
59
+ type: string
60
+ type:
61
+ type: string
62
+ type: object
63
+ dto.HomeHeroBanner:
64
+ properties:
65
+ badge_text:
66
+ type: string
67
+ cta_target_id:
68
+ type: string
69
+ cta_text:
70
+ type: string
71
+ cta_type:
72
+ type: string
73
+ id:
74
+ type: string
75
+ image_url:
76
+ type: string
77
+ subtitle:
78
+ type: string
79
+ title:
80
+ type: string
81
+ type: object
82
+ dto.HomeNotifications:
83
+ properties:
84
+ unread_count:
85
+ type: integer
86
+ type: object
87
+ dto.HomePromoStrip:
88
+ properties:
89
+ cta_target_id:
90
+ type: string
91
+ cta_text:
92
+ type: string
93
+ cta_type:
94
+ type: string
95
+ id:
96
+ type: string
97
+ text:
98
+ type: string
99
+ type: object
100
+ dto.HomeResponse:
101
+ properties:
102
+ beginner_class_sections:
103
+ items:
104
+ $ref: '#/definitions/dto.HomeBeginnerClassSection'
105
+ type: array
106
+ event_sections:
107
+ items:
108
+ $ref: '#/definitions/dto.HomeEventSection'
109
+ type: array
110
+ hero_banners:
111
+ items:
112
+ $ref: '#/definitions/dto.HomeHeroBanner'
113
+ type: array
114
+ notifications:
115
+ $ref: '#/definitions/dto.HomeNotifications'
116
+ promo_strip:
117
+ $ref: '#/definitions/dto.HomePromoStrip'
118
+ search:
119
+ $ref: '#/definitions/dto.HomeSearch'
120
+ user:
121
+ $ref: '#/definitions/dto.HomeUser'
122
+ week_schedule:
123
+ $ref: '#/definitions/dto.HomeWeekSchedule'
124
+ type: object
125
+ dto.HomeResponseWrapper:
126
+ properties:
127
+ data:
128
+ $ref: '#/definitions/dto.HomeResponse'
129
+ message:
130
+ type: string
131
+ success:
132
+ type: boolean
133
+ type: object
134
+ dto.HomeSearch:
135
+ properties:
136
+ placeholder:
137
+ type: string
138
+ type: object
139
+ dto.HomeUser:
140
+ properties:
141
+ avatar_url:
142
+ type: string
143
+ first_name:
144
+ type: string
145
+ full_name:
146
+ type: string
147
+ id:
148
+ type: string
149
+ type: object
150
+ dto.HomeWeekSchedule:
151
+ properties:
152
+ days:
153
+ items:
154
+ $ref: '#/definitions/dto.HomeDay'
155
+ type: array
156
+ end_date:
157
+ type: string
158
+ items:
159
+ items:
160
+ $ref: '#/definitions/dto.ScheduleItem'
161
+ type: array
162
+ selected_date:
163
+ type: string
164
+ start_date:
165
+ type: string
166
+ view_all_url:
167
+ type: string
168
+ week_label:
169
+ type: string
170
+ type: object
171
+ dto.PaginationMeta:
172
+ properties:
173
+ limit:
174
+ type: integer
175
+ page:
176
+ type: integer
177
+ total_items:
178
+ type: integer
179
+ total_pages:
180
+ type: integer
181
+ type: object
182
+ dto.PriceData:
183
+ properties:
184
+ amount:
185
+ type: integer
186
+ currency:
187
+ type: string
188
+ display:
189
+ type: string
190
+ type: object
191
+ dto.ScheduleItem:
192
+ properties:
193
+ city:
194
+ type: string
195
+ class_id:
196
+ type: string
197
+ date:
198
+ type: string
199
+ display_time:
200
+ type: string
201
+ end_time:
202
+ type: string
203
+ is_booked:
204
+ type: boolean
205
+ schedule_id:
206
+ type: string
207
+ start_time:
208
+ type: string
209
+ studio_name:
210
+ type: string
211
+ teacher_name:
212
+ type: string
213
+ title:
214
+ type: string
215
+ type: object
216
+ dto.ScheduleListItem:
217
+ properties:
218
+ address:
219
+ type: string
220
+ available_slots:
221
+ type: integer
222
+ category:
223
+ type: string
224
+ city:
225
+ type: string
226
+ class_id:
227
+ type: string
228
+ date:
229
+ type: string
230
+ display_time:
231
+ type: string
232
+ end_time:
233
+ type: string
234
+ is_booked:
235
+ type: boolean
236
+ level:
237
+ type: string
238
+ price:
239
+ $ref: '#/definitions/dto.PriceData'
240
+ schedule_id:
241
+ type: string
242
+ start_time:
243
+ type: string
244
+ studio_name:
245
+ type: string
246
+ teacher_name:
247
+ type: string
248
+ thumbnail_url:
249
+ type: string
250
+ title:
251
+ type: string
252
+ type: object
253
+ dto.ScheduleListResponse:
254
+ properties:
255
+ items:
256
+ items:
257
+ $ref: '#/definitions/dto.ScheduleListItem'
258
+ type: array
259
+ pagination:
260
+ $ref: '#/definitions/dto.PaginationMeta'
261
+ type: object
262
+ dto.ScheduleListResponseWrapper:
263
+ properties:
264
+ data:
265
+ $ref: '#/definitions/dto.ScheduleListResponse'
266
+ message:
267
+ type: string
268
+ success:
269
+ type: boolean
270
+ type: object
271
+ dto.SuccessResponse-any:
272
+ properties:
273
+ data: {}
274
+ message: {}
275
+ meta_data: {}
276
+ status:
277
+ type: string
278
+ type: object
279
+ info:
280
+ contact: {}
281
+ paths:
282
+ /api/v1/class-categories:
283
+ get:
284
+ consumes:
285
+ - application/json
286
+ description: Get master list of class categories
287
+ produces:
288
+ - application/json
289
+ responses:
290
+ "200":
291
+ description: OK
292
+ schema:
293
+ $ref: '#/definitions/dto.CategoryResponseWrapper'
294
+ "400":
295
+ description: Bad Request
296
+ schema:
297
+ $ref: '#/definitions/dto.ErrorResponse'
298
+ summary: Get Class Categories
299
+ tags:
300
+ - Master Data
301
+ /api/v1/event-categories:
302
+ get:
303
+ consumes:
304
+ - application/json
305
+ description: Get master list of event categories
306
+ produces:
307
+ - application/json
308
+ responses:
309
+ "200":
310
+ description: OK
311
+ schema:
312
+ $ref: '#/definitions/dto.CategoryResponseWrapper'
313
+ "400":
314
+ description: Bad Request
315
+ schema:
316
+ $ref: '#/definitions/dto.ErrorResponse'
317
+ summary: Get Event Categories
318
+ tags:
319
+ - Master Data
320
+ /api/v1/home:
321
+ get:
322
+ consumes:
323
+ - application/json
324
+ description: Return all sections needed by the home page in one aggregated response.
325
+ parameters:
326
+ - description: Selected date for schedule card section (YYYY-MM-DD)
327
+ in: query
328
+ name: date
329
+ type: string
330
+ - description: User latitude
331
+ in: query
332
+ name: latitude
333
+ type: number
334
+ - description: User longitude
335
+ in: query
336
+ name: longitude
337
+ type: number
338
+ - description: User city
339
+ in: query
340
+ name: city
341
+ type: string
342
+ produces:
343
+ - application/json
344
+ responses:
345
+ "200":
346
+ description: OK
347
+ schema:
348
+ $ref: '#/definitions/dto.HomeResponseWrapper'
349
+ "400":
350
+ description: Bad Request
351
+ schema:
352
+ $ref: '#/definitions/dto.ErrorResponse'
353
+ security:
354
+ - BearerAuth: []
355
+ summary: Get Home Page Data
356
+ tags:
357
+ - Home Dashboard
358
+ /api/v1/payment/callback:
359
+ post:
360
+ consumes:
361
+ - application/json
362
+ description: Receive and process payment status updates from Xendit
363
+ parameters:
364
+ - description: Xendit Callback Payload
365
+ in: body
366
+ name: request
367
+ required: true
368
+ schema:
369
+ additionalProperties: true
370
+ type: object
371
+ produces:
372
+ - application/json
373
+ responses:
374
+ "200":
375
+ description: OK
376
+ schema:
377
+ $ref: '#/definitions/dto.SuccessResponse-any'
378
+ "400":
379
+ description: Bad Request
380
+ schema:
381
+ $ref: '#/definitions/dto.ErrorResponse'
382
+ summary: Handle Xendit Payment Callback
383
+ tags:
384
+ - Payment
385
+ /api/v1/schedules:
386
+ get:
387
+ consumes:
388
+ - application/json
389
+ description: Get a list of class or event schedules based on date range, city,
390
+ filters, etc.
391
+ parameters:
392
+ - description: Exact date (YYYY-MM-DD)
393
+ in: query
394
+ name: date
395
+ type: string
396
+ - description: Start date range (YYYY-MM-DD)
397
+ in: query
398
+ name: start_date
399
+ type: string
400
+ - description: End date range (YYYY-MM-DD)
401
+ in: query
402
+ name: end_date
403
+ type: string
404
+ - description: Filter by city
405
+ in: query
406
+ name: city
407
+ type: string
408
+ - description: Filter by class level (e.g., beginner)
409
+ in: query
410
+ name: level
411
+ type: string
412
+ - description: Filter by class category (e.g., salsa)
413
+ in: query
414
+ name: category
415
+ type: string
416
+ - description: 'Page number (default: 1)'
417
+ in: query
418
+ name: page
419
+ type: integer
420
+ - description: 'Items per page (default: 10)'
421
+ in: query
422
+ name: limit
423
+ type: integer
424
+ produces:
425
+ - application/json
426
+ responses:
427
+ "200":
428
+ description: OK
429
+ schema:
430
+ $ref: '#/definitions/dto.ScheduleListResponseWrapper'
431
+ "400":
432
+ description: Bad Request
433
+ schema:
434
+ $ref: '#/definitions/dto.ErrorResponse'
435
+ security:
436
+ - BearerAuth: []
437
+ summary: Get Schedule List
438
+ tags:
439
+ - Home Dashboard
440
+ swagger: "2.0"
err.txt ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # abdanhafidz.com/go-boilerplate/repositories
2
+ repositories\account_details_repository.go:12:58: undefined: entity.AccountDetail
3
+ repositories\account_details_repository.go:13:66: undefined: entity.AccountDetail
4
+ repositories\account_details_repository.go:14:80: undefined: entity.AccountDetail
5
+ repositories\account_details_repository.go:15:53: undefined: entity.AccountDetail
6
+ repositories\account_details_repository.go:16:58: undefined: entity.AccountDetail
7
+ repositories\account_repository.go:12:52: undefined: entity.Account
8
+ repositories\account_repository.go:13:67: undefined: entity.Account
9
+ repositories\account_repository.go:14:63: undefined: entity.Account
10
+ repositories\account_repository.go:15:69: undefined: entity.Account
11
+ repositories\account_repository.go:16:47: undefined: entity.Account
12
+ repositories\account_repository.go:16:47: too many errors
13
+ # abdanhafidz.com/go-boilerplate/models/dto
14
+ models\dto\account_details_dto.go:8:17: undefined: entity.Account
15
+ models\dto\account_details_dto.go:9:17: undefined: entity.AccountDetail
16
+ models\dto\authentication_dto.go:53:17: undefined: entity.Account
17
+ models\dto\option_dto.go:11:19: undefined: entity.Options
err2.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # abdanhafidz.com/go-boilerplate/repositories
2
+ repositories\account_repository.go:12:52: undefined: entity.Account
3
+ repositories\account_repository.go:13:67: undefined: entity.Account
4
+ repositories\account_repository.go:14:63: undefined: entity.Account
5
+ repositories\account_repository.go:15:69: undefined: entity.Account
6
+ repositories\account_repository.go:16:47: undefined: entity.Account
7
+ repositories\account_repository.go:17:52: undefined: entity.Account
8
+ repositories\account_repository.go:30:79: undefined: entity.Account
9
+ repositories\email_verification_repository.go:13:50: undefined: entity.EmailVerification
10
+ repositories\email_verification_repository.go:14:85: undefined: entity.EmailVerification
11
+ repositories\email_verification_repository.go:17:73: undefined: entity.EmailVerification
12
+ repositories\account_repository.go:30:79: too many errors
err3.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # abdanhafidz.com/go-boilerplate/services
2
+ services\user_service.go:70:11: assignment mismatch: 1 variable but s.jwtService.GenerateToken returns 2 values
3
+ services\user_service.go:70:38: cannot use user.ID (variable of type string) as context.Context value in argument to s.jwtService.GenerateToken: string does not implement context.Context (missing method Deadline)
4
+ services\user_service.go:70:47: cannot use "" (untyped string constant) as dto.JWTCustomClaims value in argument to s.jwtService.GenerateToken
5
+ services\user_service.go:100:11: assignment mismatch: 1 variable but s.jwtService.GenerateToken returns 2 values
6
+ services\user_service.go:100:38: cannot use user.ID (variable of type string) as context.Context value in argument to s.jwtService.GenerateToken: string does not implement context.Context (missing method Deadline)
7
+ services\user_service.go:100:47: cannot use "" (untyped string constant) as dto.JWTCustomClaims value in argument to s.jwtService.GenerateToken
err4.txt ADDED
File without changes
err_swag.txt ADDED
Binary file (1.33 kB). View file
 
err_swag2.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ 2026/03/10 20:32:28 Generate swagger docs....
2
+ 2026/03/10 20:32:28 Generate general API Info, search dir:./
3
+ 2026/03/10 20:32:30 ParseComment error in file C:\Users\asus\projects\danzapp-be\controllers\home_controller.go for comment: '// @Success 200 {object} dto.SuccessResponse[dto.HomeResponse]': cannot find type definition: dto.SuccessResponse[dto.HomeResponse]
go.mod ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module abdanhafidz.com/go-boilerplate
2
+
3
+ go 1.25.4
4
+
5
+ require (
6
+ github.com/gin-contrib/gzip v1.2.5
7
+ github.com/gin-gonic/gin v1.11.0
8
+ github.com/golang-jwt/jwt/v4 v4.5.2
9
+ github.com/google/uuid v1.6.0
10
+ github.com/joho/godotenv v1.5.1
11
+ github.com/swaggo/files v1.0.1
12
+ github.com/swaggo/gin-swagger v1.6.1
13
+ github.com/swaggo/swag v1.16.6
14
+ github.com/xendit/xendit-go/v7 v7.0.0
15
+ golang.org/x/crypto v0.46.0
16
+ gorm.io/datatypes v1.2.7
17
+ gorm.io/driver/postgres v1.6.0
18
+ gorm.io/gorm v1.31.0
19
+ )
20
+
21
+ require (
22
+ filippo.io/edwards25519 v1.1.0 // indirect
23
+ github.com/KyleBanks/depth v1.2.1 // indirect
24
+ github.com/bytedance/gopkg v0.1.3 // indirect
25
+ github.com/bytedance/sonic v1.14.2 // indirect
26
+ github.com/bytedance/sonic/loader v0.4.0 // indirect
27
+ github.com/cloudwego/base64x v0.1.6 // indirect
28
+ github.com/gabriel-vasile/mimetype v1.4.12 // indirect
29
+ github.com/gin-contrib/sse v1.1.0 // indirect
30
+ github.com/go-openapi/jsonpointer v0.22.4 // indirect
31
+ github.com/go-openapi/jsonreference v0.21.4 // indirect
32
+ github.com/go-openapi/spec v0.22.2 // indirect
33
+ github.com/go-openapi/swag/conv v0.25.4 // indirect
34
+ github.com/go-openapi/swag/jsonname v0.25.4 // indirect
35
+ github.com/go-openapi/swag/jsonutils v0.25.4 // indirect
36
+ github.com/go-openapi/swag/loading v0.25.4 // indirect
37
+ github.com/go-openapi/swag/stringutils v0.25.4 // indirect
38
+ github.com/go-openapi/swag/typeutils v0.25.4 // indirect
39
+ github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
40
+ github.com/go-playground/locales v0.14.1 // indirect
41
+ github.com/go-playground/universal-translator v0.18.1 // indirect
42
+ github.com/go-playground/validator/v10 v10.30.0 // indirect
43
+ github.com/go-sql-driver/mysql v1.8.1 // indirect
44
+ github.com/goccy/go-json v0.10.5 // indirect
45
+ github.com/goccy/go-yaml v1.19.1 // indirect
46
+ github.com/jackc/pgpassfile v1.0.0 // indirect
47
+ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
48
+ github.com/jackc/pgx/v5 v5.7.5 // indirect
49
+ github.com/jackc/puddle/v2 v2.2.2 // indirect
50
+ github.com/jinzhu/inflection v1.0.0 // indirect
51
+ github.com/jinzhu/now v1.1.5 // indirect
52
+ github.com/json-iterator/go v1.1.12 // indirect
53
+ github.com/klauspost/cpuid/v2 v2.3.0 // indirect
54
+ github.com/kr/text v0.2.0 // indirect
55
+ github.com/leodido/go-urn v1.4.0 // indirect
56
+ github.com/mattn/go-isatty v0.0.20 // indirect
57
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
58
+ github.com/modern-go/reflect2 v1.0.2 // indirect
59
+ github.com/pelletier/go-toml/v2 v2.2.4 // indirect
60
+ github.com/quic-go/qpack v0.6.0 // indirect
61
+ github.com/quic-go/quic-go v0.58.0 // indirect
62
+ github.com/rogpeppe/go-internal v1.13.1 // indirect
63
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
64
+ github.com/ugorji/go/codec v1.3.1 // indirect
65
+ go.yaml.in/yaml/v3 v3.0.4 // indirect
66
+ golang.org/x/arch v0.23.0 // indirect
67
+ golang.org/x/mod v0.31.0 // indirect
68
+ golang.org/x/net v0.48.0 // indirect
69
+ golang.org/x/sync v0.19.0 // indirect
70
+ golang.org/x/sys v0.39.0 // indirect
71
+ golang.org/x/text v0.32.0 // indirect
72
+ golang.org/x/tools v0.40.0 // indirect
73
+ google.golang.org/protobuf v1.36.11 // indirect
74
+ gorm.io/driver/mysql v1.5.6 // indirect
75
+ )
go.sum ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
2
+ filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
3
+ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
4
+ github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
5
+ github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
6
+ github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
7
+ github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
8
+ github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
9
+ github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
10
+ github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
11
+ github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
12
+ github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
13
+ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
14
+ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
15
+ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
16
+ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
17
+ github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
18
+ github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
19
+ github.com/gin-contrib/gzip v1.2.5 h1:fIZs0S+l17pIu1P5XRJOo/YNqfIuPCrZZ3TWB7pjckI=
20
+ github.com/gin-contrib/gzip v1.2.5/go.mod h1:aomRgR7ftdZV3uWY0gW/m8rChfxau0n8YVvwlOHONzw=
21
+ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
22
+ github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
23
+ github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
24
+ github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
25
+ github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
26
+ github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80=
27
+ github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8=
28
+ github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4=
29
+ github.com/go-openapi/spec v0.22.2 h1:KEU4Fb+Lp1qg0V4MxrSCPv403ZjBl8Lx1a83gIPU8Qc=
30
+ github.com/go-openapi/spec v0.22.2/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs=
31
+ github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
32
+ github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4=
33
+ github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU=
34
+ github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=
35
+ github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
36
+ github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA=
37
+ github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY=
38
+ github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo=
39
+ github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM=
40
+ github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s=
41
+ github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE=
42
+ github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8=
43
+ github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=
44
+ github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw=
45
+ github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=
46
+ github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw=
47
+ github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc=
48
+ github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4=
49
+ github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=
50
+ github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
51
+ github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
52
+ github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
53
+ github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
54
+ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
55
+ github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
56
+ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
57
+ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
58
+ github.com/go-playground/validator/v10 v10.30.0 h1:5YBPNs273uzsZJD1I8uiB4Aqg9sN6sMDVX3s6LxmhWU=
59
+ github.com/go-playground/validator/v10 v10.30.0/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
60
+ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
61
+ github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
62
+ github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
63
+ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
64
+ github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
65
+ github.com/goccy/go-yaml v1.19.1 h1:3rG3+v8pkhRqoQ/88NYNMHYVGYztCOCIZ7UQhu7H+NE=
66
+ github.com/goccy/go-yaml v1.19.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
67
+ github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
68
+ github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
69
+ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
70
+ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
71
+ github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
72
+ github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
73
+ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
74
+ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
75
+ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
76
+ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
77
+ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
78
+ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
79
+ github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
80
+ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
81
+ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
82
+ github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
83
+ github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
84
+ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
85
+ github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
86
+ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
87
+ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
88
+ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
89
+ github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
90
+ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
91
+ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
92
+ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
93
+ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
94
+ github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
95
+ github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
96
+ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
97
+ github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
98
+ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
99
+ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
100
+ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
101
+ github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
102
+ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
103
+ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
104
+ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
105
+ github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
106
+ github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA=
107
+ github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
108
+ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
109
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
110
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
111
+ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
112
+ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
113
+ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
114
+ github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
115
+ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
116
+ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
117
+ github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
118
+ github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
119
+ github.com/quic-go/quic-go v0.58.0 h1:ggY2pvZaVdB9EyojxL1p+5mptkuHyX5MOSv4dgWF4Ug=
120
+ github.com/quic-go/quic-go v0.58.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
121
+ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
122
+ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
123
+ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
124
+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
125
+ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
126
+ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
127
+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
128
+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
129
+ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
130
+ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
131
+ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
132
+ github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
133
+ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
134
+ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
135
+ github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
136
+ github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
137
+ github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY=
138
+ github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw=
139
+ github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
140
+ github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
141
+ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
142
+ github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
143
+ github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
144
+ github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
145
+ github.com/xendit/xendit-go/v7 v7.0.0 h1:A7Nhaulk1a+mOI/KgRcvb5VSQEB6nhsUGkAhi+RkrEM=
146
+ github.com/xendit/xendit-go/v7 v7.0.0/go.mod h1:W562aw0zhjzF/OUhZLc77q2iFQc9INa5tBy5xl6OLbo=
147
+ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
148
+ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
149
+ go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
150
+ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
151
+ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
152
+ golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
153
+ golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
154
+ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
155
+ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
156
+ golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
157
+ golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
158
+ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
159
+ golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
160
+ golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
161
+ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
162
+ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
163
+ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
164
+ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
165
+ golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
166
+ golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
167
+ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
168
+ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
169
+ golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
170
+ golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
171
+ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
172
+ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
173
+ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
174
+ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
175
+ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
176
+ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
177
+ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
178
+ golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
179
+ golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
180
+ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
181
+ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
182
+ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
183
+ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
184
+ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
185
+ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
186
+ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
187
+ golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
188
+ golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
189
+ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
190
+ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
191
+ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
192
+ golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
193
+ golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
194
+ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
195
+ google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
196
+ google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
197
+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
198
+ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
199
+ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
200
+ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
201
+ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
202
+ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
203
+ gorm.io/datatypes v1.2.7 h1:ww9GAhF1aGXZY3EB3cJPJ7//JiuQo7DlQA7NNlVaTdk=
204
+ gorm.io/datatypes v1.2.7/go.mod h1:M2iO+6S3hhi4nAyYe444Pcb0dcIiOMJ7QHaUXxyiNZY=
205
+ gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8=
206
+ gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
207
+ gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
208
+ gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
209
+ gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
210
+ gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
211
+ gorm.io/driver/sqlserver v1.6.0 h1:VZOBQVsVhkHU/NzNhRJKoANt5pZGQAS1Bwc6m6dgfnc=
212
+ gorm.io/driver/sqlserver v1.6.0/go.mod h1:WQzt4IJo/WHKnckU9jXBLMJIVNMVeTu25dnOzehntWw=
213
+ gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
214
+ gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
215
+ gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
go.work ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ go 1.25.4
2
+
3
+ use .
go.work.sum ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
2
+ cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
3
+ cloud.google.com/go v0.112.2 h1:ZaGT6LiG7dBzi6zNOvVZwacaXlmf3lRqnC4DQzqyRQw=
4
+ cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms=
5
+ cloud.google.com/go/longrunning v0.5.6 h1:xAe8+0YaWoCKr9t1+aWe+OeQgN/iJK1fEgZSXmjuEaE=
6
+ cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn9GqyjaBT8/mA=
7
+ cloud.google.com/go/translate v1.10.3 h1:g+B29z4gtRGsiKDoTF+bNeH25bLRokAaElygX2FcZkE=
8
+ cloud.google.com/go/translate v1.10.3/go.mod h1:GW0vC1qvPtd3pgtypCv4k4U8B7EdgK9/QEF2aJEUovs=
9
+ github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
10
+ github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4=
11
+ github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw=
12
+ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
13
+ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
14
+ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
15
+ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
16
+ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
17
+ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
18
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
19
+ github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
20
+ github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
21
+ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
22
+ github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
23
+ github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
24
+ github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
25
+ github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
26
+ github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
27
+ github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
28
+ github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
29
+ github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
30
+ github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
31
+ github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
32
+ github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
33
+ github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=
34
+ github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
35
+ github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
36
+ github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk=
37
+ github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg=
38
+ github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg=
39
+ github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I=
40
+ github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
41
+ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
42
+ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
43
+ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
44
+ github.com/google/go-pkcs11 v0.3.0 h1:PVRnTgtArZ3QQqTGtbtjtnIkzl2iY2kt24yqbrf7td8=
45
+ github.com/google/go-pkcs11 v0.3.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY=
46
+ github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
47
+ github.com/jordanlewis/gcassert v0.0.0-20250430164644-389ef753e22e/go.mod h1:ZybsQk6DWyN5t7An1MuPm1gtSZ1xDaTXS9ZjIOxvQrk=
48
+ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
49
+ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
50
+ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
51
+ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
52
+ github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
53
+ github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
54
+ github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
55
+ github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
56
+ github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
57
+ github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
58
+ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
59
+ github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
60
+ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
61
+ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
62
+ github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
63
+ github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
64
+ github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
65
+ github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
66
+ github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
67
+ github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
68
+ github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
69
+ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
70
+ go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
71
+ go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
72
+ go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
73
+ golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU=
74
+ golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=
75
+ golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
76
+ golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
77
+ golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
78
+ golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
79
+ google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
80
+ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
81
+ google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
82
+ google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
83
+ google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc=
84
+ google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M=
85
+ google.golang.org/genproto/googleapis/bytestream v0.0.0-20251014184007-4626949a642f h1:T/BJL1nqPyWStq45hQyss5sEketltFJ/eWERyJ98U5M=
86
+ google.golang.org/genproto/googleapis/bytestream v0.0.0-20251014184007-4626949a642f/go.mod h1:ejCb7yLmK6GCVHp5qpeKbm4KZew/ldg+9b8kq5MONgk=
87
+ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
88
+ rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
89
+ rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
90
+ sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
91
+ sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
logs/error_log.txt ADDED
File without changes
logs/security_log.txt ADDED
File without changes
main.go ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package main
2
+
3
+ import (
4
+ "abdanhafidz.com/go-boilerplate/provider"
5
+ "abdanhafidz.com/go-boilerplate/router"
6
+ )
7
+
8
+ func main() {
9
+ appProvider := provider.NewAppProvider()
10
+ router.RunRouter(appProvider)
11
+ }
middleware/authentication_middleware.go ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package middleware
2
+
3
+ import (
4
+ "errors"
5
+ "fmt"
6
+ "strings"
7
+
8
+ http_error "abdanhafidz.com/go-boilerplate/models/error"
9
+ "abdanhafidz.com/go-boilerplate/services"
10
+ utils "abdanhafidz.com/go-boilerplate/utils"
11
+ "github.com/gin-gonic/gin"
12
+ )
13
+
14
+ type AuthenticationMiddleware interface {
15
+ VerifyAccount(ctx *gin.Context)
16
+ }
17
+ type authenticationMiddleware struct {
18
+ jwtService services.JWTService
19
+ }
20
+
21
+ func NewAuthenticationMiddleware(jwtService services.JWTService) AuthenticationMiddleware {
22
+ return &authenticationMiddleware{
23
+ jwtService: jwtService,
24
+ }
25
+ }
26
+ func (m *authenticationMiddleware) VerifyAccount(c *gin.Context) {
27
+
28
+ authorizationBearer := c.Request.Header["Authorization"]
29
+
30
+ if authorizationBearer != nil {
31
+ token := strings.Split(authorizationBearer[0], " ")[1]
32
+ claim, err := m.jwtService.ValidateToken(c.Request.Context(), token)
33
+
34
+ if err != nil && errors.Is(err, http_error.INVALID_TOKEN) {
35
+ utils.ResponseFAILED(c, claim, http_error.INVALID_TOKEN)
36
+ c.Abort()
37
+ return
38
+ }
39
+ fmt.Println("Claims:", claim)
40
+ c.Set("user_id", claim.UserId)
41
+ c.Next()
42
+
43
+ } else {
44
+ utils.ResponseFAILED(c, "Empty Token", http_error.UNAUTHORIZED)
45
+ return
46
+ }
47
+
48
+ }
middleware/middleware.go ADDED
@@ -0,0 +1 @@
 
 
1
+ package middleware
models/dto/authentication_dto.go ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package dto
2
+
3
+ import entity "abdanhafidz.com/go-boilerplate/models/entity"
4
+
5
+ type SignInRequest struct {
6
+ EmailorUsername string `json:"email_or_username" binding:"required"`
7
+ Password string `json:"password" binding:"required"`
8
+ }
9
+
10
+ type SignUpRequest struct {
11
+ Name string `json:"name"`
12
+ Email string `json:"email" binding:"required,email"`
13
+ Username string `json:"username" binding:"required"`
14
+ Password string `json:"password" binding:"required"`
15
+ }
16
+
17
+ type CreateEmailVerificationRequest struct {
18
+ Email string `json:"email" binding:"required,email"`
19
+ }
20
+
21
+ type ChangePasswordRequest struct {
22
+ OldPassword string `json:"old_password" binding:"required" `
23
+ NewPassword string `json:"new_password" binding:"required"`
24
+ }
25
+
26
+ type UpdateUserRoleRequest struct {
27
+ Role string `json:"role" binding:"required"`
28
+ }
29
+
30
+ type ValidateVerifyEmailRequest struct {
31
+ Email string `json:"email" binding:"required,email"`
32
+ Token uint `json:"token" binding:"required"`
33
+ }
34
+
35
+ type ExternalAuthRequest struct {
36
+ OauthID string `json:"oauth_id" binding:"required"`
37
+ OauthProvider string `json:"oauth_provider" binding:"required"`
38
+ }
39
+ type ResetPasswordRequest struct {
40
+ Token uint `json:"token" binding:"required"`
41
+ NewPassword string `json:"new_password" binding:"required"`
42
+ }
43
+ type ForgotPasswordRequest struct {
44
+ Email string `json:"email" binding:"required,email"`
45
+ }
46
+
47
+ type ValidateForgotPasswordRequest struct {
48
+ Token uint `json:"token" binding:"required"`
49
+ NewPassword string `json:"new_password"`
50
+ }
51
+
52
+ type AuthenticatedUser struct {
53
+ User entity.User `json:"user"`
54
+ Token string `json:"token"`
55
+ }
models/dto/email_verification_dto.go ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ package dto
2
+
3
+ type DeleteEmailVerificationRequest struct {
4
+ Token uint `json:"token" binding:"required"`
5
+ }
models/dto/home_dto.go ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package dto
2
+
3
+ type HomeResponse struct {
4
+ User HomeUser `json:"user"`
5
+ Notifications HomeNotifications `json:"notifications"`
6
+ Search HomeSearch `json:"search"`
7
+ HeroBanners []HomeHeroBanner `json:"hero_banners"`
8
+ PromoStrip HomePromoStrip `json:"promo_strip"`
9
+ WeekSchedule HomeWeekSchedule `json:"week_schedule"`
10
+ EventSections []HomeEventSection `json:"event_sections"`
11
+ BeginnerClassSections []HomeBeginnerClassSection `json:"beginner_class_sections"`
12
+ }
13
+
14
+ type HomeUser struct {
15
+ ID string `json:"id"`
16
+ FirstName string `json:"first_name"`
17
+ FullName string `json:"full_name"`
18
+ AvatarUrl string `json:"avatar_url"`
19
+ }
20
+
21
+ type HomeNotifications struct {
22
+ UnreadCount int `json:"unread_count"`
23
+ }
24
+
25
+ type HomeSearch struct {
26
+ Placeholder string `json:"placeholder"`
27
+ }
28
+
29
+ type HomeHeroBanner struct {
30
+ ID string `json:"id"`
31
+ Title string `json:"title"`
32
+ Subtitle string `json:"subtitle"`
33
+ ImageUrl string `json:"image_url"`
34
+ BadgeText string `json:"badge_text"`
35
+ CtaText string `json:"cta_text"`
36
+ CtaType string `json:"cta_type"`
37
+ CtaTargetID string `json:"cta_target_id"`
38
+ }
39
+
40
+ type HomePromoStrip struct {
41
+ ID string `json:"id"`
42
+ Text string `json:"text"`
43
+ CtaText string `json:"cta_text"`
44
+ CtaType string `json:"cta_type"`
45
+ CtaTargetID string `json:"cta_target_id"`
46
+ }
47
+
48
+ type HomeWeekSchedule struct {
49
+ WeekLabel string `json:"week_label"`
50
+ StartDate string `json:"start_date"`
51
+ EndDate string `json:"end_date"`
52
+ SelectedDate string `json:"selected_date"`
53
+ Days []HomeDay `json:"days"`
54
+ Items []ScheduleItem `json:"items"`
55
+ ViewAllUrl string `json:"view_all_url"`
56
+ }
57
+
58
+ type HomeDay struct {
59
+ Date string `json:"date"`
60
+ DayNameShort string `json:"day_name_short"`
61
+ DayNumber int `json:"day_number"`
62
+ IsSelected bool `json:"is_selected"`
63
+ HasSchedule bool `json:"has_schedule"`
64
+ }
65
+
66
+ type ScheduleItem struct {
67
+ ScheduleID string `json:"schedule_id"`
68
+ ClassID string `json:"class_id"`
69
+ Title string `json:"title"`
70
+ Date string `json:"date"`
71
+ StartTime string `json:"start_time"`
72
+ EndTime string `json:"end_time"`
73
+ DisplayTime string `json:"display_time"`
74
+ TeacherName string `json:"teacher_name"`
75
+ StudioName string `json:"studio_name"`
76
+ City string `json:"city"`
77
+ IsBooked bool `json:"is_booked"`
78
+ }
79
+
80
+ type HomeEventSection struct {
81
+ ID string `json:"id"`
82
+ Name string `json:"name"`
83
+ ImageUrl string `json:"image_url"`
84
+ Type string `json:"type"`
85
+ }
86
+
87
+ type HomeBeginnerClassSection struct {
88
+ ID string `json:"id"`
89
+ Name string `json:"name"`
90
+ ImageUrl string `json:"image_url"`
91
+ Type string `json:"type"`
92
+ }
models/dto/home_swagger_wrapper.go ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package dto
2
+
3
+ type HomeResponseWrapper struct {
4
+ Success bool `json:"success"`
5
+ Message string `json:"message"`
6
+ Data HomeResponse `json:"data"`
7
+ }
8
+
9
+ type ScheduleListResponseWrapper struct {
10
+ Success bool `json:"success"`
11
+ Message string `json:"message"`
12
+ Data ScheduleListResponse `json:"data"`
13
+ }
14
+
15
+ type CategoryResponseWrapper struct {
16
+ Success bool `json:"success"`
17
+ Data []CategoryResponse `json:"data"`
18
+ }
models/dto/http_response_dto.go ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package dto
2
+
3
+ type SuccessResponse[TResponse any] struct {
4
+ Status string `json:"status"`
5
+ Data TResponse `json:"data"`
6
+ Message any `json:"message"`
7
+ MetaData any `json:"meta_data"`
8
+ }
9
+
10
+ type ErrorResponse struct {
11
+ Status string `json:"status"`
12
+ Error error `json:"errors"`
13
+ Message any `json:"message"`
14
+ MetaData any `json:"meta_data"`
15
+ }
models/dto/jwt_dto.go ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package dto
2
+
3
+ import (
4
+ "github.com/golang-jwt/jwt/v4"
5
+ )
6
+
7
+ type JWTCustomClaims struct {
8
+ UserId string `json:"user_id" binding:"required"`
9
+ Role string `json:"role" binding:"required"`
10
+ jwt.RegisteredClaims
11
+ }
12
+
13
+ type AccountData struct {
14
+ UserId string `json:"user_id" binding:"required"`
15
+ }