@wneng/create-keel 0.3.4 → 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.
- package/dist/index.js +211 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/templates/ci-gitee/files/PULL_REQUEST_TEMPLATE.md +13 -1
- package/src/templates/ci-gitee/files/pipeline.yml +71 -0
- package/src/templates/ci-github/files/PULL_REQUEST_TEMPLATE.md +13 -1
- package/src/templates/ci-github/files/ci.yml +169 -0
- package/src/templates/contracts-events/fragment.yaml +1 -1
- package/src/templates/contracts-rest-events/fragment.yaml +1 -1
- package/src/templates/db-go-elasticsearch/files/Makefile +18 -0
- package/src/templates/db-go-elasticsearch/files/apply_templates.go +80 -0
- package/src/templates/db-go-elasticsearch/files/db-README.md +57 -0
- package/src/templates/db-go-elasticsearch/files/go.mod +7 -0
- package/src/templates/db-go-elasticsearch/files/index-template-init.json +25 -0
- package/src/templates/db-go-elasticsearch/fragment.yaml +22 -0
- package/src/templates/db-go-migrate-mysql/files/000001_init.down.sql +3 -0
- package/src/templates/db-go-migrate-mysql/files/000001_init.up.sql +35 -0
- package/src/templates/db-go-migrate-mysql/files/Makefile +33 -0
- package/src/templates/db-go-migrate-mysql/files/db-README.md +77 -0
- package/src/templates/db-go-migrate-mysql/files/go.mod +8 -0
- package/src/templates/db-go-migrate-mysql/fragment.yaml +22 -0
- package/src/templates/db-go-migrate-postgres/files/000001_init.down.sql +3 -0
- package/src/templates/db-go-migrate-postgres/files/000001_init.up.sql +32 -0
- package/src/templates/db-go-migrate-postgres/files/Makefile +31 -0
- package/src/templates/db-go-migrate-postgres/files/db-README.md +71 -0
- package/src/templates/db-go-migrate-postgres/files/go.mod +8 -0
- package/src/templates/db-go-migrate-postgres/fragment.yaml +22 -0
- package/src/templates/db-java-elasticsearch/files/EsTemplateApplier.java +86 -0
- package/src/templates/db-java-elasticsearch/files/db-README.md +63 -0
- package/src/templates/db-java-elasticsearch/files/index-template-init.json +25 -0
- package/src/templates/db-java-elasticsearch/files/pom.xml +134 -0
- package/src/templates/db-java-elasticsearch/fragment.yaml +19 -0
- package/src/templates/db-java-flyway-mysql/files/V1__init.sql +44 -0
- package/src/templates/db-java-flyway-mysql/files/application.yaml +39 -0
- package/src/templates/db-java-flyway-mysql/files/db-README.md +102 -0
- package/src/templates/db-java-flyway-mysql/files/pom.xml +172 -0
- package/src/templates/db-java-flyway-mysql/fragment.yaml +19 -0
- package/src/templates/db-java-flyway-postgres/files/V1__init.sql +40 -0
- package/src/templates/db-java-flyway-postgres/files/application.yaml +37 -0
- package/src/templates/db-java-flyway-postgres/files/db-README.md +75 -0
- package/src/templates/db-java-flyway-postgres/files/pom.xml +166 -0
- package/src/templates/db-java-flyway-postgres/fragment.yaml +19 -0
- package/src/templates/db-node-elasticsearch/files/apply-templates.cjs +60 -0
- package/src/templates/db-node-elasticsearch/files/db-README.md +76 -0
- package/src/templates/db-node-elasticsearch/files/index-template-init.json +26 -0
- package/src/templates/db-node-elasticsearch/files/package.json +26 -0
- package/src/templates/db-node-elasticsearch/fragment.yaml +19 -0
- package/src/templates/db-node-knex-mysql/files/db-README.md +90 -0
- package/src/templates/db-node-knex-mysql/files/knexfile.cjs +72 -0
- package/src/templates/db-node-knex-mysql/files/migrations-init.cjs +42 -0
- package/src/templates/db-node-knex-mysql/files/package.json +31 -0
- package/src/templates/db-node-knex-mysql/files/seeds-dev-fixtures.cjs +38 -0
- package/src/templates/db-node-knex-mysql/files/seeds-prod-dictionaries.cjs +25 -0
- package/src/templates/db-node-knex-mysql/fragment.yaml +25 -0
- package/src/templates/db-node-knex-postgres/files/db-README.md +81 -0
- package/src/templates/db-node-knex-postgres/files/knexfile.cjs +67 -0
- package/src/templates/db-node-knex-postgres/files/migrations-init.cjs +42 -0
- package/src/templates/db-node-knex-postgres/files/package.json +31 -0
- package/src/templates/db-node-knex-postgres/files/seeds-dev-fixtures.cjs +36 -0
- package/src/templates/db-node-knex-postgres/files/seeds-prod-dictionaries.cjs +26 -0
- package/src/templates/db-node-knex-postgres/fragment.yaml +25 -0
- package/src/templates/db-python-alembic-mysql/files/0001_init.py +70 -0
- package/src/templates/db-python-alembic-mysql/files/alembic.ini +47 -0
- package/src/templates/db-python-alembic-mysql/files/db-README.md +87 -0
- package/src/templates/db-python-alembic-mysql/files/env.py +71 -0
- package/src/templates/db-python-alembic-mysql/files/pyproject.toml +52 -0
- package/src/templates/db-python-alembic-mysql/files/script.py.mako +26 -0
- package/src/templates/db-python-alembic-mysql/fragment.yaml +25 -0
- package/src/templates/db-python-alembic-postgres/files/0001_init.py +62 -0
- package/src/templates/db-python-alembic-postgres/files/alembic.ini +45 -0
- package/src/templates/db-python-alembic-postgres/files/db-README.md +70 -0
- package/src/templates/db-python-alembic-postgres/files/env.py +62 -0
- package/src/templates/db-python-alembic-postgres/files/pyproject.toml +52 -0
- package/src/templates/db-python-alembic-postgres/files/script.py.mako +25 -0
- package/src/templates/db-python-alembic-postgres/fragment.yaml +25 -0
- package/src/templates/db-python-elasticsearch/files/apply_templates.py +55 -0
- package/src/templates/db-python-elasticsearch/files/db-README.md +57 -0
- package/src/templates/db-python-elasticsearch/files/index-template-init.json +25 -0
- package/src/templates/db-python-elasticsearch/files/pyproject.toml +50 -0
- package/src/templates/db-python-elasticsearch/fragment.yaml +19 -0
- package/src/templates/docs-skeleton/files/governance-change-tiers.md +135 -0
- package/src/templates/docs-skeleton/files/governance-database.md +150 -0
- package/src/templates/docs-skeleton/fragment.yaml +6 -0
- package/src/templates/root-files/files/AGENTS.md +42 -7
- package/src/templates/server-node/files/_eslintrc.cjs +5 -1
- package/src/templates/server-python/files/README.md +75 -2
- package/src/templates/server-python/files/app-init.py +11 -1
- package/src/templates/server-python/files/config.py +40 -0
- package/src/templates/server-python/files/main.py +62 -0
- package/src/templates/server-python/files/pyproject.toml +14 -1
- package/src/templates/server-python/files/test_healthz.py +36 -0
- package/src/templates/server-python/fragment.yaml +10 -1
- package/src/templates/web-react/files/_eslintrc.cjs +5 -1
- package/src/templates/web-vue/files/_eslintrc.cjs +5 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
; Alembic configuration for <%= it.options.projectName %>-server (PostgreSQL).
|
|
2
|
+
;
|
|
3
|
+
; Connection string is read from DATABASE_URL at runtime (see env.py).
|
|
4
|
+
|
|
5
|
+
[alembic]
|
|
6
|
+
script_location = db/migrations
|
|
7
|
+
prepend_sys_path = .
|
|
8
|
+
version_path_separator = os
|
|
9
|
+
sqlalchemy.url =
|
|
10
|
+
|
|
11
|
+
output_encoding = utf-8
|
|
12
|
+
|
|
13
|
+
[loggers]
|
|
14
|
+
keys = root,sqlalchemy,alembic
|
|
15
|
+
|
|
16
|
+
[handlers]
|
|
17
|
+
keys = console
|
|
18
|
+
|
|
19
|
+
[formatters]
|
|
20
|
+
keys = generic
|
|
21
|
+
|
|
22
|
+
[logger_root]
|
|
23
|
+
level = WARN
|
|
24
|
+
handlers = console
|
|
25
|
+
qualname =
|
|
26
|
+
|
|
27
|
+
[logger_sqlalchemy]
|
|
28
|
+
level = WARN
|
|
29
|
+
handlers =
|
|
30
|
+
qualname = sqlalchemy.engine
|
|
31
|
+
|
|
32
|
+
[logger_alembic]
|
|
33
|
+
level = INFO
|
|
34
|
+
handlers =
|
|
35
|
+
qualname = alembic
|
|
36
|
+
|
|
37
|
+
[handler_console]
|
|
38
|
+
class = StreamHandler
|
|
39
|
+
args = (sys.stderr,)
|
|
40
|
+
level = NOTSET
|
|
41
|
+
formatter = generic
|
|
42
|
+
|
|
43
|
+
[formatter_generic]
|
|
44
|
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
45
|
+
datefmt = %Y-%m-%d %H:%M:%S
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# server/db/
|
|
2
|
+
|
|
3
|
+
数据库迁移、种子数据、本地数据库工具的入口。
|
|
4
|
+
|
|
5
|
+
> 跨项目的数据库归属规则(哪类 SQL 放哪个目录、与 `contracts/` 如何对齐)见 [`docs/governance/database.md`](../../docs/governance/database.md)。
|
|
6
|
+
|
|
7
|
+
## 工具与版本
|
|
8
|
+
|
|
9
|
+
| 项 | 值 |
|
|
10
|
+
|---|---|
|
|
11
|
+
| 数据库 | PostgreSQL 14+ |
|
|
12
|
+
| 迁移工具 | [Alembic](https://alembic.sqlalchemy.org) `1.13.3` |
|
|
13
|
+
| ORM | SQLAlchemy `2.0.36` |
|
|
14
|
+
| 驱动 | `psycopg[binary]` `3.2.3`(psycopg v3) |
|
|
15
|
+
|
|
16
|
+
工具版本在 `pyproject.toml` 钉死,并镜像到 `docs/03-工程规范与研发基础设施/tech-stack-server.md`。
|
|
17
|
+
|
|
18
|
+
## 目录布局
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
server/
|
|
22
|
+
├── pyproject.toml
|
|
23
|
+
├── alembic.ini
|
|
24
|
+
└── db/
|
|
25
|
+
├── README.md
|
|
26
|
+
└── migrations/
|
|
27
|
+
├── env.py
|
|
28
|
+
├── script.py.mako
|
|
29
|
+
└── versions/
|
|
30
|
+
└── <NNNN>_<description>.py
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 常用命令
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
alembic revision -m "create orders table" # 新建迁移
|
|
37
|
+
alembic revision --autogenerate -m "..." # 配合 SQLAlchemy 模型
|
|
38
|
+
alembic upgrade head # 升到最新
|
|
39
|
+
alembic downgrade -1 # 回滚一版
|
|
40
|
+
alembic current # 查看当前版本
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
环境变量 `DATABASE_URL`(默认 `postgresql+psycopg://postgres:@127.0.0.1:5432/<project>_dev`)。
|
|
44
|
+
|
|
45
|
+
## prod vs dev seeds
|
|
46
|
+
|
|
47
|
+
| 类型 | 位置 |
|
|
48
|
+
|---|---|
|
|
49
|
+
| 生产字典(必装) | 写进 `versions/` 的 revision 中(ON CONFLICT DO NOTHING) |
|
|
50
|
+
| 开发样例 | `server/db/seeds/dev/` 下手动执行 |
|
|
51
|
+
|
|
52
|
+
字典类生产种子真值在 `contracts/dictionaries/enums.yaml`;当前手动同步。
|
|
53
|
+
|
|
54
|
+
## 改动分级(参见 `docs/governance/change-tiers.md`)
|
|
55
|
+
|
|
56
|
+
| 改动 | Tier |
|
|
57
|
+
|---|---|
|
|
58
|
+
| 新建 revision 加表 / 加字段 | 3 |
|
|
59
|
+
| 修改既有 revision | **不允许** |
|
|
60
|
+
| 删字段 / 改类型(破坏性) | 4 |
|
|
61
|
+
| 改 dev seed | 1/2 |
|
|
62
|
+
| 改字典数据 | 3(同步 contracts/dictionaries/) |
|
|
63
|
+
| 升级 Alembic / SQLAlchemy / psycopg | 2,但同步改 tech-stack-server.md |
|
|
64
|
+
|
|
65
|
+
## CI 行为
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
alembic upgrade head
|
|
69
|
+
alembic downgrade base
|
|
70
|
+
```
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Alembic environment for PostgreSQL (psycopg v3).
|
|
2
|
+
|
|
3
|
+
Reads DATABASE_URL from env (or falls back to a localhost Postgres DSN);
|
|
4
|
+
imports the application's SQLAlchemy metadata for autogenerate support.
|
|
5
|
+
|
|
6
|
+
Edit `target_metadata` to point at your app's Base.metadata once you
|
|
7
|
+
introduce ORM models.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from logging.config import fileConfig
|
|
12
|
+
|
|
13
|
+
from alembic import context
|
|
14
|
+
from sqlalchemy import engine_from_config, pool
|
|
15
|
+
|
|
16
|
+
config = context.config
|
|
17
|
+
|
|
18
|
+
if config.config_file_name is not None:
|
|
19
|
+
fileConfig(config.config_file_name)
|
|
20
|
+
|
|
21
|
+
config.set_main_option(
|
|
22
|
+
"sqlalchemy.url",
|
|
23
|
+
os.environ.get(
|
|
24
|
+
"DATABASE_URL",
|
|
25
|
+
"postgresql+psycopg://postgres:@127.0.0.1:5432/" + "<%= it.options.projectName.replace(/-/g, '_') %>" + "_dev",
|
|
26
|
+
),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
target_metadata = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def run_migrations_offline() -> None:
|
|
33
|
+
url = config.get_main_option("sqlalchemy.url")
|
|
34
|
+
context.configure(
|
|
35
|
+
url=url,
|
|
36
|
+
target_metadata=target_metadata,
|
|
37
|
+
literal_binds=True,
|
|
38
|
+
dialect_opts={"paramstyle": "named"},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
with context.begin_transaction():
|
|
42
|
+
context.run_migrations()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def run_migrations_online() -> None:
|
|
46
|
+
connectable = engine_from_config(
|
|
47
|
+
config.get_section(config.config_ini_section, {}),
|
|
48
|
+
prefix="sqlalchemy.",
|
|
49
|
+
poolclass=pool.NullPool,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
with connectable.connect() as connection:
|
|
53
|
+
context.configure(connection=connection, target_metadata=target_metadata)
|
|
54
|
+
|
|
55
|
+
with context.begin_transaction():
|
|
56
|
+
context.run_migrations()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if context.is_offline_mode():
|
|
60
|
+
run_migrations_offline()
|
|
61
|
+
else:
|
|
62
|
+
run_migrations_online()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "<%= it.options.projectName %>-server"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
requires-python = ">=3.11"
|
|
5
|
+
dependencies = [
|
|
6
|
+
"fastapi==0.115.5",
|
|
7
|
+
"uvicorn[standard]==0.32.0",
|
|
8
|
+
"pydantic==2.9.2",
|
|
9
|
+
"pydantic-settings==2.6.1",
|
|
10
|
+
"alembic==1.13.3",
|
|
11
|
+
"sqlalchemy==2.0.36",
|
|
12
|
+
"psycopg[binary]==3.2.3",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
dev = [
|
|
17
|
+
"ruff>=0.6.8",
|
|
18
|
+
"mypy>=1.11.0",
|
|
19
|
+
"pytest>=8.3.0",
|
|
20
|
+
"pytest-asyncio>=0.24.0",
|
|
21
|
+
"httpx>=0.27.0",
|
|
22
|
+
"pip-audit>=2.7.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[tool.ruff]
|
|
26
|
+
line-length = 100
|
|
27
|
+
target-version = "py311"
|
|
28
|
+
extend-exclude = ["generated", ".venv", "db/migrations/versions"]
|
|
29
|
+
|
|
30
|
+
[tool.ruff.lint]
|
|
31
|
+
select = ["E", "F", "W", "I", "B", "UP", "N"]
|
|
32
|
+
ignore = []
|
|
33
|
+
|
|
34
|
+
[tool.ruff.format]
|
|
35
|
+
quote-style = "double"
|
|
36
|
+
indent-style = "space"
|
|
37
|
+
line-ending = "lf"
|
|
38
|
+
|
|
39
|
+
[tool.mypy]
|
|
40
|
+
python_version = "3.11"
|
|
41
|
+
strict_optional = true
|
|
42
|
+
disallow_untyped_defs = true
|
|
43
|
+
warn_unused_ignores = true
|
|
44
|
+
exclude = ["generated", ".venv", "db/migrations/versions"]
|
|
45
|
+
|
|
46
|
+
[tool.pytest.ini_options]
|
|
47
|
+
testpaths = ["tests"]
|
|
48
|
+
asyncio_mode = "auto"
|
|
49
|
+
|
|
50
|
+
[tool.setuptools.packages.find]
|
|
51
|
+
include = ["app*"]
|
|
52
|
+
exclude = ["tests*", "generated*", "db*"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""${message}
|
|
2
|
+
|
|
3
|
+
Revision ID: ${up_revision}
|
|
4
|
+
Revises: ${down_revision | comma,n}
|
|
5
|
+
Create Date: ${create_date}
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from typing import Sequence, Union
|
|
9
|
+
|
|
10
|
+
from alembic import op
|
|
11
|
+
import sqlalchemy as sa
|
|
12
|
+
${imports if imports else ""}
|
|
13
|
+
|
|
14
|
+
revision: str = ${repr(up_revision)}
|
|
15
|
+
down_revision: Union[str, None] = ${repr(down_revision)}
|
|
16
|
+
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
|
17
|
+
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def upgrade() -> None:
|
|
21
|
+
${upgrades if upgrades else "pass"}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def downgrade() -> None:
|
|
25
|
+
${downgrades if downgrades else "pass"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: db-python-alembic-postgres
|
|
2
|
+
version: 1.0.0
|
|
3
|
+
appliesWhen:
|
|
4
|
+
backend: python
|
|
5
|
+
database: postgres
|
|
6
|
+
priority: 40
|
|
7
|
+
files:
|
|
8
|
+
- from: files/pyproject.toml
|
|
9
|
+
to: server/pyproject.toml
|
|
10
|
+
render: true
|
|
11
|
+
- from: files/alembic.ini
|
|
12
|
+
to: server/alembic.ini
|
|
13
|
+
render: true
|
|
14
|
+
- from: files/env.py
|
|
15
|
+
to: server/db/migrations/env.py
|
|
16
|
+
render: false
|
|
17
|
+
- from: files/script.py.mako
|
|
18
|
+
to: server/db/migrations/script.py.mako
|
|
19
|
+
render: false
|
|
20
|
+
- from: files/0001_init.py
|
|
21
|
+
to: server/db/migrations/versions/0001_init.py
|
|
22
|
+
render: false
|
|
23
|
+
- from: files/db-README.md
|
|
24
|
+
to: server/db/README.md
|
|
25
|
+
render: true
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Idempotent Elasticsearch index-template applier (Python backend).
|
|
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
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from elasticsearch import Elasticsearch
|
|
20
|
+
|
|
21
|
+
TEMPLATE_DIR = Path(__file__).parent / "index-templates"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def main() -> None:
|
|
25
|
+
url = os.environ.get("ELASTICSEARCH_URL", "http://127.0.0.1:9200")
|
|
26
|
+
username = os.environ.get("ELASTICSEARCH_USERNAME")
|
|
27
|
+
password = os.environ.get("ELASTICSEARCH_PASSWORD")
|
|
28
|
+
|
|
29
|
+
kwargs: dict[str, object] = {"hosts": [url]}
|
|
30
|
+
if username and password:
|
|
31
|
+
kwargs["basic_auth"] = (username, password)
|
|
32
|
+
|
|
33
|
+
client = Elasticsearch(**kwargs)
|
|
34
|
+
client.cluster.health(wait_for_status="yellow", timeout="30s")
|
|
35
|
+
|
|
36
|
+
files = sorted(p for p in TEMPLATE_DIR.glob("*.json"))
|
|
37
|
+
if not files:
|
|
38
|
+
print(f"no index templates found in {TEMPLATE_DIR}")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
for path in files:
|
|
42
|
+
body = json.loads(path.read_text(encoding="utf-8"))
|
|
43
|
+
name = path.stem # filename minus .json
|
|
44
|
+
client.indices.put_index_template(name=name, body=body)
|
|
45
|
+
print(f"✓ applied {name}")
|
|
46
|
+
|
|
47
|
+
print(f"done; {len(files)} template(s) applied to {url}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
try:
|
|
52
|
+
main()
|
|
53
|
+
except Exception as e: # noqa: BLE001
|
|
54
|
+
print(f"✗ failed: {e}", file=sys.stderr)
|
|
55
|
+
sys.exit(1)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# server/db/
|
|
2
|
+
|
|
3
|
+
Elasticsearch index-template 入口(Python 后端)。
|
|
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)。`apply_templates.py` 按文件名字典序遍历目录,对每个文件 PUT;ES 自身保证幂等。
|
|
12
|
+
|
|
13
|
+
## 工具与版本
|
|
14
|
+
|
|
15
|
+
| 项 | 值 |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Elasticsearch | 8.15+ |
|
|
18
|
+
| Python 客户端 | `elasticsearch` `8.15.1` |
|
|
19
|
+
|
|
20
|
+
## 目录布局
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
server/
|
|
24
|
+
├── pyproject.toml
|
|
25
|
+
└── db/
|
|
26
|
+
├── README.md
|
|
27
|
+
├── apply_templates.py # 幂等 applier
|
|
28
|
+
└── index-templates/
|
|
29
|
+
└── <utc-ts>_<description>.json
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 常用命令
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
python -m server.db.apply_templates
|
|
36
|
+
# 或
|
|
37
|
+
python server/db/apply_templates.py
|
|
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;脚本失败 = CI 红。
|
|
@@ -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,50 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "<%= it.options.projectName %>-server"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
requires-python = ">=3.11"
|
|
5
|
+
dependencies = [
|
|
6
|
+
"fastapi==0.115.5",
|
|
7
|
+
"uvicorn[standard]==0.32.0",
|
|
8
|
+
"pydantic==2.9.2",
|
|
9
|
+
"pydantic-settings==2.6.1",
|
|
10
|
+
"elasticsearch==8.15.1",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[project.optional-dependencies]
|
|
14
|
+
dev = [
|
|
15
|
+
"ruff>=0.6.8",
|
|
16
|
+
"mypy>=1.11.0",
|
|
17
|
+
"pytest>=8.3.0",
|
|
18
|
+
"pytest-asyncio>=0.24.0",
|
|
19
|
+
"httpx>=0.27.0",
|
|
20
|
+
"pip-audit>=2.7.0",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[tool.ruff]
|
|
24
|
+
line-length = 100
|
|
25
|
+
target-version = "py311"
|
|
26
|
+
extend-exclude = ["generated", ".venv"]
|
|
27
|
+
|
|
28
|
+
[tool.ruff.lint]
|
|
29
|
+
select = ["E", "F", "W", "I", "B", "UP", "N"]
|
|
30
|
+
ignore = []
|
|
31
|
+
|
|
32
|
+
[tool.ruff.format]
|
|
33
|
+
quote-style = "double"
|
|
34
|
+
indent-style = "space"
|
|
35
|
+
line-ending = "lf"
|
|
36
|
+
|
|
37
|
+
[tool.mypy]
|
|
38
|
+
python_version = "3.11"
|
|
39
|
+
strict_optional = true
|
|
40
|
+
disallow_untyped_defs = true
|
|
41
|
+
warn_unused_ignores = true
|
|
42
|
+
exclude = ["generated", ".venv"]
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
testpaths = ["tests"]
|
|
46
|
+
asyncio_mode = "auto"
|
|
47
|
+
|
|
48
|
+
[tool.setuptools.packages.find]
|
|
49
|
+
include = ["app*"]
|
|
50
|
+
exclude = ["tests*", "generated*", "db*"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: db-python-elasticsearch
|
|
2
|
+
version: 1.0.0
|
|
3
|
+
appliesWhen:
|
|
4
|
+
backend: python
|
|
5
|
+
database: elasticsearch
|
|
6
|
+
priority: 40
|
|
7
|
+
files:
|
|
8
|
+
- from: files/pyproject.toml
|
|
9
|
+
to: server/pyproject.toml
|
|
10
|
+
render: true
|
|
11
|
+
- from: files/apply_templates.py
|
|
12
|
+
to: server/db/apply_templates.py
|
|
13
|
+
render: false
|
|
14
|
+
- from: files/index-template-init.json
|
|
15
|
+
to: server/db/index-templates/20260101000000_init.json
|
|
16
|
+
render: false
|
|
17
|
+
- from: files/db-README.md
|
|
18
|
+
to: server/db/README.md
|
|
19
|
+
render: true
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
last-reviewed: 2026-05-16
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# 变更分级(Change Tiers)
|
|
6
|
+
|
|
7
|
+
> AGENTS.md §6 的细则。澄清"先文档先契约再 code"在不同场景下应该有多厚。
|
|
8
|
+
|
|
9
|
+
## 0. 为什么需要分级
|
|
10
|
+
|
|
11
|
+
`AGENTS.md` §2 的"Contract First"和 §6 的"先补契约和文档,再补实现"在小改动场景下容易被 AI 过度解读。常见误读:
|
|
12
|
+
|
|
13
|
+
- 改一个 typo → 想去补 PRD
|
|
14
|
+
- 修一个不涉及 API 的 bug → 想去更新 OpenAPI
|
|
15
|
+
- 加一行配置 → 想去写 ADR
|
|
16
|
+
|
|
17
|
+
这浪费时间、噪声 PR、让团队反感框架本身。
|
|
18
|
+
|
|
19
|
+
**正确的理解**:
|
|
20
|
+
- **A. 方案讨论必须有书面结论**(载体可大可小)—— 总成立
|
|
21
|
+
- **B. 契约必须在 code 之前冻结**—— 仅当变更触及契约时成立
|
|
22
|
+
|
|
23
|
+
A + B 不是绑死的。改一个不动 API 的 bug,B 不触发;改一个 typo,A 也只需要 PR title + 一行描述。
|
|
24
|
+
|
|
25
|
+
## 1. 六档 Tier
|
|
26
|
+
|
|
27
|
+
| Tier | 名称 | 典型场景 | A:方案记录的最小载体 | B:是否动契约 |
|
|
28
|
+
|---|---|---|---|---|
|
|
29
|
+
| **0** | trivial | 拼写、注释、格式化、补测试、依赖 patch 升级 | PR title + 1-2 行描述 | 不动 |
|
|
30
|
+
| **1** | bugfix | 修 NPE、错误的 SQL、UI 显示错位(无新行为) | PR 描述:**重现 + 根因 + 修复点 + 回归测试** | 不动 |
|
|
31
|
+
| **2** | small change | 加按钮 / 排序 / 导出列、调文案、加配置项 | PR 描述(含动机 + 方案 + 影响面),或 1 页 markdown 进 `docs/过程文档/drafts/` | 通常不动 |
|
|
32
|
+
| **3** | contract change | 加 API / 字段 / 错误码 / 事件 / 状态 | 简短设计章节(写在 `docs/04` 或 `docs/05` 的对应模块里)+ contracts 更新 + CHANGELOG | **必须**先冻结契约 |
|
|
33
|
+
| **4** | architecture / breaking | 新模块、跨服务、数据迁移、鉴权模型变化、删除字段 | PRD(若来自需求)→ ADR → 详细设计 → 契约 → code | **必须** ADR + 契约 + 迁移说明 |
|
|
34
|
+
| **5** | spike | 探索性、方案未明 | 在 `spike/*` 分支自由探索 | 合入 `main` 前补齐到对应 tier |
|
|
35
|
+
|
|
36
|
+
## 2. 判定决策树
|
|
37
|
+
|
|
38
|
+
按从严到宽顺序问自己:
|
|
39
|
+
|
|
40
|
+
1. **改动改了 `contracts/` 任何文件?** → 至少 Tier 3
|
|
41
|
+
2. **改动会改变 API / DB schema / 事件 / 鉴权 / 安全语义?**(即使现在 contracts 还没收录)→ Tier 3 或 4,**先把契约补上再继续**
|
|
42
|
+
3. **删除或不兼容地修改了已有契约字段?** → Tier 4,**必须** ADR
|
|
43
|
+
4. **跨服务、跨模块边界、跨执行环境(server↔web↔mobile)?** → Tier 4
|
|
44
|
+
5. **会影响 ops/ 或 deploy/ 实际拓扑(新加一个 service / 新开一个端口 / 改加密策略)?** → Tier 4
|
|
45
|
+
6. **是探索 / 调研,方案还不清楚?** → Tier 5(用 `spike/*` 分支)
|
|
46
|
+
7. **以上都不是,纯局部、可逆的改动?** → 按体量定 Tier 0/1/2
|
|
47
|
+
|
|
48
|
+
## 3. 反例(避免过度反应)
|
|
49
|
+
|
|
50
|
+
下面这些情况 **不要** 升级 tier:
|
|
51
|
+
|
|
52
|
+
| 情形 | 正确 tier | 错误反应 |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| 修 `web/src/utils/format-date.ts` 的 typo 注释 | 0 | 写 PRD |
|
|
55
|
+
| `GET /users/{id}` 的实现里漏判 null,500 改 404 | 1(行为符合契约) | 改 OpenAPI |
|
|
56
|
+
| 给已有列表加"最近 7 天"筛选选项(前端筛选,后端无变化) | 2 | 写 ADR |
|
|
57
|
+
| `package.json` 里 `lodash 4.17.20 → 4.17.21`(patch) | 0 | 走完整升级流程 |
|
|
58
|
+
| 改 README 一段错别字 | 0 | 同步多份治理文件 |
|
|
59
|
+
| 重构一个内部 helper 函数(不导出) | 0 或 1 | 写详细设计 |
|
|
60
|
+
| 加测试覆盖既有功能 | 0 | 写 PRD |
|
|
61
|
+
|
|
62
|
+
下面这些情况 **必须** 升级 tier:
|
|
63
|
+
|
|
64
|
+
| 情形 | 正确 tier | 误判风险 |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| 给 `GET /users/{id}` 加返回字段 `lastLoginAt` | 3 | 想成 Tier 2 / 文档"顺手补" |
|
|
67
|
+
| 给已有 List API 加 `?status=` 查询参数 | 3 | 想成 Tier 2 |
|
|
68
|
+
| 把鉴权从 cookie 改为 Bearer token | 4 | 想成 Tier 3 |
|
|
69
|
+
| 把 RBAC 从扁平改为支持继承 | 4 | 想成 Tier 3 |
|
|
70
|
+
| 引入新的消息队列 / 缓存层 / 第三方支付 | 4 | 想成 Tier 3 |
|
|
71
|
+
| 删除 `User.email` 字段 | 4 | 想成 Tier 3(删除 ≠ 兼容新增) |
|
|
72
|
+
| 升级 Spring Boot major 版本(3.x → 4.x) | 4 | 想成 Tier 0(只是版本号) |
|
|
73
|
+
|
|
74
|
+
## 4. PR 模板里的 Tier 框
|
|
75
|
+
|
|
76
|
+
PR 模板顶部要求作者明示 tier。Reviewer 第一眼看到 tier 后:
|
|
77
|
+
|
|
78
|
+
- **Tier 0/1/2**:着重看代码 / 测试 / 描述完整性,不要求 ADR / 契约改动
|
|
79
|
+
- **Tier 3**:必须看到 contracts 同步、CHANGELOG 同步、契约锚点引用
|
|
80
|
+
- **Tier 4**:必须看到 ADR 存在或新增、详细设计文档、迁移 / 回滚预案
|
|
81
|
+
- **Tier 5**:分支必须是 `spike/*`;如果想合入 `main`,作者必须在合入 PR 中标明落地到了哪个 tier
|
|
82
|
+
|
|
83
|
+
CI 不强制校验 tier 标签(语义判定是 reviewer 的责任);但 CI **会**校验:
|
|
84
|
+
|
|
85
|
+
- 改了 `contracts/` 的 PR 必须同改 `contracts/CHANGELOG.md`(已有规则)
|
|
86
|
+
- `spike/*` 分支不能直接合入 `main`(PR 模板 + CODEOWNERS)
|
|
87
|
+
|
|
88
|
+
## 5. AI 在 PR 描述中的引用义务
|
|
89
|
+
|
|
90
|
+
按 tier 升级:
|
|
91
|
+
|
|
92
|
+
| Tier | 引用义务 |
|
|
93
|
+
|---|---|
|
|
94
|
+
| 0/1/2 | 不强制;PR 描述本身就是方案记录 |
|
|
95
|
+
| 3 | 必须引用契约锚点(如 `contracts/openapi/api.yaml#/paths/~1users/post`) |
|
|
96
|
+
| 4 | 必须引用 ADR / 详细设计文档;契约锚点也必须 |
|
|
97
|
+
| 5 | 必须标明 spike 分支与最终目标 tier |
|
|
98
|
+
|
|
99
|
+
AI 不要在 Tier 0/1/2 主动制造文档噪音;reviewer 也不要因"AI 没引用契约"而打回 Tier 0/1/2 的 PR。
|
|
100
|
+
|
|
101
|
+
## 6. 与 spike 的关系
|
|
102
|
+
|
|
103
|
+
`spike/*` 分支是 Tier 5 的载体。它允许 AI 与开发者**暂时**绕过 Contract First:
|
|
104
|
+
|
|
105
|
+
1. 创建 `spike/<topic>` 分支
|
|
106
|
+
2. 自由写代码、跑实验、改方向
|
|
107
|
+
3. 结论清楚后:
|
|
108
|
+
- 不采纳 → 文档进 `docs/过程文档/spike-investigations/`,分支保留备查
|
|
109
|
+
- 采纳 → 反向走完 Tier 1-4 该补的内容(PRD / ADR / 详细设计 / 契约),再合入 `main`
|
|
110
|
+
|
|
111
|
+
Spike 不是"绕过框架的永久后门"。`spike/*` 不能直接合入 `main`;合入 PR 必须用 `feat(...)` / `feat(ai)(...)` 等正式 type 重写。
|
|
112
|
+
|
|
113
|
+
## 7. 何时升级 / 降级
|
|
114
|
+
|
|
115
|
+
升级(向上):开发过程中发现影响面比预想大 → 立即升 tier,停下补对应文档 / 契约。
|
|
116
|
+
|
|
117
|
+
降级(向下):通常**不允许**。一旦你声明 Tier 3+ 并写了契约 / ADR / 设计,就保留它。降级只在明确的"改动被裁掉了"场景下成立(例如:本来要加一个字段,最后发现可以用既有字段实现),此时把 tier 改成实际生效的层级,并在 PR 描述里写明。
|
|
118
|
+
|
|
119
|
+
## 8. 与契约 SemVer 的关系
|
|
120
|
+
|
|
121
|
+
| Tier | 契约动作 | SemVer 影响 |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| 0/1/2 | 不动 | 无 |
|
|
124
|
+
| 3(兼容新增) | `MINOR` bump | `0.x.y → 0.(x+1).0`(pre-1.0)/ `x.y.z → x.(y+1).0`(≥1.0) |
|
|
125
|
+
| 3(破坏性,但还在 0.x) | `MINOR` bump + CHANGELOG 标 BREAKING | 同上(pre-1.0 允许) |
|
|
126
|
+
| 4(破坏性,≥1.0) | `MAJOR` bump | `x.y.z → (x+1).0.0` |
|
|
127
|
+
|
|
128
|
+
完整版本策略 → [`../../contracts/README.md`](../../contracts/README.md)。
|
|
129
|
+
|
|
130
|
+
## 9. 关联文档
|
|
131
|
+
|
|
132
|
+
- [`../../AGENTS.md`](../../AGENTS.md) §6(变更分级摘要)
|
|
133
|
+
- [`git-workflow.md`](git-workflow.md)(PR 模板与 CODEOWNERS 完整规则)
|
|
134
|
+
- [`checklists.md`](checklists.md)(每个 tier 的可勾选清单)
|
|
135
|
+
- [`../../contracts/README.md`](../../contracts/README.md)(契约 SemVer + CHANGELOG)
|