Template
first commit
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
root = "."
|
||||||
|
testdata_dir = "testdata"
|
||||||
|
tmp_dir = "tmp"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
args_bin = []
|
||||||
|
bin = ".\\main.exe"
|
||||||
|
cmd = "make build"
|
||||||
|
delay = 1000
|
||||||
|
exclude_dir = ["assets", "tmp", "vendor", "testdata", "node_modules"]
|
||||||
|
exclude_file = []
|
||||||
|
exclude_regex = ["_test.go"]
|
||||||
|
exclude_unchanged = false
|
||||||
|
follow_symlink = false
|
||||||
|
full_bin = ""
|
||||||
|
include_dir = []
|
||||||
|
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||||
|
include_file = []
|
||||||
|
kill_delay = "0s"
|
||||||
|
log = "build-errors.log"
|
||||||
|
poll = false
|
||||||
|
poll_interval = 0
|
||||||
|
post_cmd = []
|
||||||
|
pre_cmd = []
|
||||||
|
rerun = false
|
||||||
|
rerun_delay = 500
|
||||||
|
send_interrupt = false
|
||||||
|
stop_on_error = false
|
||||||
|
|
||||||
|
[color]
|
||||||
|
app = ""
|
||||||
|
build = "yellow"
|
||||||
|
main = "magenta"
|
||||||
|
runner = "green"
|
||||||
|
watcher = "cyan"
|
||||||
|
|
||||||
|
[log]
|
||||||
|
main_only = false
|
||||||
|
time = false
|
||||||
|
|
||||||
|
[misc]
|
||||||
|
clean_on_exit = false
|
||||||
|
|
||||||
|
[screen]
|
||||||
|
clear_on_rebuild = false
|
||||||
|
keep_scroll = true
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
version: 2
|
||||||
|
before:
|
||||||
|
hooks:
|
||||||
|
- go mod tidy
|
||||||
|
|
||||||
|
env:
|
||||||
|
- PACKAGE_PATH=github.com/<user>/<repo>/cmd
|
||||||
|
|
||||||
|
builds:
|
||||||
|
- binary: "{{ .ProjectName }}"
|
||||||
|
main: ./cmd/api
|
||||||
|
goos:
|
||||||
|
- darwin
|
||||||
|
- linux
|
||||||
|
- windows
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
- arm64
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
ldflags:
|
||||||
|
- -s -w -X {{.Env.PACKAGE_PATH}}={{.Version}}
|
||||||
|
release:
|
||||||
|
prerelease: auto
|
||||||
|
|
||||||
|
universal_binaries:
|
||||||
|
- replace: true
|
||||||
|
|
||||||
|
archives:
|
||||||
|
- name_template: >
|
||||||
|
{{- .ProjectName }}_{{- .Version }}_{{- title .Os }}_{{- if eq .Arch "amd64" }}x86_64{{- else if eq .Arch "386" }}i386{{- else }}{{ .Arch }}{{ end }}{{- if .Arm }}v{{ .Arm }}{{ end -}}
|
||||||
|
format_overrides:
|
||||||
|
- goos: windows
|
||||||
|
format: zip
|
||||||
|
builds_info:
|
||||||
|
group: root
|
||||||
|
owner: root
|
||||||
|
files:
|
||||||
|
- README.md
|
||||||
|
|
||||||
|
checksum:
|
||||||
|
name_template: 'checksums.txt'
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
FROM golang:1.24.4-alpine AS build
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN go build -o main cmd/api/main.go
|
||||||
|
|
||||||
|
FROM alpine:3.20.1 AS prod
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /app/main /app/main
|
||||||
|
EXPOSE 8070
|
||||||
|
CMD ["./main"]
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# 🚀 API Service - Sistem Manajemen Pasien
|
||||||
|
|
||||||
|
## 📑 Daftar Isi
|
||||||
|
|
||||||
|
- [✨ Fitur Utama](#-fitur-utama)
|
||||||
|
- [🏗️ Arsitektur](#️-arsitektur)
|
||||||
|
- [⚡ Quick Start](#-quick-start)
|
||||||
|
- [🔐 Autentikasi](#-autentikasi)
|
||||||
|
- [📊 API Endpoints](#-api-endpoints)
|
||||||
|
- [🛠️ Development](#️-development)
|
||||||
|
- [🚀 Deployment](#-deployment)
|
||||||
|
- [📚 Dokumentasi](#-dokumentasi)
|
||||||
|
- [🚨 Troubleshooting](#-troubleshooting)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
## ✨ Fitur Utama
|
||||||
|
|
||||||
|
### Core Features
|
||||||
|
|
||||||
|
- **🔒 JWT Authentication** - Sistem autentikasi dengan Keycloak integration
|
||||||
|
- **👥 Patient Management** - CRUD lengkap untuk data pasien
|
||||||
|
- **🔍 Dynamic Filtering** - Filter dan pencarian data pasien secara dinamis
|
||||||
|
- **📊 Advanced Search** - Pencarian dengan multiple fields dan operators
|
||||||
|
- **🏥 BPJS Integration** - Integrasi dengan layanan kesehatan BPJS
|
||||||
|
- **🩺 SATUSEHAT Integration** - Integrasi dengan platform kesehatan SATUSEHAT
|
||||||
|
- **📖 API Documentation** - Swagger/OpenAPI yang interaktif
|
||||||
|
|
||||||
|
### Developer Experience
|
||||||
|
|
||||||
|
- **🔥 Hot Reload** - Development dengan auto-restart
|
||||||
|
- **🐳 Docker Ready** - Deployment yang mudah
|
||||||
|
- **⚡ Code Generator** - Tools untuk generate handler dan model
|
||||||
|
- **🧪 Testing Suite** - Unit dan integration tests
|
||||||
|
- **📊 Health Monitoring** - Monitoring kesehatan aplikasi
|
||||||
|
- **🗄️ Multi Database** - Support PostgreSQL, MySQL, dan MongoDB
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
## 🏗️ Arsitektur
|
||||||
|
|
||||||
|
### Clean Architecture Layers
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ Presentation Layer │ ← handlers/, routes/
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ Application Layer │ ← middleware/, services/
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ Domain Layer │ ← models/, validation/
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ Infrastructure Layer │ ← database/, external APIs
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Struktur Project
|
||||||
|
|
||||||
|
```
|
||||||
|
api-service/
|
||||||
|
├── 📁 cmd/ # Entry points aplikasi
|
||||||
|
│ └── api/main.go # API server
|
||||||
|
├── 📁 internal/ # Core business logic
|
||||||
|
│ ├── config/ # Configuration management
|
||||||
|
│ ├── database/ # Database connections
|
||||||
|
│ ├── handlers/ # HTTP controllers
|
||||||
|
│ ├── middleware/ # Auth & validation middleware
|
||||||
|
│ ├── models/ # Data structures
|
||||||
|
│ ├── routes/ # API routing
|
||||||
|
│ ├── services/ # Business logic services
|
||||||
|
│ │ └── auth/ # Auth services
|
||||||
|
│ ├── utils/ # Utility functions
|
||||||
|
│ │ ├── filters/ # Dynamic filtering
|
||||||
|
│ │ └── validation/ # Data validation
|
||||||
|
│ └── server/ # HTTP server setup
|
||||||
|
├── 📁 docs/ # Documentation
|
||||||
|
└── 📁 examples/ # Example files
|
||||||
|
```
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
|
||||||
|
1. **Jangan pernah commit file .env ke repository**
|
||||||
|
2. **Gunakan environment variables untuk semua data sensitif**
|
||||||
|
3. **Gunakan secret management tools untuk production**
|
||||||
|
4. **Rotasi kunci API secara berkala**
|
||||||
|
5. **Implementasi rate limiting untuk API endpoints**
|
||||||
|
|
||||||
|
***
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"api-service/internal/server"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv" // Import the godotenv package
|
||||||
|
|
||||||
|
_ "api-service/docs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @title API Service
|
||||||
|
// @version 1.0.0
|
||||||
|
// @description A comprehensive Go API service with Swagger documentation
|
||||||
|
// @termsOfService http://swagger.io/terms/
|
||||||
|
|
||||||
|
// @contact.name API Support
|
||||||
|
// @contact.url http://www.swagger.io/support
|
||||||
|
// @contact.email support@swagger.io
|
||||||
|
|
||||||
|
// @license.name Apache 2.0
|
||||||
|
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
|
||||||
|
// @host localhost:8080
|
||||||
|
// @BasePath /api/v1
|
||||||
|
// @schemes http https
|
||||||
|
|
||||||
|
func gracefulShutdown(apiServer *http.Server, done chan bool) {
|
||||||
|
// Create context that listens for the interrupt signal from the OS.
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
// Listen for the interrupt signal.
|
||||||
|
<-ctx.Done()
|
||||||
|
|
||||||
|
log.Println("shutting down gracefully, press Ctrl+C again to force")
|
||||||
|
stop() // Allow Ctrl+C to force shutdown
|
||||||
|
|
||||||
|
// The context is used to inform the server it has 5 seconds to finish
|
||||||
|
// the request it is currently handling
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if err := apiServer.Shutdown(ctx); err != nil {
|
||||||
|
log.Printf("Server forced to shutdown with error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Server exiting")
|
||||||
|
|
||||||
|
// Notify the main goroutine that the shutdown is complete
|
||||||
|
done <- true
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.Println("Starting API Service...")
|
||||||
|
|
||||||
|
// Load environment variables from .env file
|
||||||
|
if err := godotenv.Load(); err != nil {
|
||||||
|
log.Printf("Warning: .env file not found or could not be loaded: %v", err)
|
||||||
|
log.Println("Continuing with system environment variables...")
|
||||||
|
}
|
||||||
|
|
||||||
|
server := server.NewServer()
|
||||||
|
|
||||||
|
// Create a done channel to signal when the shutdown is complete
|
||||||
|
done := make(chan bool, 1)
|
||||||
|
|
||||||
|
// Run graceful shutdown in a separate goroutine
|
||||||
|
go gracefulShutdown(server, done)
|
||||||
|
|
||||||
|
log.Printf("Server starting on port %s", server.Addr)
|
||||||
|
err := server.ListenAndServe()
|
||||||
|
if err != nil && err != http.ErrServerClosed {
|
||||||
|
panic(fmt.Sprintf("http server error: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the graceful shutdown to complete
|
||||||
|
<-done
|
||||||
|
log.Println("Graceful shutdown complete.")
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
services:
|
||||||
|
app:
|
||||||
|
container_name: general-test
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
target: prod
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8070:8070"
|
||||||
|
environment:
|
||||||
|
# Server Configuration
|
||||||
|
PORT: 8070
|
||||||
|
APP_ENV: production
|
||||||
|
GIN_MODE: release
|
||||||
|
|
||||||
|
# SATUDATA Database Configuration (PostgreSQL)
|
||||||
|
# POSTGRES_SATUDATA_CONNECTION: postgres
|
||||||
|
# POSTGRES_SATUDATA_USERNAME: postgres
|
||||||
|
# POSTGRES_SATUDATA_PASSWORD: postgres
|
||||||
|
# POSTGRES_SATUDATA_HOST: host
|
||||||
|
# POSTGRES_SATUDATA_DATABASE: postgres
|
||||||
|
# POSTGRES_SATUDATA_PORT: 5432
|
||||||
|
# POSTGRES_SATUDATA_SSLMODE: disable
|
||||||
|
|
||||||
|
|
||||||
|
# MYSQL Antrian Database
|
||||||
|
MYSQL_ANTRIAN_CONNECTION: mysql
|
||||||
|
MYSQL_ANTRIAN_HOST: host
|
||||||
|
MYSQL_ANTRIAN_USERNAME: mysql
|
||||||
|
MYSQL_ANTRIAN_PASSWORD: mysql
|
||||||
|
MYSQL_ANTRIAN_DATABASE: mysql
|
||||||
|
MYSQL_ANTRIAN_PORT: 3306
|
||||||
|
MYSQL_ANTRIAN_SSLMODE: disable
|
||||||
|
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
// Package docs Code generated by swaggo/swag. DO NOT EDIT
|
||||||
|
package docs
|
||||||
|
|
||||||
|
import "github.com/swaggo/swag"
|
||||||
|
|
||||||
|
const docTemplate = `{
|
||||||
|
"schemes": {{ marshal .Schemes }},
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"description": "{{escape .Description}}",
|
||||||
|
"title": "{{.Title}}",
|
||||||
|
"termsOfService": "http://swagger.io/terms/",
|
||||||
|
"contact": {
|
||||||
|
"name": "API Support",
|
||||||
|
"url": "http://www.swagger.io/support",
|
||||||
|
"email": "support@swagger.io"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "Apache 2.0",
|
||||||
|
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
|
||||||
|
},
|
||||||
|
"version": "{{.Version}}"
|
||||||
|
},
|
||||||
|
"host": "{{.Host}}",
|
||||||
|
"basePath": "{{.BasePath}}",
|
||||||
|
"paths": {
|
||||||
|
"/api/v1/auth/me": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Get information about the currently authenticated user",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Authentication"
|
||||||
|
],
|
||||||
|
"summary": "Get current user info",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api-service_internal_models_auth.User"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"api-service_internal_models_auth.User": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||||
|
var SwaggerInfo = &swag.Spec{
|
||||||
|
Version: "1.0.0",
|
||||||
|
Host: "localhost:8080",
|
||||||
|
BasePath: "/api/v1",
|
||||||
|
Schemes: []string{"http", "https"},
|
||||||
|
Title: "API Service",
|
||||||
|
Description: "A comprehensive Go API service with Swagger documentation",
|
||||||
|
InfoInstanceName: "swagger",
|
||||||
|
SwaggerTemplate: docTemplate,
|
||||||
|
LeftDelim: "{{",
|
||||||
|
RightDelim: "}}",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"schemes": [
|
||||||
|
"http",
|
||||||
|
"https"
|
||||||
|
],
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"description": "A comprehensive Go API service with Swagger documentation",
|
||||||
|
"title": "API Service",
|
||||||
|
"termsOfService": "http://swagger.io/terms/",
|
||||||
|
"contact": {
|
||||||
|
"name": "API Support",
|
||||||
|
"url": "http://www.swagger.io/support",
|
||||||
|
"email": "support@swagger.io"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "Apache 2.0",
|
||||||
|
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
|
||||||
|
},
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"host": "localhost:8080",
|
||||||
|
"basePath": "/api/v1",
|
||||||
|
"paths": {
|
||||||
|
"/api/v1/auth/me": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Get information about the currently authenticated user",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Authentication"
|
||||||
|
],
|
||||||
|
"summary": "Get current user info",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api-service_internal_models_auth.User"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"api-service_internal_models_auth.User": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
basePath: /api/v1
|
||||||
|
definitions:
|
||||||
|
api-service_internal_models_auth.User:
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
role:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
host: localhost:8080
|
||||||
|
info:
|
||||||
|
contact:
|
||||||
|
email: support@swagger.io
|
||||||
|
name: API Support
|
||||||
|
url: http://www.swagger.io/support
|
||||||
|
description: A comprehensive Go API service with Swagger documentation
|
||||||
|
license:
|
||||||
|
name: Apache 2.0
|
||||||
|
url: http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
termsOfService: http://swagger.io/terms/
|
||||||
|
title: API Service
|
||||||
|
version: 1.0.0
|
||||||
|
paths:
|
||||||
|
/api/v1/auth/me:
|
||||||
|
get:
|
||||||
|
description: Get information about the currently authenticated user
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api-service_internal_models_auth.User'
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
|
summary: Get current user info
|
||||||
|
tags:
|
||||||
|
- Authentication
|
||||||
|
schemes:
|
||||||
|
- http
|
||||||
|
- https
|
||||||
|
swagger: "2.0"
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
module api-service
|
||||||
|
|
||||||
|
go 1.24.4
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gin-gonic/gin v1.10.1
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/jackc/pgx/v5 v5.7.2 // Ensure pgx is a direct dependency
|
||||||
|
go.mongodb.org/mongo-driver v1.17.3
|
||||||
|
golang.org/x/crypto v0.41.0
|
||||||
|
golang.org/x/sync v0.16.0
|
||||||
|
gorm.io/driver/mysql v1.6.0 // GORM MySQL driver
|
||||||
|
gorm.io/driver/postgres v1.5.11 // Added GORM PostgreSQL driver
|
||||||
|
gorm.io/driver/sqlserver v1.6.1 // GORM SQL Server driver
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Masterminds/squirrel v1.5.4
|
||||||
|
github.com/gin-contrib/cors v1.7.6
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0
|
||||||
|
github.com/go-redis/redis_rate/v10 v10.0.1
|
||||||
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/lib/pq v1.10.9
|
||||||
|
github.com/redis/go-redis/v9 v9.16.0
|
||||||
|
github.com/spf13/viper v1.21.0
|
||||||
|
github.com/swaggo/files v1.0.1
|
||||||
|
github.com/swaggo/gin-swagger v1.6.0
|
||||||
|
github.com/swaggo/swag v1.16.6
|
||||||
|
golang.org/x/time v0.14.0
|
||||||
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
|
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||||
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||||
|
github.com/bytedance/sonic v1.14.0 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||||
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||||
|
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||||
|
github.com/go-openapi/spec v0.20.4 // indirect
|
||||||
|
github.com/go-openapi/swag v0.19.15 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||||
|
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||||
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
|
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||||
|
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.6 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/microsoft/go-mssqldb v1.8.2 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||||
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
|
github.com/xdg-go/scram v1.1.2 // indirect
|
||||||
|
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
|
golang.org/x/arch v0.20.0 // indirect
|
||||||
|
golang.org/x/mod v0.26.0 // indirect
|
||||||
|
golang.org/x/net v0.43.0 // indirect
|
||||||
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
|
golang.org/x/text v0.28.0 // indirect
|
||||||
|
golang.org/x/tools v0.35.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.7 // indirect
|
||||||
|
gorm.io/gorm v1.30.0 // indirect
|
||||||
|
)
|
||||||
@@ -0,0 +1,387 @@
|
|||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=
|
||||||
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||||
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
|
||||||
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||||
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
|
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
|
||||||
|
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
||||||
|
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||||
|
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||||
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
|
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
|
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||||
|
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
|
||||||
|
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||||
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||||
|
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
|
||||||
|
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
|
||||||
|
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||||
|
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||||
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
|
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||||
|
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
|
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
|
||||||
|
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||||
|
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
|
||||||
|
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
||||||
|
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
|
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||||
|
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
|
github.com/go-redis/redis_rate/v10 v10.0.1 h1:calPxi7tVlxojKunJwQ72kwfozdy25RjA0bCj1h0MUo=
|
||||||
|
github.com/go-redis/redis_rate/v10 v10.0.1/go.mod h1:EMiuO9+cjRkR7UvdvwMO7vbgqJkltQHtwbdIQvaBKIU=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||||
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||||
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||||
|
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||||
|
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||||
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
|
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
|
||||||
|
github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
|
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||||
|
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
|
||||||
|
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
|
||||||
|
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
|
||||||
|
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
||||||
|
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||||
|
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
|
||||||
|
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
|
||||||
|
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
|
||||||
|
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||||
|
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/microsoft/go-mssqldb v1.8.2 h1:236sewazvC8FvG6Dr3bszrVhMkAl4KYImryLkRMCd0I=
|
||||||
|
github.com/microsoft/go-mssqldb v1.8.2/go.mod h1:vp38dT33FGfVotRiTmDo3bFyaHq+p3LektQrjTULowo=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
|
||||||
|
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||||
|
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
||||||
|
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4=
|
||||||
|
github.com/redis/go-redis/v9 v9.16.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||||
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
|
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||||
|
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||||
|
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||||
|
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
|
||||||
|
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
|
||||||
|
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
|
||||||
|
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||||
|
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||||
|
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
|
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||||
|
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||||
|
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||||
|
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||||
|
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||||
|
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
|
||||||
|
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
|
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||||
|
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||||
|
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||||
|
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||||
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
|
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||||
|
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||||
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
|
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||||
|
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||||
|
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||||
|
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||||
|
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||||
|
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||||
|
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||||
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
|
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||||
|
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||||
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
|
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||||
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||||
|
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||||
|
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
|
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||||
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
|
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||||
|
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||||
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
|
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
|
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||||
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
|
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||||
|
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
||||||
|
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
||||||
|
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
||||||
|
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
|
||||||
|
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||||
|
gorm.io/driver/sqlserver v1.6.1 h1:XWISFsu2I2pqd1KJhhTZNJMx1jNQ+zVL/Q8ovDcUjtY=
|
||||||
|
gorm.io/driver/sqlserver v1.6.1/go.mod h1:VZeNn7hqX1aXoN5TPAFGWvxWG90xtA8erGn2gQmpc6U=
|
||||||
|
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||||
|
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
type Config struct {
|
||||||
|
Server ServerConfig
|
||||||
|
Validator *validator.Validate
|
||||||
|
}
|
||||||
|
type ServerConfig struct {
|
||||||
|
Port int
|
||||||
|
Mode string
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadConfig() *Config {
|
||||||
|
log.Printf("DEBUG: Raw ENV for SECURITY_MAX_INPUT_LENGTH is: '%s'", os.Getenv("SECURITY_MAX_INPUT_LENGTH"))
|
||||||
|
config := &Config{
|
||||||
|
Server: ServerConfig{
|
||||||
|
Port: getEnvAsInt("PORT", 8080),
|
||||||
|
Mode: getEnv("GIN_MODE", "debug"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// log.Printf("DEBUG: Final Config Object. MaxInputLength is: %d", config.Security.MaxInputLength)
|
||||||
|
// Initialize validator
|
||||||
|
config.Validator = validator.New()
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Lakukan hal yang sama untuk loadKeycloakConfig
|
||||||
|
|
||||||
|
|
||||||
|
// Helper functions for getting default values based on database type
|
||||||
|
func getDefaultPort(dbType string) int {
|
||||||
|
switch dbType {
|
||||||
|
case "postgres":
|
||||||
|
return 5432
|
||||||
|
case "mysql":
|
||||||
|
return 3306
|
||||||
|
case "sqlserver":
|
||||||
|
return 1433
|
||||||
|
case "mongodb":
|
||||||
|
return 27017
|
||||||
|
case "sqlite":
|
||||||
|
return 0 // SQLite doesn't use port
|
||||||
|
default:
|
||||||
|
return 5432
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultSchema(dbType string) string {
|
||||||
|
switch dbType {
|
||||||
|
case "postgres":
|
||||||
|
return "public"
|
||||||
|
case "mysql":
|
||||||
|
return ""
|
||||||
|
case "sqlserver":
|
||||||
|
return "dbo"
|
||||||
|
case "mongodb":
|
||||||
|
return ""
|
||||||
|
case "sqlite":
|
||||||
|
return ""
|
||||||
|
default:
|
||||||
|
return "public"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultSSLMode(dbType string) string {
|
||||||
|
switch dbType {
|
||||||
|
case "postgres":
|
||||||
|
return "disable"
|
||||||
|
case "mysql":
|
||||||
|
return "false"
|
||||||
|
case "sqlserver":
|
||||||
|
return "false"
|
||||||
|
case "mongodb":
|
||||||
|
return "false"
|
||||||
|
case "sqlite":
|
||||||
|
return ""
|
||||||
|
default:
|
||||||
|
return "disable"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultMaxOpenConns(dbType string) int {
|
||||||
|
switch dbType {
|
||||||
|
case "postgres":
|
||||||
|
return 25
|
||||||
|
case "mysql":
|
||||||
|
return 25
|
||||||
|
case "sqlserver":
|
||||||
|
return 25
|
||||||
|
case "mongodb":
|
||||||
|
return 100
|
||||||
|
case "sqlite":
|
||||||
|
return 1 // SQLite only supports one writer at a time
|
||||||
|
default:
|
||||||
|
return 25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultMaxIdleConns(dbType string) int {
|
||||||
|
switch dbType {
|
||||||
|
case "postgres":
|
||||||
|
return 25
|
||||||
|
case "mysql":
|
||||||
|
return 25
|
||||||
|
case "sqlserver":
|
||||||
|
return 25
|
||||||
|
case "mongodb":
|
||||||
|
return 10
|
||||||
|
case "sqlite":
|
||||||
|
return 1 // SQLite only supports one writer at a time
|
||||||
|
default:
|
||||||
|
return 25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultConnMaxLifetime(dbType string) string {
|
||||||
|
switch dbType {
|
||||||
|
case "postgres":
|
||||||
|
return "5m"
|
||||||
|
case "mysql":
|
||||||
|
return "5m"
|
||||||
|
case "sqlserver":
|
||||||
|
return "5m"
|
||||||
|
case "mongodb":
|
||||||
|
return "30m"
|
||||||
|
case "sqlite":
|
||||||
|
return "5m"
|
||||||
|
default:
|
||||||
|
return "5m"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnvFromMap(config map[string]string, key, defaultValue string) string {
|
||||||
|
if value, exists := config[key]; exists {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnvAsIntFromMap(config map[string]string, key string, defaultValue int) int {
|
||||||
|
if value, exists := config[key]; exists {
|
||||||
|
if intValue, err := strconv.Atoi(value); err == nil {
|
||||||
|
return intValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnvAsBoolFromMap(config map[string]string, key string, defaultValue bool) bool {
|
||||||
|
if value, exists := config[key]; exists {
|
||||||
|
if boolValue, err := strconv.ParseBool(value); err == nil {
|
||||||
|
return boolValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDuration(durationStr string) time.Duration {
|
||||||
|
if duration, err := time.ParseDuration(durationStr); err == nil {
|
||||||
|
return duration
|
||||||
|
}
|
||||||
|
return 5 * time.Minute
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnv(key, defaultValue string) string {
|
||||||
|
if value := os.Getenv(key); value != "" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnvAsInt(key string, defaultValue int) int {
|
||||||
|
valueStr := getEnv(key, "")
|
||||||
|
if value, err := strconv.Atoi(valueStr); err == nil {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnvAsBool(key string, defaultValue bool) bool {
|
||||||
|
valueStr := getEnv(key, "")
|
||||||
|
if value, err := strconv.ParseBool(valueStr); err == nil {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSchemes parses comma-separated schemes string into a slice
|
||||||
|
func parseSchemes(schemesStr string) []string {
|
||||||
|
if schemesStr == "" {
|
||||||
|
return []string{"http"}
|
||||||
|
}
|
||||||
|
|
||||||
|
schemes := strings.Split(schemesStr, ",")
|
||||||
|
for i, scheme := range schemes {
|
||||||
|
schemes[i] = strings.TrimSpace(scheme)
|
||||||
|
}
|
||||||
|
return schemes
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseStaticTokens parses comma-separated static tokens string into a slice
|
||||||
|
func parseStaticTokens(tokensStr string) []string {
|
||||||
|
if tokensStr == "" {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens := strings.Split(tokensStr, ",")
|
||||||
|
for i, token := range tokens {
|
||||||
|
tokens[i] = strings.TrimSpace(token)
|
||||||
|
// Remove empty tokens
|
||||||
|
if tokens[i] == "" {
|
||||||
|
tokens = append(tokens[:i], tokens[i+1:]...)
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOrigins(originsStr string) []string {
|
||||||
|
if originsStr == "" {
|
||||||
|
return []string{"http://localhost:8080"} // Default untuk pengembangan
|
||||||
|
}
|
||||||
|
origins := strings.Split(originsStr, ",")
|
||||||
|
for i, origin := range origins {
|
||||||
|
origins[i] = strings.TrimSpace(origin)
|
||||||
|
}
|
||||||
|
return origins
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) Validate() error {
|
||||||
|
var errs []string
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return fmt.Errorf("configuration validation failed: %s", strings.Join(errs, "; "))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
package database
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NullableInt32 - your existing implementation
|
||||||
|
type NullableInt32 struct {
|
||||||
|
Int32 int32 `json:"int32,omitempty"`
|
||||||
|
Valid bool `json:"valid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface for NullableInt32
|
||||||
|
func (n *NullableInt32) Scan(value interface{}) error {
|
||||||
|
var ni sql.NullInt32
|
||||||
|
if err := ni.Scan(value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n.Int32 = ni.Int32
|
||||||
|
n.Valid = ni.Valid
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface for NullableInt32
|
||||||
|
func (n NullableInt32) Value() (driver.Value, error) {
|
||||||
|
if !n.Valid {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return n.Int32, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NullableString provides consistent nullable string handling
|
||||||
|
type NullableString struct {
|
||||||
|
String string `json:"string,omitempty"`
|
||||||
|
Valid bool `json:"valid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface for NullableString
|
||||||
|
func (n *NullableString) Scan(value interface{}) error {
|
||||||
|
var ns sql.NullString
|
||||||
|
if err := ns.Scan(value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n.String = ns.String
|
||||||
|
n.Valid = ns.Valid
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface for NullableString
|
||||||
|
func (n NullableString) Value() (driver.Value, error) {
|
||||||
|
if !n.Valid {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return n.String, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NullableTime provides consistent nullable time handling
|
||||||
|
type NullableTime struct {
|
||||||
|
Time time.Time `json:"time,omitempty"`
|
||||||
|
Valid bool `json:"valid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface for NullableTime
|
||||||
|
func (n *NullableTime) Scan(value interface{}) error {
|
||||||
|
var nt sql.NullTime
|
||||||
|
if err := nt.Scan(value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n.Time = nt.Time
|
||||||
|
n.Valid = nt.Valid
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface for NullableTime
|
||||||
|
func (n NullableTime) Value() (driver.Value, error) {
|
||||||
|
if !n.Valid {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return n.Time, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata untuk pagination - dioptimalkan
|
||||||
|
type MetaResponse struct {
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
Offset int `json:"offset"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
TotalPages int `json:"total_pages"`
|
||||||
|
CurrentPage int `json:"current_page"`
|
||||||
|
HasNext bool `json:"has_next"`
|
||||||
|
HasPrev bool `json:"has_prev"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate data untuk summary
|
||||||
|
type AggregateData struct {
|
||||||
|
TotalActive int `json:"total_active"`
|
||||||
|
TotalDraft int `json:"total_draft"`
|
||||||
|
TotalInactive int `json:"total_inactive"`
|
||||||
|
ByStatus map[string]int `json:"by_status"`
|
||||||
|
ByDinas map[string]int `json:"by_dinas,omitempty"`
|
||||||
|
ByJenis map[string]int `json:"by_jenis,omitempty"`
|
||||||
|
LastUpdated *time.Time `json:"last_updated,omitempty"`
|
||||||
|
CreatedToday int `json:"created_today"`
|
||||||
|
UpdatedToday int `json:"updated_today"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error response yang konsisten
|
||||||
|
type ErrorResponse struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BaseRequest contains common fields for all BPJS requests
|
||||||
|
type BaseRequest struct {
|
||||||
|
RequestID string `json:"request_id,omitempty"`
|
||||||
|
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BaseResponse contains common response fields
|
||||||
|
type BaseResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
RequestID string `json:"request_id,omitempty"`
|
||||||
|
Timestamp string `json:"timestamp,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorResponse represents error response structure
|
||||||
|
type ErrorResponseBpjs struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
RequestID string `json:"request_id,omitempty"`
|
||||||
|
Errors map[string]interface{} `json:"errors,omitempty"`
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaginationRequest contains pagination parameters
|
||||||
|
type PaginationRequest struct {
|
||||||
|
Page int `json:"page" validate:"min=1"`
|
||||||
|
Limit int `json:"limit" validate:"min=1,max=100"`
|
||||||
|
SortBy string `json:"sort_by,omitempty"`
|
||||||
|
SortDir string `json:"sort_dir,omitempty" validate:"omitempty,oneof=asc desc"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaginationResponse contains pagination metadata
|
||||||
|
type PaginationResponse struct {
|
||||||
|
CurrentPage int `json:"current_page"`
|
||||||
|
TotalPages int `json:"total_pages"`
|
||||||
|
TotalItems int64 `json:"total_items"`
|
||||||
|
ItemsPerPage int `json:"items_per_page"`
|
||||||
|
HasNext bool `json:"has_next"`
|
||||||
|
HasPrev bool `json:"has_previous"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MetaInfo contains additional metadata
|
||||||
|
type MetaInfo struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Environment string `json:"environment"`
|
||||||
|
ServerTime string `json:"server_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetStatusCodeFromMeta(metaCode interface{}) int {
|
||||||
|
statusCode := http.StatusOK
|
||||||
|
|
||||||
|
if metaCode != nil {
|
||||||
|
switch v := metaCode.(type) {
|
||||||
|
case string:
|
||||||
|
if code, err := strconv.Atoi(v); err == nil {
|
||||||
|
if code >= 100 && code <= 599 {
|
||||||
|
statusCode = code
|
||||||
|
} else {
|
||||||
|
statusCode = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
statusCode = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
case int:
|
||||||
|
if v >= 100 && v <= 599 {
|
||||||
|
statusCode = v
|
||||||
|
} else {
|
||||||
|
statusCode = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
code := int(v)
|
||||||
|
if code >= 100 && code <= 599 {
|
||||||
|
statusCode = code
|
||||||
|
} else {
|
||||||
|
statusCode = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
statusCode = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation constants
|
||||||
|
const (
|
||||||
|
StatusDraft = "draft"
|
||||||
|
StatusActive = "active"
|
||||||
|
StatusInactive = "inactive"
|
||||||
|
StatusDeleted = "deleted"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidStatuses untuk validasi
|
||||||
|
var ValidStatuses = []string{StatusDraft, StatusActive, StatusInactive}
|
||||||
|
|
||||||
|
// IsValidStatus helper function
|
||||||
|
func IsValidStatus(status string) bool {
|
||||||
|
for _, validStatus := range ValidStatuses {
|
||||||
|
if status == validStatus {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CustomValidator wraps the validator
|
||||||
|
type CustomValidator struct {
|
||||||
|
Validator *validator.Validate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates struct
|
||||||
|
func (cv *CustomValidator) Validate(i interface{}) error {
|
||||||
|
return cv.Validator.Struct(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterCustomValidations registers custom validation rules
|
||||||
|
func RegisterCustomValidations(v *validator.Validate) {
|
||||||
|
// Validate Indonesian phone number
|
||||||
|
v.RegisterValidation("indonesian_phone", validateIndonesianPhone)
|
||||||
|
|
||||||
|
// Validate BPJS card number format
|
||||||
|
v.RegisterValidation("bpjs_card", validateBPJSCard)
|
||||||
|
|
||||||
|
// Validate Indonesian NIK
|
||||||
|
v.RegisterValidation("indonesian_nik", validateIndonesianNIK)
|
||||||
|
|
||||||
|
// Validate date format YYYY-MM-DD
|
||||||
|
v.RegisterValidation("date_format", validateDateFormat)
|
||||||
|
|
||||||
|
// Validate ICD-10 code format
|
||||||
|
v.RegisterValidation("icd10", validateICD10)
|
||||||
|
|
||||||
|
// Validate ICD-9-CM procedure code
|
||||||
|
v.RegisterValidation("icd9cm", validateICD9CM)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateIndonesianPhone(fl validator.FieldLevel) bool {
|
||||||
|
phone := fl.Field().String()
|
||||||
|
if phone == "" {
|
||||||
|
return true // Optional field
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indonesian phone number pattern: +62, 62, 08, or 8
|
||||||
|
pattern := `^(\+?62|0?8)[1-9][0-9]{7,11}$`
|
||||||
|
matched, _ := regexp.MatchString(pattern, phone)
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateBPJSCard(fl validator.FieldLevel) bool {
|
||||||
|
card := fl.Field().String()
|
||||||
|
if len(card) != 13 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// BPJS card should be numeric
|
||||||
|
pattern := `^\d{13}$`
|
||||||
|
matched, _ := regexp.MatchString(pattern, card)
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateIndonesianNIK(fl validator.FieldLevel) bool {
|
||||||
|
nik := fl.Field().String()
|
||||||
|
if len(nik) != 16 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// NIK should be numeric
|
||||||
|
pattern := `^\d{16}$`
|
||||||
|
matched, _ := regexp.MatchString(pattern, nik)
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateDateFormat(fl validator.FieldLevel) bool {
|
||||||
|
dateStr := fl.Field().String()
|
||||||
|
_, err := time.Parse("2006-01-02", dateStr)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateICD10(fl validator.FieldLevel) bool {
|
||||||
|
code := fl.Field().String()
|
||||||
|
if code == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic ICD-10 pattern: Letter followed by 2 digits, optional dot and more digits
|
||||||
|
pattern := `^[A-Z]\d{2}(\.\d+)?$`
|
||||||
|
matched, _ := regexp.MatchString(pattern, strings.ToUpper(code))
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateICD9CM(fl validator.FieldLevel) bool {
|
||||||
|
code := fl.Field().String()
|
||||||
|
if code == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic ICD-9-CM procedure pattern: 2-4 digits with optional decimal
|
||||||
|
pattern := `^\d{2,4}(\.\d+)?$`
|
||||||
|
matched, _ := regexp.MatchString(pattern, code)
|
||||||
|
return matched
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"api-service/internal/config"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterRoutes(cfg *config.Config) *gin.Engine {
|
||||||
|
// Atur mode Gin berdasarkan konfigurasi
|
||||||
|
gin.SetMode(cfg.Server.Mode)
|
||||||
|
router := gin.New()
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// GLOBAL MIDDLEWARE STACK (Middleware yang diperlukan SEMUA route)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// INISIALISASI SERVIS & HANDLER
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// SWAGGER DOCUMENTATION (Publik - TANPA SecurityHeaders)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// API GROUPS (Dengan Keamanan Ketat)
|
||||||
|
// =============================================================================
|
||||||
|
// Terapkan middleware keamanan dan validasi input HANYA ke grup API.
|
||||||
|
// Ini adalah perubahan utama.
|
||||||
|
|
||||||
|
|
||||||
|
// --- HEALTH CHECK & SYSTEM ROUTES ---
|
||||||
|
|
||||||
|
|
||||||
|
// --- API v1 GROUP ---
|
||||||
|
// =============================================================================
|
||||||
|
// PUBLIC ROUTES (No Authentication Required)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// PROTECTED ROUTES (Authentication Required)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// DEBUG ROUTES (Publik - Tanpa keamanan ketat)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/joho/godotenv/autoload"
|
||||||
|
|
||||||
|
"api-service/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
port int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer() *http.Server {
|
||||||
|
// Load configuration
|
||||||
|
cfg := config.LoadConfig()
|
||||||
|
cfg.Validate()
|
||||||
|
|
||||||
|
port, _ := strconv.Atoi(os.Getenv("PORT"))
|
||||||
|
if port == 0 {
|
||||||
|
port = cfg.Server.Port
|
||||||
|
}
|
||||||
|
|
||||||
|
// if dbService == nil { // Check if the database service is already initialized
|
||||||
|
// dbService = database.New(cfg) // Initialize only once
|
||||||
|
// }
|
||||||
|
|
||||||
|
NewServer := &Server{
|
||||||
|
port: port,
|
||||||
|
// db: dbService, // Use the global database service instance
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declare Server config
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: fmt.Sprintf(":%d", NewServer.port),
|
||||||
|
// Handler: v1.RegisterRoutes(cfg),
|
||||||
|
IdleTimeout: time.Minute,
|
||||||
|
ReadTimeout: 10 * time.Second,
|
||||||
|
WriteTimeout: 30 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
return server
|
||||||
|
}
|
||||||
@@ -0,0 +1,737 @@
|
|||||||
|
# Dynamic Query Builder
|
||||||
|
|
||||||
|
Pustaka Go yang powerful untuk membangun query SQL dan MongoDB secara dinamis dengan dukungan filtering, sorting, pagination, join, CTE, window functions, dan operasi JSON/Array.
|
||||||
|
|
||||||
|
## Fitur Utama
|
||||||
|
|
||||||
|
- **Multi-Database**: Mendukung PostgreSQL, MySQL, SQLite, SQL Server, dan MongoDB
|
||||||
|
- **Dynamic Filtering**: Berbagai operator filter (_eq, _neq, _like, _in, _between, dll.)
|
||||||
|
- **JSON Operations**: Dukungan penuh untuk query dan update data JSON
|
||||||
|
- **Array Operations**: Query array dengan berbagai operator
|
||||||
|
- **Security**: Built-in proteksi SQL injection dan kontrol akses kolom/tabel
|
||||||
|
- **Window Functions**: Dukungan ROW_NUMBER, RANK, dll.
|
||||||
|
- **CTE & Unions**: Support untuk Common Table Expressions dan UNION
|
||||||
|
- **Query Parsing**: Parse URL query parameters menjadi DynamicQuery
|
||||||
|
|
||||||
|
## Instalasi
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get github.com/Masterminds/squirrel
|
||||||
|
go get github.com/jmoiron/sqlx
|
||||||
|
go get go.mongodb.org/mongo-driver/mongo
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contoh Penggunaan
|
||||||
|
|
||||||
|
### 1. Operator Perbandingan Dasar
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Inisialisasi QueryBuilder
|
||||||
|
qb := NewQueryBuilder(DBTypePostgreSQL)
|
||||||
|
|
||||||
|
// Koneksi ke database
|
||||||
|
db, err := sqlx.Connect("postgres", "user=postgres dbname=mydb sslmode=disable")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Equal (_eq)
|
||||||
|
query := DynamicQuery{
|
||||||
|
From: "users",
|
||||||
|
Filters: []FilterGroup{{
|
||||||
|
Filters: []DynamicFilter{{
|
||||||
|
Column: "email",
|
||||||
|
Operator: OpEqual,
|
||||||
|
Value: "john@example.com",
|
||||||
|
}},
|
||||||
|
LogicOp: "AND",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var users []map[string]interface{}
|
||||||
|
err = qb.ExecuteQuery(context.Background(), db, query, &users)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Found %d users\n", len(users))
|
||||||
|
|
||||||
|
// Greater Than (_gt)
|
||||||
|
query = DynamicQuery{
|
||||||
|
From: "products",
|
||||||
|
Filters: []FilterGroup{{
|
||||||
|
Filters: []DynamicFilter{{
|
||||||
|
Column: "price",
|
||||||
|
Operator: OpGreaterThan,
|
||||||
|
Value: 100000,
|
||||||
|
}},
|
||||||
|
LogicOp: "AND",
|
||||||
|
}},
|
||||||
|
Limit: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
var products []map[string]interface{}
|
||||||
|
err = qb.ExecuteQuery(context.Background(), db, query, &products)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Found %d products with price > 100000\n", len(products))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Operator Like dan String Matching
|
||||||
|
|
||||||
|
```go
|
||||||
|
func stringMatchingExample(qb *QueryBuilder, db *sqlx.DB) {
|
||||||
|
// Case Insensitive Like (_ilike)
|
||||||
|
query := DynamicQuery{
|
||||||
|
From: "products",
|
||||||
|
Filters: []FilterGroup{{
|
||||||
|
Filters: []DynamicFilter{{
|
||||||
|
Column: "name",
|
||||||
|
Operator: OpILike,
|
||||||
|
Value: "%laptop%",
|
||||||
|
}},
|
||||||
|
LogicOp: "AND",
|
||||||
|
}},
|
||||||
|
Limit: 20,
|
||||||
|
}
|
||||||
|
|
||||||
|
var products []map[string]interface{}
|
||||||
|
qb.ExecuteQuery(context.Background(), db, query, &products)
|
||||||
|
|
||||||
|
// Contains (_contains)
|
||||||
|
query = DynamicQuery{
|
||||||
|
From: "articles",
|
||||||
|
Filters: []FilterGroup{{
|
||||||
|
Filters: []DynamicFilter{{
|
||||||
|
Column: "content",
|
||||||
|
Operator: OpContains,
|
||||||
|
Value: "golang",
|
||||||
|
}},
|
||||||
|
LogicOp: "AND",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var articles []map[string]interface{}
|
||||||
|
qb.ExecuteQuery(context.Background(), db, query, &articles)
|
||||||
|
|
||||||
|
// Starts With (_starts_with)
|
||||||
|
query = DynamicQuery{
|
||||||
|
From: "users",
|
||||||
|
Filters: []FilterGroup{{
|
||||||
|
Filters: []DynamicFilter{{
|
||||||
|
Column: "username",
|
||||||
|
Operator: OpStartsWith,
|
||||||
|
Value: "admin",
|
||||||
|
}},
|
||||||
|
LogicOp: "AND",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var admins []map[string]interface{}
|
||||||
|
qb.ExecuteQuery(context.Background(), db, query, &admins)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Operator IN, NOT IN, dan Between
|
||||||
|
|
||||||
|
```go
|
||||||
|
func inBetweenExample(qb *QueryBuilder, db *sqlx.DB) {
|
||||||
|
// In (_in)
|
||||||
|
query := DynamicQuery{
|
||||||
|
From: "products",
|
||||||
|
Filters: []FilterGroup{{
|
||||||
|
Filters: []DynamicFilter{{
|
||||||
|
Column: "category",
|
||||||
|
Operator: OpIn,
|
||||||
|
Value: []string{"electronics", "computers", "gadgets"},
|
||||||
|
}},
|
||||||
|
LogicOp: "AND",
|
||||||
|
}},
|
||||||
|
Limit: 50,
|
||||||
|
}
|
||||||
|
|
||||||
|
var products []map[string]interface{}
|
||||||
|
qb.ExecuteQuery(context.Background(), db, query, &products)
|
||||||
|
|
||||||
|
// Between (_between)
|
||||||
|
query = DynamicQuery{
|
||||||
|
From: "orders",
|
||||||
|
Filters: []FilterGroup{{
|
||||||
|
Filters: []DynamicFilter{{
|
||||||
|
Column: "order_date",
|
||||||
|
Operator: OpBetween,
|
||||||
|
Value: []interface{}{time.Now().AddDate(0, -1, 0), time.Now()},
|
||||||
|
}},
|
||||||
|
LogicOp: "AND",
|
||||||
|
}},
|
||||||
|
Sort: []SortField{{Column: "order_date", Order: "DESC"}},
|
||||||
|
Limit: 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
var orders []map[string]interface{}
|
||||||
|
qb.ExecuteQuery(context.Background(), db, query, &orders)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Operator Null dan Exists
|
||||||
|
|
||||||
|
```go
|
||||||
|
func nullExistsExample(qb *QueryBuilder, db *sqlx.DB) {
|
||||||
|
// Is Null (_null)
|
||||||
|
query := DynamicQuery{
|
||||||
|
From: "users",
|
||||||
|
Filters: []FilterGroup{{
|
||||||
|
Filters: []DynamicFilter{{
|
||||||
|
Column: "deleted_at",
|
||||||
|
Operator: OpNull,
|
||||||
|
Value: nil,
|
||||||
|
}},
|
||||||
|
LogicOp: "AND",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var activeUsers []map[string]interface{}
|
||||||
|
qb.ExecuteQuery(context.Background(), db, query, &activeUsers)
|
||||||
|
|
||||||
|
// Is Not Null (_nnull)
|
||||||
|
query = DynamicQuery{
|
||||||
|
From: "products",
|
||||||
|
Filters: []FilterGroup{{
|
||||||
|
Filters: []DynamicFilter{{
|
||||||
|
Column: "description",
|
||||||
|
Operator: OpNotNull,
|
||||||
|
Value: nil,
|
||||||
|
}},
|
||||||
|
LogicOp: "AND",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var productsWithDesc []map[string]interface{}
|
||||||
|
qb.ExecuteQuery(context.Background(), db, query, &productsWithDesc)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Operator JSON
|
||||||
|
|
||||||
|
```go
|
||||||
|
func jsonExample(qb *QueryBuilder, db *sqlx.DB) {
|
||||||
|
// JSON Contains (_json_contains)
|
||||||
|
query := DynamicQuery{
|
||||||
|
From: "products",
|
||||||
|
Filters: []FilterGroup{{
|
||||||
|
Filters: []DynamicFilter{{
|
||||||
|
Column: "attributes",
|
||||||
|
Operator: OpJsonContains,
|
||||||
|
Value: `{"color": "red"}`,
|
||||||
|
}},
|
||||||
|
LogicOp: "AND",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var redProducts []map[string]interface{}
|
||||||
|
qb.ExecuteQuery(context.Background(), db, query, &redProducts)
|
||||||
|
|
||||||
|
// JSON Exists (_json_exists)
|
||||||
|
query = DynamicQuery{
|
||||||
|
From: "users",
|
||||||
|
Filters: []FilterGroup{{
|
||||||
|
Filters: []DynamicFilter{{
|
||||||
|
Column: "profile",
|
||||||
|
Operator: OpJsonExists,
|
||||||
|
Options: map[string]interface{}{
|
||||||
|
"path": "$.social_media",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
LogicOp: "AND",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var usersWithSocialMedia []map[string]interface{}
|
||||||
|
qb.ExecuteQuery(context.Background(), db, query, &usersWithSocialMedia)
|
||||||
|
|
||||||
|
// JSON Equal (_json_eq)
|
||||||
|
query = DynamicQuery{
|
||||||
|
From: "settings",
|
||||||
|
Filters: []FilterGroup{{
|
||||||
|
Filters: []DynamicFilter{{
|
||||||
|
Column: "preferences",
|
||||||
|
Operator: OpJsonEqual,
|
||||||
|
Value: "dark_mode",
|
||||||
|
Options: map[string]interface{}{
|
||||||
|
"path": "$.theme",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
LogicOp: "AND",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var darkModeUsers []map[string]interface{}
|
||||||
|
qb.ExecuteQuery(context.Background(), db, query, &darkModeUsers)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Operator Array
|
||||||
|
|
||||||
|
```go
|
||||||
|
func arrayExample(qb *QueryBuilder, db *sqlx.DB) {
|
||||||
|
// Array Contains (_array_contains)
|
||||||
|
query := DynamicQuery{
|
||||||
|
From: "products",
|
||||||
|
Filters: []FilterGroup{{
|
||||||
|
Filters: []DynamicFilter{{
|
||||||
|
Column: "tags",
|
||||||
|
Operator: OpArrayContains,
|
||||||
|
Value: "premium",
|
||||||
|
}},
|
||||||
|
LogicOp: "AND",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var premiumProducts []map[string]interface{}
|
||||||
|
qb.ExecuteQuery(context.Background(), db, query, &premiumProducts)
|
||||||
|
|
||||||
|
// Array Length (_array_length)
|
||||||
|
query = DynamicQuery{
|
||||||
|
From: "galleries",
|
||||||
|
Filters: []FilterGroup{{
|
||||||
|
Filters: []DynamicFilter{{
|
||||||
|
Column: "images",
|
||||||
|
Operator: OpArrayLength,
|
||||||
|
Options: map[string]interface{}{
|
||||||
|
"length": 5,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
LogicOp: "AND",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var galleriesWith5Images []map[string]interface{}
|
||||||
|
qb.ExecuteQuery(context.Background(), db, query, &galleriesWith5Images)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Complex Query dengan Multiple Filters
|
||||||
|
|
||||||
|
```go
|
||||||
|
func complexQueryExample(qb *QueryBuilder, db *sqlx.DB) {
|
||||||
|
// Query kompleks dengan multiple filter groups
|
||||||
|
query := DynamicQuery{
|
||||||
|
From: "products",
|
||||||
|
Fields: []SelectField{
|
||||||
|
{Expression: "id", Alias: "product_id"},
|
||||||
|
{Expression: "name", Alias: "product_name"},
|
||||||
|
{Expression: "price", Alias: "price"},
|
||||||
|
{Expression: "category", Alias: "category"},
|
||||||
|
},
|
||||||
|
Filters: []FilterGroup{
|
||||||
|
// Filter Group 1: Active products with price range
|
||||||
|
{
|
||||||
|
Filters: []DynamicFilter{
|
||||||
|
{
|
||||||
|
Column: "status",
|
||||||
|
Operator: OpEqual,
|
||||||
|
Value: "active",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Column: "price",
|
||||||
|
Operator: OpBetween,
|
||||||
|
Value: []interface{}{100000, 500000},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LogicOp: "AND",
|
||||||
|
},
|
||||||
|
// Filter Group 2: Category OR name matching
|
||||||
|
{
|
||||||
|
Filters: []DynamicFilter{
|
||||||
|
{
|
||||||
|
Column: "category",
|
||||||
|
Operator: OpIn,
|
||||||
|
Value: []string{"electronics", "computers"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Column: "name",
|
||||||
|
Operator: OpILike,
|
||||||
|
Value: "%laptop%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LogicOp: "OR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Sort: []SortField{
|
||||||
|
{Column: "price", Order: "ASC"},
|
||||||
|
{Column: "name", Order: "ASC"},
|
||||||
|
},
|
||||||
|
Limit: 20,
|
||||||
|
Offset: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
var results []map[string]interface{}
|
||||||
|
err := qb.ExecuteQuery(context.Background(), db, query, &results)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Found %d products\n", len(results))
|
||||||
|
for _, result := range results {
|
||||||
|
fmt.Printf("Product: %s, Price: %v\n", result["product_name"], result["price"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. Query dengan JOIN
|
||||||
|
|
||||||
|
```go
|
||||||
|
func joinExample(qb *QueryBuilder, db *sqlx.DB) {
|
||||||
|
query := DynamicQuery{
|
||||||
|
From: "orders",
|
||||||
|
Aliases: "o",
|
||||||
|
Fields: []SelectField{
|
||||||
|
{Expression: "o.id", Alias: "order_id"},
|
||||||
|
{Expression: "o.order_date", Alias: "order_date"},
|
||||||
|
{Expression: "c.name", Alias: "customer_name"},
|
||||||
|
{Expression: "p.name", Alias: "product_name"},
|
||||||
|
},
|
||||||
|
Joins: []Join{
|
||||||
|
{
|
||||||
|
Type: "INNER",
|
||||||
|
Table: "customers",
|
||||||
|
Alias: "c",
|
||||||
|
OnConditions: FilterGroup{
|
||||||
|
Filters: []DynamicFilter{
|
||||||
|
{
|
||||||
|
Column: "o.customer_id",
|
||||||
|
Operator: OpEqual,
|
||||||
|
Value: "c.id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LogicOp: "AND",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "LEFT",
|
||||||
|
Table: "products",
|
||||||
|
Alias: "p",
|
||||||
|
OnConditions: FilterGroup{
|
||||||
|
Filters: []DynamicFilter{
|
||||||
|
{
|
||||||
|
Column: "o.product_id",
|
||||||
|
Operator: OpEqual,
|
||||||
|
Value: "p.id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LogicOp: "AND",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Filters: []FilterGroup{{
|
||||||
|
Filters: []DynamicFilter{
|
||||||
|
{
|
||||||
|
Column: "o.order_date",
|
||||||
|
Operator: OpGreaterThanEqual,
|
||||||
|
Value: time.Now().AddDate(0, -1, 0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LogicOp: "AND",
|
||||||
|
}},
|
||||||
|
Sort: []SortField{
|
||||||
|
{Column: "o.order_date", Order: "DESC"},
|
||||||
|
},
|
||||||
|
Limit: 50,
|
||||||
|
}
|
||||||
|
|
||||||
|
var orders []map[string]interface{}
|
||||||
|
err := qb.ExecuteQuery(context.Background(), db, query, &orders)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Found %d orders\n", len(orders))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. Query dengan Window Functions
|
||||||
|
|
||||||
|
```go
|
||||||
|
func windowFunctionExample(qb *QueryBuilder, db *sqlx.DB) {
|
||||||
|
query := DynamicQuery{
|
||||||
|
From: "sales",
|
||||||
|
Fields: []SelectField{
|
||||||
|
{Expression: "id", Alias: "sale_id"},
|
||||||
|
{Expression: "product_name", Alias: "product"},
|
||||||
|
{Expression: "amount", Alias: "sale_amount"},
|
||||||
|
},
|
||||||
|
WindowFunctions: []WindowFunction{
|
||||||
|
{
|
||||||
|
Function: "ROW_NUMBER",
|
||||||
|
Over: "product_name",
|
||||||
|
OrderBy: "amount DESC",
|
||||||
|
Alias: "rank_in_category",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Function: "SUM",
|
||||||
|
Over: "product_name",
|
||||||
|
OrderBy: "sale_date",
|
||||||
|
Frame: "ROWS UNBOUNDED PRECEDING",
|
||||||
|
Alias: "running_total",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Sort: []SortField{
|
||||||
|
{Column: "product_name", Order: "ASC"},
|
||||||
|
{Column: "amount", Order: "DESC"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var sales []map[string]interface{}
|
||||||
|
err := qb.ExecuteQuery(context.Background(), db, query, &sales)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Found %d sales records\n", len(sales))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10. MongoDB Example
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mongodbExample() {
|
||||||
|
// Inisialisasi MongoQueryBuilder
|
||||||
|
mqb := NewMongoQueryBuilder()
|
||||||
|
|
||||||
|
// Koneksi ke MongoDB
|
||||||
|
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://localhost:27017"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer client.Disconnect(context.Background())
|
||||||
|
|
||||||
|
collection := client.Database("mydb").Collection("products")
|
||||||
|
|
||||||
|
// Query dengan filter
|
||||||
|
query := DynamicQuery{
|
||||||
|
From: "products",
|
||||||
|
Filters: []FilterGroup{
|
||||||
|
{
|
||||||
|
Filters: []DynamicFilter{
|
||||||
|
{
|
||||||
|
Column: "status",
|
||||||
|
Operator: OpEqual,
|
||||||
|
Value: "active",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Column: "price",
|
||||||
|
Operator: OpBetween,
|
||||||
|
Value: []interface{}{100000, 500000},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LogicOp: "AND",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Filters: []DynamicFilter{
|
||||||
|
{
|
||||||
|
Column: "category",
|
||||||
|
Operator: OpIn,
|
||||||
|
Value: []string{"electronics", "computers"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Column: "name",
|
||||||
|
Operator: OpILike,
|
||||||
|
Value: "laptop",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LogicOp: "OR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Sort: []SortField{
|
||||||
|
{Column: "price", Order: "ASC"},
|
||||||
|
{Column: "name", Order: "ASC"},
|
||||||
|
},
|
||||||
|
Limit: 20,
|
||||||
|
Offset: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
var results []map[string]interface{}
|
||||||
|
err = mqb.ExecuteFind(context.Background(), collection, query, &results)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Found %d products\n", len(results))
|
||||||
|
for _, result := range results {
|
||||||
|
fmt.Printf("Product: %s, Price: %v\n", result["name"], result["price"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 11. URL Query Parsing
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
func urlParsingExample() {
|
||||||
|
// Parse URL query parameters
|
||||||
|
values, _ := url.ParseQuery("filter[status][_eq]=active&filter[price][_gte]=100000&sort=-created_at&limit=20")
|
||||||
|
|
||||||
|
parser := NewQueryParser()
|
||||||
|
query, err := parser.ParseQuery(values, "products")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query akan berisi:
|
||||||
|
// - Filters: status = 'active' AND price >= 100000
|
||||||
|
// - Sort: created_at DESC
|
||||||
|
// - Limit: 20
|
||||||
|
|
||||||
|
qb := NewQueryBuilder(DBTypePostgreSQL)
|
||||||
|
db, _ := sqlx.Connect("postgres", "connection-string")
|
||||||
|
|
||||||
|
var products []map[string]interface{}
|
||||||
|
err = qb.ExecuteQuery(context.Background(), db, query, &products)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Konfigurasi Keamanan
|
||||||
|
|
||||||
|
```go
|
||||||
|
func securityExample() {
|
||||||
|
qb := NewQueryBuilder(DBTypePostgreSQL)
|
||||||
|
|
||||||
|
// Aktifkan security checks
|
||||||
|
qb.SetSecurityOptions(true, 1000) // max 1000 rows
|
||||||
|
|
||||||
|
// Tentukan kolom yang diizinkan
|
||||||
|
qb.SetAllowedColumns([]string{
|
||||||
|
"id", "name", "email", "status", "created_at",
|
||||||
|
"price", "category", "description",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Tentukan tabel yang diizinkan
|
||||||
|
qb.SetAllowedTables([]string{
|
||||||
|
"users", "products", "orders", "categories",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Query dengan kolom/tabel tidak diizinkan akan error
|
||||||
|
query := DynamicQuery{
|
||||||
|
From: "users", // OK
|
||||||
|
Filters: []FilterGroup{{
|
||||||
|
Filters: []DynamicFilter{{
|
||||||
|
Column: "password", // ERROR: kolom tidak diizinkan
|
||||||
|
Operator: OpEqual,
|
||||||
|
Value: "secret",
|
||||||
|
}},
|
||||||
|
LogicOp: "AND",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Akan mengembalikan error
|
||||||
|
_, _, err := qb.BuildQuery(query)
|
||||||
|
fmt.Println(err) // "disallowed column: password"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Referensi Operator
|
||||||
|
|
||||||
|
### Operator Perbandingan
|
||||||
|
- `_eq` - Equal
|
||||||
|
- `_neq` - Not Equal
|
||||||
|
- `_gt` - Greater Than
|
||||||
|
- `_gte` - Greater Than Equal
|
||||||
|
- `_lt` - Less Than
|
||||||
|
- `_lte` - Less Than Equal
|
||||||
|
|
||||||
|
### Operator String
|
||||||
|
- `_like` - Like (case-sensitive)
|
||||||
|
- `_ilike` - Like (case-insensitive)
|
||||||
|
- `_nlike` - Not Like (case-sensitive)
|
||||||
|
- `_nilike` - Not Like (case-insensitive)
|
||||||
|
- `_contains` - Contains substring
|
||||||
|
- `_ncontains` - Not Contains substring
|
||||||
|
- `_starts_with` - Starts with
|
||||||
|
- `_ends_with` - Ends with
|
||||||
|
|
||||||
|
### Operator Set
|
||||||
|
- `_in` - In list
|
||||||
|
- `_nin` - Not In list
|
||||||
|
- `_between` - Between two values
|
||||||
|
- `_nbetween` - Not Between two values
|
||||||
|
|
||||||
|
### Operator Null
|
||||||
|
- `_null` - Is Null
|
||||||
|
- `_nnull` - Is Not Null
|
||||||
|
|
||||||
|
### Operator JSON
|
||||||
|
- `_json_contains` - JSON contains value
|
||||||
|
- `_json_ncontains` - JSON not contains value
|
||||||
|
- `_json_exists` - JSON path exists
|
||||||
|
- `_json_nexists` - JSON path not exists
|
||||||
|
- `_json_eq` - JSON path equals value
|
||||||
|
- `_json_neq` - JSON path not equals value
|
||||||
|
|
||||||
|
### Operator Array
|
||||||
|
- `_array_contains` - Array contains value
|
||||||
|
- `_array_ncontains` - Array not contains value
|
||||||
|
- `_array_length` - Array has specific length
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Selalu gunakan security checks** saat production:
|
||||||
|
```go
|
||||||
|
qb.SetSecurityOptions(true, 1000)
|
||||||
|
qb.SetAllowedColumns(allowedColumns)
|
||||||
|
qb.SetAllowedTables(allowedTables)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Gunakan context dengan timeout** untuk query:
|
||||||
|
```go
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Validasi input user** sebelum membuat DynamicFilter
|
||||||
|
|
||||||
|
4. **Gunakan prepared statements** (sudah otomatis di QueryBuilder)
|
||||||
|
|
||||||
|
5. **Log query** untuk debugging:
|
||||||
|
```go
|
||||||
|
qb.SetQueryLogging(true)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Kontribusi
|
||||||
|
|
||||||
|
Pull requests are welcome! For major changes, please open an issue first to discuss what you would like to change.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,244 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
queryUtils "api-service/internal/utils/query"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// DYNAMIC VALIDATION RULE
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// ValidationRule mendefinisikan aturan untuk memeriksa duplikat atau kondisi lain.
|
||||||
|
// Struct ini membuat validator dapat digunakan kembali untuk tabel apa pun.
|
||||||
|
type ValidationRule struct {
|
||||||
|
// TableName adalah nama tabel yang akan diperiksa.
|
||||||
|
TableName string
|
||||||
|
|
||||||
|
// UniqueColumns adalah daftar kolom yang, jika digabungkan, harus unik.
|
||||||
|
// Contoh: []string{"email"} atau []string{"first_name", "last_name", "dob"}
|
||||||
|
UniqueColumns []string
|
||||||
|
|
||||||
|
// Conditions adalah filter tambahan yang harus dipenuhi.
|
||||||
|
// Ini sangat berguna untuk aturan bisnis, seperti "status != 'deleted'".
|
||||||
|
// Gunakan queryUtils.DynamicFilter untuk fleksibilitas penuh.
|
||||||
|
Conditions []queryUtils.DynamicFilter
|
||||||
|
|
||||||
|
// ExcludeIDColumn dan ExcludeIDValue digunakan untuk operasi UPDATE,
|
||||||
|
// untuk memastikan bahwa record tidak membandingkan dirinya sendiri.
|
||||||
|
ExcludeIDColumn string
|
||||||
|
ExcludeIDValue interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUniqueFieldRule adalah helper untuk membuat aturan validasi unik untuk satu kolom.
|
||||||
|
// Ini adalah cara cepat untuk membuat aturan yang paling umum.
|
||||||
|
func NewUniqueFieldRule(tableName, uniqueColumn string, additionalConditions ...queryUtils.DynamicFilter) ValidationRule {
|
||||||
|
return ValidationRule{
|
||||||
|
TableName: tableName,
|
||||||
|
UniqueColumns: []string{uniqueColumn},
|
||||||
|
Conditions: additionalConditions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// DYNAMIC VALIDATOR
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// DynamicValidator menyediakan metode untuk menjalankan validasi berdasarkan ValidationRule.
|
||||||
|
// Ini sepenuhnya generik dan tidak terikat pada tabel atau model tertentu.
|
||||||
|
type DynamicValidator struct {
|
||||||
|
qb *queryUtils.QueryBuilder
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDynamicValidator membuat instance DynamicValidator baru.
|
||||||
|
func NewDynamicValidator(qb *queryUtils.QueryBuilder) *DynamicValidator {
|
||||||
|
return &DynamicValidator{qb: qb}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate menjalankan validasi terhadap aturan yang diberikan.
|
||||||
|
// `data` adalah map yang berisi nilai untuk kolom yang akan diperiksa (biasanya dari request body).
|
||||||
|
// Mengembalikan `true` jika ada duplikat yang ditemukan (validasi gagal), `false` jika tidak ada duplikat (validasi berhasil).
|
||||||
|
func (dv *DynamicValidator) Validate(ctx context.Context, db *sqlx.DB, rule ValidationRule, data map[string]interface{}) (bool, error) {
|
||||||
|
// LOGGING: Log validation start
|
||||||
|
fmt.Printf("[VALIDATION] Starting validation for table: %s, unique columns: %v, data: %v\n", rule.TableName, rule.UniqueColumns, data)
|
||||||
|
|
||||||
|
if len(rule.UniqueColumns) == 0 {
|
||||||
|
fmt.Printf("[VALIDATION] ERROR: ValidationRule must have at least one UniqueColumn\n")
|
||||||
|
return false, fmt.Errorf("ValidationRule must have at least one UniqueColumn")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Kumpulkan semua filter dari aturan
|
||||||
|
var allFilters []queryUtils.DynamicFilter
|
||||||
|
|
||||||
|
// Tambahkan kondisi tambahan (misalnya, status != 'deleted')
|
||||||
|
allFilters = append(allFilters, rule.Conditions...)
|
||||||
|
fmt.Printf("[VALIDATION] Added %d condition filters\n", len(rule.Conditions))
|
||||||
|
|
||||||
|
// 2. Bangun filter untuk kolom unik berdasarkan data yang diberikan
|
||||||
|
for _, colName := range rule.UniqueColumns {
|
||||||
|
value, exists := data[colName]
|
||||||
|
if !exists {
|
||||||
|
// Jika data untuk kolom unik tidak ada, ini adalah kesalahan pemrograman.
|
||||||
|
fmt.Printf("[VALIDATION] ERROR: data for unique column '%s' not found in provided data map\n", colName)
|
||||||
|
return false, fmt.Errorf("data for unique column '%s' not found in provided data map", colName)
|
||||||
|
}
|
||||||
|
allFilters = append(allFilters, queryUtils.DynamicFilter{
|
||||||
|
Column: colName,
|
||||||
|
Operator: queryUtils.OpEqual,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
fmt.Printf("[VALIDATION] Added filter for column '%s' with value: %v\n", colName, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Tambahkan filter pengecualian ID (untuk operasi UPDATE)
|
||||||
|
if rule.ExcludeIDColumn != "" {
|
||||||
|
allFilters = append(allFilters, queryUtils.DynamicFilter{
|
||||||
|
Column: rule.ExcludeIDColumn,
|
||||||
|
Operator: queryUtils.OpNotEqual,
|
||||||
|
Value: rule.ExcludeIDValue,
|
||||||
|
})
|
||||||
|
fmt.Printf("[VALIDATION] Added exclude filter for column '%s' with value: %v\n", rule.ExcludeIDColumn, rule.ExcludeIDValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Bangun dan eksekusi query untuk menghitung jumlah record yang cocok
|
||||||
|
query := queryUtils.DynamicQuery{
|
||||||
|
From: rule.TableName,
|
||||||
|
Filters: []queryUtils.FilterGroup{{Filters: allFilters, LogicOp: "AND"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("[VALIDATION] Built query with %d total filters\n", len(allFilters))
|
||||||
|
|
||||||
|
count, err := dv.qb.ExecuteCount(ctx, db, query)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[VALIDATION] ERROR: failed to execute validation query for table %s: %v\n", rule.TableName, err)
|
||||||
|
return false, fmt.Errorf("failed to execute validation query for table %s: %w", rule.TableName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("[VALIDATION] Query executed successfully, count result: %d\n", count)
|
||||||
|
|
||||||
|
// 5. Kembalikan hasil
|
||||||
|
result := count > 0
|
||||||
|
fmt.Printf("[VALIDATION] Validation result: isDuplicate=%t (count > 0: %d > 0 = %t)\n", result, count, result)
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// CONTOH PENGGUNAAN (UNTUK DITEMPATKAN DI HANDLER ANDA)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/*
|
||||||
|
// --- Cara Penggunaan di RetribusiHandler ---
|
||||||
|
|
||||||
|
// 1. Tambahkan DynamicValidator ke struct handler
|
||||||
|
type RetribusiHandler struct {
|
||||||
|
// ...
|
||||||
|
validator *validation.DynamicValidator
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Inisialisasi di constructor
|
||||||
|
func NewRetribusiHandler() *RetribusiHandler {
|
||||||
|
qb := queryUtils.NewQueryBuilder(queryUtils.DBTypePostgreSQL).SetAllowedColumns(...)
|
||||||
|
|
||||||
|
return &RetribusiHandler{
|
||||||
|
// ...
|
||||||
|
validator: validation.NewDynamicValidator(qb),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Gunakan di CreateRetribusi
|
||||||
|
func (h *RetribusiHandler) CreateRetribusi(c *gin.Context) {
|
||||||
|
var req retribusi.RetribusiCreateRequest
|
||||||
|
// ... bind dan validasi request ...
|
||||||
|
|
||||||
|
// Siapkan aturan validasi: KodeTarif harus unik di antara record yang tidak dihapus.
|
||||||
|
rule := validation.NewUniqueFieldRule(
|
||||||
|
"data_retribusi", // Nama tabel
|
||||||
|
"Kode_tarif", // Kolom yang harus unik
|
||||||
|
queryUtils.DynamicFilter{ // Kondisi tambahan
|
||||||
|
Column: "status",
|
||||||
|
Operator: queryUtils.OpNotEqual,
|
||||||
|
Value: "deleted",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Siapkan data dari request untuk divalidasi
|
||||||
|
dataToValidate := map[string]interface{}{
|
||||||
|
"Kode_tarif": req.KodeTarif,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eksekusi validasi
|
||||||
|
isDuplicate, err := h.validator.Validate(ctx, dbConn, rule, dataToValidate)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndRespondError(c, "Failed to validate Kode Tarif", err, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDuplicate {
|
||||||
|
h.respondError(c, "Kode Tarif already exists", fmt.Errorf("duplicate Kode Tarif: %s", req.KodeTarif), http.StatusConflict)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... lanjutkan proses create ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Gunakan di UpdateRetribusi
|
||||||
|
func (h *RetribusiHandler) UpdateRetribusi(c *gin.Context) {
|
||||||
|
id := c.Param("id")
|
||||||
|
var req retribusi.RetribusiUpdateRequest
|
||||||
|
// ... bind dan validasi request ...
|
||||||
|
|
||||||
|
// Siapkan aturan validasi: KodeTarif harus unik, kecuali untuk record dengan ID ini.
|
||||||
|
rule := validation.ValidationRule{
|
||||||
|
TableName: "data_retribusi",
|
||||||
|
UniqueColumns: []string{"Kode_tarif"},
|
||||||
|
Conditions: []queryUtils.DynamicFilter{
|
||||||
|
{Column: "status", Operator: queryUtils.OpNotEqual, Value: "deleted"},
|
||||||
|
},
|
||||||
|
ExcludeIDColumn: "id", // Kecualikan berdasarkan kolom 'id'
|
||||||
|
ExcludeIDValue: id, // ...dengan nilai ID dari parameter
|
||||||
|
}
|
||||||
|
|
||||||
|
dataToValidate := map[string]interface{}{
|
||||||
|
"Kode_tarif": req.KodeTarif,
|
||||||
|
}
|
||||||
|
|
||||||
|
isDuplicate, err := h.validator.Validate(ctx, dbConn, rule, dataToValidate)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndRespondError(c, "Failed to validate Kode Tarif", err, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDuplicate {
|
||||||
|
h.respondError(c, "Kode Tarif already exists", fmt.Errorf("duplicate Kode Tarif: %s", req.KodeTarif), http.StatusConflict)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... lanjutkan proses update ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Contoh Penggunaan untuk Kasus Lain ---
|
||||||
|
|
||||||
|
// Contoh: Validasi kombinasi unik untuk tabel 'users'
|
||||||
|
// (email dan company_id harus unik bersama-sama)
|
||||||
|
func (h *UserHandler) CreateUser(c *gin.Context) {
|
||||||
|
// ...
|
||||||
|
|
||||||
|
rule := validation.ValidationRule{
|
||||||
|
TableName: "users",
|
||||||
|
UniqueColumns: []string{"email", "company_id"}, // Unik komposit
|
||||||
|
}
|
||||||
|
|
||||||
|
dataToValidate := map[string]interface{}{
|
||||||
|
"email": req.Email,
|
||||||
|
"company_id": req.CompanyID,
|
||||||
|
}
|
||||||
|
|
||||||
|
isDuplicate, err := h.validator.Validate(ctx, dbConn, rule, dataToValidate)
|
||||||
|
// ... handle error dan duplicate
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
Reference in New Issue
Block a user