A submission draft can now render from an uploaded docforge template
instead of a legacy Gitea base. DB-VERIFIED against TEST_DATABASE_URL (the
head greenlit option C) before commit — not just compiled.
Schema: migration 159 adds submission_drafts.template_version_id (nullable,
FK template_versions ON DELETE SET NULL) — the snapshot pin (PRD A3). A
later template edit creates a new version; the pinned draft keeps rendering
its version.
Draft service: TemplateVersionID on the model + draftColumns + the JOIN
list + DraftPatch (two-level pointer like base_id) + Update SET. Column-sync
verified live (Create_seeds_section_rows + the new pin test both pass).
Export/preview (handlers): a template-version path checked FIRST — load the
carrier via TemplateStore.GetVersion, render via the existing Export/
RenderPreview (the carrier already carries {{slots}}; no Composer/sections
needed). Falls through to base_id / v1 if the pin is missing. Both preview
sites + the view assembly branch on it.
Store: TemplateMeta.VersionID exposes the current version's row id (slice-4
gap — a consumer needs it to pin); populated in List/Get/GetVersion + the
authoring JSON. New GET /api/templates (authenticated, firm-filtered) is the
picker list any lawyer reads; admin authoring endpoints stay gated.
Frontend: the submission editor's base picker now offers uploaded templates
as a 'tpl:<version_id>' optgroup; selecting one PATCHes template_version_id
(clearing base_id) and vice versa — mutually exclusive render paths.
Live test (submission_draft_template_live_test.go, gated): pin round-trips
Update→Get, the uploaded carrier renders ({{firm.name}}→HLC via Export), and
clearing nulls it — all PASS against real Postgres.
Verification: go build/vet/gofmt clean; bun build + bun test 274/274; slice-7
+ slice-4 store + draft/composer live tests PASS against TEST_DATABASE_URL.
Pre-existing env failures (approval/projection seed $1-type quirk,
migration136 stale deadline_rules table) are unrelated — confirmed my branch
touches none of that code.
m/paliad#157
112 lines
4.5 KiB
Go
112 lines
4.5 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
|
|
VersionID string // current version row id; "" when no version exists yet.
|
|
// A draft pins VersionID to snapshot this exact version (PRD §4 A3):
|
|
// a later template edit creates a new version and re-points current,
|
|
// but the pinned draft keeps rendering VersionID.
|
|
}
|
|
|
|
// 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)
|
|
}
|