Persistence foundation for authoring (slice 6) + generation-on-templates
(slice 7). docforge owns no tables — it defines the contract; paliad
implements it (litigationplanner pattern).
Migration 158_docforge_templates (additive, generic — NOT submission_*-named
so a second docforge consumer reuses it):
- templates — catalog row; current_version_id pins the live
version (FK added post-create to break the
templates<->versions cycle; ON DELETE SET NULL).
- template_versions — immutable snapshots; carrier .docx in a bytea
column (the TemplateStore bytea backend) + stylemap
jsonb. Versioning = snapshot-at-create (PRD A3).
- template_slots — variable slots per version; anchor = sentinel token
locating the slot in the carrier OOXML (PRD §5
lean), slot_key = the bound variable.
RLS mirrors submission_bases: firm-shared SELECT for authenticated,
mutations admin-only + gated in Go (no mutation policy = denied).
docforge root: TemplateStore interface + neutral types (TemplateMeta,
Template, TemplateSlot, *Input, TemplateFilter) + ErrTemplateNotFound.
CarrierBytes is format-opaque []byte so the root never imports the docx
adapter; the exporter wraps (CarrierBytes, Stylemap) into a docx.Carrier.
paliad: PgTemplateStore (sqlx, follows the submission_base_service pattern):
List / Get (current version) / GetVersion (pinned snapshot) / Create
(version 1 + pin) / AddVersion (next version + re-pin), all transactional.
Gated live round-trip test (TEST_DATABASE_URL) covers carrier+stylemap+slot
round-trip and the version bump. No handler wires this yet (PRD: no UI in
slice 4).
Verification: go build ./... clean, go vet clean, gofmt clean, full module
test green, migration NoDuplicateSlot structural test green.
m/paliad#157
108 lines
4.3 KiB
Go
108 lines
4.3 KiB
Go
package docforge
|
|
|
|
import "context"
|
|
|
|
// TemplateMeta is the listable metadata for a stored template — cheap to
|
|
// list because it carries no carrier bytes.
|
|
type TemplateMeta struct {
|
|
ID string
|
|
Slug string // optional human handle; may be empty
|
|
NameDE string
|
|
NameEN string
|
|
Kind string // consumer-domain tag, e.g. "submission"
|
|
SourceFormat string // "docx"
|
|
Firm string // may be empty
|
|
IsActive bool
|
|
Version int // current version number; 0 when no version exists yet
|
|
}
|
|
|
|
// TemplateSlot is one variable slot placed in a template version's carrier.
|
|
type TemplateSlot struct {
|
|
// Key is the variable bound here, in the placeholder grammar
|
|
// (e.g. "project.case_number").
|
|
Key string
|
|
// Anchor locates the slot within the carrier. With the sentinel
|
|
// strategy this is the token the authoring surface injected into the
|
|
// carrier OOXML at the slot position.
|
|
Anchor string
|
|
// Label is an optional human label for the authoring palette.
|
|
Label string
|
|
// OrderIndex orders slots for display.
|
|
OrderIndex int
|
|
}
|
|
|
|
// Template is a stored template resolved to its current version: metadata
|
|
// plus everything needed to author or generate — the carrier bytes, the
|
|
// stylemap, and the placed slots. CarrierBytes is format-opaque; the .docx
|
|
// adapter wraps (CarrierBytes, Stylemap) into a docx.Carrier at compose
|
|
// time, so this root type never imports the adapter.
|
|
type Template struct {
|
|
TemplateMeta
|
|
CarrierBytes []byte
|
|
Stylemap map[string]string
|
|
Slots []TemplateSlot
|
|
}
|
|
|
|
// TemplateMetaInput is the payload for creating a new template (the
|
|
// catalog row). ID and Version are assigned by the store.
|
|
type TemplateMetaInput struct {
|
|
Slug string // optional
|
|
NameDE string
|
|
NameEN string
|
|
Kind string // defaults to "submission" when empty
|
|
SourceFormat string // defaults to "docx" when empty
|
|
Firm string // optional
|
|
CreatedBy string // auth user id (uuid) for the audit column
|
|
}
|
|
|
|
// TemplateVersionInput is the payload for creating a template version: the
|
|
// carrier .docx, its stylemap, and the slots placed in it.
|
|
type TemplateVersionInput struct {
|
|
CarrierBytes []byte
|
|
Stylemap map[string]string
|
|
Slots []TemplateSlot
|
|
CreatedBy string // auth user id (uuid)
|
|
}
|
|
|
|
// TemplateFilter narrows a List. Zero-value fields mean "any".
|
|
type TemplateFilter struct {
|
|
Firm string // "" = any firm
|
|
Kind string // "" = any kind
|
|
ActiveOnly bool // true = is_active templates only
|
|
}
|
|
|
|
// TemplateStore persists and loads document templates. docforge defines
|
|
// the contract; the consuming application implements it (paliad against
|
|
// Postgres, with the carrier bytes in a bytea column). It is the seam the
|
|
// authoring surface writes to and the generator reads from — a second
|
|
// docforge consumer implements the same interface against its own storage.
|
|
//
|
|
// Versioning is snapshot-at-create (PRD §4 A3): Create makes version 1 and
|
|
// pins it as current; AddVersion inserts the next version and re-points
|
|
// current. Drafts pin a specific version so a later edit never shifts an
|
|
// in-flight draft.
|
|
type TemplateStore interface {
|
|
// List returns catalog metadata for templates matching the filter,
|
|
// without carrier bytes.
|
|
List(ctx context.Context, f TemplateFilter) ([]TemplateMeta, error)
|
|
|
|
// Get returns a template resolved to its current version (carrier +
|
|
// stylemap + slots). Returns ErrTemplateNotFound when id is unknown.
|
|
Get(ctx context.Context, id string) (*Template, error)
|
|
|
|
// GetVersion returns a template resolved to a specific version id —
|
|
// the path a draft uses to render its pinned snapshot. Returns
|
|
// ErrTemplateNotFound when the version is unknown.
|
|
GetVersion(ctx context.Context, versionID string) (*Template, error)
|
|
|
|
// Create inserts a new template plus its first version (version 1) and
|
|
// pins that version as current. Returns the resolved Template.
|
|
Create(ctx context.Context, meta TemplateMetaInput, first TemplateVersionInput) (*Template, error)
|
|
|
|
// AddVersion inserts the next version for an existing template and
|
|
// re-points current_version to it. Returns the resolved Template at
|
|
// the new version. Returns ErrTemplateNotFound when templateID is
|
|
// unknown.
|
|
AddVersion(ctx context.Context, templateID string, v TemplateVersionInput) (*Template, error)
|
|
}
|