create-svc 0.1.9 → 0.1.10

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 (91) hide show
  1. package/README.md +130 -11
  2. package/package.json +9 -4
  3. package/src/cli.test.ts +29 -8
  4. package/src/cli.ts +103 -70
  5. package/src/naming.test.ts +4 -2
  6. package/src/naming.ts +9 -1
  7. package/src/neon.ts +10 -8
  8. package/src/post-scaffold.ts +7 -28
  9. package/src/profiles.ts +28 -0
  10. package/src/scaffold.test.ts +126 -15
  11. package/src/scaffold.ts +94 -23
  12. package/src/vault.test.ts +33 -9
  13. package/src/vault.ts +4 -3
  14. package/templates/shared/README.md +135 -24
  15. package/templates/shared/docker-compose.yml +19 -0
  16. package/templates/shared/scripts/cloudrun/bootstrap.ts +15 -42
  17. package/templates/shared/scripts/cloudrun/cleanup.ts +17 -31
  18. package/templates/shared/scripts/cloudrun/config.ts +14 -19
  19. package/templates/shared/scripts/cloudrun/deploy.ts +19 -10
  20. package/templates/shared/scripts/cloudrun/integrations.ts +111 -0
  21. package/templates/shared/scripts/cloudrun/lib.ts +88 -112
  22. package/templates/shared/scripts/cloudrun/neon.ts +82 -13
  23. package/templates/shared/service.yaml +44 -1
  24. package/templates/variants/bun-connectrpc/Dockerfile +1 -0
  25. package/templates/variants/bun-connectrpc/Makefile +4 -1
  26. package/templates/variants/bun-connectrpc/gen/protos/chat/v1/chat_pb.ts +1078 -0
  27. package/templates/variants/bun-connectrpc/migrations/0000_init.sql +63 -0
  28. package/templates/variants/bun-connectrpc/package.json +17 -0
  29. package/templates/variants/bun-connectrpc/protos/chat/v1/chat.proto +228 -0
  30. package/templates/variants/bun-connectrpc/scripts/codegen.ts +31 -1
  31. package/templates/variants/bun-connectrpc/scripts/migrate.ts +46 -0
  32. package/templates/variants/bun-connectrpc/src/chat/service.ts +384 -0
  33. package/templates/variants/bun-connectrpc/src/chat/types.ts +142 -0
  34. package/templates/variants/bun-connectrpc/src/db/client.ts +15 -0
  35. package/templates/variants/bun-connectrpc/src/db/repository.ts +479 -0
  36. package/templates/variants/bun-connectrpc/src/db/schema.ts +75 -0
  37. package/templates/variants/bun-connectrpc/src/index.ts +294 -22
  38. package/templates/variants/bun-connectrpc/src/storage.ts +72 -0
  39. package/templates/variants/bun-connectrpc/src/webhooks.ts +35 -0
  40. package/templates/variants/bun-connectrpc/test/app.test.ts +14 -13
  41. package/templates/variants/bun-connectrpc/test/list-messages.integration.test.ts +182 -0
  42. package/templates/variants/bun-connectrpc/tsconfig.json +2 -1
  43. package/templates/variants/bun-hono/Makefile +4 -1
  44. package/templates/variants/bun-hono/migrations/0000_init.sql +63 -0
  45. package/templates/variants/bun-hono/package.json +13 -0
  46. package/templates/variants/bun-hono/scripts/migrate.ts +46 -0
  47. package/templates/variants/bun-hono/src/chat/service.ts +384 -0
  48. package/templates/variants/bun-hono/src/chat/types.ts +142 -0
  49. package/templates/variants/bun-hono/src/db/client.ts +15 -0
  50. package/templates/variants/bun-hono/src/db/repository.ts +479 -0
  51. package/templates/variants/bun-hono/src/db/schema.ts +75 -0
  52. package/templates/variants/bun-hono/src/index.ts +254 -8
  53. package/templates/variants/bun-hono/src/storage.ts +72 -0
  54. package/templates/variants/bun-hono/src/webhooks.ts +35 -0
  55. package/templates/variants/bun-hono/test/app.test.ts +60 -6
  56. package/templates/variants/bun-hono/test/list-messages.integration.test.ts +256 -0
  57. package/templates/variants/bun-hono/tsconfig.json +1 -0
  58. package/templates/variants/go-chi/Makefile +6 -2
  59. package/templates/variants/go-chi/buf.gen.yaml +2 -0
  60. package/templates/variants/go-chi/cmd/migrate/main.go +101 -0
  61. package/templates/variants/go-chi/cmd/server/main.go +16 -15
  62. package/templates/variants/go-chi/go.mod +3 -0
  63. package/templates/variants/go-chi/internal/app/service.go +763 -71
  64. package/templates/variants/go-chi/internal/config/config.go +22 -7
  65. package/templates/variants/go-chi/internal/httpapi/list_messages_integration_test.go +298 -0
  66. package/templates/variants/go-chi/internal/httpapi/routes.go +245 -43
  67. package/templates/variants/go-chi/migrations/0000_init.sql +63 -0
  68. package/templates/variants/go-chi/protos/chat/v1/chat.proto +219 -0
  69. package/templates/variants/go-chi/test/go.test.ts +4 -1
  70. package/templates/variants/go-connectrpc/Makefile +6 -2
  71. package/templates/variants/go-connectrpc/buf.gen.yaml +2 -0
  72. package/templates/variants/go-connectrpc/cmd/migrate/main.go +101 -0
  73. package/templates/variants/go-connectrpc/cmd/server/main.go +35 -11
  74. package/templates/variants/go-connectrpc/gen/chat/v1/chat.pb.go +2512 -0
  75. package/templates/variants/go-connectrpc/gen/chat/v1/chatv1connect/chat.connect.go +571 -0
  76. package/templates/variants/go-connectrpc/go.mod +4 -0
  77. package/templates/variants/go-connectrpc/internal/app/service.go +763 -71
  78. package/templates/variants/go-connectrpc/internal/config/config.go +22 -7
  79. package/templates/variants/go-connectrpc/internal/connectapi/handler.go +254 -42
  80. package/templates/variants/go-connectrpc/internal/connectapi/list_messages_integration_test.go +216 -0
  81. package/templates/variants/go-connectrpc/internal/httpapi/routes.go +41 -56
  82. package/templates/variants/go-connectrpc/migrations/0000_init.sql +63 -0
  83. package/templates/variants/go-connectrpc/protos/chat/v1/chat.proto +232 -0
  84. package/templates/shared/.env.example +0 -10
  85. package/templates/variants/go-chi/gen/dns/v1/dns.pb.go +0 -623
  86. package/templates/variants/go-chi/gen/dns/v1/dnsv1connect/dns.connect.go +0 -192
  87. package/templates/variants/go-chi/internal/connectapi/handler.go +0 -79
  88. package/templates/variants/go-chi/protos/dns/v1/dns.proto +0 -58
  89. package/templates/variants/go-connectrpc/gen/dns/v1/dns.pb.go +0 -623
  90. package/templates/variants/go-connectrpc/gen/dns/v1/dnsv1connect/dns.connect.go +0 -192
  91. package/templates/variants/go-connectrpc/protos/dns/v1/dns.proto +0 -58
@@ -3,6 +3,7 @@ package httpapi
3
3
  import (
4
4
  "encoding/json"
5
5
  "errors"
6
+ "io"
6
7
  "net/http"
7
8
  "strconv"
8
9
  "strings"
@@ -12,72 +13,47 @@ import (
12
13
  "{{MODULE_PATH}}/internal/app"
13
14
  )
14
15
 
15
- func RegisterRoutes(router chi.Router, service *app.DNSService) {
16
+ func RegisterRoutes(router chi.Router, service *app.ChatService) {
16
17
  router.Get("/healthz", func(w http.ResponseWriter, _ *http.Request) {
17
18
  writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
18
19
  })
19
-
20
- router.Route("/v1/dns/records", func(r chi.Router) {
21
- r.Get("/", func(w http.ResponseWriter, request *http.Request) {
22
- records, err := service.ListRecords(request.Context())
23
- if err != nil {
24
- writeError(w, err)
25
- return
26
- }
27
- writeJSON(w, http.StatusOK, map[string]any{"records": records})
28
- })
29
-
30
- r.Post("/", func(w http.ResponseWriter, request *http.Request) {
31
- var input app.CreateRecordInput
32
- if err := decodeJSON(request, &input); err != nil {
33
- writeError(w, err)
34
- return
35
- }
36
-
37
- record, err := service.CreateRecord(request.Context(), input)
38
- if err != nil {
39
- writeError(w, err)
40
- return
41
- }
42
- writeJSON(w, http.StatusCreated, map[string]any{"record": record})
20
+ router.Get("/readyz", func(w http.ResponseWriter, _ *http.Request) {
21
+ writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
22
+ })
23
+ router.Get("/", func(w http.ResponseWriter, _ *http.Request) {
24
+ writeJSON(w, http.StatusOK, map[string]string{
25
+ "service": "{{SERVICE_NAME}}",
26
+ "domain": "chat",
27
+ "apiOrigin": "https://api.{{SERVICE_NAME}}.anmho.com",
43
28
  })
29
+ })
44
30
 
45
- r.Route("/{recordID}", func(r chi.Router) {
46
- r.Put("/", func(w http.ResponseWriter, request *http.Request) {
47
- var input app.UpdateRecordInput
48
- if err := decodeJSON(request, &input); err != nil {
49
- writeError(w, err)
50
- return
51
- }
52
-
53
- record, err := service.UpdateRecord(request.Context(), chi.URLParam(request, "recordID"), input)
54
- if err != nil {
55
- writeError(w, err)
56
- return
57
- }
58
- writeJSON(w, http.StatusOK, map[string]any{"record": record})
59
- })
31
+ router.Post("/webhooks/{provider}", func(w http.ResponseWriter, request *http.Request) {
32
+ rawBody, err := io.ReadAll(request.Body)
33
+ if err != nil {
34
+ writeError(w, err)
35
+ return
36
+ }
37
+ event, duplicate, err := service.ProcessWebhook(request.Context(), chi.URLParam(request, "provider"), request.Header, rawBody)
38
+ if err != nil {
39
+ writeError(w, err)
40
+ return
41
+ }
42
+ status := http.StatusAccepted
43
+ if duplicate {
44
+ status = http.StatusOK
45
+ }
46
+ writeJSON(w, status, map[string]any{"event": event, "duplicate": duplicate})
47
+ })
60
48
 
61
- r.Delete("/", func(w http.ResponseWriter, request *http.Request) {
62
- if err := service.DeleteRecord(request.Context(), chi.URLParam(request, "recordID")); err != nil {
63
- writeError(w, err)
64
- return
65
- }
66
- w.WriteHeader(http.StatusNoContent)
67
- })
49
+ router.Get("/webhooks/{provider}/health", func(w http.ResponseWriter, request *http.Request) {
50
+ writeJSON(w, http.StatusOK, map[string]string{
51
+ "status": "ok",
52
+ "provider": chi.URLParam(request, "provider"),
68
53
  })
69
54
  })
70
55
  }
71
56
 
72
- func decodeJSON(request *http.Request, out any) error {
73
- defer request.Body.Close()
74
-
75
- if err := json.NewDecoder(request.Body).Decode(out); err != nil {
76
- return err
77
- }
78
- return nil
79
- }
80
-
81
57
  func writeJSON(w http.ResponseWriter, status int, payload any) {
82
58
  w.Header().Set("Content-Type", "application/json")
83
59
  w.WriteHeader(status)
@@ -85,6 +61,15 @@ func writeJSON(w http.ResponseWriter, status int, payload any) {
85
61
  }
86
62
 
87
63
  func writeError(w http.ResponseWriter, err error) {
64
+ var appErr *app.AppError
65
+ if errors.As(err, &appErr) {
66
+ writeJSON(w, appErr.Status, map[string]string{
67
+ "error": appErr.Error(),
68
+ "code": appErr.Code,
69
+ })
70
+ return
71
+ }
72
+
88
73
  status := http.StatusInternalServerError
89
74
  if errors.Is(err, strconv.ErrSyntax) || strings.Contains(strings.ToLower(err.Error()), "json") {
90
75
  status = http.StatusBadRequest
@@ -0,0 +1,63 @@
1
+ create table if not exists users (
2
+ id text primary key,
3
+ username text not null unique,
4
+ display_name text,
5
+ created_at timestamptz not null default now(),
6
+ updated_at timestamptz not null default now()
7
+ );
8
+
9
+ create table if not exists conversations (
10
+ 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,
59
+ payload_json text not null,
60
+ received_at timestamptz not null default now(),
61
+ processed_at timestamptz,
62
+ unique (provider, external_event_id)
63
+ );
@@ -0,0 +1,232 @@
1
+ syntax = "proto3";
2
+
3
+ package chat.v1;
4
+
5
+ option go_package = "{{MODULE_PATH}}/gen/chat/v1;chatv1";
6
+
7
+ service ChatService {
8
+ rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
9
+ rpc GetUser(GetUserRequest) returns (GetUserResponse);
10
+ rpc GetUserByUsername(GetUserByUsernameRequest) returns (GetUserByUsernameResponse);
11
+ rpc CreateConversation(CreateConversationRequest) returns (CreateConversationResponse);
12
+ rpc GetConversation(GetConversationRequest) returns (GetConversationResponse);
13
+ rpc UpdateConversation(UpdateConversationRequest) returns (UpdateConversationResponse);
14
+ rpc DeleteConversation(DeleteConversationRequest) returns (DeleteConversationResponse);
15
+ rpc AddConversationParticipant(AddConversationParticipantRequest) returns (AddConversationParticipantResponse);
16
+ rpc RemoveConversationParticipant(RemoveConversationParticipantRequest) returns (RemoveConversationParticipantResponse);
17
+ rpc ListMessages(ListMessagesRequest) returns (ListMessagesResponse);
18
+ rpc CreateMessage(CreateMessageRequest) returns (CreateMessageResponse);
19
+ rpc UpdateMessage(UpdateMessageRequest) returns (UpdateMessageResponse);
20
+ rpc DeleteMessage(DeleteMessageRequest) returns (DeleteMessageResponse);
21
+ rpc CreateAttachmentUpload(CreateAttachmentUploadRequest) returns (CreateAttachmentUploadResponse);
22
+ rpc FinalizeAttachment(FinalizeAttachmentRequest) returns (FinalizeAttachmentResponse);
23
+ rpc GetAttachment(GetAttachmentRequest) returns (GetAttachmentResponse);
24
+ rpc DeleteAttachment(DeleteAttachmentRequest) returns (DeleteAttachmentResponse);
25
+ }
26
+
27
+ message User {
28
+ string id = 1;
29
+ string username = 2;
30
+ string display_name = 3;
31
+ string created_at = 4;
32
+ string updated_at = 5;
33
+ }
34
+
35
+ message Conversation {
36
+ string id = 1;
37
+ string title = 2;
38
+ string created_by_user_id = 3;
39
+ repeated User participants = 4;
40
+ string created_at = 5;
41
+ string updated_at = 6;
42
+ }
43
+
44
+ message Message {
45
+ string id = 1;
46
+ string conversation_id = 2;
47
+ string user_id = 3;
48
+ string body = 4;
49
+ string edited_at = 5;
50
+ string created_at = 6;
51
+ string updated_at = 7;
52
+ repeated MessageAttachment attachments = 8;
53
+ }
54
+
55
+ message MessageAttachment {
56
+ string id = 1;
57
+ string filename = 2;
58
+ string content_type = 3;
59
+ int64 byte_size = 4;
60
+ string status = 5;
61
+ string public_url = 6;
62
+ }
63
+
64
+ message Attachment {
65
+ string id = 1;
66
+ string conversation_id = 2;
67
+ string message_id = 3;
68
+ string uploaded_by_user_id = 4;
69
+ string storage_bucket = 5;
70
+ string storage_key = 6;
71
+ string content_type = 7;
72
+ int64 byte_size = 8;
73
+ string filename = 9;
74
+ string status = 10;
75
+ string public_url = 11;
76
+ string created_at = 12;
77
+ string updated_at = 13;
78
+ }
79
+
80
+ message UploadTarget {
81
+ string method = 1;
82
+ string url = 2;
83
+ map<string, string> headers = 3;
84
+ }
85
+
86
+ message CreateUserRequest {
87
+ string username = 1;
88
+ string display_name = 2;
89
+ }
90
+
91
+ message CreateUserResponse {
92
+ User user = 1;
93
+ }
94
+
95
+ message GetUserRequest {
96
+ string user_id = 1;
97
+ }
98
+
99
+ message GetUserResponse {
100
+ User user = 1;
101
+ }
102
+
103
+ message GetUserByUsernameRequest {
104
+ string username = 1;
105
+ }
106
+
107
+ message GetUserByUsernameResponse {
108
+ User user = 1;
109
+ }
110
+
111
+ message CreateConversationRequest {
112
+ string created_by_user_id = 1;
113
+ string title = 2;
114
+ repeated string participant_user_ids = 3;
115
+ }
116
+
117
+ message CreateConversationResponse {
118
+ Conversation conversation = 1;
119
+ }
120
+
121
+ message GetConversationRequest {
122
+ string conversation_id = 1;
123
+ }
124
+
125
+ message GetConversationResponse {
126
+ Conversation conversation = 1;
127
+ }
128
+
129
+ message UpdateConversationRequest {
130
+ string conversation_id = 1;
131
+ string title = 2;
132
+ }
133
+
134
+ message UpdateConversationResponse {
135
+ Conversation conversation = 1;
136
+ }
137
+
138
+ message DeleteConversationRequest {
139
+ string conversation_id = 1;
140
+ }
141
+
142
+ message DeleteConversationResponse {}
143
+
144
+ message AddConversationParticipantRequest {
145
+ string conversation_id = 1;
146
+ string user_id = 2;
147
+ }
148
+
149
+ message AddConversationParticipantResponse {
150
+ Conversation conversation = 1;
151
+ }
152
+
153
+ message RemoveConversationParticipantRequest {
154
+ string conversation_id = 1;
155
+ string user_id = 2;
156
+ }
157
+
158
+ message RemoveConversationParticipantResponse {}
159
+
160
+ message ListMessagesRequest {
161
+ string conversation_id = 1;
162
+ string cursor = 2;
163
+ int32 limit = 3;
164
+ }
165
+
166
+ message ListMessagesResponse {
167
+ repeated Message messages = 1;
168
+ string next_cursor = 2;
169
+ }
170
+
171
+ message CreateMessageRequest {
172
+ string conversation_id = 1;
173
+ string user_id = 2;
174
+ string body = 3;
175
+ }
176
+
177
+ message CreateMessageResponse {
178
+ Message message = 1;
179
+ }
180
+
181
+ message UpdateMessageRequest {
182
+ string conversation_id = 1;
183
+ string message_id = 2;
184
+ string body = 3;
185
+ }
186
+
187
+ message UpdateMessageResponse {
188
+ Message message = 1;
189
+ }
190
+
191
+ message DeleteMessageRequest {
192
+ string conversation_id = 1;
193
+ string message_id = 2;
194
+ }
195
+
196
+ message DeleteMessageResponse {}
197
+
198
+ message CreateAttachmentUploadRequest {
199
+ string conversation_id = 1;
200
+ string user_id = 2;
201
+ string filename = 3;
202
+ string content_type = 4;
203
+ int64 byte_size = 5;
204
+ }
205
+
206
+ message CreateAttachmentUploadResponse {
207
+ Attachment attachment = 1;
208
+ UploadTarget upload = 2;
209
+ }
210
+
211
+ message FinalizeAttachmentRequest {
212
+ string attachment_id = 1;
213
+ string message_id = 2;
214
+ }
215
+
216
+ message FinalizeAttachmentResponse {
217
+ Attachment attachment = 1;
218
+ }
219
+
220
+ message GetAttachmentRequest {
221
+ string attachment_id = 1;
222
+ }
223
+
224
+ message GetAttachmentResponse {
225
+ Attachment attachment = 1;
226
+ }
227
+
228
+ message DeleteAttachmentRequest {
229
+ string attachment_id = 1;
230
+ }
231
+
232
+ message DeleteAttachmentResponse {}
@@ -1,10 +0,0 @@
1
- # Stable repo-local settings for Neon admin key lookup via Vault.
2
- # Copy to .env.local and adjust as needed.
3
-
4
- VAULT_ADDR=https://vault.example.com
5
- VAULT_SECRET_MOUNT=secret
6
- VAULT_NEON_API_KEY_PATH=provider/neon-api-key
7
- VAULT_NEON_API_KEY_FIELD=value
8
-
9
- # Do not commit VAULT_TOKEN. Prefer `vault login`; the CLI will also use
10
- # VAULT_TOKEN_FILE or ~/.vault-token when available.