@wneng/create-keel 0.3.6 → 0.4.0

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 (86) hide show
  1. package/dist/index.js +206 -13
  2. package/dist/index.js.map +1 -1
  3. package/package.json +1 -1
  4. package/src/templates/ci-gitee/files/pipeline.yml +62 -0
  5. package/src/templates/ci-github/files/ci.yml +160 -0
  6. package/src/templates/db-go-elasticsearch/files/Makefile +18 -0
  7. package/src/templates/db-go-elasticsearch/files/apply_templates.go +80 -0
  8. package/src/templates/db-go-elasticsearch/files/db-README.md +57 -0
  9. package/src/templates/db-go-elasticsearch/files/go.mod +7 -0
  10. package/src/templates/db-go-elasticsearch/files/index-template-init.json +25 -0
  11. package/src/templates/db-go-elasticsearch/fragment.yaml +22 -0
  12. package/src/templates/db-go-migrate-mysql/files/000001_init.down.sql +3 -0
  13. package/src/templates/db-go-migrate-mysql/files/000001_init.up.sql +35 -0
  14. package/src/templates/db-go-migrate-mysql/files/Makefile +33 -0
  15. package/src/templates/db-go-migrate-mysql/files/db-README.md +77 -0
  16. package/src/templates/db-go-migrate-mysql/files/go.mod +8 -0
  17. package/src/templates/db-go-migrate-mysql/fragment.yaml +22 -0
  18. package/src/templates/db-go-migrate-postgres/files/000001_init.down.sql +3 -0
  19. package/src/templates/db-go-migrate-postgres/files/000001_init.up.sql +32 -0
  20. package/src/templates/db-go-migrate-postgres/files/Makefile +31 -0
  21. package/src/templates/db-go-migrate-postgres/files/db-README.md +71 -0
  22. package/src/templates/db-go-migrate-postgres/files/go.mod +8 -0
  23. package/src/templates/db-go-migrate-postgres/fragment.yaml +22 -0
  24. package/src/templates/db-java-elasticsearch/files/EsTemplateApplier.java +86 -0
  25. package/src/templates/db-java-elasticsearch/files/db-README.md +63 -0
  26. package/src/templates/db-java-elasticsearch/files/index-template-init.json +25 -0
  27. package/src/templates/db-java-elasticsearch/files/pom.xml +134 -0
  28. package/src/templates/db-java-elasticsearch/fragment.yaml +19 -0
  29. package/src/templates/db-java-flyway-mysql/files/V1__init.sql +44 -0
  30. package/src/templates/db-java-flyway-mysql/files/application.yaml +39 -0
  31. package/src/templates/db-java-flyway-mysql/files/db-README.md +102 -0
  32. package/src/templates/db-java-flyway-mysql/files/pom.xml +172 -0
  33. package/src/templates/db-java-flyway-mysql/fragment.yaml +19 -0
  34. package/src/templates/db-java-flyway-postgres/files/V1__init.sql +40 -0
  35. package/src/templates/db-java-flyway-postgres/files/application.yaml +37 -0
  36. package/src/templates/db-java-flyway-postgres/files/db-README.md +75 -0
  37. package/src/templates/db-java-flyway-postgres/files/pom.xml +166 -0
  38. package/src/templates/db-java-flyway-postgres/fragment.yaml +19 -0
  39. package/src/templates/db-node-elasticsearch/files/apply-templates.cjs +60 -0
  40. package/src/templates/db-node-elasticsearch/files/db-README.md +76 -0
  41. package/src/templates/db-node-elasticsearch/files/index-template-init.json +26 -0
  42. package/src/templates/db-node-elasticsearch/files/package.json +26 -0
  43. package/src/templates/db-node-elasticsearch/fragment.yaml +19 -0
  44. package/src/templates/db-node-knex-mysql/files/db-README.md +90 -0
  45. package/src/templates/db-node-knex-mysql/files/knexfile.cjs +72 -0
  46. package/src/templates/db-node-knex-mysql/files/migrations-init.cjs +42 -0
  47. package/src/templates/db-node-knex-mysql/files/package.json +31 -0
  48. package/src/templates/db-node-knex-mysql/files/seeds-dev-fixtures.cjs +38 -0
  49. package/src/templates/db-node-knex-mysql/files/seeds-prod-dictionaries.cjs +25 -0
  50. package/src/templates/db-node-knex-mysql/fragment.yaml +25 -0
  51. package/src/templates/db-node-knex-postgres/files/db-README.md +81 -0
  52. package/src/templates/db-node-knex-postgres/files/knexfile.cjs +67 -0
  53. package/src/templates/db-node-knex-postgres/files/migrations-init.cjs +42 -0
  54. package/src/templates/db-node-knex-postgres/files/package.json +31 -0
  55. package/src/templates/db-node-knex-postgres/files/seeds-dev-fixtures.cjs +36 -0
  56. package/src/templates/db-node-knex-postgres/files/seeds-prod-dictionaries.cjs +26 -0
  57. package/src/templates/db-node-knex-postgres/fragment.yaml +25 -0
  58. package/src/templates/db-python-alembic-mysql/files/0001_init.py +70 -0
  59. package/src/templates/db-python-alembic-mysql/files/alembic.ini +47 -0
  60. package/src/templates/db-python-alembic-mysql/files/db-README.md +87 -0
  61. package/src/templates/db-python-alembic-mysql/files/env.py +71 -0
  62. package/src/templates/db-python-alembic-mysql/files/pyproject.toml +52 -0
  63. package/src/templates/db-python-alembic-mysql/files/script.py.mako +26 -0
  64. package/src/templates/db-python-alembic-mysql/fragment.yaml +25 -0
  65. package/src/templates/db-python-alembic-postgres/files/0001_init.py +62 -0
  66. package/src/templates/db-python-alembic-postgres/files/alembic.ini +45 -0
  67. package/src/templates/db-python-alembic-postgres/files/db-README.md +70 -0
  68. package/src/templates/db-python-alembic-postgres/files/env.py +62 -0
  69. package/src/templates/db-python-alembic-postgres/files/pyproject.toml +52 -0
  70. package/src/templates/db-python-alembic-postgres/files/script.py.mako +25 -0
  71. package/src/templates/db-python-alembic-postgres/fragment.yaml +25 -0
  72. package/src/templates/db-python-elasticsearch/files/apply_templates.py +55 -0
  73. package/src/templates/db-python-elasticsearch/files/db-README.md +57 -0
  74. package/src/templates/db-python-elasticsearch/files/index-template-init.json +25 -0
  75. package/src/templates/db-python-elasticsearch/files/pyproject.toml +50 -0
  76. package/src/templates/db-python-elasticsearch/fragment.yaml +19 -0
  77. package/src/templates/docs-skeleton/files/governance-database.md +150 -0
  78. package/src/templates/docs-skeleton/fragment.yaml +3 -0
  79. package/src/templates/root-files/files/AGENTS.md +6 -2
  80. package/src/templates/server-python/files/README.md +75 -2
  81. package/src/templates/server-python/files/app-init.py +11 -1
  82. package/src/templates/server-python/files/config.py +40 -0
  83. package/src/templates/server-python/files/main.py +62 -0
  84. package/src/templates/server-python/files/pyproject.toml +14 -1
  85. package/src/templates/server-python/files/test_healthz.py +36 -0
  86. package/src/templates/server-python/fragment.yaml +10 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wneng/create-keel",
3
- "version": "0.3.6",
3
+ "version": "0.4.0",
4
4
  "description": "Scaffolder for Contract First + Vibe Coding projects (keel conventions)",
5
5
  "keywords": [
6
6
  "scaffold",
@@ -136,6 +136,68 @@ jobs:
136
136
  script:
137
137
  - gitleaks detect --redact --no-git -v
138
138
 
139
+ <% if (it.options.database === 'mysql' || it.options.database === 'postgres') { %> db-migrate-smoke:
140
+ stage: test
141
+ <% if (it.options.database === 'postgres') { %> image: postgres:16-alpine
142
+ services:
143
+ - name: postgres:16-alpine
144
+ alias: postgres
145
+ env:
146
+ POSTGRES_PASSWORD: postgres
147
+ POSTGRES_DB: ci_smoke
148
+ <% } else { %> image: mysql:8
149
+ services:
150
+ - name: mysql:8
151
+ alias: mysql
152
+ env:
153
+ MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
154
+ MYSQL_DATABASE: ci_smoke
155
+ <% } %> script:
156
+ - cd server
157
+ <% if (it.options.backend === 'node') { %> - apk add --no-cache nodejs npm || true
158
+ - npm ci
159
+ <% if (it.options.database === 'postgres') { %> - DB_HOST=postgres DB_PORT=5432 DB_USER=postgres DB_PASSWORD=postgres DB_NAME=ci_smoke npm run db:migrate
160
+ - DB_HOST=postgres DB_PORT=5432 DB_USER=postgres DB_PASSWORD=postgres DB_NAME=ci_smoke npm run db:rollback
161
+ <% } else { %> - DB_HOST=mysql DB_PORT=3306 DB_USER=root DB_PASSWORD= DB_NAME=ci_smoke npm run db:migrate
162
+ - DB_HOST=mysql DB_PORT=3306 DB_USER=root DB_PASSWORD= DB_NAME=ci_smoke npm run db:rollback
163
+ <% } %><% } %><% if (it.options.backend === 'java') { %> - apk add --no-cache maven openjdk21 || true
164
+ <% if (it.options.database === 'postgres') { %> - SPRING_DATASOURCE_URL='jdbc:postgresql://postgres:5432/ci_smoke' SPRING_DATASOURCE_USERNAME=postgres SPRING_DATASOURCE_PASSWORD=postgres mvn -B flyway:migrate
165
+ - SPRING_DATASOURCE_URL='jdbc:postgresql://postgres:5432/ci_smoke' SPRING_DATASOURCE_USERNAME=postgres SPRING_DATASOURCE_PASSWORD=postgres mvn -B flyway:validate
166
+ <% } else { %> - SPRING_DATASOURCE_URL='jdbc:mysql://mysql:3306/ci_smoke?useSSL=false&serverTimezone=UTC' SPRING_DATASOURCE_USERNAME=root SPRING_DATASOURCE_PASSWORD= mvn -B flyway:migrate
167
+ - SPRING_DATASOURCE_URL='jdbc:mysql://mysql:3306/ci_smoke?useSSL=false&serverTimezone=UTC' SPRING_DATASOURCE_USERNAME=root SPRING_DATASOURCE_PASSWORD= mvn -B flyway:validate
168
+ <% } %><% } %><% if (it.options.backend === 'python') { %> - apk add --no-cache python3 py3-pip || true
169
+ - pip install -e .
170
+ <% if (it.options.database === 'postgres') { %> - DATABASE_URL='postgresql+psycopg://postgres:postgres@postgres:5432/ci_smoke' alembic upgrade head
171
+ - DATABASE_URL='postgresql+psycopg://postgres:postgres@postgres:5432/ci_smoke' alembic downgrade base
172
+ <% } else { %> - DATABASE_URL='mysql+pymysql://root:@mysql:3306/ci_smoke' alembic upgrade head
173
+ - DATABASE_URL='mysql+pymysql://root:@mysql:3306/ci_smoke' alembic downgrade base
174
+ <% } %><% } %><% if (it.options.backend === 'go') { %> - apk add --no-cache go || true
175
+ - go install -tags '<%= it.options.database %>' github.com/golang-migrate/migrate/v4/cmd/migrate@v4.18.1
176
+ <% if (it.options.database === 'postgres') { %> - DATABASE_URL='postgres://postgres:postgres@postgres:5432/ci_smoke?sslmode=disable' migrate -path db/migrations -database "$DATABASE_URL" up
177
+ - DATABASE_URL='postgres://postgres:postgres@postgres:5432/ci_smoke?sslmode=disable' migrate -path db/migrations -database "$DATABASE_URL" down -all
178
+ <% } else { %> - DATABASE_URL='mysql://root:@tcp(mysql:3306)/ci_smoke?multiStatements=true' migrate -path db/migrations -database "$DATABASE_URL" up
179
+ - DATABASE_URL='mysql://root:@tcp(mysql:3306)/ci_smoke?multiStatements=true' migrate -path db/migrations -database "$DATABASE_URL" down -all
180
+ <% } %><% } %><% } %>
181
+ <% if (it.options.database === 'elasticsearch') { %> db-es-template-apply:
182
+ stage: test
183
+ image: <%= it.options.backend === 'node' ? 'node:20-alpine' : it.options.backend === 'java' ? 'maven:3.9-eclipse-temurin-21' : it.options.backend === 'python' ? 'python:3.12-slim' : 'golang:1.22-alpine' %>
184
+ services:
185
+ - name: elasticsearch:8.15.1
186
+ alias: elasticsearch
187
+ env:
188
+ discovery.type: single-node
189
+ xpack.security.enabled: 'false'
190
+ ES_JAVA_OPTS: '-Xms512m -Xmx512m'
191
+ script:
192
+ - cd server
193
+ <% if (it.options.backend === 'node') { %> - npm ci
194
+ - ELASTICSEARCH_URL=http://elasticsearch:9200 npm run es:apply
195
+ <% } %><% if (it.options.backend === 'java') { %> - SPRING_ELASTICSEARCH_URIS=http://elasticsearch:9200 mvn -B test -Dtest=*ApplicationTests
196
+ <% } %><% if (it.options.backend === 'python') { %> - pip install -e .
197
+ - ELASTICSEARCH_URL=http://elasticsearch:9200 python db/apply_templates.py
198
+ <% } %><% if (it.options.backend === 'go') { %> - go mod download
199
+ - ELASTICSEARCH_URL=http://elasticsearch:9200 make es-apply
200
+ <% } %><% } %>
139
201
  <% if (it.options.backend === 'node' || it.options.frontend === 'react' || it.options.frontend === 'vue' || it.options.mobile === 'react-native' || it.options.miniapp === 'wechat') { %> npm-audit:
140
202
  stage: scan
141
203
  image: node:20-alpine
@@ -217,3 +217,163 @@ jobs:
217
217
  - run: cargo clippy --all-targets -- -D warnings
218
218
  - run: cargo test
219
219
  <% } %>
220
+ <% if (it.options.database === 'mysql' || it.options.database === 'postgres') { %> db-migrate-smoke:
221
+ runs-on: ubuntu-latest
222
+ defaults:
223
+ run:
224
+ working-directory: server
225
+ services:
226
+ <% if (it.options.database === 'postgres') { %> postgres:
227
+ image: postgres:16-alpine
228
+ env:
229
+ POSTGRES_PASSWORD: postgres
230
+ POSTGRES_DB: ci_smoke
231
+ ports: ['5432:5432']
232
+ options: >-
233
+ --health-cmd pg_isready
234
+ --health-interval 10s
235
+ --health-timeout 5s
236
+ --health-retries 5
237
+ <% } else { %> mysql:
238
+ image: mysql:8
239
+ env:
240
+ MYSQL_ROOT_PASSWORD: ''
241
+ MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
242
+ MYSQL_DATABASE: ci_smoke
243
+ ports: ['3306:3306']
244
+ options: >-
245
+ --health-cmd "mysqladmin ping -h localhost"
246
+ --health-interval 10s
247
+ --health-timeout 5s
248
+ --health-retries 5
249
+ <% } %> steps:
250
+ - uses: actions/checkout@v4
251
+ <% if (it.options.backend === 'node') { %> - uses: actions/setup-node@v4
252
+ with:
253
+ node-version: '20'
254
+ cache: npm
255
+ cache-dependency-path: server/package-lock.json
256
+ - run: npm ci
257
+ - name: migrate up
258
+ env:
259
+ <% if (it.options.database === 'postgres') { %> DB_HOST: 127.0.0.1
260
+ DB_PORT: '5432'
261
+ DB_USER: postgres
262
+ DB_PASSWORD: postgres
263
+ DB_NAME: ci_smoke
264
+ <% } else { %> DB_HOST: 127.0.0.1
265
+ DB_PORT: '3306'
266
+ DB_USER: root
267
+ DB_PASSWORD: ''
268
+ DB_NAME: ci_smoke
269
+ <% } %> run: npm run db:migrate
270
+ - name: migrate down
271
+ env:
272
+ <% if (it.options.database === 'postgres') { %> DB_HOST: 127.0.0.1
273
+ DB_PORT: '5432'
274
+ DB_USER: postgres
275
+ DB_PASSWORD: postgres
276
+ DB_NAME: ci_smoke
277
+ <% } else { %> DB_HOST: 127.0.0.1
278
+ DB_PORT: '3306'
279
+ DB_USER: root
280
+ DB_PASSWORD: ''
281
+ DB_NAME: ci_smoke
282
+ <% } %> run: npm run db:rollback
283
+ <% } %><% if (it.options.backend === 'java') { %> - uses: actions/setup-java@v4
284
+ with:
285
+ distribution: temurin
286
+ java-version: '21'
287
+ cache: maven
288
+ - name: flyway migrate + validate
289
+ env:
290
+ <% if (it.options.database === 'postgres') { %> SPRING_DATASOURCE_URL: jdbc:postgresql://127.0.0.1:5432/ci_smoke
291
+ SPRING_DATASOURCE_USERNAME: postgres
292
+ SPRING_DATASOURCE_PASSWORD: postgres
293
+ <% } else { %> SPRING_DATASOURCE_URL: jdbc:mysql://127.0.0.1:3306/ci_smoke?useSSL=false&serverTimezone=UTC
294
+ SPRING_DATASOURCE_USERNAME: root
295
+ SPRING_DATASOURCE_PASSWORD: ''
296
+ <% } %> run: |
297
+ mvn -B flyway:migrate
298
+ mvn -B flyway:validate
299
+ <% } %><% if (it.options.backend === 'python') { %> - uses: actions/setup-python@v5
300
+ with:
301
+ python-version: '3.12'
302
+ - run: pip install -e .
303
+ - name: alembic upgrade head + downgrade base
304
+ env:
305
+ <% if (it.options.database === 'postgres') { %> DATABASE_URL: postgresql+psycopg://postgres:postgres@127.0.0.1:5432/ci_smoke
306
+ <% } else { %> DATABASE_URL: mysql+pymysql://root:@127.0.0.1:3306/ci_smoke
307
+ <% } %> run: |
308
+ alembic upgrade head
309
+ alembic downgrade base
310
+ <% } %><% if (it.options.backend === 'go') { %> - uses: actions/setup-go@v5
311
+ with:
312
+ go-version: '1.22'
313
+ - name: install golang-migrate
314
+ run: |
315
+ go install -tags '<%= it.options.database %>' github.com/golang-migrate/migrate/v4/cmd/migrate@v4.18.1
316
+ - name: migrate up + down
317
+ env:
318
+ <% if (it.options.database === 'postgres') { %> DATABASE_URL: postgres://postgres:postgres@127.0.0.1:5432/ci_smoke?sslmode=disable
319
+ <% } else { %> DATABASE_URL: mysql://root:@tcp(127.0.0.1:3306)/ci_smoke?multiStatements=true
320
+ <% } %> run: |
321
+ migrate -path db/migrations -database "$DATABASE_URL" up
322
+ migrate -path db/migrations -database "$DATABASE_URL" down -all
323
+ <% } %><% } %>
324
+ <% if (it.options.database === 'elasticsearch') { %> db-es-template-apply:
325
+ runs-on: ubuntu-latest
326
+ defaults:
327
+ run:
328
+ working-directory: server
329
+ services:
330
+ elasticsearch:
331
+ image: elasticsearch:8.15.1
332
+ env:
333
+ discovery.type: single-node
334
+ xpack.security.enabled: 'false'
335
+ ES_JAVA_OPTS: '-Xms512m -Xmx512m'
336
+ ports: ['9200:9200']
337
+ options: >-
338
+ --health-cmd "curl -fsS http://localhost:9200/_cluster/health"
339
+ --health-interval 10s
340
+ --health-timeout 5s
341
+ --health-retries 12
342
+ steps:
343
+ - uses: actions/checkout@v4
344
+ <% if (it.options.backend === 'node') { %> - uses: actions/setup-node@v4
345
+ with:
346
+ node-version: '20'
347
+ cache: npm
348
+ cache-dependency-path: server/package-lock.json
349
+ - run: npm ci
350
+ - name: apply index templates
351
+ env:
352
+ ELASTICSEARCH_URL: http://127.0.0.1:9200
353
+ run: npm run es:apply
354
+ <% } %><% if (it.options.backend === 'java') { %> - uses: actions/setup-java@v4
355
+ with:
356
+ distribution: temurin
357
+ java-version: '21'
358
+ cache: maven
359
+ - name: spring boot starts and applies templates
360
+ env:
361
+ SPRING_ELASTICSEARCH_URIS: http://127.0.0.1:9200
362
+ run: mvn -B test -Dtest=*ApplicationTests
363
+ <% } %><% if (it.options.backend === 'python') { %> - uses: actions/setup-python@v5
364
+ with:
365
+ python-version: '3.12'
366
+ - run: pip install -e .
367
+ - name: apply index templates
368
+ env:
369
+ ELASTICSEARCH_URL: http://127.0.0.1:9200
370
+ run: python db/apply_templates.py
371
+ <% } %><% if (it.options.backend === 'go') { %> - uses: actions/setup-go@v5
372
+ with:
373
+ go-version: '1.22'
374
+ - run: go mod download
375
+ - name: apply index templates
376
+ env:
377
+ ELASTICSEARCH_URL: http://127.0.0.1:9200
378
+ run: make es-apply
379
+ <% } %><% } %>
@@ -0,0 +1,18 @@
1
+ .PHONY: build test lint run es-apply
2
+
3
+ ELASTICSEARCH_URL ?= http://127.0.0.1:9200
4
+
5
+ build:
6
+ go build -o ./bin/server ./cmd/server
7
+
8
+ run:
9
+ go run ./cmd/server
10
+
11
+ test:
12
+ go test ./...
13
+
14
+ lint:
15
+ golangci-lint run
16
+
17
+ es-apply:
18
+ go run ./cmd/apply-templates
@@ -0,0 +1,80 @@
1
+ // Idempotent Elasticsearch index-template applier (Go).
2
+ //
3
+ // ES 没有事务性 DDL;"migration" 退化为把 db/index-templates/*.json 全部 PUT 一次。
4
+ // ES 自身保证 PUT 同名 template 的幂等性。
5
+ //
6
+ // 改动 tier(参见 docs/governance/change-tiers.md):
7
+ // - 新增 template 文件:Tier 3
8
+ // - 修改既有 template 的 mapping:Tier 4(破坏性 - 可能 reindex)
9
+ // - 仅改 priority / 新增 alias:Tier 2
10
+ package main
11
+
12
+ import (
13
+ "bytes"
14
+ "context"
15
+ "fmt"
16
+ "log"
17
+ "os"
18
+ "path/filepath"
19
+ "sort"
20
+ "strings"
21
+ "time"
22
+
23
+ "github.com/elastic/go-elasticsearch/v8"
24
+ )
25
+
26
+ const templateDir = "db/index-templates"
27
+
28
+ func main() {
29
+ url := os.Getenv("ELASTICSEARCH_URL")
30
+ if url == "" {
31
+ url = "http://127.0.0.1:9200"
32
+ }
33
+
34
+ cfg := elasticsearch.Config{Addresses: []string{url}}
35
+ if u := os.Getenv("ELASTICSEARCH_USERNAME"); u != "" {
36
+ cfg.Username = u
37
+ cfg.Password = os.Getenv("ELASTICSEARCH_PASSWORD")
38
+ }
39
+
40
+ es, err := elasticsearch.NewClient(cfg)
41
+ if err != nil {
42
+ log.Fatalf("✗ es client: %v", err)
43
+ }
44
+
45
+ // 简易健康检查
46
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
47
+ defer cancel()
48
+ if _, err := es.Cluster.Health(es.Cluster.Health.WithContext(ctx)); err != nil {
49
+ log.Fatalf("✗ es health: %v", err)
50
+ }
51
+
52
+ files, err := filepath.Glob(filepath.Join(templateDir, "*.json"))
53
+ if err != nil {
54
+ log.Fatalf("✗ glob: %v", err)
55
+ }
56
+ sort.Strings(files)
57
+ if len(files) == 0 {
58
+ fmt.Println("no index templates found in", templateDir)
59
+ return
60
+ }
61
+
62
+ for _, path := range files {
63
+ body, err := os.ReadFile(path)
64
+ if err != nil {
65
+ log.Fatalf("✗ read %s: %v", path, err)
66
+ }
67
+ name := strings.TrimSuffix(filepath.Base(path), ".json")
68
+ res, err := es.Indices.PutIndexTemplate(name, bytes.NewReader(body))
69
+ if err != nil {
70
+ log.Fatalf("✗ apply %s: %v", name, err)
71
+ }
72
+ if res.IsError() {
73
+ log.Fatalf("✗ apply %s: %s", name, res.String())
74
+ }
75
+ _ = res.Body.Close()
76
+ fmt.Printf("✓ applied %s\n", name)
77
+ }
78
+
79
+ fmt.Printf("done; %d template(s) applied to %s\n", len(files), url)
80
+ }
@@ -0,0 +1,57 @@
1
+ # server/db/
2
+
3
+ Elasticsearch index-template 入口(Go 后端)。
4
+
5
+ > 跨项目的数据库归属规则见 [`docs/governance/database.md`](../../docs/governance/database.md)。
6
+
7
+ ## "Migration" 的语义
8
+
9
+ **Elasticsearch 没有事务性 DDL,本目录里的 JSON 不是迁移文件。**
10
+
11
+ 每个 `db/index-templates/*.json` 是一个 [Index Template v2](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-templates.html)。`cmd/apply-templates/main.go` 按文件名字典序遍历目录,对每个文件 PUT;ES 自身保证幂等。
12
+
13
+ ## 工具与版本
14
+
15
+ | 项 | 值 |
16
+ |---|---|
17
+ | Elasticsearch | 8.15+ |
18
+ | Go 客户端 | `github.com/elastic/go-elasticsearch/v8` `8.15.0` |
19
+
20
+ ## 目录布局
21
+
22
+ ```
23
+ server/
24
+ ├── go.mod
25
+ ├── Makefile # `make es-apply` 入口
26
+ ├── cmd/apply-templates/
27
+ │ └── main.go # 幂等 applier
28
+ └── db/
29
+ ├── README.md
30
+ └── index-templates/
31
+ └── <NNNNNN>_<description>.json
32
+ ```
33
+
34
+ ## 常用命令
35
+
36
+ ```bash
37
+ make es-apply # 把 db/index-templates/*.json 全部 PUT 一次
38
+ ```
39
+
40
+ 环境变量 `ELASTICSEARCH_URL`(默认 `http://127.0.0.1:9200`)、`ELASTICSEARCH_USERNAME` / `ELASTICSEARCH_PASSWORD`(可选)。
41
+
42
+ ## 改动分级(参见 `docs/governance/change-tiers.md`)
43
+
44
+ | 改动 | Tier |
45
+ |---|---|
46
+ | 新增 template 文件 | 3 |
47
+ | 修改既有 template 的 mapping 字段 | 4(破坏性 - 可能 reindex) |
48
+ | 仅改 priority / 新增 alias / 改 settings.refresh_interval | 2 |
49
+ | 删除 template | 4 |
50
+
51
+ ## 与 `contracts/asyncapi/` 的同步
52
+
53
+ 事件 ingest 的 schema 真值在 `contracts/asyncapi/asyncapi.yaml`;index-template 的 mapping 应当与之**对齐**。当前手动同步。
54
+
55
+ ## CI 行为
56
+
57
+ CI 起一个 ES 8 service container;`make es-apply` 失败 = CI 红。
@@ -0,0 +1,7 @@
1
+ module <%= it.options.projectName %>/server
2
+
3
+ go 1.22
4
+
5
+ require (
6
+ github.com/elastic/go-elasticsearch/v8 v8.15.0
7
+ )
@@ -0,0 +1,25 @@
1
+ {
2
+ "index_patterns": ["events-*"],
3
+ "priority": 100,
4
+ "template": {
5
+ "settings": {
6
+ "number_of_shards": 1,
7
+ "number_of_replicas": 1,
8
+ "refresh_interval": "5s"
9
+ },
10
+ "mappings": {
11
+ "properties": {
12
+ "@timestamp": { "type": "date" },
13
+ "tenant_id": { "type": "keyword" },
14
+ "event_type": { "type": "keyword" },
15
+ "user_id": { "type": "keyword" },
16
+ "payload": { "type": "object", "dynamic": true },
17
+ "trace_id": { "type": "keyword" }
18
+ }
19
+ }
20
+ },
21
+ "_meta": {
22
+ "description": "Sample event-stream template. Replace mappings with your domain fields.",
23
+ "managed_by": "@wneng/create-keel"
24
+ }
25
+ }
@@ -0,0 +1,22 @@
1
+ name: db-go-elasticsearch
2
+ version: 1.0.0
3
+ appliesWhen:
4
+ backend: go
5
+ database: elasticsearch
6
+ priority: 40
7
+ files:
8
+ - from: files/go.mod
9
+ to: server/go.mod
10
+ render: true
11
+ - from: files/Makefile
12
+ to: server/Makefile
13
+ render: true
14
+ - from: files/apply_templates.go
15
+ to: server/cmd/apply-templates/main.go
16
+ render: false
17
+ - from: files/index-template-init.json
18
+ to: server/db/index-templates/000001_init.json
19
+ render: false
20
+ - from: files/db-README.md
21
+ to: server/db/README.md
22
+ render: true
@@ -0,0 +1,3 @@
1
+ -- 000001_init.down.sql — 回滚 000001_init.up.sql
2
+ DROP TABLE IF EXISTS users;
3
+ DROP TABLE IF EXISTS roles;
@@ -0,0 +1,35 @@
1
+ -- 000001_init.up.sql — 初始化迁移
2
+ --
3
+ -- 命名规则:<NNNNNN>_<description>.up.sql / .down.sql。
4
+ -- 已合入 main 不可修改;改 schema 写新文件 NNNNNN+1。
5
+
6
+ CREATE TABLE IF NOT EXISTS roles (
7
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
8
+ code VARCHAR(64) NOT NULL,
9
+ name VARCHAR(128) NOT NULL,
10
+ description TEXT NULL,
11
+ created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
12
+ updated_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
13
+ PRIMARY KEY (id),
14
+ UNIQUE KEY uk_roles_code (code)
15
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
16
+
17
+ CREATE TABLE IF NOT EXISTS users (
18
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
19
+ email VARCHAR(255) NOT NULL,
20
+ password_hash VARCHAR(255) NOT NULL,
21
+ display_name VARCHAR(128) NULL,
22
+ role_id BIGINT UNSIGNED NOT NULL,
23
+ created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
24
+ updated_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
25
+ deleted_at TIMESTAMP(6) NULL,
26
+ PRIMARY KEY (id),
27
+ UNIQUE KEY uk_users_email (email),
28
+ KEY idx_users_role_id (role_id),
29
+ KEY idx_users_deleted_at (deleted_at),
30
+ CONSTRAINT fk_users_role FOREIGN KEY (role_id) REFERENCES roles (id)
31
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
32
+
33
+ INSERT IGNORE INTO roles (code, name, description) VALUES
34
+ ('admin', '系统管理员', '可执行所有管理操作'),
35
+ ('user', '普通用户', '默认注册角色');
@@ -0,0 +1,33 @@
1
+ .PHONY: build test lint run db-up db-down db-status db-create
2
+
3
+ # Default DSN: localhost MySQL with the project name as DB. Production
4
+ # overrides via DATABASE_URL env var.
5
+ DATABASE_URL ?= mysql://root:@tcp(127.0.0.1:3306)/<%= it.options.projectName %>_dev?multiStatements=true
6
+
7
+ # golang-migrate CLI: install with `go install github.com/golang-migrate/migrate/v4/cmd/migrate@v4.18.1`
8
+ MIGRATE := migrate -path db/migrations -database "$(DATABASE_URL)"
9
+
10
+ build:
11
+ go build -o ./bin/server ./cmd/server
12
+
13
+ run:
14
+ go run ./cmd/server
15
+
16
+ test:
17
+ go test ./...
18
+
19
+ lint:
20
+ golangci-lint run
21
+
22
+ db-up:
23
+ $(MIGRATE) up
24
+
25
+ db-down:
26
+ $(MIGRATE) down 1
27
+
28
+ db-status:
29
+ $(MIGRATE) version
30
+
31
+ # Usage: make db-create name=create_orders
32
+ db-create:
33
+ $(MIGRATE) create -ext sql -dir db/migrations -seq $(name)
@@ -0,0 +1,77 @@
1
+ # server/db/
2
+
3
+ 数据库迁移、种子数据、本地数据库工具的入口。
4
+
5
+ > 跨项目的数据库归属规则见 [`docs/governance/database.md`](../../docs/governance/database.md)。
6
+
7
+ ## 工具与版本
8
+
9
+ | 项 | 值 |
10
+ |---|---|
11
+ | 数据库 | MySQL 8 / MariaDB 10.6+ |
12
+ | 迁移工具 | [golang-migrate](https://github.com/golang-migrate/migrate) `v4.18.1` |
13
+ | 驱动 | `github.com/go-sql-driver/mysql` `v1.8.1` |
14
+
15
+ 需要本地安装 CLI:
16
+ ```bash
17
+ go install -tags 'mysql' github.com/golang-migrate/migrate/v4/cmd/migrate@v4.18.1
18
+ ```
19
+
20
+ 工具版本在 `go.mod` 钉死,并镜像到 `docs/03-工程规范与研发基础设施/tech-stack-server.md`。
21
+
22
+ ## 目录布局
23
+
24
+ ```
25
+ server/
26
+ ├── go.mod
27
+ ├── Makefile # db-up / db-down / db-create 入口
28
+ └── db/
29
+ ├── README.md
30
+ └── migrations/
31
+ ├── <NNNNNN>_<description>.up.sql
32
+ └── <NNNNNN>_<description>.down.sql
33
+ ```
34
+
35
+ ## 文件命名规则
36
+
37
+ - 迁移成对出现:`<NNNNNN>_<description>.up.sql` + `.down.sql`
38
+ - 6 位序号,`make db-create name=...` 自动递增
39
+ - 已合入 main 不可修改;改 schema 写新成对文件
40
+
41
+ ## 常用命令
42
+
43
+ ```bash
44
+ make db-create name=create_orders # 新建迁移成对文件
45
+ make db-up # 升到最新
46
+ make db-down # 回滚最近一条
47
+ make db-status # 查看当前版本
48
+ ```
49
+
50
+ 环境变量 `DATABASE_URL`(默认 `mysql://root:@tcp(127.0.0.1:3306)/<project>_dev?multiStatements=true`)。
51
+
52
+ ## prod vs dev seeds
53
+
54
+ | 类型 | 位置 |
55
+ |---|---|
56
+ | 生产字典(必装) | 写进迁移文件的 INSERT IGNORE 语句 |
57
+ | 开发样例 | `server/db/seeds/dev/` 下手动执行 |
58
+
59
+ 字典类生产种子真值在 `contracts/dictionaries/enums.yaml`;当前手动同步。
60
+
61
+ ## 改动分级(参见 `docs/governance/change-tiers.md`)
62
+
63
+ | 改动 | Tier |
64
+ |---|---|
65
+ | 新增迁移加表 / 加字段 | 3 |
66
+ | 修改既有迁移 | **不允许** |
67
+ | 删字段 / 改类型(破坏性) | 4 |
68
+ | 改 dev seed | 1/2 |
69
+ | 改字典数据 | 3(同步 contracts/dictionaries/) |
70
+ | 升级 golang-migrate / 驱动 | 2,但同步改 tech-stack-server.md |
71
+
72
+ ## CI 行为
73
+
74
+ ```bash
75
+ make db-up
76
+ make db-down
77
+ ```
@@ -0,0 +1,8 @@
1
+ module <%= it.options.projectName %>/server
2
+
3
+ go 1.22
4
+
5
+ require (
6
+ github.com/golang-migrate/migrate/v4 v4.18.1
7
+ github.com/go-sql-driver/mysql v1.8.1
8
+ )
@@ -0,0 +1,22 @@
1
+ name: db-go-migrate-mysql
2
+ version: 1.0.0
3
+ appliesWhen:
4
+ backend: go
5
+ database: mysql
6
+ priority: 40
7
+ files:
8
+ - from: files/go.mod
9
+ to: server/go.mod
10
+ render: true
11
+ - from: files/Makefile
12
+ to: server/Makefile
13
+ render: true
14
+ - from: files/000001_init.up.sql
15
+ to: server/db/migrations/000001_init.up.sql
16
+ render: false
17
+ - from: files/000001_init.down.sql
18
+ to: server/db/migrations/000001_init.down.sql
19
+ render: false
20
+ - from: files/db-README.md
21
+ to: server/db/README.md
22
+ render: true
@@ -0,0 +1,3 @@
1
+ -- 000001_init.down.sql — 回滚 000001_init.up.sql
2
+ DROP TABLE IF EXISTS users;
3
+ DROP TABLE IF EXISTS roles;