create-svc 0.1.10 → 0.1.11

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 (168) hide show
  1. package/README.md +46 -43
  2. package/bin/create-service.mjs +2 -0
  3. package/package.json +12 -9
  4. package/src/cli.test.ts +28 -10
  5. package/src/cli.ts +195 -30
  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 +231 -40
  14. package/src/scaffold.ts +84 -29
  15. package/src/vault.test.ts +61 -1
  16. package/src/vault.ts +77 -15
  17. package/templates/shared/.github/workflows/ci.yml +2 -1
  18. package/templates/shared/.github/workflows/deploy.yml +2 -0
  19. package/templates/shared/README.md +124 -47
  20. package/templates/shared/grafana/alerts.yaml +54 -0
  21. package/templates/shared/grafana/waitlist-dashboard.json +63 -0
  22. package/templates/shared/scripts/authctl.ts +231 -0
  23. package/templates/shared/scripts/cloudrun/bootstrap.ts +14 -5
  24. package/templates/shared/scripts/cloudrun/cleanup.ts +64 -4
  25. package/templates/shared/scripts/cloudrun/cli.ts +324 -7
  26. package/templates/shared/scripts/cloudrun/config.ts +11 -4
  27. package/templates/shared/scripts/cloudrun/deploy.ts +0 -4
  28. package/templates/shared/scripts/cloudrun/lib.ts +174 -41
  29. package/templates/shared/scripts/cloudrun/neon.ts +45 -0
  30. package/templates/shared/scripts/dev.ts +22 -0
  31. package/templates/shared/scripts/ensure-local-db.ts +3 -0
  32. package/templates/shared/scripts/local-docker.ts +63 -0
  33. package/templates/shared/scripts/local-env.ts +27 -0
  34. package/templates/shared/scripts/seed.ts +73 -0
  35. package/templates/shared/scripts/wait-for-db.ts +32 -0
  36. package/templates/shared/service.config.ts +59 -0
  37. package/templates/shared/service.yaml +24 -44
  38. package/templates/targets/workers/.github/workflows/ci.yml +19 -0
  39. package/templates/targets/workers/.github/workflows/deploy.yml +19 -0
  40. package/templates/targets/workers/Makefile +33 -0
  41. package/templates/targets/workers/README.md +75 -0
  42. package/templates/targets/workers/package.json +35 -0
  43. package/templates/targets/workers/scripts/workers/cli.ts +397 -0
  44. package/templates/targets/workers/src/auth.ts +178 -0
  45. package/templates/targets/workers/src/index.ts +198 -0
  46. package/templates/targets/workers/src/storage.ts +370 -0
  47. package/templates/targets/workers/test/app.test.ts +108 -0
  48. package/templates/targets/workers/tsconfig.json +11 -0
  49. package/templates/targets/workers/wrangler.toml +24 -0
  50. package/templates/variants/bun-connectrpc/Makefile +14 -8
  51. package/templates/variants/bun-connectrpc/gen/protos/waitlist/v1/waitlist_pb.ts +424 -0
  52. package/templates/variants/bun-connectrpc/migrations/0000_init.sql +12 -55
  53. package/templates/variants/bun-connectrpc/package.json +12 -5
  54. package/templates/variants/bun-connectrpc/protos/waitlist/v1/waitlist.proto +91 -0
  55. package/templates/variants/bun-connectrpc/scripts/codegen.ts +1 -1
  56. package/templates/variants/bun-connectrpc/scripts/migrate.ts +4 -1
  57. package/templates/variants/bun-connectrpc/src/auth.ts +200 -0
  58. package/templates/variants/bun-connectrpc/src/db/repository.ts +67 -420
  59. package/templates/variants/bun-connectrpc/src/db/schema.ts +15 -64
  60. package/templates/variants/bun-connectrpc/src/index.ts +76 -176
  61. package/templates/variants/bun-connectrpc/src/temporal/activities.ts +14 -0
  62. package/templates/variants/bun-connectrpc/src/temporal/worker.ts +38 -0
  63. package/templates/variants/bun-connectrpc/src/temporal/workflows.ts +10 -0
  64. package/templates/variants/bun-connectrpc/src/waitlist/service.ts +172 -0
  65. package/templates/variants/bun-connectrpc/src/waitlist/types.ts +45 -0
  66. package/templates/variants/bun-connectrpc/test/app.test.ts +4 -4
  67. package/templates/variants/bun-connectrpc/test/waitlist.integration.test.ts +71 -0
  68. package/templates/variants/bun-hono/Makefile +14 -8
  69. package/templates/variants/bun-hono/migrations/0000_init.sql +12 -55
  70. package/templates/variants/bun-hono/package.json +12 -5
  71. package/templates/variants/bun-hono/scripts/migrate.ts +4 -1
  72. package/templates/variants/bun-hono/src/auth.ts +181 -0
  73. package/templates/variants/bun-hono/src/db/repository.ts +68 -421
  74. package/templates/variants/bun-hono/src/db/schema.ts +15 -64
  75. package/templates/variants/bun-hono/src/index.ts +65 -180
  76. package/templates/variants/bun-hono/src/temporal/activities.ts +14 -0
  77. package/templates/variants/bun-hono/src/temporal/worker.ts +38 -0
  78. package/templates/variants/bun-hono/src/temporal/workflows.ts +10 -0
  79. package/templates/variants/bun-hono/src/waitlist/service.ts +166 -0
  80. package/templates/variants/bun-hono/src/waitlist/types.ts +50 -0
  81. package/templates/variants/bun-hono/test/app.test.ts +72 -41
  82. package/templates/variants/bun-hono/test/waitlist.integration.test.ts +102 -0
  83. package/templates/variants/go-chi/Makefile +27 -11
  84. package/templates/variants/go-chi/atlas.hcl +8 -0
  85. package/templates/variants/go-chi/cmd/server/main.go +21 -10
  86. package/templates/variants/go-chi/go.mod +1 -3
  87. package/templates/variants/go-chi/internal/app/service.go +202 -685
  88. package/templates/variants/go-chi/internal/auth/middleware.go +289 -0
  89. package/templates/variants/go-chi/internal/auth/middleware_test.go +38 -0
  90. package/templates/variants/go-chi/internal/config/config.go +27 -11
  91. package/templates/variants/go-chi/internal/httpapi/routes.go +78 -157
  92. package/templates/variants/go-chi/internal/httpapi/waitlist_integration_test.go +199 -0
  93. package/templates/variants/go-chi/internal/temporal/activities.go +27 -0
  94. package/templates/variants/go-chi/internal/temporal/worker.go +42 -0
  95. package/templates/variants/go-chi/internal/temporal/workflows.go +18 -0
  96. package/templates/variants/go-chi/migrations/0000_init.sql +12 -55
  97. package/templates/variants/go-chi/migrations/atlas.sum +2 -0
  98. package/templates/variants/go-chi/package.json +7 -1
  99. package/templates/variants/go-connectrpc/Makefile +26 -9
  100. package/templates/variants/go-connectrpc/atlas.hcl +8 -0
  101. package/templates/variants/go-connectrpc/buf.gen.yaml +2 -2
  102. package/templates/variants/go-connectrpc/cmd/server/main.go +23 -12
  103. package/templates/variants/go-connectrpc/gen/waitlist/v1/waitlist.pb.go +960 -0
  104. package/templates/variants/go-connectrpc/gen/waitlist/v1/waitlistv1connect/waitlist.connect.go +283 -0
  105. package/templates/variants/go-connectrpc/go.mod +1 -1
  106. package/templates/variants/go-connectrpc/internal/app/service.go +202 -685
  107. package/templates/variants/go-connectrpc/internal/auth/middleware.go +289 -0
  108. package/templates/variants/go-connectrpc/internal/auth/middleware_test.go +38 -0
  109. package/templates/variants/go-connectrpc/internal/config/config.go +27 -11
  110. package/templates/variants/go-connectrpc/internal/connectapi/handler.go +78 -201
  111. package/templates/variants/go-connectrpc/internal/connectapi/waitlist_integration_test.go +122 -0
  112. package/templates/variants/go-connectrpc/internal/httpapi/routes.go +147 -9
  113. package/templates/variants/go-connectrpc/internal/temporal/activities.go +27 -0
  114. package/templates/variants/go-connectrpc/internal/temporal/worker.go +42 -0
  115. package/templates/variants/go-connectrpc/internal/temporal/workflows.go +18 -0
  116. package/templates/variants/go-connectrpc/migrations/0000_init.sql +12 -55
  117. package/templates/variants/go-connectrpc/migrations/atlas.sum +2 -0
  118. package/templates/variants/go-connectrpc/package.json +7 -1
  119. package/templates/variants/go-connectrpc/protos/waitlist/v1/waitlist.proto +93 -0
  120. package/templates/root/.github/workflows/buf-publish.yml +0 -19
  121. package/templates/root/.github/workflows/ci.yml +0 -26
  122. package/templates/root/.github/workflows/deploy.yml +0 -22
  123. package/templates/root/Dockerfile +0 -23
  124. package/templates/root/README.md +0 -69
  125. package/templates/root/buf.gen.yaml +0 -10
  126. package/templates/root/buf.yaml +0 -9
  127. package/templates/root/cmd/server/main.go +0 -44
  128. package/templates/root/gen/dns/v1/dns.pb.go +0 -623
  129. package/templates/root/gen/dns/v1/dnsv1connect/dns.connect.go +0 -192
  130. package/templates/root/go.mod +0 -10
  131. package/templates/root/internal/app/service.go +0 -152
  132. package/templates/root/internal/app/token_source.go +0 -50
  133. package/templates/root/internal/cloudflare/client.go +0 -160
  134. package/templates/root/internal/config/config.go +0 -55
  135. package/templates/root/internal/connectapi/handler.go +0 -79
  136. package/templates/root/internal/httpapi/routes.go +0 -93
  137. package/templates/root/internal/vault/client.go +0 -148
  138. package/templates/root/package.json +0 -12
  139. package/templates/root/protos/dns/v1/dns.proto +0 -58
  140. package/templates/root/scripts/cloudrun/bootstrap.ts +0 -65
  141. package/templates/root/scripts/cloudrun/config.ts +0 -50
  142. package/templates/root/scripts/cloudrun/deploy.ts +0 -41
  143. package/templates/root/scripts/cloudrun/lib.ts +0 -244
  144. package/templates/root/service.yaml +0 -50
  145. package/templates/root/test/go.test.ts +0 -19
  146. package/templates/shared/scripts/cloudrun/integrations.ts +0 -111
  147. package/templates/variants/bun-connectrpc/gen/protos/chat/v1/chat_pb.ts +0 -1078
  148. package/templates/variants/bun-connectrpc/protos/chat/v1/chat.proto +0 -228
  149. package/templates/variants/bun-connectrpc/src/chat/service.ts +0 -384
  150. package/templates/variants/bun-connectrpc/src/chat/types.ts +0 -142
  151. package/templates/variants/bun-connectrpc/src/storage.ts +0 -72
  152. package/templates/variants/bun-connectrpc/src/webhooks.ts +0 -35
  153. package/templates/variants/bun-connectrpc/test/list-messages.integration.test.ts +0 -182
  154. package/templates/variants/bun-hono/src/chat/service.ts +0 -384
  155. package/templates/variants/bun-hono/src/chat/types.ts +0 -142
  156. package/templates/variants/bun-hono/src/storage.ts +0 -72
  157. package/templates/variants/bun-hono/src/webhooks.ts +0 -35
  158. package/templates/variants/bun-hono/test/list-messages.integration.test.ts +0 -256
  159. package/templates/variants/go-chi/buf.gen.yaml +0 -12
  160. package/templates/variants/go-chi/buf.yaml +0 -9
  161. package/templates/variants/go-chi/cmd/migrate/main.go +0 -101
  162. package/templates/variants/go-chi/internal/httpapi/list_messages_integration_test.go +0 -298
  163. package/templates/variants/go-chi/protos/chat/v1/chat.proto +0 -219
  164. package/templates/variants/go-connectrpc/cmd/migrate/main.go +0 -101
  165. package/templates/variants/go-connectrpc/gen/chat/v1/chat.pb.go +0 -2512
  166. package/templates/variants/go-connectrpc/gen/chat/v1/chatv1connect/chat.connect.go +0 -571
  167. package/templates/variants/go-connectrpc/internal/connectapi/list_messages_integration_test.go +0 -216
  168. package/templates/variants/go-connectrpc/protos/chat/v1/chat.proto +0 -232
@@ -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, "/v1/waitlist", 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 != "" {