create-svc 0.1.9 → 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 (163) hide show
  1. package/README.md +138 -16
  2. package/bin/create-service.mjs +2 -0
  3. package/package.json +19 -11
  4. package/src/cli.test.ts +46 -7
  5. package/src/cli.ts +282 -84
  6. package/src/git-bootstrap.test.ts +40 -0
  7. package/src/git-bootstrap.ts +110 -0
  8. package/src/naming.test.ts +5 -2
  9. package/src/naming.ts +32 -1
  10. package/src/neon.ts +10 -8
  11. package/src/post-scaffold.test.ts +19 -0
  12. package/src/post-scaffold.ts +18 -26
  13. package/src/profiles.ts +25 -0
  14. package/src/scaffold.test.ts +320 -18
  15. package/src/scaffold.ts +154 -28
  16. package/src/vault.test.ts +94 -10
  17. package/src/vault.ts +81 -18
  18. package/templates/shared/.github/workflows/ci.yml +2 -1
  19. package/templates/shared/.github/workflows/deploy.yml +2 -0
  20. package/templates/shared/README.md +217 -29
  21. package/templates/shared/docker-compose.yml +19 -0
  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 +24 -42
  26. package/templates/shared/scripts/cloudrun/cleanup.ts +81 -35
  27. package/templates/shared/scripts/cloudrun/cli.ts +324 -7
  28. package/templates/shared/scripts/cloudrun/config.ts +21 -19
  29. package/templates/shared/scripts/cloudrun/deploy.ts +16 -11
  30. package/templates/shared/scripts/cloudrun/lib.ts +232 -123
  31. package/templates/shared/scripts/cloudrun/neon.ts +127 -13
  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 -1
  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 +397 -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/Dockerfile +1 -0
  53. package/templates/variants/bun-connectrpc/Makefile +17 -8
  54. package/templates/variants/bun-connectrpc/gen/protos/waitlist/v1/waitlist_pb.ts +424 -0
  55. package/templates/variants/bun-connectrpc/migrations/0000_init.sql +20 -0
  56. package/templates/variants/bun-connectrpc/package.json +25 -1
  57. package/templates/variants/bun-connectrpc/protos/waitlist/v1/waitlist.proto +91 -0
  58. package/templates/variants/bun-connectrpc/scripts/codegen.ts +31 -1
  59. package/templates/variants/bun-connectrpc/scripts/migrate.ts +49 -0
  60. package/templates/variants/bun-connectrpc/src/auth.ts +200 -0
  61. package/templates/variants/bun-connectrpc/src/db/client.ts +15 -0
  62. package/templates/variants/bun-connectrpc/src/db/repository.ts +126 -0
  63. package/templates/variants/bun-connectrpc/src/db/schema.ts +26 -0
  64. package/templates/variants/bun-connectrpc/src/index.ts +194 -22
  65. package/templates/variants/bun-connectrpc/src/temporal/activities.ts +14 -0
  66. package/templates/variants/bun-connectrpc/src/temporal/worker.ts +38 -0
  67. package/templates/variants/bun-connectrpc/src/temporal/workflows.ts +10 -0
  68. package/templates/variants/bun-connectrpc/src/waitlist/service.ts +172 -0
  69. package/templates/variants/bun-connectrpc/src/waitlist/types.ts +45 -0
  70. package/templates/variants/bun-connectrpc/test/app.test.ts +14 -13
  71. package/templates/variants/bun-connectrpc/test/waitlist.integration.test.ts +71 -0
  72. package/templates/variants/bun-connectrpc/tsconfig.json +2 -1
  73. package/templates/variants/bun-hono/Makefile +17 -8
  74. package/templates/variants/bun-hono/migrations/0000_init.sql +20 -0
  75. package/templates/variants/bun-hono/package.json +21 -1
  76. package/templates/variants/bun-hono/scripts/migrate.ts +49 -0
  77. package/templates/variants/bun-hono/src/auth.ts +181 -0
  78. package/templates/variants/bun-hono/src/db/client.ts +15 -0
  79. package/templates/variants/bun-hono/src/db/repository.ts +126 -0
  80. package/templates/variants/bun-hono/src/db/schema.ts +26 -0
  81. package/templates/variants/bun-hono/src/index.ts +141 -10
  82. package/templates/variants/bun-hono/src/temporal/activities.ts +14 -0
  83. package/templates/variants/bun-hono/src/temporal/worker.ts +38 -0
  84. package/templates/variants/bun-hono/src/temporal/workflows.ts +10 -0
  85. package/templates/variants/bun-hono/src/waitlist/service.ts +166 -0
  86. package/templates/variants/bun-hono/src/waitlist/types.ts +50 -0
  87. package/templates/variants/bun-hono/test/app.test.ts +90 -5
  88. package/templates/variants/bun-hono/test/waitlist.integration.test.ts +102 -0
  89. package/templates/variants/bun-hono/tsconfig.json +1 -0
  90. package/templates/variants/go-chi/Makefile +30 -10
  91. package/templates/variants/go-chi/atlas.hcl +8 -0
  92. package/templates/variants/go-chi/cmd/server/main.go +25 -13
  93. package/templates/variants/go-chi/go.mod +3 -2
  94. package/templates/variants/go-chi/internal/app/service.go +279 -70
  95. package/templates/variants/go-chi/internal/auth/middleware.go +289 -0
  96. package/templates/variants/go-chi/internal/auth/middleware_test.go +38 -0
  97. package/templates/variants/go-chi/internal/config/config.go +38 -7
  98. package/templates/variants/go-chi/internal/httpapi/routes.go +170 -47
  99. package/templates/variants/go-chi/internal/httpapi/waitlist_integration_test.go +199 -0
  100. package/templates/variants/go-chi/internal/temporal/activities.go +27 -0
  101. package/templates/variants/go-chi/internal/temporal/worker.go +42 -0
  102. package/templates/variants/go-chi/internal/temporal/workflows.go +18 -0
  103. package/templates/variants/go-chi/migrations/0000_init.sql +20 -0
  104. package/templates/variants/go-chi/migrations/atlas.sum +2 -0
  105. package/templates/variants/go-chi/package.json +7 -1
  106. package/templates/variants/go-chi/test/go.test.ts +4 -1
  107. package/templates/variants/go-connectrpc/Makefile +29 -8
  108. package/templates/variants/go-connectrpc/atlas.hcl +8 -0
  109. package/templates/variants/go-connectrpc/buf.gen.yaml +2 -0
  110. package/templates/variants/go-connectrpc/cmd/server/main.go +44 -9
  111. package/templates/variants/go-connectrpc/gen/waitlist/v1/waitlist.pb.go +960 -0
  112. package/templates/variants/go-connectrpc/gen/waitlist/v1/waitlistv1connect/waitlist.connect.go +283 -0
  113. package/templates/variants/go-connectrpc/go.mod +4 -0
  114. package/templates/variants/go-connectrpc/internal/app/service.go +279 -70
  115. package/templates/variants/go-connectrpc/internal/auth/middleware.go +289 -0
  116. package/templates/variants/go-connectrpc/internal/auth/middleware_test.go +38 -0
  117. package/templates/variants/go-connectrpc/internal/config/config.go +38 -7
  118. package/templates/variants/go-connectrpc/internal/connectapi/handler.go +129 -40
  119. package/templates/variants/go-connectrpc/internal/connectapi/waitlist_integration_test.go +122 -0
  120. package/templates/variants/go-connectrpc/internal/httpapi/routes.go +170 -47
  121. package/templates/variants/go-connectrpc/internal/temporal/activities.go +27 -0
  122. package/templates/variants/go-connectrpc/internal/temporal/worker.go +42 -0
  123. package/templates/variants/go-connectrpc/internal/temporal/workflows.go +18 -0
  124. package/templates/variants/go-connectrpc/migrations/0000_init.sql +20 -0
  125. package/templates/variants/go-connectrpc/migrations/atlas.sum +2 -0
  126. package/templates/variants/go-connectrpc/package.json +7 -1
  127. package/templates/variants/go-connectrpc/protos/waitlist/v1/waitlist.proto +93 -0
  128. package/templates/root/.github/workflows/buf-publish.yml +0 -19
  129. package/templates/root/.github/workflows/ci.yml +0 -26
  130. package/templates/root/.github/workflows/deploy.yml +0 -22
  131. package/templates/root/Dockerfile +0 -23
  132. package/templates/root/README.md +0 -69
  133. package/templates/root/buf.gen.yaml +0 -10
  134. package/templates/root/buf.yaml +0 -9
  135. package/templates/root/cmd/server/main.go +0 -44
  136. package/templates/root/gen/dns/v1/dns.pb.go +0 -623
  137. package/templates/root/gen/dns/v1/dnsv1connect/dns.connect.go +0 -192
  138. package/templates/root/go.mod +0 -10
  139. package/templates/root/internal/app/service.go +0 -152
  140. package/templates/root/internal/app/token_source.go +0 -50
  141. package/templates/root/internal/cloudflare/client.go +0 -160
  142. package/templates/root/internal/config/config.go +0 -55
  143. package/templates/root/internal/connectapi/handler.go +0 -79
  144. package/templates/root/internal/httpapi/routes.go +0 -93
  145. package/templates/root/internal/vault/client.go +0 -148
  146. package/templates/root/package.json +0 -12
  147. package/templates/root/protos/dns/v1/dns.proto +0 -58
  148. package/templates/root/scripts/cloudrun/bootstrap.ts +0 -65
  149. package/templates/root/scripts/cloudrun/config.ts +0 -50
  150. package/templates/root/scripts/cloudrun/deploy.ts +0 -41
  151. package/templates/root/scripts/cloudrun/lib.ts +0 -244
  152. package/templates/root/service.yaml +0 -50
  153. package/templates/root/test/go.test.ts +0 -19
  154. package/templates/shared/.env.example +0 -10
  155. package/templates/variants/go-chi/buf.gen.yaml +0 -10
  156. package/templates/variants/go-chi/buf.yaml +0 -9
  157. package/templates/variants/go-chi/gen/dns/v1/dns.pb.go +0 -623
  158. package/templates/variants/go-chi/gen/dns/v1/dnsv1connect/dns.connect.go +0 -192
  159. package/templates/variants/go-chi/internal/connectapi/handler.go +0 -79
  160. package/templates/variants/go-chi/protos/dns/v1/dns.proto +0 -58
  161. package/templates/variants/go-connectrpc/gen/dns/v1/dns.pb.go +0 -623
  162. package/templates/variants/go-connectrpc/gen/dns/v1/dnsv1connect/dns.connect.go +0 -192
  163. package/templates/variants/go-connectrpc/protos/dns/v1/dns.proto +0 -58
@@ -1,192 +0,0 @@
1
- // Code generated by protoc-gen-connect-go. DO NOT EDIT.
2
- //
3
- // Source: dns/v1/dns.proto
4
-
5
- package dnsv1connect
6
-
7
- import (
8
- connect "connectrpc.com/connect"
9
- context "context"
10
- errors "errors"
11
- v1 "{{MODULE_PATH}}/gen/dns/v1"
12
- http "net/http"
13
- strings "strings"
14
- )
15
-
16
- // This is a compile-time assertion to ensure that this generated file and the connect package are
17
- // compatible. If you get a compiler error that this constant is not defined, this code was
18
- // generated with a version of connect newer than the one compiled into your binary. You can fix the
19
- // problem by either regenerating this code with an older version of connect or updating the connect
20
- // version compiled into your binary.
21
- const _ = connect.IsAtLeastVersion1_13_0
22
-
23
- const (
24
- // DNSServiceName is the fully-qualified name of the DNSService service.
25
- DNSServiceName = "dns.v1.DNSService"
26
- )
27
-
28
- // These constants are the fully-qualified names of the RPCs defined in this package. They're
29
- // exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
30
- //
31
- // Note that these are different from the fully-qualified method names used by
32
- // google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
33
- // reflection-formatted method names, remove the leading slash and convert the remaining slash to a
34
- // period.
35
- const (
36
- // DNSServiceListRecordsProcedure is the fully-qualified name of the DNSService's ListRecords RPC.
37
- DNSServiceListRecordsProcedure = "/dns.v1.DNSService/ListRecords"
38
- // DNSServiceCreateRecordProcedure is the fully-qualified name of the DNSService's CreateRecord RPC.
39
- DNSServiceCreateRecordProcedure = "/dns.v1.DNSService/CreateRecord"
40
- // DNSServiceUpdateRecordProcedure is the fully-qualified name of the DNSService's UpdateRecord RPC.
41
- DNSServiceUpdateRecordProcedure = "/dns.v1.DNSService/UpdateRecord"
42
- // DNSServiceDeleteRecordProcedure is the fully-qualified name of the DNSService's DeleteRecord RPC.
43
- DNSServiceDeleteRecordProcedure = "/dns.v1.DNSService/DeleteRecord"
44
- )
45
-
46
- // DNSServiceClient is a client for the dns.v1.DNSService service.
47
- type DNSServiceClient interface {
48
- ListRecords(context.Context, *connect.Request[v1.ListRecordsRequest]) (*connect.Response[v1.ListRecordsResponse], error)
49
- CreateRecord(context.Context, *connect.Request[v1.CreateRecordRequest]) (*connect.Response[v1.CreateRecordResponse], error)
50
- UpdateRecord(context.Context, *connect.Request[v1.UpdateRecordRequest]) (*connect.Response[v1.UpdateRecordResponse], error)
51
- DeleteRecord(context.Context, *connect.Request[v1.DeleteRecordRequest]) (*connect.Response[v1.DeleteRecordResponse], error)
52
- }
53
-
54
- // NewDNSServiceClient constructs a client for the dns.v1.DNSService service. By default, it uses
55
- // the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends
56
- // uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or
57
- // connect.WithGRPCWeb() options.
58
- //
59
- // The URL supplied here should be the base URL for the Connect or gRPC server (for example,
60
- // http://api.acme.com or https://acme.com/grpc).
61
- func NewDNSServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) DNSServiceClient {
62
- baseURL = strings.TrimRight(baseURL, "/")
63
- dNSServiceMethods := v1.File_dns_v1_dns_proto.Services().ByName("DNSService").Methods()
64
- return &dNSServiceClient{
65
- listRecords: connect.NewClient[v1.ListRecordsRequest, v1.ListRecordsResponse](
66
- httpClient,
67
- baseURL+DNSServiceListRecordsProcedure,
68
- connect.WithSchema(dNSServiceMethods.ByName("ListRecords")),
69
- connect.WithClientOptions(opts...),
70
- ),
71
- createRecord: connect.NewClient[v1.CreateRecordRequest, v1.CreateRecordResponse](
72
- httpClient,
73
- baseURL+DNSServiceCreateRecordProcedure,
74
- connect.WithSchema(dNSServiceMethods.ByName("CreateRecord")),
75
- connect.WithClientOptions(opts...),
76
- ),
77
- updateRecord: connect.NewClient[v1.UpdateRecordRequest, v1.UpdateRecordResponse](
78
- httpClient,
79
- baseURL+DNSServiceUpdateRecordProcedure,
80
- connect.WithSchema(dNSServiceMethods.ByName("UpdateRecord")),
81
- connect.WithClientOptions(opts...),
82
- ),
83
- deleteRecord: connect.NewClient[v1.DeleteRecordRequest, v1.DeleteRecordResponse](
84
- httpClient,
85
- baseURL+DNSServiceDeleteRecordProcedure,
86
- connect.WithSchema(dNSServiceMethods.ByName("DeleteRecord")),
87
- connect.WithClientOptions(opts...),
88
- ),
89
- }
90
- }
91
-
92
- // dNSServiceClient implements DNSServiceClient.
93
- type dNSServiceClient struct {
94
- listRecords *connect.Client[v1.ListRecordsRequest, v1.ListRecordsResponse]
95
- createRecord *connect.Client[v1.CreateRecordRequest, v1.CreateRecordResponse]
96
- updateRecord *connect.Client[v1.UpdateRecordRequest, v1.UpdateRecordResponse]
97
- deleteRecord *connect.Client[v1.DeleteRecordRequest, v1.DeleteRecordResponse]
98
- }
99
-
100
- // ListRecords calls dns.v1.DNSService.ListRecords.
101
- func (c *dNSServiceClient) ListRecords(ctx context.Context, req *connect.Request[v1.ListRecordsRequest]) (*connect.Response[v1.ListRecordsResponse], error) {
102
- return c.listRecords.CallUnary(ctx, req)
103
- }
104
-
105
- // CreateRecord calls dns.v1.DNSService.CreateRecord.
106
- func (c *dNSServiceClient) CreateRecord(ctx context.Context, req *connect.Request[v1.CreateRecordRequest]) (*connect.Response[v1.CreateRecordResponse], error) {
107
- return c.createRecord.CallUnary(ctx, req)
108
- }
109
-
110
- // UpdateRecord calls dns.v1.DNSService.UpdateRecord.
111
- func (c *dNSServiceClient) UpdateRecord(ctx context.Context, req *connect.Request[v1.UpdateRecordRequest]) (*connect.Response[v1.UpdateRecordResponse], error) {
112
- return c.updateRecord.CallUnary(ctx, req)
113
- }
114
-
115
- // DeleteRecord calls dns.v1.DNSService.DeleteRecord.
116
- func (c *dNSServiceClient) DeleteRecord(ctx context.Context, req *connect.Request[v1.DeleteRecordRequest]) (*connect.Response[v1.DeleteRecordResponse], error) {
117
- return c.deleteRecord.CallUnary(ctx, req)
118
- }
119
-
120
- // DNSServiceHandler is an implementation of the dns.v1.DNSService service.
121
- type DNSServiceHandler interface {
122
- ListRecords(context.Context, *connect.Request[v1.ListRecordsRequest]) (*connect.Response[v1.ListRecordsResponse], error)
123
- CreateRecord(context.Context, *connect.Request[v1.CreateRecordRequest]) (*connect.Response[v1.CreateRecordResponse], error)
124
- UpdateRecord(context.Context, *connect.Request[v1.UpdateRecordRequest]) (*connect.Response[v1.UpdateRecordResponse], error)
125
- DeleteRecord(context.Context, *connect.Request[v1.DeleteRecordRequest]) (*connect.Response[v1.DeleteRecordResponse], error)
126
- }
127
-
128
- // NewDNSServiceHandler builds an HTTP handler from the service implementation. It returns the path
129
- // on which to mount the handler and the handler itself.
130
- //
131
- // By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
132
- // and JSON codecs. They also support gzip compression.
133
- func NewDNSServiceHandler(svc DNSServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
134
- dNSServiceMethods := v1.File_dns_v1_dns_proto.Services().ByName("DNSService").Methods()
135
- dNSServiceListRecordsHandler := connect.NewUnaryHandler(
136
- DNSServiceListRecordsProcedure,
137
- svc.ListRecords,
138
- connect.WithSchema(dNSServiceMethods.ByName("ListRecords")),
139
- connect.WithHandlerOptions(opts...),
140
- )
141
- dNSServiceCreateRecordHandler := connect.NewUnaryHandler(
142
- DNSServiceCreateRecordProcedure,
143
- svc.CreateRecord,
144
- connect.WithSchema(dNSServiceMethods.ByName("CreateRecord")),
145
- connect.WithHandlerOptions(opts...),
146
- )
147
- dNSServiceUpdateRecordHandler := connect.NewUnaryHandler(
148
- DNSServiceUpdateRecordProcedure,
149
- svc.UpdateRecord,
150
- connect.WithSchema(dNSServiceMethods.ByName("UpdateRecord")),
151
- connect.WithHandlerOptions(opts...),
152
- )
153
- dNSServiceDeleteRecordHandler := connect.NewUnaryHandler(
154
- DNSServiceDeleteRecordProcedure,
155
- svc.DeleteRecord,
156
- connect.WithSchema(dNSServiceMethods.ByName("DeleteRecord")),
157
- connect.WithHandlerOptions(opts...),
158
- )
159
- return "/dns.v1.DNSService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
160
- switch r.URL.Path {
161
- case DNSServiceListRecordsProcedure:
162
- dNSServiceListRecordsHandler.ServeHTTP(w, r)
163
- case DNSServiceCreateRecordProcedure:
164
- dNSServiceCreateRecordHandler.ServeHTTP(w, r)
165
- case DNSServiceUpdateRecordProcedure:
166
- dNSServiceUpdateRecordHandler.ServeHTTP(w, r)
167
- case DNSServiceDeleteRecordProcedure:
168
- dNSServiceDeleteRecordHandler.ServeHTTP(w, r)
169
- default:
170
- http.NotFound(w, r)
171
- }
172
- })
173
- }
174
-
175
- // UnimplementedDNSServiceHandler returns CodeUnimplemented from all methods.
176
- type UnimplementedDNSServiceHandler struct{}
177
-
178
- func (UnimplementedDNSServiceHandler) ListRecords(context.Context, *connect.Request[v1.ListRecordsRequest]) (*connect.Response[v1.ListRecordsResponse], error) {
179
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("dns.v1.DNSService.ListRecords is not implemented"))
180
- }
181
-
182
- func (UnimplementedDNSServiceHandler) CreateRecord(context.Context, *connect.Request[v1.CreateRecordRequest]) (*connect.Response[v1.CreateRecordResponse], error) {
183
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("dns.v1.DNSService.CreateRecord is not implemented"))
184
- }
185
-
186
- func (UnimplementedDNSServiceHandler) UpdateRecord(context.Context, *connect.Request[v1.UpdateRecordRequest]) (*connect.Response[v1.UpdateRecordResponse], error) {
187
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("dns.v1.DNSService.UpdateRecord is not implemented"))
188
- }
189
-
190
- func (UnimplementedDNSServiceHandler) DeleteRecord(context.Context, *connect.Request[v1.DeleteRecordRequest]) (*connect.Response[v1.DeleteRecordResponse], error) {
191
- return nil, connect.NewError(connect.CodeUnimplemented, errors.New("dns.v1.DNSService.DeleteRecord is not implemented"))
192
- }
@@ -1,10 +0,0 @@
1
- module {{MODULE_PATH}}
2
-
3
- go 1.25.4
4
-
5
- require (
6
- connectrpc.com/connect v1.19.1
7
- github.com/go-chi/chi/v5 v5.2.2
8
- golang.org/x/net v0.42.0
9
- google.golang.org/protobuf v1.36.10
10
- )
@@ -1,152 +0,0 @@
1
- package app
2
-
3
- import (
4
- "context"
5
- "net/http"
6
- "time"
7
-
8
- "{{MODULE_PATH}}/internal/cloudflare"
9
- )
10
-
11
- type Record struct {
12
- ID string `json:"id"`
13
- Type string `json:"type"`
14
- Name string `json:"name"`
15
- Content string `json:"content"`
16
- TTL int `json:"ttl"`
17
- Proxied bool `json:"proxied"`
18
- }
19
-
20
- type CreateRecordInput struct {
21
- Type string `json:"type"`
22
- Name string `json:"name"`
23
- Content string `json:"content"`
24
- TTL int `json:"ttl"`
25
- Proxied bool `json:"proxied"`
26
- }
27
-
28
- type UpdateRecordInput struct {
29
- Type string `json:"type"`
30
- Name string `json:"name"`
31
- Content string `json:"content"`
32
- TTL int `json:"ttl"`
33
- Proxied bool `json:"proxied"`
34
- }
35
-
36
- type TokenSource interface {
37
- Token(context.Context) (string, error)
38
- }
39
-
40
- type DNSService struct {
41
- zoneID string
42
- apiBaseURL string
43
- tokenSource TokenSource
44
- httpClient *http.Client
45
- }
46
-
47
- func NewDNSService(zoneID string, apiBaseURL string, tokenSource TokenSource) *DNSService {
48
- return &DNSService{
49
- zoneID: zoneID,
50
- apiBaseURL: apiBaseURL,
51
- tokenSource: tokenSource,
52
- httpClient: &http.Client{
53
- Timeout: 20 * time.Second,
54
- },
55
- }
56
- }
57
-
58
- func (s *DNSService) ListRecords(ctx context.Context) ([]Record, error) {
59
- client, err := s.cloudflareClient(ctx)
60
- if err != nil {
61
- return nil, err
62
- }
63
-
64
- records, err := client.ListRecords(ctx, s.zoneID)
65
- if err != nil {
66
- return nil, err
67
- }
68
-
69
- out := make([]Record, 0, len(records))
70
- for _, record := range records {
71
- out = append(out, Record{
72
- ID: record.ID,
73
- Type: record.Type,
74
- Name: record.Name,
75
- Content: record.Content,
76
- TTL: record.TTL,
77
- Proxied: record.Proxied,
78
- })
79
- }
80
- return out, nil
81
- }
82
-
83
- func (s *DNSService) CreateRecord(ctx context.Context, input CreateRecordInput) (Record, error) {
84
- client, err := s.cloudflareClient(ctx)
85
- if err != nil {
86
- return Record{}, err
87
- }
88
-
89
- record, err := client.CreateRecord(ctx, s.zoneID, cloudflare.RecordInput{
90
- Type: input.Type,
91
- Name: input.Name,
92
- Content: input.Content,
93
- TTL: input.TTL,
94
- Proxied: input.Proxied,
95
- })
96
- if err != nil {
97
- return Record{}, err
98
- }
99
-
100
- return Record{
101
- ID: record.ID,
102
- Type: record.Type,
103
- Name: record.Name,
104
- Content: record.Content,
105
- TTL: record.TTL,
106
- Proxied: record.Proxied,
107
- }, nil
108
- }
109
-
110
- func (s *DNSService) UpdateRecord(ctx context.Context, id string, input UpdateRecordInput) (Record, error) {
111
- client, err := s.cloudflareClient(ctx)
112
- if err != nil {
113
- return Record{}, err
114
- }
115
-
116
- record, err := client.UpdateRecord(ctx, s.zoneID, id, cloudflare.RecordInput{
117
- Type: input.Type,
118
- Name: input.Name,
119
- Content: input.Content,
120
- TTL: input.TTL,
121
- Proxied: input.Proxied,
122
- })
123
- if err != nil {
124
- return Record{}, err
125
- }
126
-
127
- return Record{
128
- ID: record.ID,
129
- Type: record.Type,
130
- Name: record.Name,
131
- Content: record.Content,
132
- TTL: record.TTL,
133
- Proxied: record.Proxied,
134
- }, nil
135
- }
136
-
137
- func (s *DNSService) DeleteRecord(ctx context.Context, id string) error {
138
- client, err := s.cloudflareClient(ctx)
139
- if err != nil {
140
- return err
141
- }
142
- return client.DeleteRecord(ctx, s.zoneID, id)
143
- }
144
-
145
- func (s *DNSService) cloudflareClient(ctx context.Context) (*cloudflare.Client, error) {
146
- token, err := s.tokenSource.Token(ctx)
147
- if err != nil {
148
- return nil, err
149
- }
150
-
151
- return cloudflare.NewClient(s.apiBaseURL, token, s.httpClient), nil
152
- }
@@ -1,50 +0,0 @@
1
- package app
2
-
3
- import (
4
- "context"
5
- "os"
6
- "strings"
7
- "sync"
8
- )
9
-
10
- type SecretResolver interface {
11
- Get(ctx context.Context, path string, key string) (string, error)
12
- }
13
-
14
- type CloudflareTokenSource struct {
15
- resolver SecretResolver
16
- secretPath string
17
- secretKey string
18
-
19
- mu sync.Mutex
20
- cached string
21
- }
22
-
23
- func NewCloudflareTokenSource(resolver SecretResolver, secretPath string, secretKey string) *CloudflareTokenSource {
24
- return &CloudflareTokenSource{
25
- resolver: resolver,
26
- secretPath: secretPath,
27
- secretKey: secretKey,
28
- }
29
- }
30
-
31
- func (s *CloudflareTokenSource) Token(ctx context.Context) (string, error) {
32
- if token := strings.TrimSpace(os.Getenv("CLOUDFLARE_API_TOKEN")); token != "" {
33
- return token, nil
34
- }
35
-
36
- s.mu.Lock()
37
- defer s.mu.Unlock()
38
-
39
- if s.cached != "" {
40
- return s.cached, nil
41
- }
42
-
43
- token, err := s.resolver.Get(ctx, s.secretPath, s.secretKey)
44
- if err != nil {
45
- return "", err
46
- }
47
-
48
- s.cached = token
49
- return token, nil
50
- }
@@ -1,160 +0,0 @@
1
- package cloudflare
2
-
3
- import (
4
- "bytes"
5
- "context"
6
- "encoding/json"
7
- "fmt"
8
- "io"
9
- "net/http"
10
- "net/url"
11
- "strings"
12
- )
13
-
14
- type Client struct {
15
- baseURL string
16
- token string
17
- client *http.Client
18
- }
19
-
20
- type Record struct {
21
- ID string
22
- Type string
23
- Name string
24
- Content string
25
- TTL int
26
- Proxied bool
27
- }
28
-
29
- type RecordInput struct {
30
- Type string `json:"type"`
31
- Name string `json:"name"`
32
- Content string `json:"content"`
33
- TTL int `json:"ttl"`
34
- Proxied bool `json:"proxied"`
35
- }
36
-
37
- func NewClient(baseURL string, token string, client *http.Client) *Client {
38
- return &Client{
39
- baseURL: strings.TrimRight(baseURL, "/"),
40
- token: strings.TrimSpace(token),
41
- client: client,
42
- }
43
- }
44
-
45
- func (c *Client) ListRecords(ctx context.Context, zoneID string) ([]Record, error) {
46
- endpoint := fmt.Sprintf("%s/zones/%s/dns_records?per_page=200", c.baseURL, url.PathEscape(zoneID))
47
- var payload response[[]record]
48
- if err := c.doJSON(ctx, http.MethodGet, endpoint, nil, &payload); err != nil {
49
- return nil, err
50
- }
51
-
52
- out := make([]Record, 0, len(payload.Result))
53
- for _, record := range payload.Result {
54
- out = append(out, Record{
55
- ID: record.ID,
56
- Type: record.Type,
57
- Name: record.Name,
58
- Content: record.Content,
59
- TTL: record.TTL,
60
- Proxied: record.Proxied,
61
- })
62
- }
63
- return out, nil
64
- }
65
-
66
- func (c *Client) CreateRecord(ctx context.Context, zoneID string, input RecordInput) (Record, error) {
67
- endpoint := fmt.Sprintf("%s/zones/%s/dns_records", c.baseURL, url.PathEscape(zoneID))
68
- var payload response[record]
69
- if err := c.doJSON(ctx, http.MethodPost, endpoint, input, &payload); err != nil {
70
- return Record{}, err
71
- }
72
- return Record{
73
- ID: payload.Result.ID,
74
- Type: payload.Result.Type,
75
- Name: payload.Result.Name,
76
- Content: payload.Result.Content,
77
- TTL: payload.Result.TTL,
78
- Proxied: payload.Result.Proxied,
79
- }, nil
80
- }
81
-
82
- func (c *Client) UpdateRecord(ctx context.Context, zoneID string, recordID string, input RecordInput) (Record, error) {
83
- endpoint := fmt.Sprintf("%s/zones/%s/dns_records/%s", c.baseURL, url.PathEscape(zoneID), url.PathEscape(recordID))
84
- var payload response[record]
85
- if err := c.doJSON(ctx, http.MethodPut, endpoint, input, &payload); err != nil {
86
- return Record{}, err
87
- }
88
- return Record{
89
- ID: payload.Result.ID,
90
- Type: payload.Result.Type,
91
- Name: payload.Result.Name,
92
- Content: payload.Result.Content,
93
- TTL: payload.Result.TTL,
94
- Proxied: payload.Result.Proxied,
95
- }, nil
96
- }
97
-
98
- func (c *Client) DeleteRecord(ctx context.Context, zoneID string, recordID string) error {
99
- endpoint := fmt.Sprintf("%s/zones/%s/dns_records/%s", c.baseURL, url.PathEscape(zoneID), url.PathEscape(recordID))
100
- return c.doJSON(ctx, http.MethodDelete, endpoint, nil, nil)
101
- }
102
-
103
- func (c *Client) doJSON(ctx context.Context, method string, endpoint string, body any, out any) error {
104
- if c.token == "" {
105
- return fmt.Errorf("cloudflare token is empty")
106
- }
107
-
108
- var reader io.Reader
109
- if body != nil {
110
- payload, err := json.Marshal(body)
111
- if err != nil {
112
- return err
113
- }
114
- reader = bytes.NewReader(payload)
115
- }
116
-
117
- req, err := http.NewRequestWithContext(ctx, method, endpoint, reader)
118
- if err != nil {
119
- return err
120
- }
121
- req.Header.Set("Authorization", "Bearer "+c.token)
122
- req.Header.Set("Content-Type", "application/json")
123
-
124
- resp, err := c.client.Do(req)
125
- if err != nil {
126
- return err
127
- }
128
- defer resp.Body.Close()
129
-
130
- raw, err := io.ReadAll(resp.Body)
131
- if err != nil {
132
- return err
133
- }
134
-
135
- if resp.StatusCode < 200 || resp.StatusCode >= 300 {
136
- return fmt.Errorf("cloudflare %s %s failed: status=%d body=%s", method, endpoint, resp.StatusCode, strings.TrimSpace(string(raw)))
137
- }
138
-
139
- if out == nil {
140
- return nil
141
- }
142
- if err := json.Unmarshal(raw, out); err != nil {
143
- return err
144
- }
145
- return nil
146
- }
147
-
148
- type response[T any] struct {
149
- Success bool `json:"success"`
150
- Result T `json:"result"`
151
- }
152
-
153
- type record struct {
154
- ID string `json:"id"`
155
- Type string `json:"type"`
156
- Name string `json:"name"`
157
- Content string `json:"content"`
158
- TTL int `json:"ttl"`
159
- Proxied bool `json:"proxied"`
160
- }
@@ -1,55 +0,0 @@
1
- package config
2
-
3
- import (
4
- "fmt"
5
- "os"
6
- "strings"
7
- )
8
-
9
- type Config struct {
10
- Port string
11
- VaultAddr string
12
- VaultRoleIDFile string
13
- VaultSecretIDFile string
14
- VaultSecretPath string
15
- VaultSecretKey string
16
- CloudflareZoneID string
17
- CloudflareAPIBaseURL string
18
- }
19
-
20
- func Load() (Config, error) {
21
- cfg := Config{
22
- Port: envOr("PORT", "8080"),
23
- VaultAddr: strings.TrimSpace(os.Getenv("VAULT_ADDR")),
24
- VaultRoleIDFile: envOr("VAULT_ROLE_ID_FILE", "/var/run/secrets/vault-role-id/value"),
25
- VaultSecretIDFile: envOr("VAULT_SECRET_ID_FILE", "/var/run/secrets/vault-secret-id/value"),
26
- VaultSecretPath: strings.TrimSpace(os.Getenv("VAULT_SECRET_PATH")),
27
- VaultSecretKey: strings.TrimSpace(os.Getenv("VAULT_SECRET_KEY")),
28
- CloudflareZoneID: strings.TrimSpace(os.Getenv("CLOUDFLARE_ZONE_ID")),
29
- CloudflareAPIBaseURL: envOr("CLOUDFLARE_API_BASE_URL", "https://api.cloudflare.com/client/v4"),
30
- }
31
-
32
- if cfg.CloudflareZoneID == "" {
33
- return Config{}, fmt.Errorf("CLOUDFLARE_ZONE_ID is required")
34
- }
35
- if strings.TrimSpace(os.Getenv("CLOUDFLARE_API_TOKEN")) == "" {
36
- if cfg.VaultAddr == "" {
37
- return Config{}, fmt.Errorf("VAULT_ADDR is required when CLOUDFLARE_API_TOKEN is not set")
38
- }
39
- if cfg.VaultSecretPath == "" {
40
- return Config{}, fmt.Errorf("VAULT_SECRET_PATH is required when CLOUDFLARE_API_TOKEN is not set")
41
- }
42
- if cfg.VaultSecretKey == "" {
43
- return Config{}, fmt.Errorf("VAULT_SECRET_KEY is required when CLOUDFLARE_API_TOKEN is not set")
44
- }
45
- }
46
-
47
- return cfg, nil
48
- }
49
-
50
- func envOr(key string, fallback string) string {
51
- if value := strings.TrimSpace(os.Getenv(key)); value != "" {
52
- return value
53
- }
54
- return fallback
55
- }
@@ -1,79 +0,0 @@
1
- package connectapi
2
-
3
- import (
4
- "context"
5
- "net/http"
6
-
7
- "connectrpc.com/connect"
8
-
9
- dnsv1 "{{MODULE_PATH}}/gen/dns/v1"
10
- dnsv1connect "{{MODULE_PATH}}/gen/dns/v1/dnsv1connect"
11
- "{{MODULE_PATH}}/internal/app"
12
- )
13
-
14
- type Handler struct {
15
- service *app.DNSService
16
- }
17
-
18
- func NewHandler(service *app.DNSService) (string, http.Handler) {
19
- return dnsv1connect.NewDNSServiceHandler(&Handler{service: service})
20
- }
21
-
22
- func (h *Handler) ListRecords(ctx context.Context, _ *connect.Request[dnsv1.ListRecordsRequest]) (*connect.Response[dnsv1.ListRecordsResponse], error) {
23
- records, err := h.service.ListRecords(ctx)
24
- if err != nil {
25
- return nil, connect.NewError(connect.CodeInternal, err)
26
- }
27
-
28
- response := &dnsv1.ListRecordsResponse{Records: make([]*dnsv1.Record, 0, len(records))}
29
- for _, record := range records {
30
- response.Records = append(response.Records, toProtoRecord(record))
31
- }
32
- return connect.NewResponse(response), nil
33
- }
34
-
35
- func (h *Handler) CreateRecord(ctx context.Context, request *connect.Request[dnsv1.CreateRecordRequest]) (*connect.Response[dnsv1.CreateRecordResponse], error) {
36
- record, err := h.service.CreateRecord(ctx, app.CreateRecordInput{
37
- Type: request.Msg.GetType(),
38
- Name: request.Msg.GetName(),
39
- Content: request.Msg.GetContent(),
40
- TTL: int(request.Msg.GetTtl()),
41
- Proxied: request.Msg.GetProxied(),
42
- })
43
- if err != nil {
44
- return nil, connect.NewError(connect.CodeInternal, err)
45
- }
46
- return connect.NewResponse(&dnsv1.CreateRecordResponse{Record: toProtoRecord(record)}), nil
47
- }
48
-
49
- func (h *Handler) UpdateRecord(ctx context.Context, request *connect.Request[dnsv1.UpdateRecordRequest]) (*connect.Response[dnsv1.UpdateRecordResponse], error) {
50
- record, err := h.service.UpdateRecord(ctx, request.Msg.GetId(), app.UpdateRecordInput{
51
- Type: request.Msg.GetType(),
52
- Name: request.Msg.GetName(),
53
- Content: request.Msg.GetContent(),
54
- TTL: int(request.Msg.GetTtl()),
55
- Proxied: request.Msg.GetProxied(),
56
- })
57
- if err != nil {
58
- return nil, connect.NewError(connect.CodeInternal, err)
59
- }
60
- return connect.NewResponse(&dnsv1.UpdateRecordResponse{Record: toProtoRecord(record)}), nil
61
- }
62
-
63
- func (h *Handler) DeleteRecord(ctx context.Context, request *connect.Request[dnsv1.DeleteRecordRequest]) (*connect.Response[dnsv1.DeleteRecordResponse], error) {
64
- if err := h.service.DeleteRecord(ctx, request.Msg.GetId()); err != nil {
65
- return nil, connect.NewError(connect.CodeInternal, err)
66
- }
67
- return connect.NewResponse(&dnsv1.DeleteRecordResponse{}), nil
68
- }
69
-
70
- func toProtoRecord(record app.Record) *dnsv1.Record {
71
- return &dnsv1.Record{
72
- Id: record.ID,
73
- Type: record.Type,
74
- Name: record.Name,
75
- Content: record.Content,
76
- Ttl: int32(record.TTL),
77
- Proxied: record.Proxied,
78
- }
79
- }