blasphem/pkg/flow/flow.go
2022-11-20 08:49:24 -05:00

211 lines
4.7 KiB
Go

// flow is the data entry flow.
package flow
import (
"fmt"
"time"
"dynatron.me/x/blasphem/internal/common"
"dynatron.me/x/blasphem/internal/generate"
)
type (
FlowResultType string
FlowID string
Step string
HandlerKey string
Errors interface{}
Context interface{}
FlowStore map[FlowID]Handler
FlowManager struct {
flows FlowStore
}
FlowResult struct {
Type FlowResultType `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 []FlowSchemaItem `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"`
}
FlowSchemaItem struct {
Type string `json:"type"`
Name string `json:"name"`
Required bool `json:"required"`
}
FlowSchema []FlowSchemaItem
FlowRequest struct {
ClientID common.ClientID `json:"client_id"`
Handler []*HandlerKey `json:"handler"`
RedirectURI string `json:"redirect_uri"`
}
)
type (
Schemer interface {
FlowSchema() FlowSchema
}
Handler interface {
Base() FlowHandlerBase
FlowID() FlowID
flowCtime() time.Time
}
)
const (
StepInit Step = "init"
)
func (fs *FlowSchema) 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 FlowHandlerBase struct {
ID FlowID // ID is the FlowID
Handler HandlerKey // Handler key
Context Context // flow Context
ClientID common.ClientID
RedirectURI string
Schema FlowSchema
// curStep is the current step set by the flow manager
curStep Step
ctime time.Time
}
func (f *FlowHandlerBase) Step() Step { return f.curStep }
func (f *FlowHandlerBase) Base() FlowHandlerBase { return *f }
func (f *FlowHandlerBase) FlowID() FlowID {
return f.ID
}
func (f *FlowHandlerBase) flowCtime() time.Time { return f.ctime }
func NewFlowHandlerBase(f *FlowRequest, sch Schemer, hand string) FlowHandlerBase {
return FlowHandlerBase{
ID: FlowID(generate.UUID()),
Handler: HandlerKey(hand),
ClientID: f.ClientID,
RedirectURI: f.RedirectURI,
Schema: sch.FlowSchema(),
curStep: StepInit,
ctime: time.Now(),
}
}
func (hk *HandlerKey) String() string {
return string(*hk)
}
func (fm *FlowHandlerBase) Handlers() []*HandlerKey {
return []*HandlerKey{&fm.Handler, nil}
}
func resultErrs(e Errors) Errors {
if e == nil {
return []string{}
}
return e
}
func (fm *FlowHandlerBase) ShowForm(errs Errors) *FlowResult {
res := &FlowResult{
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 FlowResultType = "form"
TypeCreateEntry FlowResultType = "create_entry"
TypeAbort FlowResultType = "abort"
TypeExternalStep FlowResultType = "external"
TypeExternalStepDone FlowResultType = "external_done"
TypeShowProgress FlowResultType = "progress"
TypeShowProgressDone FlowResultType = "progress_done"
TypeMenu FlowResultType = "menu"
)
func (f *FlowHandlerBase) 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 * 30
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
}