From dc2fd43928ba4a2cf76cf2be4da504d2f92c14da Mon Sep 17 00:00:00 2001 From: Dmitry Ng <19asdek91@gmail.com> Date: Fri, 8 May 2026 18:54:14 +0300 Subject: [PATCH] feat(assistants): support flowID=0 in REST API to create assistant with new flow - Allow POST /flows/0/assistants/ to create a new flow together with the assistant, mirroring the existing GraphQL createAssistant(flowID: 0) behavior - Require both assistants.create and flows.create permissions when flowID=0 - Add explicit flow ownership check for non-zero flowID using flows.admin scope - Load flow data in the response by fetching it via assistant.FlowID after creation, ensuring AssistantFlow is fully populated in all cases --- backend/pkg/server/docs/docs.go | 2 +- backend/pkg/server/docs/swagger.json | 2 +- backend/pkg/server/docs/swagger.yaml | 2 +- backend/pkg/server/services/assistants.go | 53 ++++++++++++++++++----- 4 files changed, 46 insertions(+), 13 deletions(-) diff --git a/backend/pkg/server/docs/docs.go b/backend/pkg/server/docs/docs.go index ddd07d2f..0c5d5236 100644 --- a/backend/pkg/server/docs/docs.go +++ b/backend/pkg/server/docs/docs.go @@ -1390,7 +1390,7 @@ const docTemplate = `{ { "minimum": 0, "type": "integer", - "description": "flow id", + "description": "flow id (use 0 to create a new flow together with the assistant)", "name": "flowID", "in": "path", "required": true diff --git a/backend/pkg/server/docs/swagger.json b/backend/pkg/server/docs/swagger.json index 9a629ab3..c90caccb 100644 --- a/backend/pkg/server/docs/swagger.json +++ b/backend/pkg/server/docs/swagger.json @@ -1382,7 +1382,7 @@ { "minimum": 0, "type": "integer", - "description": "flow id", + "description": "flow id (use 0 to create a new flow together with the assistant)", "name": "flowID", "in": "path", "required": true diff --git a/backend/pkg/server/docs/swagger.yaml b/backend/pkg/server/docs/swagger.yaml index 1c151407..975abf8c 100644 --- a/backend/pkg/server/docs/swagger.yaml +++ b/backend/pkg/server/docs/swagger.yaml @@ -2903,7 +2903,7 @@ paths: consumes: - application/json parameters: - - description: flow id + - description: flow id (use 0 to create a new flow together with the assistant) in: path minimum: 0 name: flowID diff --git a/backend/pkg/server/services/assistants.go b/backend/pkg/server/services/assistants.go index 16eb0661..2000934c 100644 --- a/backend/pkg/server/services/assistants.go +++ b/backend/pkg/server/services/assistants.go @@ -234,7 +234,7 @@ func (s *AssistantService) GetFlowAssistant(c *gin.Context) { // @Accept json // @Produce json // @Security BearerAuth -// @Param flowID path int true "flow id" minimum(0) +// @Param flowID path int true "flow id (use 0 to create a new flow together with the assistant)" minimum(0) // @Param json body models.CreateAssistant true "assistant model to create" // @Success 201 {object} response.successResp{data=models.AssistantFlow} "assistant created successful" // @Failure 400 {object} response.errorResp "invalid assistant request data" @@ -245,7 +245,7 @@ func (s *AssistantService) CreateFlowAssistant(c *gin.Context) { var ( err error flowID uint64 - assistant models.AssistantFlow + resp models.AssistantFlow createAssistant models.CreateAssistant ) @@ -267,14 +267,42 @@ func (s *AssistantService) CreateFlowAssistant(c *gin.Context) { return } + uid := c.GetUint64("uid") privs := c.GetStringSlice("prm") - if !slices.Contains(privs, "assistants.create") { - logger.FromContext(c).Errorf("error filtering user role permissions: permission not found") - response.Error(c, response.ErrNotPermitted, nil) - return + + if flowID == 0 { + // Creating a new flow together with the assistant requires both permissions. + if !slices.Contains(privs, "assistants.create") || !slices.Contains(privs, "flows.create") { + logger.FromContext(c).Errorf("error filtering user role permissions: permission not found") + response.Error(c, response.ErrNotPermitted, nil) + return + } + } else { + if !slices.Contains(privs, "assistants.create") { + logger.FromContext(c).Errorf("error filtering user role permissions: permission not found") + response.Error(c, response.ErrNotPermitted, nil) + return + } + + // Verify the flow exists and belongs to the user (unless admin). + var flow models.Flow + var flowScope func(db *gorm.DB) *gorm.DB + if slices.Contains(privs, "flows.admin") { + flowScope = func(db *gorm.DB) *gorm.DB { return db.Where("id = ?", flowID) } + } else { + flowScope = func(db *gorm.DB) *gorm.DB { return db.Where("id = ? AND user_id = ?", flowID, uid) } + } + if err = s.db.Model(&flow).Scopes(flowScope).Take(&flow).Error; err != nil { + logger.FromContext(c).WithError(err).Errorf("error getting flow by id") + if gorm.IsRecordNotFoundError(err) { + response.Error(c, response.ErrFlowsNotFound, err) + } else { + response.Error(c, response.ErrInternal, err) + } + return + } } - uid := c.GetUint64("uid") prvname := provider.ProviderName(createAssistant.Provider) prv, err := s.pc.GetProvider(c, prvname, int64(uid)) @@ -309,14 +337,19 @@ func (s *AssistantService) CreateFlowAssistant(c *gin.Context) { return } - err = s.db.Model(&assistant).Where("id = ?", aw.GetAssistantID()).Take(&assistant).Error - if err != nil { + if err = s.db.Model(&resp.Assistant).Where("id = ?", aw.GetAssistantID()).Take(&resp.Assistant).Error; err != nil { logger.FromContext(c).WithError(err).Errorf("error getting assistant by id") response.Error(c, response.ErrInternal, err) return } - response.Success(c, http.StatusCreated, assistant) + if err = s.db.Model(&resp.Flow).Where("id = ?", resp.Assistant.FlowID).Take(&resp.Flow).Error; err != nil { + logger.FromContext(c).WithError(err).Errorf("error getting flow by id") + response.Error(c, response.ErrInternal, err) + return + } + + response.Success(c, http.StatusCreated, resp) } // PatchAssistant is a function to patch assistant