Make repeated channel-monitor setup safer

Admins often recreate monitors with the same endpoint, model, and request settings. A server-side duplicate keeps the stored API key out of the browser, creates a disabled copy for review, and uses stable operation identity to recover ambiguous retries without creating extra rows.

Constraint: Stored monitor API keys must never be returned to the browser
Constraint: Applying a request template must preserve internal duplicate recovery metadata
Rejected: Rebuild the monitor from list data | list responses only contain a masked API key
Rejected: Copy runtime state and history | a duplicate should start as an unverified configuration
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep duplicated monitors disabled until an administrator reviews and enables them
Tested: Go unit tests for repository, service, and admin handler; integration-tag compile; go vet; golangci-lint v2.9; frontend Vitest, ESLint, typecheck, production build; Playwright duplicate flow
Not-tested: PostgreSQL container integration locally because Docker is unavailable; CI will execute the database-backed suite
This commit is contained in:
yan9651688
2026-07-16 16:30:19 +08:00
parent 393a8fe56a
commit e2e375d694
19 changed files with 1512 additions and 29 deletions
@@ -1,6 +1,8 @@
package admin
import (
"context"
"log/slog"
"strconv"
"strings"
"time"
@@ -333,6 +335,63 @@ func (h *ChannelMonitorHandler) Create(c *gin.Context) {
response.Created(c, channelMonitorToResponse(m))
}
// Duplicate POST /api/v1/admin/channel-monitors/:id/duplicate
func (h *ChannelMonitorHandler) Duplicate(c *gin.Context) {
id, ok := ParseChannelMonitorID(c)
if !ok {
return
}
subject, _ := middleware2.GetAuthSubjectFromContext(c)
actorScope := adminActorScope(c)
result, err := executeAdminIdempotent(
c,
"admin.channel_monitors.duplicate",
struct {
MonitorID int64 `json:"monitor_id"`
}{MonitorID: id},
service.DefaultWriteIdempotencyTTL(),
func(ctx context.Context) (any, error) {
monitor, err := h.monitorService.Duplicate(
ctx,
id,
subject.UserID,
actorScope,
c.GetHeader("Idempotency-Key"),
)
if err != nil {
return nil, err
}
return channelMonitorToResponse(monitor), nil
},
)
if err != nil {
reason := infraerrors.Reason(err)
if reason == infraerrors.Reason(service.ErrIdempotencyInProgress) || reason == infraerrors.Reason(service.ErrIdempotencyStoreUnavail) {
recovered, recoverErr := h.monitorService.RecoverDuplicate(
c.Request.Context(),
id,
actorScope,
c.GetHeader("Idempotency-Key"),
)
if recoverErr != nil {
slog.Warn("channel_monitor_duplicate_recovery_failed", "monitor_id", id, "actor_scope", actorScope, "reason", reason, "error", recoverErr)
} else if recovered != nil {
c.Header("X-Idempotency-Recovered", "true")
response.Success(c, channelMonitorToResponse(recovered))
return
}
}
response.ErrorFrom(c, err)
return
}
if result != nil && result.Replayed {
c.Header("X-Idempotency-Replayed", "true")
}
response.Success(c, result.Data)
}
// Update PUT /api/v1/admin/channel-monitors/:id
func (h *ChannelMonitorHandler) Update(c *gin.Context) {
id, ok := ParseChannelMonitorID(c)