// Package database provides the persistence layer for kereru-forms. // // The Store interface deliberately hides SQL details so additional providers // (PostgreSQL, MariaDB) can be added later without changing the API layer. package database import ( "context" "encoding/json" "errors" "time" ) // Common persistence errors. var ( ErrNotFound = errors.New("not found") ErrConflict = errors.New("conflict") ErrAccessCode = errors.New("access code is not valid") ErrCodeAlreadyIn = errors.New("access code has already been used") ) // QuestionOption is a selectable value for a select question. type QuestionOption struct { Value string `json:"value"` Label string `json:"label"` } // Section groups questions within a form. type Section struct { ID string FormID string Position int Identifier string Title *string Description *string } // Question is a single question belonging to a form version. type Question struct { ID string FormID string SectionID *string Position int Key string Label string Description *string Type string Required bool Options []QuestionOption MinValue *float64 MaxValue *float64 Placeholder *string Multiline bool } // Form is a concrete version of a logical form. type Form struct { ID string FormGroupID string Version int Title string Description *string Introduction *string IsActive bool RequiresAccessCode bool SignatureRequired bool ExternalReferenceRequired bool ExternalReferenceLabel *string LogoURL *string PDFURL *string CompanyName *string ContactEmail *string ContactPhone *string ContactURL *string CreatedAt time.Time UpdatedAt time.Time Questions []Question Sections []Section } // FormSummary is a lightweight form listing entry. type FormSummary struct { ID string FormGroupID string Version int Title string Description *string LogoURL *string PDFURL *string ExternalReferenceRequired bool ExternalReferenceLabel *string IsActive bool QuestionCount int CreatedAt time.Time UpdatedAt time.Time } // Answer is a captured answer inside a submission. type Answer struct { QuestionID string QuestionKey string Value json.RawMessage } // Signature is a captured digital signature. type Signature struct { Type string `json:"type"` DataURL string `json:"dataUrl"` Name string `json:"name"` Title *string `json:"title,omitempty"` SignedAt time.Time `json:"signedAt"` } // Submission is a completed form response. type Submission struct { ID string FormID string FormVersion int UserID string UserIDType string SubmittedAt time.Time IPAddress *string UserAgent *string Locale *string Timezone *string Place *string Referer *string ExternalReference *string Signature *Signature OfflinePDFName *string OfflinePDF []byte Answers []Answer } // SubmissionSummary is a lightweight submission listing entry. type SubmissionSummary struct { ID string FormID string FormVersion int UserID string UserIDType string ExternalReference *string SubmittedAt time.Time } // AccessCode is a single-use code granting access to a form. type AccessCode struct { ID string FormID string Code string Used bool UsedAt *time.Time CreatedAt time.Time } // Store is the persistence contract used by the API layer. type Store interface { Close() error CreateForm(ctx context.Context, form *Form) error UpdateForm(ctx context.Context, form *Form) error GetForm(ctx context.Context, id string) (*Form, error) ListForms(ctx context.Context, includeInactive bool) ([]FormSummary, error) SetFormActive(ctx context.Context, id string, active bool) error DeleteForm(ctx context.Context, id string) error CreateSubmission(ctx context.Context, submission *Submission, accessCode string) error GetSubmission(ctx context.Context, id string) (*Submission, error) GetSubmissionOfflinePDF(ctx context.Context, id string) ([]byte, string, error) ListSubmissions(ctx context.Context, formID string) ([]SubmissionSummary, error) CreateAccessCodes(ctx context.Context, codes []AccessCode) error ListAccessCodes(ctx context.Context, formID string) ([]AccessCode, error) Ping(ctx context.Context) error }