refactor: prefix every table with its module
Two tables were named after the concept they came from rather than the module
they belong to, so the schema read as if the template tables were part of the
paper module. Renamed (data preserved, `RENAME TABLE` moves rows in place):
paper_template -> template the 模板 module
section_field -> template_field_library the 字段库 the 模板 module owns
The paper tables and `template_field` already followed the rule. The rename
carries through everything that named a module:
models Template, TemplateField, TemplateFieldLibrary
schemas Template*, TemplateFieldLibrary*
crud app/crud/template.py, app/crud/template_field_library.py
API /template-field-library (was /section-fields); handlers are now
named after library entries, which removes the ambiguity with
TemplateField — a placement, a different thing entirely
client src/api/templateFieldLibrary.ts
`paper_template_filed_sort` is deliberately untouched: it is a column of the
paper module, spelled as the feature was specified.
TiDB v8.5 with tidb_enable_foreign_key on — as this cluster runs — enforces
foreign keys rather than ignoring them, so the docs' "TiDB does not enforce
foreign keys" was wrong. Corrected, with what actually follows from it: the
rename was rehearsed (RENAME TABLE carries a referencing constraint along), the
API keeps checking first so a violation names the row instead of surfacing a
driver error, and the ORM cascades stay so behaviour does not depend on a
cluster setting.
Revision f27a1c6d9e04 verified both ways; 40 smoke checks, type-check and build
all pass.
This commit is contained in:
+49
-25
@@ -57,17 +57,19 @@ Browser ──HTTP/JSON──▶ FastAPI ──SQLAlchemy──▶ TiDB (k3s)
|
||||
|
||||
## Domain Model
|
||||
|
||||
Six tables, and one rule that the last three follow from.
|
||||
Six tables. Every one is prefixed with the module it belongs to, and a model
|
||||
class is named after its table, so the schema can be read as three groups:
|
||||
|
||||
```
|
||||
section_field the reusable heading library
|
||||
template_field_library the 字段库: reusable headings
|
||||
id, name, level, font_size, font_color
|
||||
|
||||
paper_template a named outline
|
||||
template a named outline (模板)
|
||||
id, name, abstract
|
||||
|
||||
template_field the join, and the only home of display order
|
||||
id, template_id → paper_template, field_id → section_field, sort
|
||||
template_field a field placed in a template — and the only
|
||||
id, template_id → template, home of display order
|
||||
field_id → template_field_library, sort
|
||||
|
||||
paper the document
|
||||
id, title, template_id, abstract, author, status, keywords,
|
||||
@@ -80,9 +82,15 @@ paper_sentence_reference the citations of one sentence
|
||||
id, sentence_id → paper_sentence, reference_id, quote, sort
|
||||
```
|
||||
|
||||
`template_field` and `template_field_library` are one word apart and mean
|
||||
opposite things. The first is a *placement* — this template puts this field
|
||||
here, `sort` included. The second is the *catalogue* the field was picked from,
|
||||
shared by every template and owned by none. The library is managed from the
|
||||
模板 menu, because it is the raw material a template is built out of.
|
||||
|
||||
### The field library is flat, not a tree
|
||||
|
||||
`section_field` has no `parent_id`. Hierarchy is expressed only by `level`
|
||||
`template_field_library` has no `parent_id`. Hierarchy is expressed only by `level`
|
||||
(1 for `1.`, 2 for `1.1`), which is a *rendering hint*: it drives indentation
|
||||
and numbering semantics in the UI and nothing else.
|
||||
|
||||
@@ -123,7 +131,7 @@ work.
|
||||
|
||||
`paper_sentence.paper_template_filed_sort` holds the `sort` of the template
|
||||
placement the sentence belongs to — **not** `template_field.id`, and not
|
||||
`section_field.id`. Together with `sort`, the sentence's own position inside
|
||||
`template_field_library.id`. Together with `sort`, the sentence's own position inside
|
||||
that paragraph, it is everything needed to place a line of text.
|
||||
|
||||
The indirection is the feature. Two templates have no rows in common, but they
|
||||
@@ -161,22 +169,34 @@ rejected with `422` rather than stored as a half-record. `reference_id` is a
|
||||
plain nullable integer with **no** foreign key, because the reference library
|
||||
does not exist yet; a citation may therefore be written now and linked later.
|
||||
|
||||
### TiDB does not enforce foreign keys
|
||||
### Foreign keys are declared, and this cluster enforces them
|
||||
|
||||
TiDB parses `FOREIGN KEY` for compatibility, and from v6.6 honours it when
|
||||
`tidb_enable_foreign_key` is on. On the cluster this project runs against it is
|
||||
on, so the constraints are real integrity rather than documentation: a dangling
|
||||
insert is rejected (`1452`) and `ON DELETE CASCADE` / `SET NULL` actually fire.
|
||||
Two consequences are worth knowing before touching a migration:
|
||||
|
||||
TiDB parses `FOREIGN KEY` for compatibility and then ignores it. The constraints
|
||||
are declared to document the relationships, and the integrity they would provide
|
||||
is enforced in the application layer instead:
|
||||
- **Tables must be created in dependency order**, and a rename must be checked
|
||||
rather than assumed. `RENAME TABLE` does carry a referencing constraint along
|
||||
with the renamed table here (the rename in revision `f27a1c6d9e04` was
|
||||
rehearsed against the cluster before it was written), but that is a property
|
||||
of the deployment, not of SQL.
|
||||
- **The API still checks first.** A database violation surfaces as a generic
|
||||
driver error, while the application refuses with a message that names the row
|
||||
and the count:
|
||||
- deleting a library field still placed in a template → `409`, naming the
|
||||
field and how many templates use it;
|
||||
- deleting a template a paper is written against → `409`, naming the template
|
||||
and how many papers use it — the template *is* that paper's structure, so
|
||||
removing it would empty the paper rather than tidy up;
|
||||
- creating a template that references a missing field → `400`, and creating
|
||||
or patching a paper that references a missing template likewise.
|
||||
|
||||
- deleting a field still placed in a template is refused with `409`, naming the
|
||||
field and how many templates use it;
|
||||
- deleting a template that a paper is written against is refused with `409`,
|
||||
naming the template and how many papers use it — the template is that paper's
|
||||
structure, so removing it would empty the paper rather than tidy up;
|
||||
- creating a template that references a missing field is refused with `400`, and
|
||||
creating or patching a paper that references a missing template likewise;
|
||||
- `PaperTemplate.items`, `Paper.sentences` and `PaperSentence.citations` all use
|
||||
`cascade="all, delete-orphan"`, so deleting a row removes its dependents.
|
||||
The ORM cascades stay as well. `Template.items`, `Paper.sentences` and
|
||||
`PaperSentence.citations` use `cascade="all, delete-orphan"`, so deleting a row
|
||||
removes its dependents whether or not the database would have done it too —
|
||||
which keeps behaviour identical on a cluster with foreign keys switched off.
|
||||
|
||||
## API
|
||||
|
||||
@@ -185,10 +205,10 @@ All routes are mounted under `/api`. Interactive docs at `/docs`.
|
||||
| Method | Path | Notes |
|
||||
|---|---|---|
|
||||
| `GET` | `/health` | liveness plus a TiDB probe |
|
||||
| `GET` | `/section-fields` | `keyword`, `level`, `page`, `page_size` |
|
||||
| `POST` | `/section-fields` | create |
|
||||
| `GET` `PATCH` `DELETE` | `/section-fields/{id}` | read / partial update / delete |
|
||||
| `POST` | `/section-fields/batch-delete` | body `{ "ids": [...] }` |
|
||||
| `GET` | `/template-field-library` | `keyword`, `level`, `page`, `page_size` |
|
||||
| `POST` | `/template-field-library` | create a library entry |
|
||||
| `GET` `PATCH` `DELETE` | `/template-field-library/{id}` | read / partial update / delete |
|
||||
| `POST` | `/template-field-library/batch-delete` | body `{ "ids": [...] }` |
|
||||
| `GET` | `/templates` | `keyword` matches name **or** abstract |
|
||||
| `POST` | `/templates` | name + abstract + ordered `fields` |
|
||||
| `GET` `PATCH` `DELETE` | `/templates/{id}` | `PATCH` with `fields` replaces the selection |
|
||||
@@ -211,6 +231,9 @@ Conventions worth knowing:
|
||||
it before using it in a CSS rule.
|
||||
- List endpoints return `{ items, total, page, page_size, pages }`.
|
||||
- A template read returns `fields` already ordered by `sort`; clients never sort.
|
||||
- Route paths mirror table names one for one: `/templates` for `template`,
|
||||
`/template-field-library` for `template_field_library`, `/papers` for
|
||||
`paper`.
|
||||
- A paper document returns its `paragraphs` in ascending position order,
|
||||
already carrying their sentences and citation numbering. The client sorts
|
||||
nothing and merges nothing: two implementations of the same ordering rule
|
||||
@@ -311,7 +334,8 @@ silent re-shape of the document is exactly what the preview exists to prevent.
|
||||
paper-doc/
|
||||
├── backend/ # FastAPI application
|
||||
│ ├── app/
|
||||
│ │ ├── api/routes/ # route handlers (health, papers, section_fields, templates)
|
||||
│ │ ├── api/routes/ # route handlers (health, papers, templates,
|
||||
│ │ │ # template_field_library)
|
||||
│ │ ├── core/ # settings and configuration
|
||||
│ │ ├── crud/ # data-access helpers
|
||||
│ │ ├── db/ # engine, session, declarative base
|
||||
|
||||
Reference in New Issue
Block a user