// 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 }