fix: невалидное имя варианта проекта — предупреждение вместо ошибки

Имя варианта "main" или с недопустимыми символами больше не отклоняет
запрос ошибкой 400 — теперь автоматически конвертируется в валидное
(sanitizeProjectVariantName), а API/UI показывают variant_warning вместо
блокировки создания/переименования варианта.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-09-17 11:56:55 +03:00
co-authored by Claude Sonnet 5
parent 006d7e87c5
commit 2f3bdc609a
6 changed files with 140 additions and 80 deletions
+12 -8
View File
@@ -1613,6 +1613,14 @@ func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.Connect
c.JSON(http.StatusOK, simplified)
})
// ProjectWithWarning flattens a project's fields (via embedding) alongside an
// optional variant_warning, set when an invalid variant name (reserved "main" or
// disallowed characters) was auto-converted into a valid one instead of rejected.
type ProjectWithWarning struct {
*models.Project
VariantWarning string `json:"variant_warning,omitempty"`
}
projects.POST("", func(c *gin.Context) {
var req services.CreateProjectRequest
if err := c.ShouldBindJSON(&req); err != nil {
@@ -1623,17 +1631,15 @@ func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.Connect
c.JSON(http.StatusBadRequest, gin.H{"error": "project code is required"})
return
}
project, err := projectService.Create(dbUsername, &req)
project, variantWarning, err := projectService.Create(dbUsername, &req)
if err != nil {
respondByErrCase(c, err,
errCase{services.ErrReservedMainVariant, http.StatusBadRequest, "invalid request"},
errCase{services.ErrProjectCodeInvalidChars, http.StatusBadRequest, "invalid request"},
errCase{services.ErrProjectVariantInvalidChars, http.StatusBadRequest, "invalid request"},
errCase{services.ErrProjectCodeExists, http.StatusConflict, "conflict detected"},
)
return
}
c.JSON(http.StatusCreated, project)
c.JSON(http.StatusCreated, ProjectWithWarning{Project: project, VariantWarning: variantWarning})
})
projects.GET("/:uuid", func(c *gin.Context) {
@@ -1654,20 +1660,18 @@ func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.Connect
respondError(c, http.StatusBadRequest, "invalid request", err)
return
}
project, err := projectService.Update(c.Param("uuid"), dbUsername, &req)
project, variantWarning, err := projectService.Update(c.Param("uuid"), dbUsername, &req)
if err != nil {
respondByErrCase(c, err,
errCase{services.ErrReservedMainVariant, http.StatusBadRequest, "invalid request"},
errCase{services.ErrCannotRenameMainVariant, http.StatusBadRequest, "invalid request"},
errCase{services.ErrProjectCodeInvalidChars, http.StatusBadRequest, "invalid request"},
errCase{services.ErrProjectVariantInvalidChars, http.StatusBadRequest, "invalid request"},
errCase{services.ErrProjectCodeExists, http.StatusConflict, "conflict detected"},
errCase{services.ErrProjectNotFound, http.StatusNotFound, "resource not found"},
errCase{services.ErrProjectForbidden, http.StatusForbidden, "access denied"},
)
return
}
c.JSON(http.StatusOK, project)
c.JSON(http.StatusOK, ProjectWithWarning{Project: project, VariantWarning: variantWarning})
})
projects.POST("/:uuid/archive", func(c *gin.Context) {