Skip to content

Commit 88fc4ef

Browse files
committed
Implement more workflow management endpoints.
1 parent 4fac571 commit 88fc4ef

File tree

9 files changed

+333
-33
lines changed

9 files changed

+333
-33
lines changed

api/handle_workflows.go

Lines changed: 145 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,19 @@ func handleListWorkflowsForScenario(uc usecases.Usecases) func(c *gin.Context) {
3131
func handleCreateWorkflowRule(uc usecases.Usecases) func(c *gin.Context) {
3232
return func(c *gin.Context) {
3333
ctx := c.Request.Context()
34-
scenarioId := c.Param("scenarioId")
3534

36-
var payload dto.PostWorkflowRuleDto
35+
var payload dto.CreateWorkflowRuleDto
3736

3837
if err := c.ShouldBindJSON(&payload); presentError(ctx, c, err) {
38+
c.Status(http.StatusBadRequest)
3939
return
4040
}
4141

4242
uc := usecasesWithCreds(ctx, uc)
4343
scenarioUsecase := uc.NewScenarioUsecase()
4444

4545
params := models.WorkflowRule{
46-
ScenarioId: scenarioId,
46+
ScenarioId: payload.ScenarioId,
4747
Name: payload.Name,
4848
}
4949

@@ -59,22 +59,21 @@ func handleCreateWorkflowRule(uc usecases.Usecases) func(c *gin.Context) {
5959
func handleUpdateWorkflowRule(uc usecases.Usecases) func(c *gin.Context) {
6060
return func(c *gin.Context) {
6161
ctx := c.Request.Context()
62-
scenarioId := c.Param("scenarioId")
6362
ruleId := c.Param("ruleId")
6463

65-
var payload dto.PostWorkflowRuleDto
64+
var payload dto.UpdateWorkflowRuleDto
6665

6766
if err := c.ShouldBindJSON(&payload); presentError(ctx, c, err) {
67+
c.Status(http.StatusBadRequest)
6868
return
6969
}
7070

7171
uc := usecasesWithCreds(ctx, uc)
7272
scenarioUsecase := uc.NewScenarioUsecase()
7373

7474
params := models.WorkflowRule{
75-
Id: ruleId,
76-
ScenarioId: scenarioId,
77-
Name: payload.Name,
75+
Id: ruleId,
76+
Name: payload.Name,
7877
}
7978

8079
rule, err := scenarioUsecase.UpdateWorkflowRule(ctx, params)
@@ -86,6 +85,144 @@ func handleUpdateWorkflowRule(uc usecases.Usecases) func(c *gin.Context) {
8685
}
8786
}
8887

88+
func handleCreateWorkflowCondition(uc usecases.Usecases) func(c *gin.Context) {
89+
return func(c *gin.Context) {
90+
ctx := c.Request.Context()
91+
ruleId := c.Param("ruleId")
92+
93+
var payload dto.PostWorkflowConditionDto
94+
95+
if err := c.ShouldBindJSON(&payload); presentError(ctx, c, err) {
96+
c.Status(http.StatusBadRequest)
97+
return
98+
}
99+
if err := dto.ValidateWorkflowCondition(payload); presentError(ctx, c, err) {
100+
c.Status(http.StatusBadRequest)
101+
return
102+
}
103+
104+
uc := usecasesWithCreds(ctx, uc)
105+
scenarioUsecase := uc.NewScenarioUsecase()
106+
107+
params := models.WorkflowCondition{
108+
RuleId: ruleId,
109+
Function: payload.Function,
110+
Params: payload.Params,
111+
}
112+
113+
condition, err := scenarioUsecase.CreateWorkflowCondition(ctx, params)
114+
if presentError(ctx, c, err) {
115+
return
116+
}
117+
118+
c.JSON(http.StatusCreated, dto.AdaptWorkflowCondition(condition))
119+
}
120+
}
121+
122+
func handleUpdateWorkflowCondition(uc usecases.Usecases) func(c *gin.Context) {
123+
return func(c *gin.Context) {
124+
ctx := c.Request.Context()
125+
ruleId := c.Param("ruleId")
126+
conditionId := c.Param("conditionId")
127+
128+
var payload dto.PostWorkflowConditionDto
129+
130+
if err := c.ShouldBindJSON(&payload); presentError(ctx, c, err) {
131+
c.Status(http.StatusBadRequest)
132+
return
133+
}
134+
if err := dto.ValidateWorkflowCondition(payload); presentError(ctx, c, err) {
135+
c.Status(http.StatusBadRequest)
136+
return
137+
}
138+
139+
uc := usecasesWithCreds(ctx, uc)
140+
scenarioUsecase := uc.NewScenarioUsecase()
141+
142+
params := models.WorkflowCondition{
143+
Id: conditionId,
144+
RuleId: ruleId,
145+
Function: payload.Function,
146+
Params: payload.Params,
147+
}
148+
149+
condition, err := scenarioUsecase.UpdateWorkflowCondition(ctx, params)
150+
if presentError(ctx, c, err) {
151+
return
152+
}
153+
154+
c.JSON(http.StatusCreated, dto.AdaptWorkflowCondition(condition))
155+
}
156+
}
157+
158+
func handleCreateWorkflowAction(uc usecases.Usecases) func(c *gin.Context) {
159+
return func(c *gin.Context) {
160+
ctx := c.Request.Context()
161+
ruleId := c.Param("ruleId")
162+
163+
var payload dto.PostWorkflowActionDto
164+
165+
if err := c.ShouldBindJSON(&payload); presentError(ctx, c, err) {
166+
return
167+
}
168+
if err := dto.ValidateWorkflowAction(payload); presentError(ctx, c, err) {
169+
c.Status(http.StatusBadRequest)
170+
return
171+
}
172+
173+
uc := usecasesWithCreds(ctx, uc)
174+
scenarioUsecase := uc.NewScenarioUsecase()
175+
176+
params := models.WorkflowAction{
177+
RuleId: ruleId,
178+
Action: payload.Action,
179+
Params: payload.Params,
180+
}
181+
182+
action, err := scenarioUsecase.CreateWorkflowAction(ctx, params)
183+
if presentError(ctx, c, err) {
184+
return
185+
}
186+
187+
c.JSON(http.StatusCreated, dto.AdaptWorkflowAction(action))
188+
}
189+
}
190+
191+
func handleUpdateWorkflowAction(uc usecases.Usecases) func(c *gin.Context) {
192+
return func(c *gin.Context) {
193+
ctx := c.Request.Context()
194+
ruleId := c.Param("ruleId")
195+
actionId := c.Param("actionId")
196+
197+
var payload dto.PostWorkflowActionDto
198+
199+
if err := c.ShouldBindJSON(&payload); presentError(ctx, c, err) {
200+
return
201+
}
202+
if err := dto.ValidateWorkflowAction(payload); presentError(ctx, c, err) {
203+
c.Status(http.StatusBadRequest)
204+
return
205+
}
206+
207+
uc := usecasesWithCreds(ctx, uc)
208+
scenarioUsecase := uc.NewScenarioUsecase()
209+
210+
params := models.WorkflowAction{
211+
Id: actionId,
212+
RuleId: ruleId,
213+
Action: payload.Action,
214+
Params: payload.Params,
215+
}
216+
217+
action, err := scenarioUsecase.UpdateWorkflowAction(ctx, params)
218+
if presentError(ctx, c, err) {
219+
return
220+
}
221+
222+
c.JSON(http.StatusCreated, dto.AdaptWorkflowAction(action))
223+
}
224+
}
225+
89226
func handleReorderWorkflowRules(uc usecases.Usecases) func(c *gin.Context) {
90227
return func(c *gin.Context) {
91228
ctx := c.Request.Context()

api/routes.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,10 @@ func addRoutes(r *gin.Engine, conf Configuration, uc usecases.Usecases, auth uti
290290

291291
router.GET("/workflows/:scenarioId", tom, handleListWorkflowsForScenario(uc))
292292
router.POST("/workflows/:scenarioId/reorder", tom, handleReorderWorkflowRules(uc))
293-
router.POST("/workflows/:scenarioId/rule", tom, handleCreateWorkflowRule(uc))
294-
router.PUT("/workflows/:scenarioId/rule/:ruleId", tom, handleUpdateWorkflowRule(uc))
293+
router.POST("/workflows/rule", tom, handleCreateWorkflowRule(uc))
294+
router.PUT("/workflows/rule/:ruleId", tom, handleUpdateWorkflowRule(uc))
295+
router.POST("/workflows/rule/:ruleId/condition", tom, handleCreateWorkflowCondition(uc))
296+
router.PUT("/workflows/rule/:ruleId/condition/:conditionId", tom, handleUpdateWorkflowCondition(uc))
297+
router.POST("/workflows/rule/:ruleId/action", tom, handleCreateWorkflowAction(uc))
298+
router.PUT("/workflows/rule/:ruleId/action/:actionId", tom, handleUpdateWorkflowAction(uc))
295299
}

dto/workflows.go

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,45 @@ import (
55

66
"github.com/checkmarble/marble-backend/models"
77
"github.com/checkmarble/marble-backend/pure_utils"
8+
"github.com/cockroachdb/errors"
9+
"github.com/gin-gonic/gin/binding"
10+
"github.com/go-playground/validator/v10"
811
)
912

1013
type WorkflowRuleDto struct {
1114
Id string `json:"id"`
1215
Name string `json:"name"`
1316
}
1417

15-
type PostWorkflowRuleDto struct {
18+
type CreateWorkflowRuleDto struct {
19+
ScenarioId string `json:"scenario_id" binding:"required,uuid"`
20+
Name string `json:"name" binding:"required"`
21+
}
22+
23+
type UpdateWorkflowRuleDto struct {
1624
Name string `json:"name" binding:"required"`
1725
}
1826

1927
type WorkflowConditionDto struct {
20-
Id string `json:"id"`
21-
Function string `json:"function"`
22-
Params json.RawMessage `json:"params"`
28+
Id string `json:"id"`
29+
Function models.WorkflowConditionType `json:"function"`
30+
Params json.RawMessage `json:"params,omitempty"`
31+
}
32+
33+
type PostWorkflowConditionDto struct {
34+
Function models.WorkflowConditionType `json:"function" binding:"required"`
35+
Params json.RawMessage `json:"params"`
2336
}
2437

2538
type WorkflowActionDto struct {
2639
Id string `json:"id"`
2740
Action string `json:"action"`
28-
Params json.RawMessage `json:"params"`
41+
Params json.RawMessage `json:"params,omitempty"`
42+
}
43+
44+
type PostWorkflowActionDto struct {
45+
Action models.WorkflowType `json:"action" binding:"required"`
46+
Params json.RawMessage `json:"params"`
2947
}
3048

3149
type WorkflowDto struct {
@@ -65,3 +83,38 @@ func AdaptWorkflowAction(m models.WorkflowAction) WorkflowActionDto {
6583
Params: m.Params,
6684
}
6785
}
86+
87+
func ValidateWorkflowCondition(cond PostWorkflowConditionDto) error {
88+
switch cond.Function {
89+
case models.WorkflowConditionAlways, models.WorkflowConditionNever:
90+
if cond.Params != nil {
91+
return errors.Wrapf(models.BadParameterError, "workflow condition %s does not take parameters", cond.Function)
92+
}
93+
case models.WorkflowConditionIfOutcomeIn:
94+
if err := json.Unmarshal(cond.Params, new([]string)); err != nil {
95+
return errors.Join(models.BadParameterError, json.Unmarshal(cond.Params, new([]string)))
96+
}
97+
default:
98+
return errors.Wrapf(models.BadParameterError, "unknown workflow condition type: %s", cond.Function)
99+
}
100+
101+
return nil
102+
}
103+
104+
func ValidateWorkflowAction(cond PostWorkflowActionDto) error {
105+
switch cond.Action {
106+
case models.WorkflowCreateCase, models.WorkflowAddToCaseIfPossible:
107+
var params models.WorkflowCaseParams
108+
109+
if err := json.Unmarshal(cond.Params, &params); err != nil {
110+
return errors.Join(models.BadParameterError, json.Unmarshal(cond.Params, new(models.WorkflowCaseParams)))
111+
}
112+
if err := binding.Validator.Engine().(*validator.Validate).Struct(params); err != nil {
113+
return errors.Join(models.BadParameterError, err)
114+
}
115+
default:
116+
return errors.Wrapf(models.BadParameterError, "unknown workflow action type: %s", cond.Action)
117+
}
118+
119+
return nil
120+
}

integration_test/scenario_flow_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -414,16 +414,16 @@ func setupScenarioAndPublish(
414414
assert.FailNow(t, "Could not create workflow rule", err)
415415
}
416416

417-
_, err = scenarioUsecase.CreateWorkflowCondition(ctx, organizationId, models.WorkflowCondition{
417+
_, err = scenarioUsecase.CreateWorkflowCondition(ctx, models.WorkflowCondition{
418418
RuleId: rule.Id,
419-
Function: "if_outcome_in",
419+
Function: models.WorkflowConditionIfOutcomeIn,
420420
Params: []byte(`["decline", "review"]`),
421421
})
422422
if err != nil {
423423
assert.FailNow(t, "Could not create workflow condition", err)
424424
}
425425

426-
_, err = scenarioUsecase.CreateWorkflowAction(ctx, organizationId, models.WorkflowAction{
426+
_, err = scenarioUsecase.CreateWorkflowAction(ctx, models.WorkflowAction{
427427
RuleId: rule.Id,
428428
Action: models.WorkflowAddToCaseIfPossible,
429429
Params: fmt.Appendf(nil, `{"inbox_id": "%s"}`, inboxId),

models/workflows.go

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,40 @@ type WorkflowRule struct {
2626
UpdatedAt *time.Time
2727
}
2828

29+
type WorkflowConditionType string
30+
31+
const (
32+
WorkflowConditionUnknown WorkflowConditionType = "unknown"
33+
WorkflowConditionAlways = "always"
34+
WorkflowConditionNever = "never"
35+
WorkflowConditionIfOutcomeIn = "if_outcome_in"
36+
)
37+
38+
var (
39+
ValidWorkflowConditions = []WorkflowConditionType{
40+
WorkflowConditionAlways,
41+
WorkflowConditionNever,
42+
WorkflowConditionIfOutcomeIn,
43+
}
44+
)
45+
46+
func WorkflowConditionFromString(s string) WorkflowConditionType {
47+
switch s {
48+
case WorkflowConditionAlways:
49+
return WorkflowConditionAlways
50+
case WorkflowConditionNever:
51+
return WorkflowConditionNever
52+
case WorkflowConditionIfOutcomeIn:
53+
return WorkflowConditionIfOutcomeIn
54+
default:
55+
return WorkflowConditionUnknown
56+
}
57+
}
58+
2959
type WorkflowCondition struct {
3060
Id string
3161
RuleId string
32-
Function string
62+
Function WorkflowConditionType
3363
Params json.RawMessage
3464

3565
CreatedAt time.Time
@@ -67,8 +97,8 @@ type WorkflowActionSpec[T any] struct {
6797
}
6898

6999
type WorkflowCaseParams struct {
70-
InboxId *uuid.UUID `json:"inbox_id"`
71-
TitleTemplate *ast.Node `json:"title_template"`
100+
InboxId uuid.UUID `json:"inbox_id" binding:"required,uuid"`
101+
TitleTemplate *ast.Node `json:"title_template"`
72102
}
73103

74104
type WorkflowExecution struct {

repositories/dbmodels/db_scenario_workflows.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func AdaptWorkflowCondition(db DbWorkflowCondition) (models.WorkflowCondition, e
6969
return models.WorkflowCondition{
7070
Id: db.Id,
7171
RuleId: db.RuleId,
72-
Function: db.Function,
72+
Function: models.WorkflowConditionFromString(db.Function),
7373
Params: db.Params,
7474
CreatedAt: db.CreatedAt,
7575
UpdatedAt: db.UpdatedAt,

0 commit comments

Comments
 (0)