nvoi 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rubocop.yml +19 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +151 -0
- data/Makefile +26 -0
- data/Rakefile +16 -0
- data/doc/config-schema.yaml +357 -0
- data/examples/apex-wildcard/deploy.yml +68 -0
- data/examples/golang/.gitignore +19 -0
- data/examples/golang/Dockerfile +43 -0
- data/examples/golang/README.md +59 -0
- data/examples/golang/deploy.enc +0 -0
- data/examples/golang/deploy.yml +54 -0
- data/examples/golang/go.mod +39 -0
- data/examples/golang/go.sum +96 -0
- data/examples/golang/main.go +177 -0
- data/examples/golang/models/user.go +17 -0
- data/examples/golang-postgres-multi/.gitignore +18 -0
- data/examples/golang-postgres-multi/Dockerfile +39 -0
- data/examples/golang-postgres-multi/README.md +211 -0
- data/examples/golang-postgres-multi/deploy.yml +67 -0
- data/examples/golang-postgres-multi/go.mod +45 -0
- data/examples/golang-postgres-multi/go.sum +108 -0
- data/examples/golang-postgres-multi/main.go +197 -0
- data/examples/golang-postgres-multi/models/user.go +17 -0
- data/examples/postgres-multi/.env.production.example +11 -0
- data/examples/postgres-multi/README.md +112 -0
- data/examples/postgres-multi/deploy.yml +74 -0
- data/examples/postgres-single/.env.production.example +11 -0
- data/examples/postgres-single/.gitignore +15 -0
- data/examples/postgres-single/Dockerfile +35 -0
- data/examples/postgres-single/README.md +76 -0
- data/examples/postgres-single/deploy.yml +56 -0
- data/examples/postgres-single/go.mod +45 -0
- data/examples/postgres-single/go.sum +108 -0
- data/examples/postgres-single/main.go +184 -0
- data/examples/rails-single/.dockerignore +51 -0
- data/examples/rails-single/.env.production.example +11 -0
- data/examples/rails-single/.github/dependabot.yml +12 -0
- data/examples/rails-single/.github/workflows/ci.yml +39 -0
- data/examples/rails-single/.gitignore +20 -0
- data/examples/rails-single/.node-version +1 -0
- data/examples/rails-single/.rubocop.yml +8 -0
- data/examples/rails-single/.ruby-version +1 -0
- data/examples/rails-single/Dockerfile +86 -0
- data/examples/rails-single/Gemfile +56 -0
- data/examples/rails-single/Gemfile.lock +350 -0
- data/examples/rails-single/Procfile.dev +3 -0
- data/examples/rails-single/README.md +17 -0
- data/examples/rails-single/Rakefile +6 -0
- data/examples/rails-single/app/assets/builds/.keep +0 -0
- data/examples/rails-single/app/assets/images/.keep +0 -0
- data/examples/rails-single/app/assets/stylesheets/application.tailwind.css +1 -0
- data/examples/rails-single/app/controllers/application_controller.rb +4 -0
- data/examples/rails-single/app/controllers/concerns/.keep +0 -0
- data/examples/rails-single/app/controllers/users_controller.rb +19 -0
- data/examples/rails-single/app/helpers/application_helper.rb +2 -0
- data/examples/rails-single/app/javascript/application.js +3 -0
- data/examples/rails-single/app/javascript/controllers/application.js +9 -0
- data/examples/rails-single/app/javascript/controllers/hello_controller.js +7 -0
- data/examples/rails-single/app/javascript/controllers/index.js +8 -0
- data/examples/rails-single/app/jobs/application_job.rb +7 -0
- data/examples/rails-single/app/mailers/application_mailer.rb +4 -0
- data/examples/rails-single/app/models/application_record.rb +3 -0
- data/examples/rails-single/app/models/concerns/.keep +0 -0
- data/examples/rails-single/app/models/user.rb +2 -0
- data/examples/rails-single/app/views/layouts/application.html.erb +28 -0
- data/examples/rails-single/app/views/layouts/mailer.html.erb +13 -0
- data/examples/rails-single/app/views/layouts/mailer.text.erb +1 -0
- data/examples/rails-single/app/views/pwa/manifest.json.erb +22 -0
- data/examples/rails-single/app/views/pwa/service-worker.js +26 -0
- data/examples/rails-single/app/views/users/index.html.erb +38 -0
- data/examples/rails-single/bin/brakeman +7 -0
- data/examples/rails-single/bin/bundle +109 -0
- data/examples/rails-single/bin/dev +11 -0
- data/examples/rails-single/bin/docker-entrypoint +14 -0
- data/examples/rails-single/bin/jobs +6 -0
- data/examples/rails-single/bin/kamal +27 -0
- data/examples/rails-single/bin/rails +4 -0
- data/examples/rails-single/bin/rake +4 -0
- data/examples/rails-single/bin/rubocop +8 -0
- data/examples/rails-single/bin/setup +37 -0
- data/examples/rails-single/bin/thrust +5 -0
- data/examples/rails-single/bun.lock +224 -0
- data/examples/rails-single/config/application.rb +42 -0
- data/examples/rails-single/config/boot.rb +4 -0
- data/examples/rails-single/config/cable.yml +17 -0
- data/examples/rails-single/config/cache.yml +16 -0
- data/examples/rails-single/config/credentials.yml.enc +1 -0
- data/examples/rails-single/config/database.yml +100 -0
- data/examples/rails-single/config/environment.rb +5 -0
- data/examples/rails-single/config/environments/development.rb +69 -0
- data/examples/rails-single/config/environments/production.rb +87 -0
- data/examples/rails-single/config/environments/test.rb +50 -0
- data/examples/rails-single/config/initializers/assets.rb +7 -0
- data/examples/rails-single/config/initializers/content_security_policy.rb +25 -0
- data/examples/rails-single/config/initializers/filter_parameter_logging.rb +8 -0
- data/examples/rails-single/config/initializers/inflections.rb +16 -0
- data/examples/rails-single/config/locales/en.yml +31 -0
- data/examples/rails-single/config/puma.rb +41 -0
- data/examples/rails-single/config/queue.yml +18 -0
- data/examples/rails-single/config/recurring.yml +15 -0
- data/examples/rails-single/config/routes.rb +4 -0
- data/examples/rails-single/config.ru +6 -0
- data/examples/rails-single/db/cable_schema.rb +11 -0
- data/examples/rails-single/db/cache_schema.rb +12 -0
- data/examples/rails-single/db/migrate/20251123095526_create_users.rb +10 -0
- data/examples/rails-single/db/queue_schema.rb +129 -0
- data/examples/rails-single/db/seeds.rb +9 -0
- data/examples/rails-single/deploy.yml +57 -0
- data/examples/rails-single/lib/tasks/.keep +0 -0
- data/examples/rails-single/log/.keep +0 -0
- data/examples/rails-single/package.json +17 -0
- data/examples/rails-single/public/400.html +114 -0
- data/examples/rails-single/public/404.html +114 -0
- data/examples/rails-single/public/406-unsupported-browser.html +114 -0
- data/examples/rails-single/public/422.html +114 -0
- data/examples/rails-single/public/500.html +114 -0
- data/examples/rails-single/public/icon.png +0 -0
- data/examples/rails-single/public/icon.svg +3 -0
- data/examples/rails-single/public/robots.txt +1 -0
- data/examples/rails-single/script/.keep +0 -0
- data/examples/rails-single/vendor/.keep +0 -0
- data/examples/rails-single/yarn.lock +188 -0
- data/exe/nvoi +6 -0
- data/lib/nvoi/cli.rb +190 -0
- data/lib/nvoi/cloudflare/client.rb +287 -0
- data/lib/nvoi/config/config.rb +248 -0
- data/lib/nvoi/config/env_resolver.rb +63 -0
- data/lib/nvoi/config/loader.rb +102 -0
- data/lib/nvoi/config/naming.rb +196 -0
- data/lib/nvoi/config/ssh_keys.rb +82 -0
- data/lib/nvoi/config/types.rb +274 -0
- data/lib/nvoi/constants.rb +59 -0
- data/lib/nvoi/credentials/crypto.rb +88 -0
- data/lib/nvoi/credentials/editor.rb +272 -0
- data/lib/nvoi/credentials/manager.rb +173 -0
- data/lib/nvoi/deployer/cleaner.rb +36 -0
- data/lib/nvoi/deployer/image_builder.rb +23 -0
- data/lib/nvoi/deployer/infrastructure.rb +126 -0
- data/lib/nvoi/deployer/orchestrator.rb +146 -0
- data/lib/nvoi/deployer/retry.rb +67 -0
- data/lib/nvoi/deployer/service_deployer.rb +311 -0
- data/lib/nvoi/deployer/tunnel_manager.rb +57 -0
- data/lib/nvoi/deployer/types.rb +8 -0
- data/lib/nvoi/errors.rb +67 -0
- data/lib/nvoi/k8s/renderer.rb +44 -0
- data/lib/nvoi/k8s/templates.rb +29 -0
- data/lib/nvoi/logger.rb +72 -0
- data/lib/nvoi/providers/aws.rb +403 -0
- data/lib/nvoi/providers/base.rb +111 -0
- data/lib/nvoi/providers/hetzner.rb +288 -0
- data/lib/nvoi/providers/hetzner_client.rb +170 -0
- data/lib/nvoi/remote/docker_manager.rb +203 -0
- data/lib/nvoi/remote/ssh_executor.rb +72 -0
- data/lib/nvoi/remote/volume_manager.rb +103 -0
- data/lib/nvoi/service/delete.rb +234 -0
- data/lib/nvoi/service/deploy.rb +80 -0
- data/lib/nvoi/service/exec.rb +144 -0
- data/lib/nvoi/service/provider.rb +36 -0
- data/lib/nvoi/steps/application_deployer.rb +26 -0
- data/lib/nvoi/steps/database_provisioner.rb +60 -0
- data/lib/nvoi/steps/k3s_cluster_setup.rb +105 -0
- data/lib/nvoi/steps/k3s_provisioner.rb +351 -0
- data/lib/nvoi/steps/server_provisioner.rb +43 -0
- data/lib/nvoi/steps/services_provisioner.rb +29 -0
- data/lib/nvoi/steps/tunnel_configurator.rb +66 -0
- data/lib/nvoi/steps/volume_provisioner.rb +154 -0
- data/lib/nvoi/version.rb +5 -0
- data/lib/nvoi.rb +79 -0
- data/templates/app-deployment.yaml.erb +102 -0
- data/templates/app-ingress.yaml.erb +20 -0
- data/templates/app-secret.yaml.erb +10 -0
- data/templates/app-service.yaml.erb +12 -0
- data/templates/db-statefulset.yaml.erb +76 -0
- data/templates/service-deployment.yaml.erb +91 -0
- data/templates/worker-deployment.yaml.erb +50 -0
- metadata +361 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Golang + PostgreSQL Multi-Server Example
|
|
2
|
+
|
|
3
|
+
Demonstrates 3-server deployment with Golang app and PostgreSQL database on separate nodes.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
**3 Servers:**
|
|
8
|
+
- **Main (master)**: K3s control plane
|
|
9
|
+
- **Worker 1**: Application pods (Golang + Gin)
|
|
10
|
+
- **Worker 2**: Database (PostgreSQL StatefulSet)
|
|
11
|
+
|
|
12
|
+
This setup validates:
|
|
13
|
+
- Multi-server provisioning on Hetzner
|
|
14
|
+
- K3s cluster formation (master + workers)
|
|
15
|
+
- Node affinity (app and database on separate workers)
|
|
16
|
+
- Cross-node connectivity via K8s service DNS
|
|
17
|
+
- Persistent storage for PostgreSQL
|
|
18
|
+
|
|
19
|
+
## What It Does
|
|
20
|
+
|
|
21
|
+
- **Visit `/`**: Creates a random user → Returns all users with hostname
|
|
22
|
+
- **Visit `/health`**: Returns health status with database connectivity check
|
|
23
|
+
- **Database**: PostgreSQL persists data across deployments
|
|
24
|
+
|
|
25
|
+
## Prerequisites
|
|
26
|
+
|
|
27
|
+
1. Domain registered and managed by Cloudflare DNS
|
|
28
|
+
2. Hetzner Cloud account with API token
|
|
29
|
+
3. NVOI CLI built (`make build` from project root)
|
|
30
|
+
|
|
31
|
+
## Configuration
|
|
32
|
+
|
|
33
|
+
The `.env` file contains valid credentials (copied from `examples/golang`).
|
|
34
|
+
|
|
35
|
+
Update `deploy.yml`:
|
|
36
|
+
- `domain`: Your Cloudflare domain
|
|
37
|
+
- `subdomain`: Your desired subdomain
|
|
38
|
+
|
|
39
|
+
## Deploy
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# From project root
|
|
43
|
+
make deploy-multi
|
|
44
|
+
|
|
45
|
+
# Or manually
|
|
46
|
+
cd examples/golang-postgres-multi
|
|
47
|
+
../../build/nvoi deploy
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Deployment Process
|
|
51
|
+
|
|
52
|
+
1. **Provision 3 Hetzner servers** (master + 2 workers)
|
|
53
|
+
2. **Install K3s** on master node
|
|
54
|
+
3. **Join workers** to K3s cluster
|
|
55
|
+
4. **Deploy PostgreSQL** as StatefulSet on worker
|
|
56
|
+
5. **Deploy Golang app** as Deployment on worker
|
|
57
|
+
6. **Configure Cloudflare tunnel** for HTTPS access
|
|
58
|
+
|
|
59
|
+
## Test After Deployment
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Health check
|
|
63
|
+
curl https://golang-pg-multi.yourdomain.com/health
|
|
64
|
+
|
|
65
|
+
# Create users and view all
|
|
66
|
+
curl https://golang-pg-multi.yourdomain.com/
|
|
67
|
+
|
|
68
|
+
# Multiple requests to see load balancing
|
|
69
|
+
for i in {1..5}; do
|
|
70
|
+
curl https://golang-pg-multi.yourdomain.com/ | jq '.hostname'
|
|
71
|
+
done
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Verify Multi-Server Setup
|
|
75
|
+
|
|
76
|
+
SSH into master server:
|
|
77
|
+
```bash
|
|
78
|
+
ssh root@<master-ip>
|
|
79
|
+
|
|
80
|
+
# Check K3s cluster nodes
|
|
81
|
+
kubectl get nodes -o wide
|
|
82
|
+
|
|
83
|
+
# Verify pods are on different nodes
|
|
84
|
+
kubectl get pods -o wide
|
|
85
|
+
|
|
86
|
+
# Check StatefulSet (database)
|
|
87
|
+
kubectl get statefulsets
|
|
88
|
+
|
|
89
|
+
# Check services
|
|
90
|
+
kubectl get services
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Expected output:
|
|
94
|
+
- 3 nodes: 1 master (control-plane) + 2 workers
|
|
95
|
+
- PostgreSQL pod on one worker node
|
|
96
|
+
- App pods on another worker node
|
|
97
|
+
- Database accessible via `db-example-golang-pg-multi:5432`
|
|
98
|
+
|
|
99
|
+
## Database Connection
|
|
100
|
+
|
|
101
|
+
App connects to PostgreSQL via K8s service DNS:
|
|
102
|
+
```
|
|
103
|
+
postgresql://appuser:password@db-example-golang-pg-multi:5432/app_production
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Service name pattern: `db-{application.name}`
|
|
107
|
+
|
|
108
|
+
## Local Testing
|
|
109
|
+
|
|
110
|
+
Test the app locally (without deployment):
|
|
111
|
+
```bash
|
|
112
|
+
# Install PostgreSQL locally first
|
|
113
|
+
docker run -d \
|
|
114
|
+
-p 5432:5432 \
|
|
115
|
+
-e POSTGRES_DB=app_production \
|
|
116
|
+
-e POSTGRES_USER=appuser \
|
|
117
|
+
-e POSTGRES_PASSWORD=secure_postgres_password_12345 \
|
|
118
|
+
postgres:16-alpine
|
|
119
|
+
|
|
120
|
+
# Update DATABASE_URL for local connection
|
|
121
|
+
export DATABASE_URL="postgresql://appuser:secure_postgres_password_12345@localhost:5432/app_production"
|
|
122
|
+
|
|
123
|
+
# Run the app
|
|
124
|
+
go run main.go
|
|
125
|
+
|
|
126
|
+
# Test endpoints
|
|
127
|
+
curl http://localhost:3000/health
|
|
128
|
+
curl http://localhost:3000/
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Response Example
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"hostname": "web-7d4f8b9c5-abc12",
|
|
136
|
+
"message": "User created on this visit!",
|
|
137
|
+
"new_user": {
|
|
138
|
+
"id": 1,
|
|
139
|
+
"name": "Alice Smith",
|
|
140
|
+
"email": "user-1699999999@example.com",
|
|
141
|
+
"created_at": "2024-11-20T12:00:00Z",
|
|
142
|
+
"updated_at": "2024-11-20T12:00:00Z"
|
|
143
|
+
},
|
|
144
|
+
"total_users": 5,
|
|
145
|
+
"all_users": [...]
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Cleanup
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# Delete all resources
|
|
153
|
+
../../build/nvoi delete
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
This removes:
|
|
157
|
+
- All 3 Hetzner servers
|
|
158
|
+
- Firewall rules
|
|
159
|
+
- Private network
|
|
160
|
+
- Cloudflare tunnel
|
|
161
|
+
|
|
162
|
+
## Troubleshooting
|
|
163
|
+
|
|
164
|
+
### Database connection fails
|
|
165
|
+
```bash
|
|
166
|
+
# SSH to master node
|
|
167
|
+
ssh root@<master-ip>
|
|
168
|
+
|
|
169
|
+
# Check database pod
|
|
170
|
+
kubectl get pods | grep db-
|
|
171
|
+
kubectl logs <db-pod-name>
|
|
172
|
+
|
|
173
|
+
# Check database service
|
|
174
|
+
kubectl get service db-example-golang-pg-multi
|
|
175
|
+
|
|
176
|
+
# Test connection from app pod
|
|
177
|
+
kubectl exec -it <app-pod> -- sh
|
|
178
|
+
nc -zv db-example-golang-pg-multi 5432
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### App not starting
|
|
182
|
+
```bash
|
|
183
|
+
# Check app pods
|
|
184
|
+
kubectl get pods
|
|
185
|
+
kubectl describe pod <app-pod-name>
|
|
186
|
+
kubectl logs <app-pod-name>
|
|
187
|
+
|
|
188
|
+
# Verify environment variables
|
|
189
|
+
kubectl get secret example-golang-pg-multi-db -o yaml
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Workers not joining cluster
|
|
193
|
+
```bash
|
|
194
|
+
# On master
|
|
195
|
+
kubectl get nodes
|
|
196
|
+
|
|
197
|
+
# Check K3s logs on worker
|
|
198
|
+
ssh root@<worker-ip>
|
|
199
|
+
journalctl -u k3s-agent -f
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Technical Details
|
|
203
|
+
|
|
204
|
+
- **Language**: Go 1.21
|
|
205
|
+
- **Framework**: Gin (HTTP router)
|
|
206
|
+
- **ORM**: GORM with PostgreSQL driver
|
|
207
|
+
- **Database**: PostgreSQL 16 (alpine)
|
|
208
|
+
- **Orchestration**: Kubernetes (K3s)
|
|
209
|
+
- **Multi-stage build** for minimal image size
|
|
210
|
+
- **Health checks** for zero-downtime deployments
|
|
211
|
+
- **Connection pooling** (10 idle, 100 max connections)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
application:
|
|
2
|
+
name: example-golang-pg-multi
|
|
3
|
+
environment: production
|
|
4
|
+
|
|
5
|
+
# Provider configuration
|
|
6
|
+
domain_provider:
|
|
7
|
+
cloudflare:
|
|
8
|
+
api_token: $CLOUDFLARE_API_TOKEN
|
|
9
|
+
account_id: $CLOUDFLARE_ACCOUNT_ID
|
|
10
|
+
|
|
11
|
+
compute_provider:
|
|
12
|
+
hetzner:
|
|
13
|
+
api_token: $HETZNER_API_TOKEN
|
|
14
|
+
server_type: cx22 # Default for all servers
|
|
15
|
+
server_location: nbg1
|
|
16
|
+
|
|
17
|
+
# Multi-instance server configuration: 3 servers total
|
|
18
|
+
# 1 master (K3s control plane) + 2 workers (app + database)
|
|
19
|
+
servers:
|
|
20
|
+
master:
|
|
21
|
+
master: true
|
|
22
|
+
type: cx22 # Main server (control plane)
|
|
23
|
+
location: nbg1
|
|
24
|
+
|
|
25
|
+
workers:
|
|
26
|
+
type: cx32 # Larger instances for workloads
|
|
27
|
+
count: 2 # Two workers: one for app, one for database
|
|
28
|
+
location: nbg1
|
|
29
|
+
|
|
30
|
+
# Container retention
|
|
31
|
+
keep_count: 2
|
|
32
|
+
|
|
33
|
+
# Application service (Golang + Gin)
|
|
34
|
+
# Connects to PostgreSQL via K3s service DNS
|
|
35
|
+
app:
|
|
36
|
+
web:
|
|
37
|
+
servers: [workers]
|
|
38
|
+
domain: rb.run # Change to your domain
|
|
39
|
+
subdomain: golang-pg-multi # Change to your subdomain
|
|
40
|
+
port: 3000
|
|
41
|
+
healthcheck:
|
|
42
|
+
type: http
|
|
43
|
+
path: /health
|
|
44
|
+
port: 3000
|
|
45
|
+
interval: 10s
|
|
46
|
+
timeout: 5s
|
|
47
|
+
retries: 3
|
|
48
|
+
env:
|
|
49
|
+
# Database connection vars auto-injected by CLI
|
|
50
|
+
GIN_MODE: release
|
|
51
|
+
|
|
52
|
+
# PostgreSQL database (runs on WORKER via K3s scheduling)
|
|
53
|
+
# Deployed as StatefulSet with persistent volume
|
|
54
|
+
database:
|
|
55
|
+
servers: [workers]
|
|
56
|
+
adapter: postgres
|
|
57
|
+
image: postgres:16-alpine
|
|
58
|
+
volume: postgres_data
|
|
59
|
+
secrets:
|
|
60
|
+
POSTGRES_DB: app_production
|
|
61
|
+
POSTGRES_USER: appuser
|
|
62
|
+
POSTGRES_PASSWORD: $DB_PASSWORD
|
|
63
|
+
|
|
64
|
+
# Environment variables (non-sensitive)
|
|
65
|
+
env:
|
|
66
|
+
APP_NAME: golang-pg-multi-demo
|
|
67
|
+
LOG_LEVEL: info
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module github.com/nvoi/example-app
|
|
2
|
+
|
|
3
|
+
go 1.23
|
|
4
|
+
|
|
5
|
+
toolchain go1.24.4
|
|
6
|
+
|
|
7
|
+
require (
|
|
8
|
+
github.com/gin-gonic/gin v1.9.1
|
|
9
|
+
gorm.io/driver/postgres v1.5.4
|
|
10
|
+
gorm.io/gorm v1.25.5
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
require (
|
|
14
|
+
github.com/bytedance/sonic v1.9.1 // indirect
|
|
15
|
+
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
|
16
|
+
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
|
17
|
+
github.com/gin-contrib/sse v0.1.0 // indirect
|
|
18
|
+
github.com/go-playground/locales v0.14.1 // indirect
|
|
19
|
+
github.com/go-playground/universal-translator v0.18.1 // indirect
|
|
20
|
+
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
|
21
|
+
github.com/goccy/go-json v0.10.2 // indirect
|
|
22
|
+
github.com/jackc/pgpassfile v1.0.0 // indirect
|
|
23
|
+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
|
24
|
+
github.com/jackc/pgx/v5 v5.4.3 // indirect
|
|
25
|
+
github.com/jinzhu/inflection v1.0.0 // indirect
|
|
26
|
+
github.com/jinzhu/now v1.1.5 // indirect
|
|
27
|
+
github.com/json-iterator/go v1.1.12 // indirect
|
|
28
|
+
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
|
29
|
+
github.com/kr/text v0.2.0 // indirect
|
|
30
|
+
github.com/leodido/go-urn v1.2.4 // indirect
|
|
31
|
+
github.com/mattn/go-isatty v0.0.19 // indirect
|
|
32
|
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
|
33
|
+
github.com/modern-go/reflect2 v1.0.2 // indirect
|
|
34
|
+
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
|
35
|
+
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
|
36
|
+
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
|
37
|
+
github.com/ugorji/go/codec v1.2.11 // indirect
|
|
38
|
+
golang.org/x/arch v0.3.0 // indirect
|
|
39
|
+
golang.org/x/crypto v0.14.0 // indirect
|
|
40
|
+
golang.org/x/net v0.10.0 // indirect
|
|
41
|
+
golang.org/x/sys v0.26.0 // indirect
|
|
42
|
+
golang.org/x/text v0.13.0 // indirect
|
|
43
|
+
google.golang.org/protobuf v1.30.0 // indirect
|
|
44
|
+
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
45
|
+
)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
|
2
|
+
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
|
3
|
+
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
|
4
|
+
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
|
5
|
+
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
|
6
|
+
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
|
7
|
+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
8
|
+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
9
|
+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
10
|
+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
11
|
+
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
|
12
|
+
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
|
13
|
+
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
|
14
|
+
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
|
15
|
+
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
|
16
|
+
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
|
17
|
+
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
|
18
|
+
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
|
19
|
+
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
|
20
|
+
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
|
21
|
+
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
|
22
|
+
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
|
23
|
+
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
|
24
|
+
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
|
25
|
+
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
|
26
|
+
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
|
27
|
+
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
|
28
|
+
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
|
29
|
+
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
30
|
+
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
31
|
+
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
|
32
|
+
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
|
33
|
+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
|
34
|
+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
|
35
|
+
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
|
|
36
|
+
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
|
37
|
+
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
|
38
|
+
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
|
39
|
+
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
|
40
|
+
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
|
41
|
+
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
|
42
|
+
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
|
43
|
+
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
|
44
|
+
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
|
45
|
+
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
|
46
|
+
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
|
47
|
+
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
|
48
|
+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
49
|
+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
50
|
+
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
|
51
|
+
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
|
52
|
+
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
|
53
|
+
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
54
|
+
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
55
|
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
|
56
|
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
57
|
+
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
|
58
|
+
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
|
59
|
+
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
|
60
|
+
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
|
61
|
+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
62
|
+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
63
|
+
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
|
64
|
+
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
|
65
|
+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
66
|
+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
67
|
+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
68
|
+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
69
|
+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
70
|
+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
71
|
+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
|
72
|
+
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
73
|
+
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
74
|
+
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
|
75
|
+
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
76
|
+
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
|
77
|
+
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
|
78
|
+
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
|
79
|
+
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
|
80
|
+
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
|
81
|
+
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
|
82
|
+
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
|
83
|
+
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
|
84
|
+
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
|
85
|
+
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
|
86
|
+
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
|
87
|
+
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
88
|
+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
89
|
+
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
|
90
|
+
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
91
|
+
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
|
92
|
+
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|
93
|
+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
|
94
|
+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
95
|
+
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
|
96
|
+
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
|
97
|
+
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
|
98
|
+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
99
|
+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
100
|
+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
101
|
+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
102
|
+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
103
|
+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
104
|
+
gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo=
|
|
105
|
+
gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0=
|
|
106
|
+
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
|
107
|
+
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
|
108
|
+
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"log"
|
|
7
|
+
"math/rand"
|
|
8
|
+
"net/http"
|
|
9
|
+
"os"
|
|
10
|
+
"os/signal"
|
|
11
|
+
"syscall"
|
|
12
|
+
"time"
|
|
13
|
+
|
|
14
|
+
"github.com/gin-gonic/gin"
|
|
15
|
+
"github.com/nvoi/example-app/models"
|
|
16
|
+
"gorm.io/driver/postgres"
|
|
17
|
+
"gorm.io/gorm"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
var db *gorm.DB
|
|
21
|
+
|
|
22
|
+
func main() {
|
|
23
|
+
// Initialize database
|
|
24
|
+
var err error
|
|
25
|
+
db, err = initDB()
|
|
26
|
+
if err != nil {
|
|
27
|
+
log.Fatalf("Failed to initialize database: %v", err)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Auto-migrate database schema
|
|
31
|
+
if err := db.AutoMigrate(&models.User{}); err != nil {
|
|
32
|
+
log.Fatalf("Failed to migrate database: %v", err)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Setup router
|
|
36
|
+
router := setupRouter()
|
|
37
|
+
|
|
38
|
+
// Configure server
|
|
39
|
+
srv := &http.Server{
|
|
40
|
+
Addr: ":3000",
|
|
41
|
+
Handler: router,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Start server in goroutine
|
|
45
|
+
go func() {
|
|
46
|
+
log.Println("Starting server on :3000")
|
|
47
|
+
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
48
|
+
log.Fatalf("Server failed to start: %v", err)
|
|
49
|
+
}
|
|
50
|
+
}()
|
|
51
|
+
|
|
52
|
+
// Wait for interrupt signal for graceful shutdown
|
|
53
|
+
quit := make(chan os.Signal, 1)
|
|
54
|
+
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
55
|
+
<-quit
|
|
56
|
+
|
|
57
|
+
log.Println("Shutting down server...")
|
|
58
|
+
|
|
59
|
+
// Graceful shutdown with 5 second timeout
|
|
60
|
+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
61
|
+
defer cancel()
|
|
62
|
+
|
|
63
|
+
if err := srv.Shutdown(ctx); err != nil {
|
|
64
|
+
log.Fatalf("Server forced to shutdown: %v", err)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
log.Println("Server exited")
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func initDB() (*gorm.DB, error) {
|
|
71
|
+
// Get database URL from environment
|
|
72
|
+
databaseURL := os.Getenv("DATABASE_URL")
|
|
73
|
+
if databaseURL == "" {
|
|
74
|
+
return nil, fmt.Errorf("DATABASE_URL environment variable not set")
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Connect to PostgreSQL
|
|
78
|
+
db, err := gorm.Open(postgres.Open(databaseURL), &gorm.Config{})
|
|
79
|
+
if err != nil {
|
|
80
|
+
return nil, fmt.Errorf("failed to connect to database: %w", err)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Test connection
|
|
84
|
+
sqlDB, err := db.DB()
|
|
85
|
+
if err != nil {
|
|
86
|
+
return nil, fmt.Errorf("failed to get database instance: %w", err)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if err := sqlDB.Ping(); err != nil {
|
|
90
|
+
return nil, fmt.Errorf("failed to ping database: %w", err)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Configure connection pool
|
|
94
|
+
sqlDB.SetMaxIdleConns(10)
|
|
95
|
+
sqlDB.SetMaxOpenConns(100)
|
|
96
|
+
sqlDB.SetConnMaxLifetime(time.Hour)
|
|
97
|
+
|
|
98
|
+
log.Println("Database connected successfully")
|
|
99
|
+
return db, nil
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
func setupRouter() *gin.Engine {
|
|
103
|
+
// Set Gin mode from environment
|
|
104
|
+
if mode := os.Getenv("GIN_MODE"); mode != "" {
|
|
105
|
+
gin.SetMode(mode)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
router := gin.Default()
|
|
109
|
+
|
|
110
|
+
// Health check endpoint (required for deployment)
|
|
111
|
+
router.GET("/health", healthCheck)
|
|
112
|
+
|
|
113
|
+
// Main endpoint: creates user on every visit, returns all users
|
|
114
|
+
router.GET("/", handleVisit)
|
|
115
|
+
|
|
116
|
+
return router
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Health check handler (required for zero-downtime deployments)
|
|
120
|
+
func healthCheck(c *gin.Context) {
|
|
121
|
+
// Check database connectivity
|
|
122
|
+
sqlDB, err := db.DB()
|
|
123
|
+
if err != nil {
|
|
124
|
+
c.JSON(http.StatusServiceUnavailable, gin.H{
|
|
125
|
+
"status": "unhealthy",
|
|
126
|
+
"error": "database connection failed",
|
|
127
|
+
})
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if err := sqlDB.Ping(); err != nil {
|
|
132
|
+
c.JSON(http.StatusServiceUnavailable, gin.H{
|
|
133
|
+
"status": "unhealthy",
|
|
134
|
+
"error": "database ping failed",
|
|
135
|
+
})
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Get hostname to verify pod identity
|
|
140
|
+
hostname, _ := os.Hostname()
|
|
141
|
+
|
|
142
|
+
c.JSON(http.StatusOK, gin.H{
|
|
143
|
+
"status": "healthy",
|
|
144
|
+
"service": "golang-postgres-multi",
|
|
145
|
+
"hostname": hostname,
|
|
146
|
+
"time": time.Now().Format(time.RFC3339),
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Main handler: creates a new user on every visit and returns all users
|
|
151
|
+
func handleVisit(c *gin.Context) {
|
|
152
|
+
// Create a new user with random name
|
|
153
|
+
newUser := models.User{
|
|
154
|
+
Name: generateRandomName(),
|
|
155
|
+
Email: fmt.Sprintf("user-%d@example.com", time.Now().UnixNano()),
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if err := db.Create(&newUser).Error; err != nil {
|
|
159
|
+
c.JSON(http.StatusInternalServerError, gin.H{
|
|
160
|
+
"error": "Failed to create user",
|
|
161
|
+
})
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Fetch all users
|
|
166
|
+
var users []models.User
|
|
167
|
+
if err := db.Find(&users).Error; err != nil {
|
|
168
|
+
c.JSON(http.StatusInternalServerError, gin.H{
|
|
169
|
+
"error": "Failed to fetch users",
|
|
170
|
+
})
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Get deployment info
|
|
175
|
+
hostname, _ := os.Hostname()
|
|
176
|
+
|
|
177
|
+
// Return response
|
|
178
|
+
c.JSON(http.StatusOK, gin.H{
|
|
179
|
+
"hostname": hostname,
|
|
180
|
+
"message": "User created on this visit!",
|
|
181
|
+
"new_user": newUser,
|
|
182
|
+
"total_users": len(users),
|
|
183
|
+
"all_users": users,
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Generate random name for demo purposes
|
|
188
|
+
func generateRandomName() string {
|
|
189
|
+
firstNames := []string{"Alice", "Bob", "Charlie", "Diana", "Eve", "Frank", "Grace", "Henry", "Ivy", "Jack"}
|
|
190
|
+
lastNames := []string{"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez"}
|
|
191
|
+
|
|
192
|
+
rand.Seed(time.Now().UnixNano())
|
|
193
|
+
firstName := firstNames[rand.Intn(len(firstNames))]
|
|
194
|
+
lastName := lastNames[rand.Intn(len(lastNames))]
|
|
195
|
+
|
|
196
|
+
return fmt.Sprintf("%s %s", firstName, lastName)
|
|
197
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package models
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"time"
|
|
5
|
+
|
|
6
|
+
"gorm.io/gorm"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// User represents a user in the system
|
|
10
|
+
type User struct {
|
|
11
|
+
ID uint `gorm:"primarykey" json:"id"`
|
|
12
|
+
Name string `gorm:"not null" json:"name" binding:"required"`
|
|
13
|
+
Email string `gorm:"uniqueIndex;not null" json:"email" binding:"required,email"`
|
|
14
|
+
CreatedAt time.Time `json:"created_at"`
|
|
15
|
+
UpdatedAt time.Time `json:"updated_at"`
|
|
16
|
+
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
|
17
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Provider credentials (required)
|
|
2
|
+
CLOUDFLARE_API_TOKEN=your_cloudflare_token_here
|
|
3
|
+
CLOUDFLARE_ACCOUNT_ID=your_cloudflare_account_id_here
|
|
4
|
+
HETZNER_API_TOKEN=your_hetzner_token_here
|
|
5
|
+
|
|
6
|
+
# Database password
|
|
7
|
+
DB_PASSWORD=your_secure_db_password_here
|
|
8
|
+
|
|
9
|
+
# Application secrets
|
|
10
|
+
SECRET_KEY_BASE=generate_with_openssl_rand_hex_64
|
|
11
|
+
JWT_SECRET=generate_with_openssl_rand_hex_32
|