Add configuration revisions system and project variant deletion

Features:
- Configuration versioning: immutable snapshots in local_configuration_versions
- Revisions UI: /configs/:uuid/revisions page to view version history
- Clone from version: ability to clone configuration from specific revision
- Project variant deletion: DELETE /api/projects/:uuid endpoint
- Updated CLAUDE.md with new architecture details and endpoints

Architecture updates:
- local_configuration_versions table for immutable snapshots
- Version tracking on each configuration save
- Rollback capability to previous versions
- Variant deletion with main variant protection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-16 22:30:33 +03:00
parent 8508ee2921
commit 2e973b6d78
10 changed files with 407 additions and 17 deletions

View File

@@ -930,6 +930,7 @@ func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.Connect
router.GET("/configurator", webHandler.Configurator)
router.GET("/projects", webHandler.Projects)
router.GET("/projects/:uuid", webHandler.ProjectDetail)
router.GET("/configs/:uuid/revisions", webHandler.ConfigRevisions)
router.GET("/pricelists", webHandler.Pricelists)
router.GET("/pricelists/:id", webHandler.PricelistDetail)
@@ -1129,15 +1130,20 @@ func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.Connect
configs.POST("/:uuid/clone", func(c *gin.Context) {
uuid := c.Param("uuid")
var req struct {
Name string `json:"name"`
Name string `json:"name"`
FromVersion int `json:"from_version"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
config, err := configService.CloneNoAuth(uuid, req.Name, dbUsername)
config, err := configService.CloneNoAuthToProjectFromVersion(uuid, req.Name, dbUsername, nil, req.FromVersion)
if err != nil {
if errors.Is(err, services.ErrConfigVersionNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "version not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
@@ -1579,6 +1585,23 @@ func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.Connect
c.JSON(http.StatusOK, gin.H{"message": "project reactivated"})
})
projects.DELETE("/:uuid", func(c *gin.Context) {
if err := projectService.DeleteVariant(c.Param("uuid"), dbUsername); err != nil {
switch {
case errors.Is(err, services.ErrCannotDeleteMainVariant):
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
case errors.Is(err, services.ErrProjectNotFound):
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
case errors.Is(err, services.ErrProjectForbidden):
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
default:
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
return
}
c.JSON(http.StatusOK, gin.H{"message": "variant deleted"})
})
projects.GET("/:uuid/configs", func(c *gin.Context) {
triggerPull("configs", &configsPullState, syncConfigurationsFromServer)