Files
paliad/pkg/docforge/store.go
mAi 47deeaf5ed feat(docforge): slice 4 — template tables + TemplateStore (t-paliad-349)
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
2026-05-29 15:35:36 +02:00

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)
}