kereru-forms/backend/internal/database/store.go

177 lines
4.8 KiB
Go
Raw Permalink Normal View History

// 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"`
}
2026-10-25 10:53:17 +00:00
// 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
2026-10-25 10:53:17 +00:00
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
2026-10-25 10:53:17 +00:00
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
}