fix(subscriptions): reject concurrent duplicate rules

Fixes #66
This commit is contained in:
ShukeBta
2026-08-10 19:30:17 +08:00
parent c32f23a626
commit c0d743d89d
18 changed files with 584 additions and 10 deletions
+22
View File
@@ -0,0 +1,22 @@
package handler
import (
"errors"
"net/http"
"github.com/gin-gonic/gin"
"github.com/ShukeBta/MediaStationGo/internal/service"
)
func writeSubscriptionConflict(c *gin.Context, err error) bool {
if !errors.Is(err, service.ErrSubscriptionAlreadyExists) {
return false
}
body := gin.H{"error": "subscription already exists"}
if existingID := service.SubscriptionAlreadyExistsID(err); existingID != "" {
body["existing_id"] = existingID
}
c.JSON(http.StatusConflict, body)
return true
}
@@ -0,0 +1,32 @@
package handler
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/ShukeBta/MediaStationGo/internal/service"
)
func TestWriteSubscriptionConflictReturns409WithExistingID(t *testing.T) {
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
err := &service.SubscriptionAlreadyExistsError{ExistingID: "existing-sub"}
if !writeSubscriptionConflict(c, err) {
t.Fatal("expected conflict to be handled")
}
if w.Code != http.StatusConflict {
t.Fatalf("status = %d body=%s", w.Code, w.Body.String())
}
var body map[string]any
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
t.Fatal(err)
}
if body["existing_id"] != "existing-sub" {
t.Fatalf("body = %#v", body)
}
}
+4 -4
View File
@@ -58,14 +58,14 @@ func updateSubscriptionHandler(svc *service.Container) gin.HandlerFunc {
c.Status(http.StatusNoContent)
return
}
if err := svc.Repo.DB.WithContext(c.Request.Context()).
Model(&model.Subscription{}).
Where("id = ?", c.Param("id")).
Updates(updates).Error; err != nil {
if err := svc.Subscription.Update(c.Request.Context(), c.Param("id"), updates); err != nil {
logSubscriptionWarn(svc, "subscription update failed",
zap.String("user_id", subscriptionRequestUserID(c)),
zap.String("subscription_id", c.Param("id")),
zap.Error(err))
if writeSubscriptionConflict(c, err) {
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
+6
View File
@@ -97,6 +97,9 @@ func createSubscriptionHandler(svc *service.Container) gin.HandlerFunc {
zap.String("feed_kind", subscriptionFeedKind(req.FeedURL)),
zap.Bool("enabled", enabled),
zap.Error(err))
if writeSubscriptionConflict(c, err) {
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
@@ -199,6 +202,9 @@ func restoreSubscriptionHandler(svc *service.Container) gin.HandlerFunc {
zap.String("user_id", subscriptionRequestUserID(c)),
zap.String("subscription_id", c.Param("id")),
zap.Error(err))
if writeSubscriptionConflict(c, err) {
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}