create-svc 0.1.10 → 0.1.12

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 (171) hide show
  1. package/README.md +51 -47
  2. package/index.ts +2 -2
  3. package/package.json +10 -9
  4. package/src/cli.test.ts +28 -10
  5. package/src/cli.ts +196 -33
  6. package/src/git-bootstrap.test.ts +40 -0
  7. package/src/git-bootstrap.ts +110 -0
  8. package/src/naming.test.ts +1 -0
  9. package/src/naming.ts +23 -0
  10. package/src/post-scaffold.test.ts +19 -0
  11. package/src/post-scaffold.ts +17 -4
  12. package/src/profiles.ts +2 -5
  13. package/src/scaffold.test.ts +232 -41
  14. package/src/scaffold.ts +81 -36
  15. package/src/service.test.ts +30 -0
  16. package/src/service.ts +65 -0
  17. package/src/vault.test.ts +61 -1
  18. package/src/vault.ts +77 -15
  19. package/templates/shared/.github/workflows/ci.yml +2 -1
  20. package/templates/shared/.github/workflows/deploy.yml +2 -0
  21. package/templates/shared/README.md +124 -47
  22. package/templates/shared/grafana/alerts.yaml +54 -0
  23. package/templates/shared/grafana/waitlist-dashboard.json +63 -0
  24. package/templates/shared/scripts/authctl.ts +231 -0
  25. package/templates/shared/scripts/cloudrun/bootstrap.ts +14 -5
  26. package/templates/shared/scripts/cloudrun/cleanup.ts +64 -4
  27. package/templates/shared/scripts/cloudrun/cli.ts +329 -7
  28. package/templates/shared/scripts/cloudrun/config.ts +11 -4
  29. package/templates/shared/scripts/cloudrun/deploy.ts +0 -4
  30. package/templates/shared/scripts/cloudrun/lib.ts +174 -41
  31. package/templates/shared/scripts/cloudrun/neon.ts +45 -0
  32. package/templates/shared/scripts/dev.ts +22 -0
  33. package/templates/shared/scripts/ensure-local-db.ts +3 -0
  34. package/templates/shared/scripts/local-docker.ts +63 -0
  35. package/templates/shared/scripts/local-env.ts +27 -0
  36. package/templates/shared/scripts/seed.ts +73 -0
  37. package/templates/shared/scripts/wait-for-db.ts +32 -0
  38. package/templates/shared/service.config.ts +59 -0
  39. package/templates/shared/service.yaml +24 -44
  40. package/templates/targets/workers/.github/workflows/ci.yml +19 -0
  41. package/templates/targets/workers/.github/workflows/deploy.yml +19 -0
  42. package/templates/targets/workers/Makefile +33 -0
  43. package/templates/targets/workers/README.md +75 -0
  44. package/templates/targets/workers/package.json +35 -0
  45. package/templates/targets/workers/scripts/workers/cli.ts +402 -0
  46. package/templates/targets/workers/src/auth.ts +178 -0
  47. package/templates/targets/workers/src/index.ts +198 -0
  48. package/templates/targets/workers/src/storage.ts +370 -0
  49. package/templates/targets/workers/test/app.test.ts +108 -0
  50. package/templates/targets/workers/tsconfig.json +11 -0
  51. package/templates/targets/workers/wrangler.toml +24 -0
  52. package/templates/variants/bun-connectrpc/Makefile +14 -8
  53. package/templates/variants/bun-connectrpc/gen/protos/waitlist/v1/waitlist_pb.ts +424 -0
  54. package/templates/variants/bun-connectrpc/migrations/0000_init.sql +12 -55
  55. package/templates/variants/bun-connectrpc/package.json +12 -5
  56. package/templates/variants/bun-connectrpc/protos/waitlist/v1/waitlist.proto +91 -0
  57. package/templates/variants/bun-connectrpc/scripts/codegen.ts +1 -1
  58. package/templates/variants/bun-connectrpc/scripts/migrate.ts +4 -1
  59. package/templates/variants/bun-connectrpc/src/auth.ts +200 -0
  60. package/templates/variants/bun-connectrpc/src/db/repository.ts +67 -420
  61. package/templates/variants/bun-connectrpc/src/db/schema.ts +15 -64
  62. package/templates/variants/bun-connectrpc/src/index.ts +76 -176
  63. package/templates/variants/bun-connectrpc/src/temporal/activities.ts +14 -0
  64. package/templates/variants/bun-connectrpc/src/temporal/worker.ts +38 -0
  65. package/templates/variants/bun-connectrpc/src/temporal/workflows.ts +10 -0
  66. package/templates/variants/bun-connectrpc/src/waitlist/service.ts +172 -0
  67. package/templates/variants/bun-connectrpc/src/waitlist/types.ts +45 -0
  68. package/templates/variants/bun-connectrpc/test/app.test.ts +4 -4
  69. package/templates/variants/bun-connectrpc/test/waitlist.integration.test.ts +71 -0
  70. package/templates/variants/bun-hono/Makefile +14 -8
  71. package/templates/variants/bun-hono/migrations/0000_init.sql +12 -55
  72. package/templates/variants/bun-hono/package.json +12 -5
  73. package/templates/variants/bun-hono/scripts/migrate.ts +4 -1
  74. package/templates/variants/bun-hono/src/auth.ts +181 -0
  75. package/templates/variants/bun-hono/src/db/repository.ts +68 -421
  76. package/templates/variants/bun-hono/src/db/schema.ts +15 -64
  77. package/templates/variants/bun-hono/src/index.ts +65 -180
  78. package/templates/variants/bun-hono/src/temporal/activities.ts +14 -0
  79. package/templates/variants/bun-hono/src/temporal/worker.ts +38 -0
  80. package/templates/variants/bun-hono/src/temporal/workflows.ts +10 -0
  81. package/templates/variants/bun-hono/src/waitlist/service.ts +166 -0
  82. package/templates/variants/bun-hono/src/waitlist/types.ts +50 -0
  83. package/templates/variants/bun-hono/test/app.test.ts +72 -41
  84. package/templates/variants/bun-hono/test/waitlist.integration.test.ts +102 -0
  85. package/templates/variants/go-chi/Makefile +27 -11
  86. package/templates/variants/go-chi/atlas.hcl +8 -0
  87. package/templates/variants/go-chi/cmd/server/main.go +21 -10
  88. package/templates/variants/go-chi/go.mod +1 -3
  89. package/templates/variants/go-chi/internal/app/service.go +202 -685
  90. package/templates/variants/go-chi/internal/auth/middleware.go +289 -0
  91. package/templates/variants/go-chi/internal/auth/middleware_test.go +38 -0
  92. package/templates/variants/go-chi/internal/config/config.go +27 -11
  93. package/templates/variants/go-chi/internal/httpapi/routes.go +78 -157
  94. package/templates/variants/go-chi/internal/httpapi/waitlist_integration_test.go +199 -0
  95. package/templates/variants/go-chi/internal/temporal/activities.go +27 -0
  96. package/templates/variants/go-chi/internal/temporal/worker.go +42 -0
  97. package/templates/variants/go-chi/internal/temporal/workflows.go +18 -0
  98. package/templates/variants/go-chi/migrations/0000_init.sql +12 -55
  99. package/templates/variants/go-chi/migrations/atlas.sum +2 -0
  100. package/templates/variants/go-chi/package.json +7 -1
  101. package/templates/variants/go-connectrpc/Makefile +26 -9
  102. package/templates/variants/go-connectrpc/atlas.hcl +8 -0
  103. package/templates/variants/go-connectrpc/buf.gen.yaml +2 -2
  104. package/templates/variants/go-connectrpc/cmd/server/main.go +23 -12
  105. package/templates/variants/go-connectrpc/gen/waitlist/v1/waitlist.pb.go +960 -0
  106. package/templates/variants/go-connectrpc/gen/waitlist/v1/waitlistv1connect/waitlist.connect.go +283 -0
  107. package/templates/variants/go-connectrpc/go.mod +1 -1
  108. package/templates/variants/go-connectrpc/internal/app/service.go +202 -685
  109. package/templates/variants/go-connectrpc/internal/auth/middleware.go +289 -0
  110. package/templates/variants/go-connectrpc/internal/auth/middleware_test.go +38 -0
  111. package/templates/variants/go-connectrpc/internal/config/config.go +27 -11
  112. package/templates/variants/go-connectrpc/internal/connectapi/handler.go +78 -201
  113. package/templates/variants/go-connectrpc/internal/connectapi/waitlist_integration_test.go +122 -0
  114. package/templates/variants/go-connectrpc/internal/httpapi/routes.go +147 -9
  115. package/templates/variants/go-connectrpc/internal/temporal/activities.go +27 -0
  116. package/templates/variants/go-connectrpc/internal/temporal/worker.go +42 -0
  117. package/templates/variants/go-connectrpc/internal/temporal/workflows.go +18 -0
  118. package/templates/variants/go-connectrpc/migrations/0000_init.sql +12 -55
  119. package/templates/variants/go-connectrpc/migrations/atlas.sum +2 -0
  120. package/templates/variants/go-connectrpc/package.json +7 -1
  121. package/templates/variants/go-connectrpc/protos/waitlist/v1/waitlist.proto +93 -0
  122. package/templates/root/.github/workflows/buf-publish.yml +0 -19
  123. package/templates/root/.github/workflows/ci.yml +0 -26
  124. package/templates/root/.github/workflows/deploy.yml +0 -22
  125. package/templates/root/Dockerfile +0 -23
  126. package/templates/root/README.md +0 -69
  127. package/templates/root/buf.gen.yaml +0 -10
  128. package/templates/root/buf.yaml +0 -9
  129. package/templates/root/cmd/server/main.go +0 -44
  130. package/templates/root/gen/dns/v1/dns.pb.go +0 -623
  131. package/templates/root/gen/dns/v1/dnsv1connect/dns.connect.go +0 -192
  132. package/templates/root/go.mod +0 -10
  133. package/templates/root/internal/app/service.go +0 -152
  134. package/templates/root/internal/app/token_source.go +0 -50
  135. package/templates/root/internal/cloudflare/client.go +0 -160
  136. package/templates/root/internal/config/config.go +0 -55
  137. package/templates/root/internal/connectapi/handler.go +0 -79
  138. package/templates/root/internal/httpapi/routes.go +0 -93
  139. package/templates/root/internal/vault/client.go +0 -148
  140. package/templates/root/package.json +0 -12
  141. package/templates/root/protos/dns/v1/dns.proto +0 -58
  142. package/templates/root/scripts/cloudrun/bootstrap.ts +0 -65
  143. package/templates/root/scripts/cloudrun/config.ts +0 -50
  144. package/templates/root/scripts/cloudrun/deploy.ts +0 -41
  145. package/templates/root/scripts/cloudrun/lib.ts +0 -244
  146. package/templates/root/service.yaml +0 -50
  147. package/templates/root/test/go.test.ts +0 -19
  148. package/templates/shared/scripts/cloudrun/integrations.ts +0 -111
  149. package/templates/variants/bun-connectrpc/gen/protos/chat/v1/chat_pb.ts +0 -1078
  150. package/templates/variants/bun-connectrpc/protos/chat/v1/chat.proto +0 -228
  151. package/templates/variants/bun-connectrpc/src/chat/service.ts +0 -384
  152. package/templates/variants/bun-connectrpc/src/chat/types.ts +0 -142
  153. package/templates/variants/bun-connectrpc/src/storage.ts +0 -72
  154. package/templates/variants/bun-connectrpc/src/webhooks.ts +0 -35
  155. package/templates/variants/bun-connectrpc/test/list-messages.integration.test.ts +0 -182
  156. package/templates/variants/bun-hono/src/chat/service.ts +0 -384
  157. package/templates/variants/bun-hono/src/chat/types.ts +0 -142
  158. package/templates/variants/bun-hono/src/storage.ts +0 -72
  159. package/templates/variants/bun-hono/src/webhooks.ts +0 -35
  160. package/templates/variants/bun-hono/test/list-messages.integration.test.ts +0 -256
  161. package/templates/variants/go-chi/buf.gen.yaml +0 -12
  162. package/templates/variants/go-chi/buf.yaml +0 -9
  163. package/templates/variants/go-chi/cmd/migrate/main.go +0 -101
  164. package/templates/variants/go-chi/internal/httpapi/list_messages_integration_test.go +0 -298
  165. package/templates/variants/go-chi/protos/chat/v1/chat.proto +0 -219
  166. package/templates/variants/go-connectrpc/cmd/migrate/main.go +0 -101
  167. package/templates/variants/go-connectrpc/gen/chat/v1/chat.pb.go +0 -2512
  168. package/templates/variants/go-connectrpc/gen/chat/v1/chatv1connect/chat.connect.go +0 -571
  169. package/templates/variants/go-connectrpc/internal/connectapi/list_messages_integration_test.go +0 -216
  170. package/templates/variants/go-connectrpc/protos/chat/v1/chat.proto +0 -232
  171. /package/bin/{create-svc.mjs → service.mjs} +0 -0
@@ -0,0 +1,289 @@
1
+ package auth
2
+
3
+ import (
4
+ "context"
5
+ "crypto"
6
+ "crypto/ecdsa"
7
+ "crypto/ed25519"
8
+ "crypto/elliptic"
9
+ "crypto/rsa"
10
+ "crypto/sha256"
11
+ "encoding/base64"
12
+ "encoding/json"
13
+ "errors"
14
+ "math/big"
15
+ "net/http"
16
+ "strings"
17
+ "sync"
18
+ "time"
19
+ )
20
+
21
+ type Config struct {
22
+ Enabled bool
23
+ Issuer string
24
+ Audience string
25
+ JWKSURL string
26
+ }
27
+
28
+ type jwksCache struct {
29
+ mu sync.Mutex
30
+ expiresAt time.Time
31
+ keys []jwk
32
+ }
33
+
34
+ type jwtHeader struct {
35
+ Alg string `json:"alg"`
36
+ Kid string `json:"kid"`
37
+ }
38
+
39
+ type jwtClaims struct {
40
+ Issuer string `json:"iss"`
41
+ Audience json.RawMessage `json:"aud"`
42
+ ExpiresAt int64 `json:"exp"`
43
+ NotBefore int64 `json:"nbf"`
44
+ }
45
+
46
+ type jwksDocument struct {
47
+ Keys []jwk `json:"keys"`
48
+ }
49
+
50
+ type jwk struct {
51
+ Kty string `json:"kty"`
52
+ Kid string `json:"kid"`
53
+ Alg string `json:"alg"`
54
+ N string `json:"n"`
55
+ E string `json:"e"`
56
+ Crv string `json:"crv"`
57
+ X string `json:"x"`
58
+ Y string `json:"y"`
59
+ }
60
+
61
+ func Middleware(cfg Config) func(http.Handler) http.Handler {
62
+ cache := &jwksCache{}
63
+ return func(next http.Handler) http.Handler {
64
+ return http.HandlerFunc(func(w http.ResponseWriter, request *http.Request) {
65
+ if !cfg.Enabled || publicPath(request) {
66
+ next.ServeHTTP(w, request)
67
+ return
68
+ }
69
+ token := bearerToken(request.Header.Get("Authorization"))
70
+ if token == "" || verifyToken(request.Context(), token, cfg, cache) != nil {
71
+ writeUnauthorized(w)
72
+ return
73
+ }
74
+ next.ServeHTTP(w, request)
75
+ })
76
+ }
77
+ }
78
+
79
+ func publicPath(request *http.Request) bool {
80
+ path := request.URL.Path
81
+ return path == "/" || path == "/healthz" || path == "/readyz" || strings.HasPrefix(path, "/webhooks/")
82
+ }
83
+
84
+ func verifyToken(ctx context.Context, token string, cfg Config, cache *jwksCache) error {
85
+ if cfg.Issuer == "" || cfg.Audience == "" || cfg.JWKSURL == "" {
86
+ return errors.New("auth config is incomplete")
87
+ }
88
+ parts := strings.Split(token, ".")
89
+ if len(parts) != 3 {
90
+ return errors.New("token must have three parts")
91
+ }
92
+
93
+ var header jwtHeader
94
+ if err := decodeJSON(parts[0], &header); err != nil {
95
+ return err
96
+ }
97
+ var claims jwtClaims
98
+ if err := decodeJSON(parts[1], &claims); err != nil {
99
+ return err
100
+ }
101
+ key, err := cache.key(ctx, cfg.JWKSURL, header.Kid)
102
+ if err != nil {
103
+ return err
104
+ }
105
+ if err := verifySignature(header.Alg, key, []byte(parts[0]+"."+parts[1]), parts[2]); err != nil {
106
+ return err
107
+ }
108
+ return validateClaims(claims, cfg)
109
+ }
110
+
111
+ func (cache *jwksCache) key(ctx context.Context, jwksURL string, kid string) (jwk, error) {
112
+ cache.mu.Lock()
113
+ defer cache.mu.Unlock()
114
+
115
+ if time.Now().After(cache.expiresAt) {
116
+ request, err := http.NewRequestWithContext(ctx, http.MethodGet, jwksURL, nil)
117
+ if err != nil {
118
+ return jwk{}, err
119
+ }
120
+ response, err := http.DefaultClient.Do(request)
121
+ if err != nil {
122
+ return jwk{}, err
123
+ }
124
+ defer response.Body.Close()
125
+ if response.StatusCode < 200 || response.StatusCode >= 300 {
126
+ return jwk{}, errors.New("jwks fetch failed")
127
+ }
128
+ var document jwksDocument
129
+ if err := json.NewDecoder(response.Body).Decode(&document); err != nil {
130
+ return jwk{}, err
131
+ }
132
+ cache.keys = document.Keys
133
+ cache.expiresAt = time.Now().Add(5 * time.Minute)
134
+ }
135
+
136
+ if kid == "" && len(cache.keys) == 1 {
137
+ return cache.keys[0], nil
138
+ }
139
+ for _, key := range cache.keys {
140
+ if key.Kid == kid {
141
+ return key, nil
142
+ }
143
+ }
144
+ return jwk{}, errors.New("jwk not found")
145
+ }
146
+
147
+ func verifySignature(alg string, key jwk, signingInput []byte, encodedSignature string) error {
148
+ signature, err := base64.RawURLEncoding.DecodeString(encodedSignature)
149
+ if err != nil {
150
+ return err
151
+ }
152
+ digest := sha256.Sum256(signingInput)
153
+ switch alg {
154
+ case "RS256":
155
+ publicKey, err := rsaPublicKey(key)
156
+ if err != nil {
157
+ return err
158
+ }
159
+ return rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, digest[:], signature)
160
+ case "ES256":
161
+ publicKey, err := ecdsaPublicKey(key)
162
+ if err != nil {
163
+ return err
164
+ }
165
+ if len(signature) != 64 {
166
+ return errors.New("invalid ES256 signature")
167
+ }
168
+ r := new(big.Int).SetBytes(signature[:32])
169
+ s := new(big.Int).SetBytes(signature[32:])
170
+ if !ecdsa.Verify(publicKey, digest[:], r, s) {
171
+ return errors.New("invalid ES256 signature")
172
+ }
173
+ return nil
174
+ case "EdDSA":
175
+ publicKey, err := ed25519PublicKey(key)
176
+ if err != nil {
177
+ return err
178
+ }
179
+ if !ed25519.Verify(publicKey, signingInput, signature) {
180
+ return errors.New("invalid EdDSA signature")
181
+ }
182
+ return nil
183
+ default:
184
+ return errors.New("unsupported jwt alg")
185
+ }
186
+ }
187
+
188
+ func rsaPublicKey(key jwk) (*rsa.PublicKey, error) {
189
+ n, err := base64.RawURLEncoding.DecodeString(key.N)
190
+ if err != nil {
191
+ return nil, err
192
+ }
193
+ eBytes, err := base64.RawURLEncoding.DecodeString(key.E)
194
+ if err != nil {
195
+ return nil, err
196
+ }
197
+ e := 0
198
+ for _, b := range eBytes {
199
+ e = e<<8 + int(b)
200
+ }
201
+ return &rsa.PublicKey{N: new(big.Int).SetBytes(n), E: e}, nil
202
+ }
203
+
204
+ func ecdsaPublicKey(key jwk) (*ecdsa.PublicKey, error) {
205
+ if key.Crv != "P-256" {
206
+ return nil, errors.New("unsupported ecdsa curve")
207
+ }
208
+ x, err := base64.RawURLEncoding.DecodeString(key.X)
209
+ if err != nil {
210
+ return nil, err
211
+ }
212
+ y, err := base64.RawURLEncoding.DecodeString(key.Y)
213
+ if err != nil {
214
+ return nil, err
215
+ }
216
+ return &ecdsa.PublicKey{Curve: elliptic.P256(), X: new(big.Int).SetBytes(x), Y: new(big.Int).SetBytes(y)}, nil
217
+ }
218
+
219
+ func ed25519PublicKey(key jwk) (ed25519.PublicKey, error) {
220
+ if key.Crv != "Ed25519" {
221
+ return nil, errors.New("unsupported eddsa curve")
222
+ }
223
+ x, err := base64.RawURLEncoding.DecodeString(key.X)
224
+ if err != nil {
225
+ return nil, err
226
+ }
227
+ if len(x) != ed25519.PublicKeySize {
228
+ return nil, errors.New("invalid Ed25519 public key")
229
+ }
230
+ return ed25519.PublicKey(x), nil
231
+ }
232
+
233
+ func validateClaims(claims jwtClaims, cfg Config) error {
234
+ now := time.Now().Unix()
235
+ if claims.Issuer != cfg.Issuer {
236
+ return errors.New("issuer mismatch")
237
+ }
238
+ if !audienceMatches(claims.Audience, cfg.Audience) {
239
+ return errors.New("audience mismatch")
240
+ }
241
+ if claims.ExpiresAt == 0 || claims.ExpiresAt <= now-30 {
242
+ return errors.New("token expired")
243
+ }
244
+ if claims.NotBefore != 0 && claims.NotBefore > now+30 {
245
+ return errors.New("token not active")
246
+ }
247
+ return nil
248
+ }
249
+
250
+ func audienceMatches(raw json.RawMessage, expected string) bool {
251
+ var single string
252
+ if err := json.Unmarshal(raw, &single); err == nil {
253
+ return single == expected
254
+ }
255
+ var many []string
256
+ if err := json.Unmarshal(raw, &many); err == nil {
257
+ for _, audience := range many {
258
+ if audience == expected {
259
+ return true
260
+ }
261
+ }
262
+ }
263
+ return false
264
+ }
265
+
266
+ func decodeJSON(encoded string, out any) error {
267
+ payload, err := base64.RawURLEncoding.DecodeString(encoded)
268
+ if err != nil {
269
+ return err
270
+ }
271
+ return json.Unmarshal(payload, out)
272
+ }
273
+
274
+ func bearerToken(value string) string {
275
+ fields := strings.Fields(value)
276
+ if len(fields) != 2 || !strings.EqualFold(fields[0], "Bearer") {
277
+ return ""
278
+ }
279
+ return fields[1]
280
+ }
281
+
282
+ func writeUnauthorized(w http.ResponseWriter) {
283
+ w.Header().Set("Content-Type", "application/json")
284
+ w.WriteHeader(http.StatusUnauthorized)
285
+ _ = json.NewEncoder(w).Encode(map[string]string{
286
+ "error": "invalid bearer token",
287
+ "code": "unauthorized",
288
+ })
289
+ }
@@ -0,0 +1,38 @@
1
+ package auth
2
+
3
+ import (
4
+ "net/http"
5
+ "net/http/httptest"
6
+ "testing"
7
+ )
8
+
9
+ func TestMiddlewareRejectsProtectedPathWithoutBearerToken(t *testing.T) {
10
+ handler := Middleware(Config{
11
+ Enabled: true,
12
+ Issuer: "https://auth.anmho.com",
13
+ Audience: "api://{{SERVICE_ID}}",
14
+ JWKSURL: "https://auth.anmho.com/api/auth/jwks",
15
+ })(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
16
+ w.WriteHeader(http.StatusNoContent)
17
+ }))
18
+
19
+ response := httptest.NewRecorder()
20
+ handler.ServeHTTP(response, httptest.NewRequest(http.MethodPost, "/waitlist.v1.WaitlistService/JoinWaitlist", nil))
21
+
22
+ if response.Code != http.StatusUnauthorized {
23
+ t.Fatalf("expected 401, got %d", response.Code)
24
+ }
25
+ }
26
+
27
+ func TestMiddlewareLeavesHealthPublic(t *testing.T) {
28
+ handler := Middleware(Config{Enabled: true})(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
29
+ w.WriteHeader(http.StatusNoContent)
30
+ }))
31
+
32
+ response := httptest.NewRecorder()
33
+ handler.ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/healthz", nil))
34
+
35
+ if response.Code != http.StatusNoContent {
36
+ t.Fatalf("expected health to pass through, got %d", response.Code)
37
+ }
38
+ }
@@ -7,28 +7,44 @@ import (
7
7
  )
8
8
 
9
9
  type Config struct {
10
- Port string
11
- DatabaseURL string
12
- AttachmentBucket string
13
- AttachmentPublicBaseURL string
10
+ Port string
11
+ DatabaseURL string
12
+ TemporalEnabled bool
13
+ TemporalAddress string
14
+ TemporalNamespace string
15
+ TemporalTaskQueue string
16
+ TemporalAPIKey string
17
+ AuthEnabled bool
18
+ AuthIssuer string
19
+ AuthAudience string
20
+ AuthJWKSURL string
14
21
  }
15
22
 
16
23
  func Load() (Config, error) {
17
24
  cfg := Config{
18
- Port: envOr("PORT", "8080"),
19
- DatabaseURL: strings.TrimSpace(os.Getenv("DATABASE_URL")),
20
- AttachmentBucket: strings.TrimSpace(os.Getenv("ATTACHMENT_BUCKET")),
21
- AttachmentPublicBaseURL: strings.TrimSpace(os.Getenv("ATTACHMENT_PUBLIC_BASE_URL")),
25
+ Port: envOr("PORT", "8080"),
26
+ DatabaseURL: strings.TrimSpace(os.Getenv("DATABASE_URL")),
27
+ TemporalEnabled: envBool("TEMPORAL_ENABLED"),
28
+ TemporalAddress: envOr("TEMPORAL_ADDRESS", "localhost:7233"),
29
+ TemporalNamespace: envOr("TEMPORAL_NAMESPACE", "default"),
30
+ TemporalTaskQueue: envOr("TEMPORAL_TASK_QUEUE", "{{SERVICE_NAME}}"),
31
+ TemporalAPIKey: strings.TrimSpace(os.Getenv("TEMPORAL_API_KEY")),
32
+ AuthEnabled: envBool("AUTH_ENABLED"),
33
+ AuthIssuer: envOr("AUTH_ISSUER", "{{AUTH_ISSUER}}"),
34
+ AuthAudience: envOr("AUTH_AUDIENCE", "{{AUTH_AUDIENCE}}"),
35
+ AuthJWKSURL: envOr("AUTH_JWKS_URL", "{{AUTH_JWKS_URL}}"),
22
36
  }
23
37
  if cfg.DatabaseURL == "" {
24
38
  return Config{}, errors.New("DATABASE_URL is required")
25
39
  }
26
- if cfg.AttachmentBucket == "" {
27
- return Config{}, errors.New("ATTACHMENT_BUCKET is required")
28
- }
29
40
  return cfg, nil
30
41
  }
31
42
 
43
+ func envBool(key string) bool {
44
+ value := strings.TrimSpace(strings.ToLower(os.Getenv(key)))
45
+ return value == "1" || value == "true" || value == "yes" || value == "on"
46
+ }
47
+
32
48
  func envOr(key string, fallback string) string {
33
49
  value := os.Getenv(key)
34
50
  if value != "" {