Feat v0.3.5 settings icd clone (#19)
All checks were successful
All checks were successful
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #19.
This commit is contained in:
@@ -376,6 +376,98 @@ func (h *WorkflowHandler) GetVersion(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, v)
|
||||
}
|
||||
|
||||
// ── Clone ────────────────────────────────────
|
||||
|
||||
// Clone deep-copies a workflow and all its stages.
|
||||
// POST /api/v1/workflows/:id/clone
|
||||
func (h *WorkflowHandler) Clone(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
srcID := c.Param("id")
|
||||
|
||||
src, err := h.stores.Workflows.GetByID(ctx, srcID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "workflow not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load workflow"})
|
||||
return
|
||||
}
|
||||
|
||||
stages, err := h.stores.Workflows.ListStages(ctx, srcID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load stages"})
|
||||
return
|
||||
}
|
||||
|
||||
clone := &models.Workflow{
|
||||
TeamID: src.TeamID,
|
||||
Name: "Copy of " + src.Name,
|
||||
Slug: src.Slug + "-copy",
|
||||
Description: src.Description,
|
||||
Branding: src.Branding,
|
||||
EntryMode: src.EntryMode,
|
||||
IsActive: false,
|
||||
Version: 0,
|
||||
OnComplete: src.OnComplete,
|
||||
Retention: src.Retention,
|
||||
WebhookURL: src.WebhookURL,
|
||||
WebhookSecret: src.WebhookSecret,
|
||||
StalenessTimeoutHours: src.StalenessTimeoutHours,
|
||||
CreatedBy: c.GetString("user_id"),
|
||||
}
|
||||
|
||||
// v0.31.2: team-scoped route injects force_team_id
|
||||
if ftid, ok := c.Get("force_team_id"); ok {
|
||||
tid := ftid.(string)
|
||||
clone.TeamID = &tid
|
||||
}
|
||||
|
||||
if err := h.stores.Workflows.Create(ctx, clone); err != nil {
|
||||
if strings.Contains(err.Error(), "unique") || strings.Contains(err.Error(), "UNIQUE") {
|
||||
// Slug collision — append short suffix
|
||||
clone.Slug = src.Slug + "-copy-" + store.NewID()[:6]
|
||||
if err2 := h.stores.Workflows.Create(ctx, clone); err2 != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create clone: " + err2.Error()})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create clone: " + err.Error()})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, st := range stages {
|
||||
cloneSt := &models.WorkflowStage{
|
||||
WorkflowID: clone.ID,
|
||||
Ordinal: st.Ordinal,
|
||||
Name: st.Name,
|
||||
AssignmentTeamID: st.AssignmentTeamID,
|
||||
FormTemplate: st.FormTemplate,
|
||||
StageMode: st.StageMode,
|
||||
Audience: st.Audience,
|
||||
StageType: st.StageType,
|
||||
AutoTransition: st.AutoTransition,
|
||||
StageConfig: st.StageConfig,
|
||||
BranchRules: st.BranchRules,
|
||||
StarlarkHook: st.StarlarkHook,
|
||||
SurfacePkgID: st.SurfacePkgID,
|
||||
SLASeconds: st.SLASeconds,
|
||||
}
|
||||
if err := h.stores.Workflows.CreateStage(ctx, cloneSt); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to clone stage: " + err.Error()})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
clonedStages, _ := h.stores.Workflows.ListStages(ctx, clone.ID)
|
||||
if clonedStages == nil {
|
||||
clonedStages = []models.WorkflowStage{}
|
||||
}
|
||||
clone.Stages = clonedStages
|
||||
c.JSON(http.StatusCreated, clone)
|
||||
}
|
||||
|
||||
// ── Helpers ─────────────────────────────────
|
||||
|
||||
func slugify(name string) string {
|
||||
|
||||
Reference in New Issue
Block a user