blasphem/pkg/flow/flow.go
2022-12-18 09:55:50 -05:00

201 lines
4.2 KiB
Go

// flow is the data entry flow.
package flow
import (
"fmt"
"time"
"dynatron.me/x/blasphem/internal/generate"
)
type (
ResultType string
FlowID string
Step string
HandlerKey string
Errors interface{}
Context interface{}
FlowStore map[FlowID]Handler
FlowManager struct {
flows FlowStore
}
Result struct {
Type ResultType `json:"type"`
ID FlowID `json:"flow_id"`
Handler []*HandlerKey `json:"handler"`
Title *string `json:"title,omitempty"`
Data map[string]interface{} `json:"data,omitempty"`
StepID *Step `json:"step_id,omitempty"`
Schema []SchemaItem `json:"data_schema"`
Extra *string `json:"extra,omitempty"`
Required *bool `json:"required,omitempty"`
Errors interface{} `json:"errors"`
Description *string `json:"description,omitempty"`
DescPlace *string `json:"description_placeholders"`
URL *string `json:"url,omitempty"`
Reason *string `json:"reason,omitempty"`
Context *string `json:"context,omitempty"`
Result interface{} `json:"result,omitempty"`
LastStep *string `json:"last_step"`
Options map[string]interface{} `json:"options,omitempty"`
Version *int `json:"version,omitempty"`
}
SchemaItem struct {
Type string `json:"type"`
Name string `json:"name"`
Required bool `json:"required"`
}
Schema []SchemaItem
)
type (
Schemer interface {
FlowSchema() Schema
}
Handler interface {
BaseHandler() FlowHandler
FlowID() FlowID
flowCtime() time.Time
}
)
const (
StepInit Step = "init"
)
func (fs *Schema) CheckRequired(rm map[string]interface{}) error {
for _, si := range *fs {
if si.Required {
if _, ok := rm[si.Name]; !ok {
return fmt.Errorf("missing required param %s", si.Name)
}
}
}
return nil
}
func NewFlowManager() *FlowManager {
return &FlowManager{
flows: make(FlowStore),
}
}
func stepPtr(s Step) *Step { return &s }
type FlowHandler struct {
ID FlowID // ID is the FlowID
Handler HandlerKey // Handler key
Context Context // flow Context
Schema Schema
// curStep is the current step set by the flow manager
curStep Step
ctime time.Time
}
func (f *FlowHandler) Step() Step { return f.curStep }
func (f *FlowHandler) BaseHandler() FlowHandler { return *f }
func (f *FlowHandler) FlowID() FlowID {
return f.ID
}
func (f *FlowHandler) flowCtime() time.Time { return f.ctime }
func NewFlowHandlerBase(sch Schemer, hand string) FlowHandler {
return FlowHandler{
ID: FlowID(generate.UUID()),
Handler: HandlerKey(hand),
Schema: sch.FlowSchema(),
curStep: StepInit,
ctime: time.Now(),
}
}
func (hk *HandlerKey) String() string {
return string(*hk)
}
func (fm *FlowHandler) Handlers() []*HandlerKey {
return []*HandlerKey{&fm.Handler, nil}
}
func resultErrs(e Errors) Errors {
if e == nil {
return []string{}
}
return e
}
func (fm *FlowHandler) ShowForm(errs Errors) *Result {
res := &Result{
Type: TypeForm,
ID: fm.ID,
StepID: stepPtr(fm.curStep),
Schema: fm.Schema,
Handler: fm.Handlers(),
Errors: resultErrs(errs),
}
return res
}
func (fm *FlowManager) Delete(id FlowID) {
delete(fm.flows, id)
}
const (
TypeForm ResultType = "form"
TypeCreateEntry ResultType = "create_entry"
TypeAbort ResultType = "abort"
TypeExternalStep ResultType = "external"
TypeExternalStepDone ResultType = "external_done"
TypeShowProgress ResultType = "progress"
TypeShowProgressDone ResultType = "progress_done"
TypeMenu ResultType = "menu"
)
func (f *FlowHandler) touch() {
f.ctime = time.Now()
}
func (fm *FlowManager) Register(f Handler) {
fm.flows.cull()
fm.flows[f.FlowID()] = f
}
func (fs *FlowManager) Remove(f Handler) {
delete(fs.flows, f.FlowID())
}
const cullAge = time.Minute * 10
func (fs FlowStore) cull() {
for k, v := range fs {
if time.Now().Sub(v.flowCtime()) > cullAge {
delete(fs, k)
}
}
}
func (fs *FlowManager) Get(id FlowID) Handler {
f, ok := fs.flows[id]
if ok {
return f
}
return nil
}