212 lines
4.7 KiB
Go
212 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
|
||
|
}
|