create-svc 0.1.52 → 0.1.54

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 (39) hide show
  1. package/README.md +6 -0
  2. package/package.json +1 -1
  3. package/src/scaffold.test.ts +31 -9
  4. package/src/scaffold.ts +23 -0
  5. package/src/service-runtime/cloudrun/cli.ts +10 -0
  6. package/src/service-runtime/cloudrun/config.ts +3 -0
  7. package/src/service-runtime/cloudrun/observability.ts +18 -0
  8. package/templates/shared/.github/workflows/deploy.yml +1 -1
  9. package/templates/shared/.github/workflows/preview.yml +4 -3
  10. package/templates/shared/README.md +37 -0
  11. package/templates/shared/service.jsonc +8 -0
  12. package/templates/targets/workers/package.json +1 -1
  13. package/templates/variants/bun-connectrpc/Makefile +4 -1
  14. package/templates/variants/bun-connectrpc/migrations/0000_init.sql +10 -0
  15. package/templates/variants/bun-connectrpc/package.json +1 -0
  16. package/templates/variants/bun-connectrpc/src/db/repository.ts +52 -3
  17. package/templates/variants/bun-connectrpc/src/db/schema.ts +13 -0
  18. package/templates/variants/bun-connectrpc/src/index.ts +47 -4
  19. package/templates/variants/bun-connectrpc/src/waitlist/service.ts +22 -0
  20. package/templates/variants/bun-connectrpc/src/waitlist/types.ts +16 -0
  21. package/templates/variants/bun-hono/Makefile +4 -1
  22. package/templates/variants/bun-hono/migrations/0000_init.sql +10 -0
  23. package/templates/variants/bun-hono/package.json +1 -0
  24. package/templates/variants/bun-hono/src/db/repository.ts +52 -3
  25. package/templates/variants/bun-hono/src/db/schema.ts +13 -0
  26. package/templates/variants/bun-hono/src/index.ts +34 -8
  27. package/templates/variants/bun-hono/src/waitlist/service.ts +22 -0
  28. package/templates/variants/bun-hono/src/waitlist/types.ts +16 -0
  29. package/templates/variants/bun-hono/test/app.test.ts +13 -0
  30. package/templates/variants/go-chi/Makefile +4 -1
  31. package/templates/variants/go-chi/internal/app/service.go +96 -0
  32. package/templates/variants/go-chi/internal/httpapi/routes.go +56 -7
  33. package/templates/variants/go-chi/migrations/0000_init.sql +10 -0
  34. package/templates/variants/go-chi/migrations/atlas.sum +2 -2
  35. package/templates/variants/go-connectrpc/Makefile +4 -1
  36. package/templates/variants/go-connectrpc/internal/app/service.go +96 -0
  37. package/templates/variants/go-connectrpc/internal/httpapi/routes.go +56 -7
  38. package/templates/variants/go-connectrpc/migrations/0000_init.sql +10 -0
  39. package/templates/variants/go-connectrpc/migrations/atlas.sum +2 -2
@@ -3,10 +3,12 @@ package httpapi
3
3
  import (
4
4
  "encoding/json"
5
5
  "errors"
6
+ "fmt"
6
7
  "io"
7
8
  "net/http"
8
9
  "strconv"
9
10
  "strings"
11
+ "time"
10
12
 
11
13
  "github.com/go-chi/chi/v5"
12
14
 
@@ -130,18 +132,35 @@ func RegisterRoutes(router chi.Router, service *app.WaitlistService) {
130
132
  writeError(w, err)
131
133
  return
132
134
  }
133
- trigger, err := service.RecordTrigger(request.Context(), app.RecordTriggerInput{
134
- Type: "webhook." + chi.URLParam(request, "provider"),
135
- Payload: map[string]any{
136
- "headers": request.Header,
137
- "rawBody": string(rawBody),
138
- },
135
+ provider := chi.URLParam(request, "provider")
136
+ payload := parseWebhookPayload(rawBody)
137
+ result, err := service.RecordWebhookEvent(request.Context(), app.RecordWebhookEventInput{
138
+ Provider: provider,
139
+ ExternalEventID: webhookEventID(payload, request.Header),
140
+ Payload: payload,
141
+ Headers: headersPayload(request.Header),
139
142
  })
140
143
  if err != nil {
141
144
  writeError(w, err)
142
145
  return
143
146
  }
144
- writeJSON(w, http.StatusAccepted, map[string]any{"trigger": trigger})
147
+ if !result.Duplicate {
148
+ if _, err := service.RecordTrigger(request.Context(), app.RecordTriggerInput{
149
+ Type: "webhook." + provider,
150
+ Payload: map[string]any{
151
+ "headers": headersPayload(request.Header),
152
+ "rawBody": string(rawBody),
153
+ },
154
+ }); err != nil {
155
+ writeError(w, err)
156
+ return
157
+ }
158
+ }
159
+ if result.Duplicate {
160
+ writeJSON(w, http.StatusOK, result)
161
+ return
162
+ }
163
+ writeJSON(w, http.StatusAccepted, result)
145
164
  })
146
165
 
147
166
  router.Get("/webhooks/{provider}/health", func(w http.ResponseWriter, request *http.Request) {
@@ -157,6 +176,36 @@ func decodeJSON(request *http.Request, out any) error {
157
176
  return json.NewDecoder(request.Body).Decode(out)
158
177
  }
159
178
 
179
+ func parseWebhookPayload(rawBody []byte) map[string]any {
180
+ var payload map[string]any
181
+ if len(rawBody) == 0 || json.Unmarshal(rawBody, &payload) != nil {
182
+ return map[string]any{"rawBody": string(rawBody)}
183
+ }
184
+ return payload
185
+ }
186
+
187
+ func webhookEventID(payload map[string]any, headers http.Header) string {
188
+ if id, ok := payload["id"].(string); ok && id != "" {
189
+ return id
190
+ }
191
+ if id := headers.Get("X-Webhook-Event-Id"); id != "" {
192
+ return id
193
+ }
194
+ return fmt.Sprintf("evt_%d", time.Now().UnixNano())
195
+ }
196
+
197
+ func headersPayload(headers http.Header) map[string]any {
198
+ out := make(map[string]any, len(headers))
199
+ for key, values := range headers {
200
+ if len(values) == 1 {
201
+ out[key] = values[0]
202
+ continue
203
+ }
204
+ out[key] = values
205
+ }
206
+ return out
207
+ }
208
+
160
209
  func decodeOptionalJSON(request *http.Request, out any) error {
161
210
  defer request.Body.Close()
162
211
  decoder := json.NewDecoder(request.Body)
@@ -18,3 +18,13 @@ create table if not exists waitlist_triggers (
18
18
  created_at timestamptz not null default now(),
19
19
  processed_at timestamptz
20
20
  );
21
+
22
+ create table if not exists webhook_events (
23
+ id text primary key,
24
+ provider text not null,
25
+ external_event_id text not null,
26
+ payload_json text not null,
27
+ headers_json text not null,
28
+ received_at timestamptz not null default now(),
29
+ unique (provider, external_event_id)
30
+ );
@@ -1,2 +1,2 @@
1
- h1:D9GgvtEf0xxnRDaTb/C3cPtRbncVXsK6Ef0KYQuLMRw=
2
- 0000_init.sql h1:dBpLGyoeZNRpjT6RvQXiqx3p2ICAChKy2jVqCIYuWQg=
1
+ h1:AhMVwNnmHR10XivPa/5MXyIZrG7LPmekS6SpgjTGJyA=
2
+ 0000_init.sql h1:9Sm7GI+kmYAsnRTFdZkIZl0bsQN5EUlN/AopryCCRgs=