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
@@ -13,7 +13,7 @@ import (
13
13
  "{{MODULE_PATH}}/internal/app"
14
14
  )
15
15
 
16
- func RegisterRoutes(router chi.Router, service *app.ChatService) {
16
+ func RegisterRoutes(router chi.Router, service *app.WaitlistService) {
17
17
  router.Get("/healthz", func(w http.ResponseWriter, _ *http.Request) {
18
18
  writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
19
19
  })
@@ -23,27 +23,125 @@ func RegisterRoutes(router chi.Router, service *app.ChatService) {
23
23
  router.Get("/", func(w http.ResponseWriter, _ *http.Request) {
24
24
  writeJSON(w, http.StatusOK, map[string]string{
25
25
  "service": "{{SERVICE_NAME}}",
26
- "domain": "chat",
26
+ "domain": "waitlist",
27
27
  "apiOrigin": "https://api.{{SERVICE_NAME}}.anmho.com",
28
28
  })
29
29
  })
30
30
 
31
+ router.Post("/v1/waitlist", func(w http.ResponseWriter, request *http.Request) {
32
+ var input app.JoinWaitlistInput
33
+ if err := decodeJSON(request, &input); err != nil {
34
+ writeError(w, err)
35
+ return
36
+ }
37
+ result, err := service.JoinWaitlist(request.Context(), input)
38
+ if err != nil {
39
+ writeError(w, err)
40
+ return
41
+ }
42
+ status := http.StatusOK
43
+ if result.Created {
44
+ status = http.StatusCreated
45
+ }
46
+ writeJSON(w, status, result)
47
+ })
48
+
49
+ router.Get("/v1/waitlist", func(w http.ResponseWriter, request *http.Request) {
50
+ entry, err := service.GetEntryByEmail(request.Context(), request.URL.Query().Get("email"))
51
+ if err != nil {
52
+ writeError(w, err)
53
+ return
54
+ }
55
+ writeJSON(w, http.StatusOK, map[string]any{"entry": entry})
56
+ })
57
+
58
+ router.Get("/v1/waitlist/{entryID}", func(w http.ResponseWriter, request *http.Request) {
59
+ entry, err := service.GetEntry(request.Context(), chi.URLParam(request, "entryID"))
60
+ if err != nil {
61
+ writeError(w, err)
62
+ return
63
+ }
64
+ writeJSON(w, http.StatusOK, map[string]any{"entry": entry})
65
+ })
66
+
67
+ router.Get("/v1/admin/waitlist", func(w http.ResponseWriter, request *http.Request) {
68
+ entries, err := service.ListEntries(request.Context(), app.ListWaitlistEntriesInput{
69
+ Status: request.URL.Query().Get("status"),
70
+ Limit: optionalInt(request.URL.Query().Get("limit")),
71
+ })
72
+ if err != nil {
73
+ writeError(w, err)
74
+ return
75
+ }
76
+ writeJSON(w, http.StatusOK, map[string]any{"entries": entries})
77
+ })
78
+
79
+ router.Get("/v1/admin/waitlist/export", func(w http.ResponseWriter, request *http.Request) {
80
+ csv, err := service.ExportEntries(request.Context(), app.ListWaitlistEntriesInput{
81
+ Status: request.URL.Query().Get("status"),
82
+ Limit: optionalInt(request.URL.Query().Get("limit")),
83
+ })
84
+ if err != nil {
85
+ writeError(w, err)
86
+ return
87
+ }
88
+ w.Header().Set("Content-Type", "text/csv; charset=utf-8")
89
+ w.Header().Set("Content-Disposition", `attachment; filename="waitlist.csv"`)
90
+ w.WriteHeader(http.StatusOK)
91
+ _, _ = w.Write([]byte(csv))
92
+ })
93
+
94
+ router.Patch("/v1/admin/waitlist/{entryID}", func(w http.ResponseWriter, request *http.Request) {
95
+ var input app.UpdateWaitlistEntryInput
96
+ if err := decodeJSON(request, &input); err != nil {
97
+ writeError(w, err)
98
+ return
99
+ }
100
+ input.EntryID = chi.URLParam(request, "entryID")
101
+ entry, err := service.UpdateEntry(request.Context(), input)
102
+ if err != nil {
103
+ writeError(w, err)
104
+ return
105
+ }
106
+ writeJSON(w, http.StatusOK, map[string]any{"entry": entry})
107
+ })
108
+
109
+ router.Post("/v1/triggers/waitlist", func(w http.ResponseWriter, request *http.Request) {
110
+ var payload map[string]any
111
+ if err := decodeOptionalJSON(request, &payload); err != nil {
112
+ writeError(w, err)
113
+ return
114
+ }
115
+ trigger, err := service.RecordTrigger(request.Context(), app.RecordTriggerInput{
116
+ Type: stringValue(payload, "type", "manual"),
117
+ EntryID: stringValue(payload, "entry_id", ""),
118
+ Payload: payload,
119
+ })
120
+ if err != nil {
121
+ writeError(w, err)
122
+ return
123
+ }
124
+ writeJSON(w, http.StatusAccepted, map[string]any{"trigger": trigger})
125
+ })
126
+
31
127
  router.Post("/webhooks/{provider}", func(w http.ResponseWriter, request *http.Request) {
32
128
  rawBody, err := io.ReadAll(request.Body)
33
129
  if err != nil {
34
130
  writeError(w, err)
35
131
  return
36
132
  }
37
- event, duplicate, err := service.ProcessWebhook(request.Context(), chi.URLParam(request, "provider"), request.Header, rawBody)
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
+ },
139
+ })
38
140
  if err != nil {
39
141
  writeError(w, err)
40
142
  return
41
143
  }
42
- status := http.StatusAccepted
43
- if duplicate {
44
- status = http.StatusOK
45
- }
46
- writeJSON(w, status, map[string]any{"event": event, "duplicate": duplicate})
144
+ writeJSON(w, http.StatusAccepted, map[string]any{"trigger": trigger})
47
145
  })
48
146
 
49
147
  router.Get("/webhooks/{provider}/health", func(w http.ResponseWriter, request *http.Request) {
@@ -54,6 +152,20 @@ func RegisterRoutes(router chi.Router, service *app.ChatService) {
54
152
  })
55
153
  }
56
154
 
155
+ func decodeJSON(request *http.Request, out any) error {
156
+ defer request.Body.Close()
157
+ return json.NewDecoder(request.Body).Decode(out)
158
+ }
159
+
160
+ func decodeOptionalJSON(request *http.Request, out any) error {
161
+ defer request.Body.Close()
162
+ decoder := json.NewDecoder(request.Body)
163
+ if err := decoder.Decode(out); err != nil && !errors.Is(err, io.EOF) {
164
+ return err
165
+ }
166
+ return nil
167
+ }
168
+
57
169
  func writeJSON(w http.ResponseWriter, status int, payload any) {
58
170
  w.Header().Set("Content-Type", "application/json")
59
171
  w.WriteHeader(status)
@@ -71,8 +183,34 @@ func writeError(w http.ResponseWriter, err error) {
71
183
  }
72
184
 
73
185
  status := http.StatusInternalServerError
74
- if errors.Is(err, strconv.ErrSyntax) || strings.Contains(strings.ToLower(err.Error()), "json") {
186
+ if strings.Contains(strings.ToLower(err.Error()), "json") {
75
187
  status = http.StatusBadRequest
76
188
  }
77
189
  writeJSON(w, status, map[string]string{"error": err.Error()})
78
190
  }
191
+
192
+ func stringValue(values map[string]any, key string, fallback string) string {
193
+ if values == nil {
194
+ return fallback
195
+ }
196
+ value, ok := values[key]
197
+ if !ok && key == "entry_id" {
198
+ value, ok = values["entryId"]
199
+ }
200
+ if !ok {
201
+ return fallback
202
+ }
203
+ text, ok := value.(string)
204
+ if !ok {
205
+ return fallback
206
+ }
207
+ return strings.TrimSpace(text)
208
+ }
209
+
210
+ func optionalInt(value string) int {
211
+ parsed, err := strconv.Atoi(strings.TrimSpace(value))
212
+ if err != nil {
213
+ return 0
214
+ }
215
+ return parsed
216
+ }
@@ -0,0 +1,27 @@
1
+ package temporalapp
2
+
3
+ import "context"
4
+
5
+ type WaitlistFollowUpInput struct {
6
+ TriggerID string
7
+ Email string
8
+ Type string
9
+ }
10
+
11
+ type WaitlistFollowUpResult struct {
12
+ Status string
13
+ TriggerID string
14
+ Email string
15
+ Type string
16
+ }
17
+
18
+ type Activities struct{}
19
+
20
+ func (a *Activities) RecordWaitlistFollowUp(ctx context.Context, input WaitlistFollowUpInput) (WaitlistFollowUpResult, error) {
21
+ return WaitlistFollowUpResult{
22
+ Status: "queued",
23
+ TriggerID: input.TriggerID,
24
+ Email: input.Email,
25
+ Type: input.Type,
26
+ }, nil
27
+ }
@@ -0,0 +1,42 @@
1
+ package temporalapp
2
+
3
+ import (
4
+ "go.temporal.io/sdk/client"
5
+ "go.temporal.io/sdk/worker"
6
+ )
7
+
8
+ type WorkerConfig struct {
9
+ Address string
10
+ Namespace string
11
+ TaskQueue string
12
+ APIKey string
13
+ }
14
+
15
+ func StartWorker(cfg WorkerConfig) (func(), error) {
16
+ options := client.Options{
17
+ HostPort: cfg.Address,
18
+ Namespace: cfg.Namespace,
19
+ }
20
+ if cfg.APIKey != "" {
21
+ options.Credentials = client.NewAPIKeyStaticCredentials(cfg.APIKey)
22
+ }
23
+
24
+ temporalClient, err := client.Dial(options)
25
+ if err != nil {
26
+ return nil, err
27
+ }
28
+
29
+ temporalWorker := worker.New(temporalClient, cfg.TaskQueue, worker.Options{})
30
+ temporalWorker.RegisterWorkflow(WaitlistFollowUpWorkflow)
31
+ temporalWorker.RegisterActivity(&Activities{})
32
+
33
+ if err := temporalWorker.Start(); err != nil {
34
+ temporalClient.Close()
35
+ return nil, err
36
+ }
37
+
38
+ return func() {
39
+ temporalWorker.Stop()
40
+ temporalClient.Close()
41
+ }, nil
42
+ }
@@ -0,0 +1,18 @@
1
+ package temporalapp
2
+
3
+ import (
4
+ "time"
5
+
6
+ "go.temporal.io/sdk/workflow"
7
+ )
8
+
9
+ func WaitlistFollowUpWorkflow(ctx workflow.Context, input WaitlistFollowUpInput) (WaitlistFollowUpResult, error) {
10
+ options := workflow.ActivityOptions{
11
+ StartToCloseTimeout: time.Minute,
12
+ }
13
+ ctx = workflow.WithActivityOptions(ctx, options)
14
+
15
+ var result WaitlistFollowUpResult
16
+ err := workflow.ExecuteActivity(ctx, "RecordWaitlistFollowUp", input).Get(ctx, &result)
17
+ return result, err
18
+ }
@@ -1,63 +1,20 @@
1
- create table if not exists users (
1
+ create table if not exists waitlist_entries (
2
2
  id text primary key,
3
- username text not null unique,
4
- display_name text,
3
+ email text not null unique,
4
+ name text,
5
+ company text,
6
+ source text,
7
+ status text not null default 'joined',
5
8
  created_at timestamptz not null default now(),
6
9
  updated_at timestamptz not null default now()
7
10
  );
8
11
 
9
- create table if not exists conversations (
12
+ create table if not exists waitlist_triggers (
10
13
  id text primary key,
11
- title text,
12
- created_by_user_id text not null references users(id),
13
- deleted_at timestamptz,
14
- created_at timestamptz not null default now(),
15
- updated_at timestamptz not null default now()
16
- );
17
-
18
- create table if not exists conversation_participants (
19
- conversation_id text not null references conversations(id),
20
- user_id text not null references users(id),
21
- joined_at timestamptz not null default now(),
22
- primary key (conversation_id, user_id)
23
- );
24
-
25
- create table if not exists messages (
26
- id text primary key,
27
- conversation_id text not null references conversations(id),
28
- user_id text not null references users(id),
29
- body text not null,
30
- edited_at timestamptz,
31
- deleted_at timestamptz,
32
- created_at timestamptz not null default now(),
33
- updated_at timestamptz not null default now()
34
- );
35
-
36
- create table if not exists attachments (
37
- id text primary key,
38
- conversation_id text not null references conversations(id),
39
- message_id text references messages(id),
40
- uploaded_by_user_id text not null references users(id),
41
- storage_bucket text not null,
42
- storage_key text not null,
43
- content_type text not null,
44
- byte_size bigint not null,
45
- filename text not null,
46
- status text not null,
47
- deleted_at timestamptz,
48
- created_at timestamptz not null default now(),
49
- updated_at timestamptz not null default now()
50
- );
51
-
52
- create table if not exists webhook_events (
53
- id text primary key,
54
- provider text not null,
55
- external_event_id text not null,
56
- event_type text not null,
57
- signature_valid text not null,
58
- status text not null,
14
+ type text not null,
15
+ entry_id text references waitlist_entries(id),
16
+ status text not null default 'queued',
59
17
  payload_json text not null,
60
- received_at timestamptz not null default now(),
61
- processed_at timestamptz,
62
- unique (provider, external_event_id)
18
+ created_at timestamptz not null default now(),
19
+ processed_at timestamptz
63
20
  );
@@ -0,0 +1,2 @@
1
+ h1:D9GgvtEf0xxnRDaTb/C3cPtRbncVXsK6Ef0KYQuLMRw=
2
+ 0000_init.sql h1:dBpLGyoeZNRpjT6RvQXiqx3p2ICAChKy2jVqCIYuWQg=
@@ -3,9 +3,15 @@
3
3
  "private": true,
4
4
  "type": "module",
5
5
  "bin": {
6
- "svc-cloudrun": "./scripts/cloudrun/cli.ts"
6
+ "service": "./scripts/cloudrun/cli.ts"
7
+ },
8
+ "scripts": {
9
+ "service": "bun run ./scripts/cloudrun/cli.ts",
10
+ "auth": "bun run ./scripts/cloudrun/cli.ts auth",
11
+ "dashboards": "bun run ./scripts/cloudrun/cli.ts dashboards"
7
12
  },
8
13
  "dependencies": {
14
+ "@anmho/authctl": "0.1.1",
9
15
  "@clack/prompts": "^1.2.0",
10
16
  "@neondatabase/api-client": "^2.7.1"
11
17
  }
@@ -0,0 +1,93 @@
1
+ syntax = "proto3";
2
+
3
+ package waitlist.v1;
4
+
5
+ option go_package = "{{MODULE_PATH}}/gen/waitlist/v1;waitlistv1";
6
+
7
+ service WaitlistService {
8
+ rpc JoinWaitlist(JoinWaitlistRequest) returns (JoinWaitlistResponse);
9
+ rpc GetWaitlistEntry(GetWaitlistEntryRequest) returns (GetWaitlistEntryResponse);
10
+ rpc GetWaitlistEntryByEmail(GetWaitlistEntryByEmailRequest) returns (GetWaitlistEntryResponse);
11
+ rpc ListWaitlistEntries(ListWaitlistEntriesRequest) returns (ListWaitlistEntriesResponse);
12
+ rpc UpdateWaitlistEntry(UpdateWaitlistEntryRequest) returns (GetWaitlistEntryResponse);
13
+ rpc ExportWaitlistEntries(ExportWaitlistEntriesRequest) returns (ExportWaitlistEntriesResponse);
14
+ rpc RecordTrigger(RecordTriggerRequest) returns (RecordTriggerResponse);
15
+ }
16
+
17
+ message WaitlistEntry {
18
+ string id = 1;
19
+ string email = 2;
20
+ string name = 3;
21
+ string company = 4;
22
+ string source = 5;
23
+ string status = 6;
24
+ string created_at = 7;
25
+ string updated_at = 8;
26
+ }
27
+
28
+ message WaitlistTrigger {
29
+ string id = 1;
30
+ string type = 2;
31
+ string entry_id = 3;
32
+ string status = 4;
33
+ string payload_json = 5;
34
+ string created_at = 6;
35
+ string processed_at = 7;
36
+ }
37
+
38
+ message JoinWaitlistRequest {
39
+ string email = 1;
40
+ string name = 2;
41
+ string company = 3;
42
+ string source = 4;
43
+ }
44
+
45
+ message JoinWaitlistResponse {
46
+ WaitlistEntry entry = 1;
47
+ bool created = 2;
48
+ }
49
+
50
+ message GetWaitlistEntryRequest {
51
+ string entry_id = 1;
52
+ }
53
+
54
+ message GetWaitlistEntryByEmailRequest {
55
+ string email = 1;
56
+ }
57
+
58
+ message GetWaitlistEntryResponse {
59
+ WaitlistEntry entry = 1;
60
+ }
61
+
62
+ message ListWaitlistEntriesRequest {
63
+ string status = 1;
64
+ uint32 limit = 2;
65
+ }
66
+
67
+ message ListWaitlistEntriesResponse {
68
+ repeated WaitlistEntry entries = 1;
69
+ }
70
+
71
+ message UpdateWaitlistEntryRequest {
72
+ string entry_id = 1;
73
+ string status = 2;
74
+ }
75
+
76
+ message ExportWaitlistEntriesRequest {
77
+ string status = 1;
78
+ uint32 limit = 2;
79
+ }
80
+
81
+ message ExportWaitlistEntriesResponse {
82
+ string csv = 1;
83
+ }
84
+
85
+ message RecordTriggerRequest {
86
+ string type = 1;
87
+ string entry_id = 2;
88
+ string payload_json = 3;
89
+ }
90
+
91
+ message RecordTriggerResponse {
92
+ WaitlistTrigger trigger = 1;
93
+ }
@@ -1,19 +0,0 @@
1
- name: buf-publish
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
- paths:
8
- - "protos/**"
9
-
10
- jobs:
11
- publish:
12
- if: ${{ vars.BUF_MODULE != '' }}
13
- runs-on: ubuntu-latest
14
- steps:
15
- - uses: actions/checkout@v4
16
- - uses: bufbuild/buf-setup-action@v1
17
- - run: buf push
18
- env:
19
- BUF_TOKEN: ${{ secrets.BUF_TOKEN }}
@@ -1,26 +0,0 @@
1
- name: ci
2
-
3
- on:
4
- pull_request:
5
- push:
6
- branches:
7
- - main
8
-
9
- jobs:
10
- test:
11
- runs-on: ubuntu-latest
12
- steps:
13
- - uses: actions/checkout@v4
14
- - uses: actions/setup-go@v5
15
- with:
16
- go-version: '1.25.4'
17
- - uses: oven-sh/setup-bun@v2
18
- - uses: bufbuild/buf-setup-action@v1
19
- - name: Install proto plugins
20
- run: |
21
- go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.10
22
- go install connectrpc.com/connect/cmd/protoc-gen-connect-go@v1.19.1
23
- echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
24
- - run: bun gen
25
- - run: bun lint
26
- - run: bun test
@@ -1,22 +0,0 @@
1
- name: deploy
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
-
8
- jobs:
9
- deploy:
10
- runs-on: ubuntu-latest
11
- permissions:
12
- contents: read
13
- id-token: write
14
- steps:
15
- - uses: actions/checkout@v4
16
- - uses: oven-sh/setup-bun@v2
17
- - uses: google-github-actions/auth@v3
18
- with:
19
- workload_identity_provider: ${{ vars.GCP_WIF_PROVIDER }}
20
- service_account: ${{ vars.GCP_DEPLOYER_SERVICE_ACCOUNT }}
21
- - uses: google-github-actions/setup-gcloud@v2
22
- - run: bun run deploy -- --ci
@@ -1,23 +0,0 @@
1
- FROM golang:1.25.4 AS builder
2
-
3
- WORKDIR /app
4
-
5
- COPY go.mod ./
6
- COPY gen ./gen
7
- COPY internal ./internal
8
- COPY cmd ./cmd
9
-
10
- RUN go mod download
11
- RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /out/server ./cmd/server
12
-
13
- FROM gcr.io/distroless/base-debian12
14
-
15
- WORKDIR /app
16
-
17
- COPY --from=builder /out/server /app/server
18
-
19
- ENV PORT=8080
20
-
21
- EXPOSE 8080
22
-
23
- ENTRYPOINT ["/app/server"]
@@ -1,69 +0,0 @@
1
- # {{SERVICE_NAME}}
2
-
3
- Cloud Run API scaffold generated by `create-service`.
4
-
5
- ## What it includes
6
-
7
- - Chi HTTP routes
8
- - ConnectRPC handlers
9
- - real Cloud Run service manifest in [service.yaml](service.yaml)
10
- - Bun-based Cloud Run deploy config in [scripts/cloudrun/config.ts](scripts/cloudrun/config.ts)
11
- - Vault-backed Cloudflare DNS CRUD example
12
- - script-first deployment via Bun
13
-
14
- ## Prerequisites
15
-
16
- - Bun
17
- - Go 1.25+
18
- - `gcloud`
19
- - `gh`
20
- - `buf`
21
- - `protoc`
22
- - `protoc-gen-go`
23
- - `protoc-gen-connect-go`
24
-
25
- Install the Go protobuf plugins:
26
-
27
- ```bash
28
- go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.10
29
- go install connectrpc.com/connect/cmd/protoc-gen-connect-go@v1.19.1
30
- ```
31
-
32
- ## Commands
33
-
34
- ```bash
35
- bun dev
36
- bun gen
37
- bun lint
38
- bun test
39
- bun run deploy
40
- ```
41
-
42
- `bun test` uses Bun's test runner to invoke `go test ./...` so the command surface stays Bun-first.
43
-
44
- ## First deploy
45
-
46
- The runtime reads Vault AppRole credentials from Secret Manager. On the first deploy, seed those two secret values locally:
47
-
48
- ```bash
49
- export BOOTSTRAP_VAULT_ROLE_ID=...
50
- export BOOTSTRAP_VAULT_SECRET_ID=...
51
- bun run deploy
52
- ```
53
-
54
- `bun run deploy` will:
55
-
56
- 1. enable required GCP services
57
- 2. create runtime and deployer service accounts
58
- 3. create the Secret Manager secrets if missing
59
- 4. wire GitHub OIDC for `main` deploys
60
- 5. build the image and apply the Cloud Run manifest through the Bun deploy helper
61
-
62
- ## Local fallback
63
-
64
- For local development, you can skip Vault and provide the Cloudflare token directly:
65
-
66
- ```bash
67
- export CLOUDFLARE_API_TOKEN=...
68
- bun dev
69
- ```
@@ -1,10 +0,0 @@
1
- version: v2
2
- plugins:
3
- - local: protoc-gen-go
4
- out: gen
5
- opt:
6
- - paths=source_relative
7
- - local: protoc-gen-connect-go
8
- out: gen
9
- opt:
10
- - paths=source_relative
@@ -1,9 +0,0 @@
1
- version: v2
2
- modules:
3
- - path: protos
4
- lint:
5
- use:
6
- - STANDARD
7
- breaking:
8
- use:
9
- - FILE