database-analysis
Use this skill to analyse, audit, or modify database models, migrations, and CRUD helpers. Reference: `portal/models.py`, `portal/database.py`, `alembic/versions/`.
来源信息
- 仓库
- fossasia/eventyay-interpretation
- 最近来源活动
- 2026年8月20日 15:41
- 检测到的 SKILL.md 语言
- 英语
- 星标
- 1,540
- 分支
- 9
安装方式
默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。
检查来源文件
决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。
正在显示 SKILL.md
SKILL.md
来源说明 · 只读预览- name
- database-analysis
- description
- Use this skill to analyse, audit, or modify database models, migrations, and CRUD helpers. Reference: `portal/models.py`, `portal/database.py`, `alembic/versions/`.
# Skill: Database Analysis
> Use this skill to analyse, audit, or modify database models, migrations, and CRUD helpers.
> Reference: `portal/models.py`, `portal/database.py`, `alembic/versions/`.
---
## Quick Schema Reference
```
events ─────────┬─── rooms ───── booths ─── invite_tokens
│ │
│ └─── booth_memberships ─── users
└─── booths ───────────── event_memberships ─ users
```
See [DATABASE_MAP.md](../../context/DATABASE_MAP.md) for full column details.
---
## Key Design Decisions
1. **`mediamtx_path` is a runtime property**, not a stored column on `DBBooth`.
- Always derived via `make_mediamtx_path(event.slug, language_code)`.
- Requires `event` relationship to be loaded: use `joinedload(DBBooth.event)`.
2. **API keys are Fernet-encrypted** in the `events` table.
- Use `portal.crypto.encrypt_val(key)` to store.
- Use `portal.crypto.decrypt_val(encrypted)` to read.
- `API_KEY_ENCRYPTION_KEY` env var must be set; raises `RuntimeError` if default.
3. **Unique index on `(event_id, language_code)`** in `booths` — one booth per language per event.
4. **`InviteToken.token` is a 64-char hex string** (32 bytes of entropy via `secrets.token_hex(32)`).
5. **Session pattern uses `async with session.begin()`** — auto-commits and auto-rolls back.
```python
async with get_session() as session:
result = await create_event(session, slug='test', display_name='Test')
# committed on exit
```
---
## Working with Relationships
### Loading `event` on `DBBooth` (for `mediamtx_path`)
```python
from sqlalchemy.orm import joinedload
stmt = select(DBBooth).options(joinedload(DBBooth.event)).where(DBBooth.id == booth_id)
```
### Loading booth with room and event
```python
stmt = (
select(DBBooth)
.join(Event)
.options(joinedload(DBBooth.room))
.where(Event.slug == event_slug)
.where(DBBooth.language_code == language_code)
)
```
### Invite token with booth and event (for redemption)
```python
result = await session.execute(
select(InviteToken)
.options(joinedload(InviteToken.booth).joinedload(DBBooth.event))
.where(InviteToken.token == token_str)
)
```
---
## Migration Workflow
1. Edit `portal/models.py`.
2. Generate migration:
```bash
uv run alembic revision --autogenerate -m "short_description"
```
3. **Review generated file** — autogenerate is not always correct:
- SQLite requires `batch_alter_table` for column additions/drops. See migration 008 as the canonical example.
- For new tables, autogenerate is usually correct.
4. Apply:
```bash
uv run alembic upgrade head
```
5. Test:
```bash
uv run pytest tests/test_database.py -v
```
### Migration 008 Reference Pattern (SQLite column add)
```python
def upgrade() -> None:
with op.batch_alter_table('booths') as batch_op:
batch_op.add_column(sa.Column('new_column', sa.String(20), server_default=sa.text("'default'"), nullable=False))
```
---
## CRUD Helper Patterns
### Upsert pattern (event/booth memberships)
`set_event_membership` and `set_booth_membership` use an upsert-like pattern:
- Query for existing membership → update role if found → create new if not found.
### Invite token redemption
`redeem_invite_token` raises `ValueError` (not HTTP exception) on invalid token:
```python
tok = await redeem_invite_token(session, token_str)
# Raises ValueError: "Token has already been used." or "Token has expired."
# Returns None if token not found
```
The route handler converts these to HTTP 403 / 404.
### Revoke token
`revoke_invite_token` sets `used_at = utc_now()` on the token (same effect as redemption).
---
## Testing Database Code
Use in-memory SQLite + `configure()` override in test fixtures:
```python
from portal.database import configure, init_db, drop_db
from portal.config import settings
async def setup_test_db():
configure('sqlite+aiosqlite:///:memory:')
await init_db()
async def teardown_test_db():
await drop_db()
```
Never use the production database URL in tests.
---
## Common Database Issues
| Issue | Cause | Fix |
|---|---|---|
| `mediamtx_path` raises `AttributeError` | `event` relationship not loaded | Use `joinedload(DBBooth.event)` in query |
| `IntegrityError` on booth create | Duplicate `(event_id, language_code)` | Catch `IntegrityError` and return 409 |
| Migration fails on SQLite | Column alter without `batch_alter_table` | Rewrite migration with batch context |
| `decrypt_val` raises `ValueError` | Wrong `API_KEY_ENCRYPTION_KEY` | Key must match the one used to encrypt |
| `InviteToken.is_expired` incorrect | Missing timezone on `expires_at` | `expires_at` must be timezone-aware (UTC) |
在 GitHub 查看