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.
Files changed (178) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +19 -0
  3. data/Gemfile +9 -0
  4. data/Gemfile.lock +151 -0
  5. data/Makefile +26 -0
  6. data/Rakefile +16 -0
  7. data/doc/config-schema.yaml +357 -0
  8. data/examples/apex-wildcard/deploy.yml +68 -0
  9. data/examples/golang/.gitignore +19 -0
  10. data/examples/golang/Dockerfile +43 -0
  11. data/examples/golang/README.md +59 -0
  12. data/examples/golang/deploy.enc +0 -0
  13. data/examples/golang/deploy.yml +54 -0
  14. data/examples/golang/go.mod +39 -0
  15. data/examples/golang/go.sum +96 -0
  16. data/examples/golang/main.go +177 -0
  17. data/examples/golang/models/user.go +17 -0
  18. data/examples/golang-postgres-multi/.gitignore +18 -0
  19. data/examples/golang-postgres-multi/Dockerfile +39 -0
  20. data/examples/golang-postgres-multi/README.md +211 -0
  21. data/examples/golang-postgres-multi/deploy.yml +67 -0
  22. data/examples/golang-postgres-multi/go.mod +45 -0
  23. data/examples/golang-postgres-multi/go.sum +108 -0
  24. data/examples/golang-postgres-multi/main.go +197 -0
  25. data/examples/golang-postgres-multi/models/user.go +17 -0
  26. data/examples/postgres-multi/.env.production.example +11 -0
  27. data/examples/postgres-multi/README.md +112 -0
  28. data/examples/postgres-multi/deploy.yml +74 -0
  29. data/examples/postgres-single/.env.production.example +11 -0
  30. data/examples/postgres-single/.gitignore +15 -0
  31. data/examples/postgres-single/Dockerfile +35 -0
  32. data/examples/postgres-single/README.md +76 -0
  33. data/examples/postgres-single/deploy.yml +56 -0
  34. data/examples/postgres-single/go.mod +45 -0
  35. data/examples/postgres-single/go.sum +108 -0
  36. data/examples/postgres-single/main.go +184 -0
  37. data/examples/rails-single/.dockerignore +51 -0
  38. data/examples/rails-single/.env.production.example +11 -0
  39. data/examples/rails-single/.github/dependabot.yml +12 -0
  40. data/examples/rails-single/.github/workflows/ci.yml +39 -0
  41. data/examples/rails-single/.gitignore +20 -0
  42. data/examples/rails-single/.node-version +1 -0
  43. data/examples/rails-single/.rubocop.yml +8 -0
  44. data/examples/rails-single/.ruby-version +1 -0
  45. data/examples/rails-single/Dockerfile +86 -0
  46. data/examples/rails-single/Gemfile +56 -0
  47. data/examples/rails-single/Gemfile.lock +350 -0
  48. data/examples/rails-single/Procfile.dev +3 -0
  49. data/examples/rails-single/README.md +17 -0
  50. data/examples/rails-single/Rakefile +6 -0
  51. data/examples/rails-single/app/assets/builds/.keep +0 -0
  52. data/examples/rails-single/app/assets/images/.keep +0 -0
  53. data/examples/rails-single/app/assets/stylesheets/application.tailwind.css +1 -0
  54. data/examples/rails-single/app/controllers/application_controller.rb +4 -0
  55. data/examples/rails-single/app/controllers/concerns/.keep +0 -0
  56. data/examples/rails-single/app/controllers/users_controller.rb +19 -0
  57. data/examples/rails-single/app/helpers/application_helper.rb +2 -0
  58. data/examples/rails-single/app/javascript/application.js +3 -0
  59. data/examples/rails-single/app/javascript/controllers/application.js +9 -0
  60. data/examples/rails-single/app/javascript/controllers/hello_controller.js +7 -0
  61. data/examples/rails-single/app/javascript/controllers/index.js +8 -0
  62. data/examples/rails-single/app/jobs/application_job.rb +7 -0
  63. data/examples/rails-single/app/mailers/application_mailer.rb +4 -0
  64. data/examples/rails-single/app/models/application_record.rb +3 -0
  65. data/examples/rails-single/app/models/concerns/.keep +0 -0
  66. data/examples/rails-single/app/models/user.rb +2 -0
  67. data/examples/rails-single/app/views/layouts/application.html.erb +28 -0
  68. data/examples/rails-single/app/views/layouts/mailer.html.erb +13 -0
  69. data/examples/rails-single/app/views/layouts/mailer.text.erb +1 -0
  70. data/examples/rails-single/app/views/pwa/manifest.json.erb +22 -0
  71. data/examples/rails-single/app/views/pwa/service-worker.js +26 -0
  72. data/examples/rails-single/app/views/users/index.html.erb +38 -0
  73. data/examples/rails-single/bin/brakeman +7 -0
  74. data/examples/rails-single/bin/bundle +109 -0
  75. data/examples/rails-single/bin/dev +11 -0
  76. data/examples/rails-single/bin/docker-entrypoint +14 -0
  77. data/examples/rails-single/bin/jobs +6 -0
  78. data/examples/rails-single/bin/kamal +27 -0
  79. data/examples/rails-single/bin/rails +4 -0
  80. data/examples/rails-single/bin/rake +4 -0
  81. data/examples/rails-single/bin/rubocop +8 -0
  82. data/examples/rails-single/bin/setup +37 -0
  83. data/examples/rails-single/bin/thrust +5 -0
  84. data/examples/rails-single/bun.lock +224 -0
  85. data/examples/rails-single/config/application.rb +42 -0
  86. data/examples/rails-single/config/boot.rb +4 -0
  87. data/examples/rails-single/config/cable.yml +17 -0
  88. data/examples/rails-single/config/cache.yml +16 -0
  89. data/examples/rails-single/config/credentials.yml.enc +1 -0
  90. data/examples/rails-single/config/database.yml +100 -0
  91. data/examples/rails-single/config/environment.rb +5 -0
  92. data/examples/rails-single/config/environments/development.rb +69 -0
  93. data/examples/rails-single/config/environments/production.rb +87 -0
  94. data/examples/rails-single/config/environments/test.rb +50 -0
  95. data/examples/rails-single/config/initializers/assets.rb +7 -0
  96. data/examples/rails-single/config/initializers/content_security_policy.rb +25 -0
  97. data/examples/rails-single/config/initializers/filter_parameter_logging.rb +8 -0
  98. data/examples/rails-single/config/initializers/inflections.rb +16 -0
  99. data/examples/rails-single/config/locales/en.yml +31 -0
  100. data/examples/rails-single/config/puma.rb +41 -0
  101. data/examples/rails-single/config/queue.yml +18 -0
  102. data/examples/rails-single/config/recurring.yml +15 -0
  103. data/examples/rails-single/config/routes.rb +4 -0
  104. data/examples/rails-single/config.ru +6 -0
  105. data/examples/rails-single/db/cable_schema.rb +11 -0
  106. data/examples/rails-single/db/cache_schema.rb +12 -0
  107. data/examples/rails-single/db/migrate/20251123095526_create_users.rb +10 -0
  108. data/examples/rails-single/db/queue_schema.rb +129 -0
  109. data/examples/rails-single/db/seeds.rb +9 -0
  110. data/examples/rails-single/deploy.yml +57 -0
  111. data/examples/rails-single/lib/tasks/.keep +0 -0
  112. data/examples/rails-single/log/.keep +0 -0
  113. data/examples/rails-single/package.json +17 -0
  114. data/examples/rails-single/public/400.html +114 -0
  115. data/examples/rails-single/public/404.html +114 -0
  116. data/examples/rails-single/public/406-unsupported-browser.html +114 -0
  117. data/examples/rails-single/public/422.html +114 -0
  118. data/examples/rails-single/public/500.html +114 -0
  119. data/examples/rails-single/public/icon.png +0 -0
  120. data/examples/rails-single/public/icon.svg +3 -0
  121. data/examples/rails-single/public/robots.txt +1 -0
  122. data/examples/rails-single/script/.keep +0 -0
  123. data/examples/rails-single/vendor/.keep +0 -0
  124. data/examples/rails-single/yarn.lock +188 -0
  125. data/exe/nvoi +6 -0
  126. data/lib/nvoi/cli.rb +190 -0
  127. data/lib/nvoi/cloudflare/client.rb +287 -0
  128. data/lib/nvoi/config/config.rb +248 -0
  129. data/lib/nvoi/config/env_resolver.rb +63 -0
  130. data/lib/nvoi/config/loader.rb +102 -0
  131. data/lib/nvoi/config/naming.rb +196 -0
  132. data/lib/nvoi/config/ssh_keys.rb +82 -0
  133. data/lib/nvoi/config/types.rb +274 -0
  134. data/lib/nvoi/constants.rb +59 -0
  135. data/lib/nvoi/credentials/crypto.rb +88 -0
  136. data/lib/nvoi/credentials/editor.rb +272 -0
  137. data/lib/nvoi/credentials/manager.rb +173 -0
  138. data/lib/nvoi/deployer/cleaner.rb +36 -0
  139. data/lib/nvoi/deployer/image_builder.rb +23 -0
  140. data/lib/nvoi/deployer/infrastructure.rb +126 -0
  141. data/lib/nvoi/deployer/orchestrator.rb +146 -0
  142. data/lib/nvoi/deployer/retry.rb +67 -0
  143. data/lib/nvoi/deployer/service_deployer.rb +311 -0
  144. data/lib/nvoi/deployer/tunnel_manager.rb +57 -0
  145. data/lib/nvoi/deployer/types.rb +8 -0
  146. data/lib/nvoi/errors.rb +67 -0
  147. data/lib/nvoi/k8s/renderer.rb +44 -0
  148. data/lib/nvoi/k8s/templates.rb +29 -0
  149. data/lib/nvoi/logger.rb +72 -0
  150. data/lib/nvoi/providers/aws.rb +403 -0
  151. data/lib/nvoi/providers/base.rb +111 -0
  152. data/lib/nvoi/providers/hetzner.rb +288 -0
  153. data/lib/nvoi/providers/hetzner_client.rb +170 -0
  154. data/lib/nvoi/remote/docker_manager.rb +203 -0
  155. data/lib/nvoi/remote/ssh_executor.rb +72 -0
  156. data/lib/nvoi/remote/volume_manager.rb +103 -0
  157. data/lib/nvoi/service/delete.rb +234 -0
  158. data/lib/nvoi/service/deploy.rb +80 -0
  159. data/lib/nvoi/service/exec.rb +144 -0
  160. data/lib/nvoi/service/provider.rb +36 -0
  161. data/lib/nvoi/steps/application_deployer.rb +26 -0
  162. data/lib/nvoi/steps/database_provisioner.rb +60 -0
  163. data/lib/nvoi/steps/k3s_cluster_setup.rb +105 -0
  164. data/lib/nvoi/steps/k3s_provisioner.rb +351 -0
  165. data/lib/nvoi/steps/server_provisioner.rb +43 -0
  166. data/lib/nvoi/steps/services_provisioner.rb +29 -0
  167. data/lib/nvoi/steps/tunnel_configurator.rb +66 -0
  168. data/lib/nvoi/steps/volume_provisioner.rb +154 -0
  169. data/lib/nvoi/version.rb +5 -0
  170. data/lib/nvoi.rb +79 -0
  171. data/templates/app-deployment.yaml.erb +102 -0
  172. data/templates/app-ingress.yaml.erb +20 -0
  173. data/templates/app-secret.yaml.erb +10 -0
  174. data/templates/app-service.yaml.erb +12 -0
  175. data/templates/db-statefulset.yaml.erb +76 -0
  176. data/templates/service-deployment.yaml.erb +91 -0
  177. data/templates/worker-deployment.yaml.erb +50 -0
  178. 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