Add (record change) triggers
This commit is contained in:
@@ -605,5 +605,151 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Triggers",
|
||||
"description": "CRM Triggers",
|
||||
"package": "crm",
|
||||
"entrypoint": "trigger",
|
||||
"path": "/trigger",
|
||||
"authentication": [],
|
||||
"apis": [
|
||||
{
|
||||
"name": "list",
|
||||
"method": "GET",
|
||||
"path": "/",
|
||||
"title": "List available triggers",
|
||||
"parameters": {
|
||||
"get": [
|
||||
{
|
||||
"type": "uint64",
|
||||
"name": "moduleID",
|
||||
"required": false,
|
||||
"title": "Filter triggers by module"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "create",
|
||||
"method": "POST",
|
||||
"title": "Create trigger",
|
||||
"path": "/",
|
||||
"parameters": {
|
||||
"post": [
|
||||
{
|
||||
"type": "uint64",
|
||||
"name": "moduleID",
|
||||
"required": false,
|
||||
"title": "Module ID"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "name",
|
||||
"required": true,
|
||||
"title": "Name"
|
||||
},
|
||||
{
|
||||
"type": "[]string",
|
||||
"name": "actions",
|
||||
"required": false,
|
||||
"title": "Actions that trigger this trigger"
|
||||
},
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "enabled",
|
||||
"required": false,
|
||||
"title": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "source",
|
||||
"required": false,
|
||||
"title": "Trigger source code"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "read",
|
||||
"path": "/{triggerID}",
|
||||
"method": "GET",
|
||||
"title": "Get trigger details",
|
||||
"parameters": {
|
||||
"path": [
|
||||
{
|
||||
"type": "uint64",
|
||||
"name": "triggerID",
|
||||
"required": true,
|
||||
"title": "Trigger ID"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "edit",
|
||||
"method": "POST",
|
||||
"title": "Edit trigger",
|
||||
"path": "/{triggerID}",
|
||||
"parameters": {
|
||||
"path": [
|
||||
{
|
||||
"type": "uint64",
|
||||
"name": "triggerID",
|
||||
"required": true,
|
||||
"title": "Trigger ID"
|
||||
}
|
||||
],
|
||||
"post": [
|
||||
{
|
||||
"type": "uint64",
|
||||
"name": "moduleID",
|
||||
"required": false,
|
||||
"title": "Module ID"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "name",
|
||||
"required": true,
|
||||
"title": "Name"
|
||||
},
|
||||
{
|
||||
"type": "[]string",
|
||||
"name": "actions",
|
||||
"required": false,
|
||||
"title": "Actions that trigger this trigger"
|
||||
},
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "enabled",
|
||||
"required": false,
|
||||
"title": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "source",
|
||||
"required": false,
|
||||
"title": "Trigger source code"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "delete",
|
||||
"path": "/{triggerID}",
|
||||
"method": "Delete",
|
||||
"title": "Delete trigger",
|
||||
"parameters": {
|
||||
"path": [
|
||||
{
|
||||
"type": "uint64",
|
||||
"name": "triggerID",
|
||||
"required": true,
|
||||
"title": "Trigger ID"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
{
|
||||
"Title": "Triggers",
|
||||
"Description": "CRM Triggers",
|
||||
"Package": "crm",
|
||||
"Interface": "Trigger",
|
||||
"Struct": null,
|
||||
"Parameters": null,
|
||||
"Protocol": "",
|
||||
"Authentication": [],
|
||||
"Path": "/trigger",
|
||||
"APIs": [
|
||||
{
|
||||
"Name": "list",
|
||||
"Method": "GET",
|
||||
"Title": "List available triggers",
|
||||
"Path": "/",
|
||||
"Parameters": {
|
||||
"get": [
|
||||
{
|
||||
"name": "moduleID",
|
||||
"required": false,
|
||||
"title": "Filter triggers by module",
|
||||
"type": "uint64"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "create",
|
||||
"Method": "POST",
|
||||
"Title": "Create trigger",
|
||||
"Path": "/",
|
||||
"Parameters": {
|
||||
"post": [
|
||||
{
|
||||
"name": "moduleID",
|
||||
"required": false,
|
||||
"title": "Module ID",
|
||||
"type": "uint64"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"required": true,
|
||||
"title": "Name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "actions",
|
||||
"required": false,
|
||||
"title": "Actions that trigger this trigger",
|
||||
"type": "[]string"
|
||||
},
|
||||
{
|
||||
"name": "enabled",
|
||||
"required": false,
|
||||
"title": "Enabled",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "source",
|
||||
"required": false,
|
||||
"title": "Trigger source code",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "read",
|
||||
"Method": "GET",
|
||||
"Title": "Get trigger details",
|
||||
"Path": "/{triggerID}",
|
||||
"Parameters": {
|
||||
"path": [
|
||||
{
|
||||
"name": "triggerID",
|
||||
"required": true,
|
||||
"title": "Trigger ID",
|
||||
"type": "uint64"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "edit",
|
||||
"Method": "POST",
|
||||
"Title": "Edit trigger",
|
||||
"Path": "/{triggerID}",
|
||||
"Parameters": {
|
||||
"path": [
|
||||
{
|
||||
"name": "triggerID",
|
||||
"required": true,
|
||||
"title": "Trigger ID",
|
||||
"type": "uint64"
|
||||
}
|
||||
],
|
||||
"post": [
|
||||
{
|
||||
"name": "moduleID",
|
||||
"required": false,
|
||||
"title": "Module ID",
|
||||
"type": "uint64"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"required": true,
|
||||
"title": "Name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "actions",
|
||||
"required": false,
|
||||
"title": "Actions that trigger this trigger",
|
||||
"type": "[]string"
|
||||
},
|
||||
{
|
||||
"name": "enabled",
|
||||
"required": false,
|
||||
"title": "Enabled",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "source",
|
||||
"required": false,
|
||||
"title": "Trigger source code",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "delete",
|
||||
"Method": "Delete",
|
||||
"Title": "Delete trigger",
|
||||
"Path": "/{triggerID}",
|
||||
"Parameters": {
|
||||
"path": [
|
||||
{
|
||||
"name": "triggerID",
|
||||
"required": true,
|
||||
"title": "Trigger ID",
|
||||
"type": "uint64"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+1
-1
@@ -37,7 +37,7 @@ function types {
|
||||
CGO_ENABLED=0 go build -o ./build/gen-type-set codegen/v2/type-set.go
|
||||
fi
|
||||
|
||||
./build/gen-type-set --types Module,Page,Chart -no-pk-types ModuleField --output crm/types/type.gen.go
|
||||
./build/gen-type-set --types Module,Page,Chart,Trigger -no-pk-types ModuleField --output crm/types/type.gen.go
|
||||
|
||||
./build/gen-type-set --types MessageAttachment --output sam/types/attachment.gen.go
|
||||
./build/gen-type-set --types Channel --output sam/types/channel.gen.go
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,15 @@
|
||||
CREATE TABLE `crm_trigger` (
|
||||
`id` BIGINT(20) UNSIGNED NOT NULL,
|
||||
`name` VARCHAR(64) NOT NULL COMMENT 'The name of the trigger',
|
||||
`enabled` BOOLEAN NOT NULL COMMENT 'Trigger enabled?',
|
||||
`actions` TEXT NOT NULL COMMENT 'All actions that trigger it',
|
||||
`source` TEXT NOT NULL COMMENT 'Trigger source',
|
||||
`rel_module` BIGINT(20) UNSIGNED NULL COMMENT 'Primary module',
|
||||
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME DEFAULT NULL,
|
||||
`deleted_at` DATETIME DEFAULT NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
@@ -0,0 +1,94 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/titpetric/factory"
|
||||
"gopkg.in/Masterminds/squirrel.v1"
|
||||
|
||||
"github.com/crusttech/crust/crm/types"
|
||||
)
|
||||
|
||||
type (
|
||||
TriggerRepository interface {
|
||||
With(ctx context.Context, db *factory.DB) TriggerRepository
|
||||
|
||||
FindByID(id uint64) (*types.Trigger, error)
|
||||
Find(filter types.TriggerFilter) (types.TriggerSet, error)
|
||||
Create(mod *types.Trigger) (*types.Trigger, error)
|
||||
Update(mod *types.Trigger) (*types.Trigger, error)
|
||||
DeleteByID(id uint64) error
|
||||
}
|
||||
|
||||
trigger struct {
|
||||
*repository
|
||||
}
|
||||
)
|
||||
|
||||
func Trigger(ctx context.Context, db *factory.DB) TriggerRepository {
|
||||
return (&trigger{}).With(ctx, db)
|
||||
}
|
||||
|
||||
func (r *trigger) With(ctx context.Context, db *factory.DB) TriggerRepository {
|
||||
return &trigger{
|
||||
repository: r.repository.With(ctx, db),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *trigger) FindByID(id uint64) (*types.Trigger, error) {
|
||||
mod := &types.Trigger{}
|
||||
query := r.query().Where("id = ?", id)
|
||||
|
||||
if sql, args, err := query.ToSql(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return mod, r.db().Get(mod, sql, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *trigger) Find(filter types.TriggerFilter) (mod types.TriggerSet, err error) {
|
||||
query := r.query()
|
||||
|
||||
if filter.ModuleID > 0 {
|
||||
query = query.Where("rel_module = ?", filter.ModuleID)
|
||||
}
|
||||
spew.Dump(filter)
|
||||
spew.Dump(query.ToSql())
|
||||
|
||||
if sql, args, err := query.ToSql(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return mod, r.db().Select(&mod, sql, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (r trigger) query() (query squirrel.SelectBuilder) {
|
||||
query = squirrel.Select().
|
||||
Columns(
|
||||
"id", "name", "actions", "enabled", "source", "rel_module",
|
||||
"created_at", "updated_at", "deleted_at").
|
||||
From("crm_trigger").
|
||||
OrderBy("id DESC")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *trigger) Create(mod *types.Trigger) (*types.Trigger, error) {
|
||||
mod.ID = factory.Sonyflake.NextID()
|
||||
mod.CreatedAt = time.Now()
|
||||
|
||||
return mod, r.db().Insert("crm_trigger", mod)
|
||||
}
|
||||
|
||||
func (r *trigger) Update(mod *types.Trigger) (*types.Trigger, error) {
|
||||
now := time.Now()
|
||||
mod.UpdatedAt = &now
|
||||
return mod, r.db().Replace("crm_trigger", mod)
|
||||
}
|
||||
|
||||
func (r *trigger) DeleteByID(id uint64) error {
|
||||
_, err := r.db().Exec("DELETE FROM crm_trigger WHERE id = ?", id)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package handlers
|
||||
|
||||
/*
|
||||
Hello! This file is auto-generated from `docs/src/spec.json`.
|
||||
|
||||
For development:
|
||||
In order to update the generated files, edit this file under the location,
|
||||
add your struct fields, imports, API definitions and whatever you want, and:
|
||||
|
||||
1. run [spec](https://github.com/titpetric/spec) in the same folder,
|
||||
2. run `./_gen.php` in this folder.
|
||||
|
||||
You may edit `trigger.go`, `trigger.util.go` or `trigger_test.go` to
|
||||
implement your API calls, helper functions and tests. The file `trigger.go`
|
||||
is only generated the first time, and will not be overwritten if it exists.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-chi/chi"
|
||||
"net/http"
|
||||
|
||||
"github.com/titpetric/factory/resputil"
|
||||
|
||||
"github.com/crusttech/crust/crm/rest/request"
|
||||
)
|
||||
|
||||
// Internal API interface
|
||||
type TriggerAPI interface {
|
||||
List(context.Context, *request.TriggerList) (interface{}, error)
|
||||
Create(context.Context, *request.TriggerCreate) (interface{}, error)
|
||||
Read(context.Context, *request.TriggerRead) (interface{}, error)
|
||||
Edit(context.Context, *request.TriggerEdit) (interface{}, error)
|
||||
Delete(context.Context, *request.TriggerDelete) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
type Trigger struct {
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Create func(http.ResponseWriter, *http.Request)
|
||||
Read func(http.ResponseWriter, *http.Request)
|
||||
Edit func(http.ResponseWriter, *http.Request)
|
||||
Delete func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
func NewTrigger(th TriggerAPI) *Trigger {
|
||||
return &Trigger{
|
||||
List: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewTriggerList()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return th.List(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Create: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewTriggerCreate()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return th.Create(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Read: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewTriggerRead()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return th.Read(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Edit: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewTriggerEdit()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return th.Edit(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Delete: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewTriggerDelete()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return th.Delete(r.Context(), params)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (th *Trigger) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Route("/trigger", func(r chi.Router) {
|
||||
r.Get("/", th.List)
|
||||
r.Post("/", th.Create)
|
||||
r.Get("/{triggerID}", th.Read)
|
||||
r.Post("/{triggerID}", th.Edit)
|
||||
r.Delete("/{triggerID}", th.Delete)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package request
|
||||
|
||||
/*
|
||||
Hello! This file is auto-generated from `docs/src/spec.json`.
|
||||
|
||||
For development:
|
||||
In order to update the generated files, edit this file under the location,
|
||||
add your struct fields, imports, API definitions and whatever you want, and:
|
||||
|
||||
1. run [spec](https://github.com/titpetric/spec) in the same folder,
|
||||
2. run `./_gen.php` in this folder.
|
||||
|
||||
You may edit `trigger.go`, `trigger.util.go` or `trigger_test.go` to
|
||||
implement your API calls, helper functions and tests. The file `trigger.go`
|
||||
is only generated the first time, and will not be overwritten if it exists.
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var _ = chi.URLParam
|
||||
var _ = multipart.FileHeader{}
|
||||
|
||||
// Trigger list request parameters
|
||||
type TriggerList struct {
|
||||
ModuleID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
func NewTriggerList() *TriggerList {
|
||||
return &TriggerList{}
|
||||
}
|
||||
|
||||
func (t *TriggerList) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(t)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return errors.Wrap(err, "error parsing http request body")
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
get := map[string]string{}
|
||||
post := map[string]string{}
|
||||
urlQuery := r.URL.Query()
|
||||
for name, param := range urlQuery {
|
||||
get[name] = string(param[0])
|
||||
}
|
||||
postVars := r.Form
|
||||
for name, param := range postVars {
|
||||
post[name] = string(param[0])
|
||||
}
|
||||
|
||||
if val, ok := get["moduleID"]; ok {
|
||||
|
||||
t.ModuleID = parseUInt64(val)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewTriggerList()
|
||||
|
||||
// Trigger create request parameters
|
||||
type TriggerCreate struct {
|
||||
ModuleID uint64 `json:",string"`
|
||||
Name string
|
||||
Actions []string
|
||||
Enabled bool
|
||||
Source string
|
||||
}
|
||||
|
||||
func NewTriggerCreate() *TriggerCreate {
|
||||
return &TriggerCreate{}
|
||||
}
|
||||
|
||||
func (t *TriggerCreate) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(t)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return errors.Wrap(err, "error parsing http request body")
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
get := map[string]string{}
|
||||
post := map[string]string{}
|
||||
urlQuery := r.URL.Query()
|
||||
for name, param := range urlQuery {
|
||||
get[name] = string(param[0])
|
||||
}
|
||||
postVars := r.Form
|
||||
for name, param := range postVars {
|
||||
post[name] = string(param[0])
|
||||
}
|
||||
|
||||
if val, ok := post["moduleID"]; ok {
|
||||
|
||||
t.ModuleID = parseUInt64(val)
|
||||
}
|
||||
if val, ok := post["name"]; ok {
|
||||
|
||||
t.Name = val
|
||||
}
|
||||
if val, ok := post["enabled"]; ok {
|
||||
|
||||
t.Enabled = parseBool(val)
|
||||
}
|
||||
if val, ok := post["source"]; ok {
|
||||
|
||||
t.Source = val
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewTriggerCreate()
|
||||
|
||||
// Trigger read request parameters
|
||||
type TriggerRead struct {
|
||||
TriggerID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
func NewTriggerRead() *TriggerRead {
|
||||
return &TriggerRead{}
|
||||
}
|
||||
|
||||
func (t *TriggerRead) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(t)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return errors.Wrap(err, "error parsing http request body")
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
get := map[string]string{}
|
||||
post := map[string]string{}
|
||||
urlQuery := r.URL.Query()
|
||||
for name, param := range urlQuery {
|
||||
get[name] = string(param[0])
|
||||
}
|
||||
postVars := r.Form
|
||||
for name, param := range postVars {
|
||||
post[name] = string(param[0])
|
||||
}
|
||||
|
||||
t.TriggerID = parseUInt64(chi.URLParam(r, "triggerID"))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewTriggerRead()
|
||||
|
||||
// Trigger edit request parameters
|
||||
type TriggerEdit struct {
|
||||
TriggerID uint64 `json:",string"`
|
||||
ModuleID uint64 `json:",string"`
|
||||
Name string
|
||||
Actions []string
|
||||
Enabled bool
|
||||
Source string
|
||||
}
|
||||
|
||||
func NewTriggerEdit() *TriggerEdit {
|
||||
return &TriggerEdit{}
|
||||
}
|
||||
|
||||
func (t *TriggerEdit) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(t)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return errors.Wrap(err, "error parsing http request body")
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
get := map[string]string{}
|
||||
post := map[string]string{}
|
||||
urlQuery := r.URL.Query()
|
||||
for name, param := range urlQuery {
|
||||
get[name] = string(param[0])
|
||||
}
|
||||
postVars := r.Form
|
||||
for name, param := range postVars {
|
||||
post[name] = string(param[0])
|
||||
}
|
||||
|
||||
t.TriggerID = parseUInt64(chi.URLParam(r, "triggerID"))
|
||||
if val, ok := post["moduleID"]; ok {
|
||||
|
||||
t.ModuleID = parseUInt64(val)
|
||||
}
|
||||
if val, ok := post["name"]; ok {
|
||||
|
||||
t.Name = val
|
||||
}
|
||||
if val, ok := post["enabled"]; ok {
|
||||
|
||||
t.Enabled = parseBool(val)
|
||||
}
|
||||
if val, ok := post["source"]; ok {
|
||||
|
||||
t.Source = val
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewTriggerEdit()
|
||||
|
||||
// Trigger delete request parameters
|
||||
type TriggerDelete struct {
|
||||
TriggerID uint64 `json:",string"`
|
||||
}
|
||||
|
||||
func NewTriggerDelete() *TriggerDelete {
|
||||
return &TriggerDelete{}
|
||||
}
|
||||
|
||||
func (t *TriggerDelete) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(t)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return errors.Wrap(err, "error parsing http request body")
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
get := map[string]string{}
|
||||
post := map[string]string{}
|
||||
urlQuery := r.URL.Query()
|
||||
for name, param := range urlQuery {
|
||||
get[name] = string(param[0])
|
||||
}
|
||||
postVars := r.Form
|
||||
for name, param := range postVars {
|
||||
post[name] = string(param[0])
|
||||
}
|
||||
|
||||
t.TriggerID = parseUInt64(chi.URLParam(r, "triggerID"))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewTriggerDelete()
|
||||
+5
-7
@@ -9,11 +9,10 @@ import (
|
||||
|
||||
func MountRoutes(jwtAuth auth.TokenEncoder) func(chi.Router) {
|
||||
var (
|
||||
module = Module{}.New()
|
||||
page = Page{}.New()
|
||||
chart = Chart{}.New()
|
||||
workflow = Workflow{}.New()
|
||||
job = Job{}.New()
|
||||
module = Module{}.New()
|
||||
page = Page{}.New()
|
||||
chart = Chart{}.New()
|
||||
trigger = Trigger{}.New()
|
||||
)
|
||||
|
||||
// Initialize handers & controllers.
|
||||
@@ -25,8 +24,7 @@ func MountRoutes(jwtAuth auth.TokenEncoder) func(chi.Router) {
|
||||
handlers.NewPage(page).MountRoutes(r)
|
||||
handlers.NewModule(module).MountRoutes(r)
|
||||
handlers.NewChart(chart).MountRoutes(r)
|
||||
handlers.NewWorkflow(workflow).MountRoutes(r)
|
||||
handlers.NewJob(job).MountRoutes(r)
|
||||
handlers.NewTrigger(trigger).MountRoutes(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/titpetric/factory/resputil"
|
||||
|
||||
"github.com/crusttech/crust/crm/rest/request"
|
||||
"github.com/crusttech/crust/crm/service"
|
||||
"github.com/crusttech/crust/crm/types"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var _ = errors.Wrap
|
||||
|
||||
type Trigger struct {
|
||||
trigger service.TriggerService
|
||||
}
|
||||
|
||||
func (Trigger) New() *Trigger {
|
||||
return &Trigger{
|
||||
trigger: service.DefaultTrigger,
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl *Trigger) List(ctx context.Context, r *request.TriggerList) (interface{}, error) {
|
||||
filter := types.TriggerFilter{}
|
||||
|
||||
if r.ModuleID > 0 {
|
||||
filter.ModuleID = r.ModuleID
|
||||
}
|
||||
|
||||
return ctrl.trigger.With(ctx).Find(filter)
|
||||
}
|
||||
|
||||
func (ctrl *Trigger) Create(ctx context.Context, r *request.TriggerCreate) (interface{}, error) {
|
||||
trigger := &types.Trigger{
|
||||
Name: r.Name,
|
||||
Actions: r.Actions,
|
||||
Enabled: r.Enabled,
|
||||
Source: r.Source,
|
||||
}
|
||||
|
||||
if r.ModuleID > 0 {
|
||||
trigger.ModuleID = r.ModuleID
|
||||
}
|
||||
|
||||
return ctrl.trigger.With(ctx).Create(trigger)
|
||||
}
|
||||
|
||||
func (ctrl *Trigger) Read(ctx context.Context, r *request.TriggerRead) (interface{}, error) {
|
||||
return ctrl.trigger.With(ctx).FindByID(r.TriggerID)
|
||||
}
|
||||
|
||||
func (ctrl *Trigger) Edit(ctx context.Context, r *request.TriggerEdit) (interface{}, error) {
|
||||
if trigger, err := ctrl.trigger.FindByID(r.TriggerID); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
trigger.Name = r.Name
|
||||
trigger.Actions = r.Actions
|
||||
trigger.Enabled = r.Enabled
|
||||
trigger.Source = r.Source
|
||||
|
||||
if r.ModuleID > 0 {
|
||||
trigger.ModuleID = r.ModuleID
|
||||
}
|
||||
|
||||
return ctrl.trigger.With(ctx).Update(trigger)
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl *Trigger) Delete(ctx context.Context, r *request.TriggerDelete) (interface{}, error) {
|
||||
return resputil.OK(), ctrl.trigger.With(ctx).DeleteByID(r.TriggerID)
|
||||
}
|
||||
@@ -50,7 +50,7 @@ func TestMain(m *testing.M) {
|
||||
|
||||
// clean up tables
|
||||
{
|
||||
for _, name := range []string{"crm_module", "crm_module_form", "crm_record", "crm_record_column", "crm_page", "sys_user"} {
|
||||
for _, name := range []string{"crm_chart", "crm_trigger", "crm_module", "crm_module_form", "crm_record", "crm_record_column", "crm_page", "sys_user"} {
|
||||
_, err := db.Exec("truncate " + name)
|
||||
if err != nil {
|
||||
panic("Error when clearing " + name + ": " + err.Error())
|
||||
|
||||
@@ -5,22 +5,20 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
o sync.Once
|
||||
DefaultRecord RecordService
|
||||
DefaultModule ModuleService
|
||||
DefaultChart ChartService
|
||||
DefaultPage PageService
|
||||
DefaultWorkflow WorkflowService
|
||||
DefaultJob JobService
|
||||
o sync.Once
|
||||
DefaultRecord RecordService
|
||||
DefaultModule ModuleService
|
||||
DefaultTrigger TriggerService
|
||||
DefaultChart ChartService
|
||||
DefaultPage PageService
|
||||
)
|
||||
|
||||
func Init() {
|
||||
o.Do(func() {
|
||||
DefaultRecord = Record()
|
||||
DefaultModule = Module()
|
||||
DefaultTrigger = Trigger()
|
||||
DefaultPage = Page()
|
||||
DefaultChart = Chart()
|
||||
DefaultWorkflow = Workflow()
|
||||
DefaultJob = Job()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/titpetric/factory"
|
||||
|
||||
"github.com/crusttech/crust/crm/repository"
|
||||
"github.com/crusttech/crust/crm/types"
|
||||
)
|
||||
|
||||
type (
|
||||
trigger struct {
|
||||
db *factory.DB
|
||||
ctx context.Context
|
||||
|
||||
triggerRepo repository.TriggerRepository
|
||||
moduleRepo repository.ModuleRepository
|
||||
}
|
||||
|
||||
TriggerService interface {
|
||||
With(ctx context.Context) TriggerService
|
||||
|
||||
FindByID(triggerID uint64) (*types.Trigger, error)
|
||||
Find(filter types.TriggerFilter) (set types.TriggerSet, err error)
|
||||
|
||||
Create(trigger *types.Trigger) (*types.Trigger, error)
|
||||
Update(trigger *types.Trigger) (*types.Trigger, error)
|
||||
DeleteByID(triggerID uint64) error
|
||||
}
|
||||
)
|
||||
|
||||
func Trigger() TriggerService {
|
||||
return (&trigger{}).With(context.Background())
|
||||
}
|
||||
|
||||
func (s *trigger) With(ctx context.Context) TriggerService {
|
||||
db := repository.DB(ctx)
|
||||
return &trigger{
|
||||
db: db,
|
||||
ctx: ctx,
|
||||
triggerRepo: repository.Trigger(ctx, db),
|
||||
moduleRepo: repository.Module(ctx, db),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *trigger) FindByID(id uint64) (*types.Trigger, error) {
|
||||
return s.triggerRepo.FindByID(id)
|
||||
}
|
||||
|
||||
func (s *trigger) Find(filter types.TriggerFilter) (types.TriggerSet, error) {
|
||||
return s.triggerRepo.Find(filter)
|
||||
}
|
||||
|
||||
func (s *trigger) Create(trigger *types.Trigger) (p *types.Trigger, err error) {
|
||||
validate := func() error {
|
||||
return nil
|
||||
}
|
||||
if err := validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, s.db.Transaction(func() (err error) {
|
||||
p, err = s.triggerRepo.Create(trigger)
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
func (s *trigger) Update(trigger *types.Trigger) (p *types.Trigger, err error) {
|
||||
validate := func() error {
|
||||
if trigger.ID == 0 {
|
||||
return errors.New("could not update trigger, invalid ID")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, s.db.Transaction(func() (err error) {
|
||||
p, err = s.triggerRepo.Update(trigger)
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
func (s *trigger) DeleteByID(id uint64) error {
|
||||
return s.triggerRepo.DeleteByID(id)
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/crusttech/crust/crm/types"
|
||||
)
|
||||
|
||||
func TestTrigger(t *testing.T) {
|
||||
repository := Trigger().With(context.Background())
|
||||
|
||||
// the trigger object we're working with
|
||||
trigger := &types.Trigger{
|
||||
Name: "Test",
|
||||
ModuleID: 123,
|
||||
}
|
||||
|
||||
{
|
||||
{
|
||||
m, err := repository.Update(trigger)
|
||||
assert(t, m == nil, "Expected empty return for invalid update, got %#v", m)
|
||||
assert(t, err != nil, "Expected error when updating invalid content")
|
||||
}
|
||||
|
||||
// create trigger
|
||||
m, err := repository.Create(trigger)
|
||||
assert(t, err == nil, "Error when creating trigger: %+v", err)
|
||||
assert(t, m.ID > 0, "Expected auto generated ID")
|
||||
|
||||
{
|
||||
_, err := repository.Create(trigger)
|
||||
assert(t, err == nil, "Unexpected error when creating trigger, %+v", err)
|
||||
}
|
||||
|
||||
// fetch created trigger
|
||||
{
|
||||
ms, err := repository.FindByID(m.ID)
|
||||
assert(t, err == nil, "Error when retrieving trigger by id: %+v", err)
|
||||
assert(t, ms.ID == m.ID, "Expected ID from database to match, %+v", errors.Errorf("%d != %d", m.ID, ms.ID))
|
||||
assert(t, ms.Name == m.Name, "Expected Name from database to match, %+v", errors.Errorf("%s != %s", m.Name, ms.Name))
|
||||
}
|
||||
|
||||
// update created trigger
|
||||
{
|
||||
m.Name = "Updated test"
|
||||
_, err := repository.Update(m)
|
||||
assert(t, err == nil, "Error when updating trigger, %+v", err)
|
||||
}
|
||||
|
||||
// re-fetch trigger
|
||||
{
|
||||
ms, err := repository.FindByID(m.ID)
|
||||
assert(t, err == nil, "Error when retrieving trigger by id: %+v", err)
|
||||
assert(t, ms.ID == m.ID, "re-fetch: Expected ID from database to match, %d != %d", m.ID, ms.ID)
|
||||
assert(t, ms.Name == m.Name, "Expected Name from database to match, %s != %s", m.Name, ms.Name)
|
||||
}
|
||||
|
||||
// delete trigger
|
||||
{
|
||||
err := repository.DeleteByID(m.ID)
|
||||
assert(t, err == nil, "Error when deleting trigger by id: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
ActionSet []string
|
||||
Trigger struct {
|
||||
ID uint64 `json:"triggerID,string" db:"id"`
|
||||
ModuleID uint64 `json:"moduleID,string,omitempty" db:"rel_module"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Actions ActionSet `json:"actions" db:"actions"`
|
||||
Enabled bool `json:"enabled" db:"enabled"`
|
||||
Source string `json:"source" db:"source"`
|
||||
|
||||
CreatedAt time.Time `db:"created_at" json:"createdAt,omitempty"`
|
||||
UpdatedAt *time.Time `db:"updated_at" json:"updatedAt,omitempty"`
|
||||
DeletedAt *time.Time `db:"deleted_at" json:"deletedAt,omitempty"`
|
||||
}
|
||||
|
||||
TriggerFilter struct {
|
||||
ModuleID uint64 `json:"moduleID,string"`
|
||||
}
|
||||
)
|
||||
|
||||
func (set *ActionSet) Scan(src interface{}) error {
|
||||
if ser, ok := src.([]uint8); ok {
|
||||
var tmp = make([]string, 0)
|
||||
for _, a := range strings.Split(string(ser), ",") {
|
||||
if a = strings.TrimSpace(a); len(a) > 0 {
|
||||
tmp = append(tmp, a)
|
||||
}
|
||||
}
|
||||
|
||||
*set = ActionSet(tmp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (set ActionSet) Value() (driver.Value, error) {
|
||||
return strings.Trim(strings.Join(set, ","), " ,"), nil
|
||||
}
|
||||
@@ -19,6 +19,11 @@ type (
|
||||
// This type is auto-generated.
|
||||
ChartSet []*Chart
|
||||
|
||||
// TriggerSet slice of Trigger
|
||||
//
|
||||
// This type is auto-generated.
|
||||
TriggerSet []*Trigger
|
||||
|
||||
// ModuleFieldSet slice of ModuleField
|
||||
//
|
||||
// This type is auto-generated.
|
||||
@@ -193,6 +198,62 @@ func (set ChartSet) IDs() (IDs []uint64) {
|
||||
return
|
||||
}
|
||||
|
||||
// Walk iterates through every slice item and calls w(Trigger) err
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (set TriggerSet) Walk(w func(*Trigger) error) (err error) {
|
||||
for i := range set {
|
||||
if err = w(set[i]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Filter iterates through every slice item, calls f(Trigger) (bool, err) and return filtered slice
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (set TriggerSet) Filter(f func(*Trigger) (bool, error)) (out TriggerSet, err error) {
|
||||
var ok bool
|
||||
out = TriggerSet{}
|
||||
for i := range set {
|
||||
if ok, err = f(set[i]); err != nil {
|
||||
return
|
||||
} else if ok {
|
||||
out = append(out, set[i])
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// FindByID finds items from slice by its ID property
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (set TriggerSet) FindByID(ID uint64) *Trigger {
|
||||
for i := range set {
|
||||
if set[i].ID == ID {
|
||||
return set[i]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IDs returns a slice of uint64s from all items in the set
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (set TriggerSet) IDs() (IDs []uint64) {
|
||||
IDs = make([]uint64, len(set))
|
||||
|
||||
for i := range set {
|
||||
IDs[i] = set[i].ID
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Walk iterates through every slice item and calls w(ModuleField) err
|
||||
//
|
||||
// This function is auto-generated.
|
||||
|
||||
+26
-23
@@ -369,82 +369,85 @@ CRM module pages
|
||||
|
||||
|
||||
|
||||
# Workflows
|
||||
# Triggers
|
||||
|
||||
CRM workflow definitions
|
||||
CRM Triggers
|
||||
|
||||
## List available workflows
|
||||
## List available triggers
|
||||
|
||||
#### Method
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/workflow/` | HTTP/S | GET | |
|
||||
| `/trigger/` | HTTP/S | GET | |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Method | Description | Default | Required? |
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| moduleID | uint64 | GET | Filter triggers by module | N/A | NO |
|
||||
|
||||
## Create new workflow
|
||||
## Create trigger
|
||||
|
||||
#### Method
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/workflow/` | HTTP/S | POST | |
|
||||
| `/trigger/` | HTTP/S | POST | |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Method | Description | Default | Required? |
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| name | string | POST | Workflow name | N/A | YES |
|
||||
| tasks | types.WorkflowTaskSet | POST | Type ID | N/A | NO |
|
||||
| onError | types.WorkflowTaskSet | POST | Type ID | N/A | NO |
|
||||
| timeout | int | POST | Timeout in seconds | N/A | NO |
|
||||
| moduleID | uint64 | POST | Module ID | N/A | NO |
|
||||
| name | string | POST | Name | N/A | YES |
|
||||
| actions | []string | POST | Actions that trigger this trigger | N/A | NO |
|
||||
| enabled | bool | POST | Enabled | N/A | NO |
|
||||
| source | string | POST | Trigger source code | N/A | NO |
|
||||
|
||||
## Get workflow details
|
||||
## Get trigger details
|
||||
|
||||
#### Method
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/workflow/{workflowID}` | HTTP/S | GET | |
|
||||
| `/trigger/{triggerID}` | HTTP/S | GET | |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Method | Description | Default | Required? |
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| workflowID | string | PATH | Workflow ID | N/A | YES |
|
||||
| triggerID | uint64 | PATH | Trigger ID | N/A | YES |
|
||||
|
||||
## Update workflow details
|
||||
## Edit trigger
|
||||
|
||||
#### Method
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/workflow/{workflowID}` | HTTP/S | POST | |
|
||||
| `/trigger/{triggerID}` | HTTP/S | POST | |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Method | Description | Default | Required? |
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| workflowID | string | PATH | Workflow ID | N/A | YES |
|
||||
| name | string | POST | Workflow name | N/A | YES |
|
||||
| tasks | types.WorkflowTaskSet | POST | Type ID | N/A | NO |
|
||||
| onError | types.WorkflowTaskSet | POST | Type ID | N/A | NO |
|
||||
| timeout | int | POST | Timeout in seconds | N/A | NO |
|
||||
| triggerID | uint64 | PATH | Trigger ID | N/A | YES |
|
||||
| moduleID | uint64 | POST | Module ID | N/A | NO |
|
||||
| name | string | POST | Name | N/A | YES |
|
||||
| actions | []string | POST | Actions that trigger this trigger | N/A | NO |
|
||||
| enabled | bool | POST | Enabled | N/A | NO |
|
||||
| source | string | POST | Trigger source code | N/A | NO |
|
||||
|
||||
## Delete workflow
|
||||
## Delete trigger
|
||||
|
||||
#### Method
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/workflow/{workflowID}` | HTTP/S | DELETE | |
|
||||
| `/trigger/{triggerID}` | HTTP/S | Delete | |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Method | Description | Default | Required? |
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| workflowID | string | PATH | Workflow ID | N/A | YES |
|
||||
| triggerID | uint64 | PATH | Trigger ID | N/A | YES |
|
||||
Reference in New Issue
Block a user