Fix STRM branch retention

This commit is contained in:
King committed 2026-07-28 16:12:16 +08:00
1 parent 2417a0d36f
commit a6277573dd
3 files changed
+174 -6

No files matched your search

+18 -4
View File
@@ -67,6 +67,15 @@ type strmBranchDTO struct {
Source string `json:"source,omitempty"` Source string `json:"source,omitempty"`
} }
type strmBranchPatchDTO struct {
ParentID *string `json:"parent_id"`
Path *string `json:"path"`
Recursive *bool `json:"recursive"`
RetentionDays *int `json:"retention_days"`
BranchType *string `json:"branch_type"`
Status *string `json:"status"`
}
func (h *Handler) listStrmTasks(w http.ResponseWriter, r *http.Request) { func (h *Handler) listStrmTasks(w http.ResponseWriter, r *http.Request) {
if !ensureServiceReady(w, h.strm != nil) { if !ensureServiceReady(w, h.strm != nil) {
return return
@@ -292,14 +301,19 @@ func (h *Handler) updateStrmBranch(w http.ResponseWriter, r *http.Request) {
writeErr(w, err) writeErr(w, err)
return return
} }
var in strmBranchDTO var in strmBranchPatchDTO
if err := decodeJSON(r, &in); err != nil { if err := decodeJSON(r, &in); err != nil {
writeErr(w, err) writeErr(w, err)
return return
} }
branch := fromStrmBranchDTO(taskID, in) updated, err := h.strm.UpdateBranch(r.Context(), taskID, branchID, strm.BranchPatch{
branch.ID = branchID ParentID: in.ParentID,
updated, err := h.strm.UpdateBranch(r.Context(), branch) Path: in.Path,
Recursive: in.Recursive,
RetentionDays: in.RetentionDays,
BranchType: in.BranchType,
Status: in.Status,
})
if err != nil { if err != nil {
writeErr(w, err) writeErr(w, err)
return return
+94
View File
@@ -0,0 +1,94 @@
package strm
import (
"testing"
"time"
"litepan/internal/domain"
)
func TestUpdateBranchRetentionPreservesPathAndRefreshesExpiry(t *testing.T) {
svc, st := testService(t)
svc.branches = st.StrmBranches
ctx := t.Context()
accountID, err := st.Accounts.Create(ctx, &domain.Account{
Name: "测试账号",
DriverType: "localfs",
IsActive: true,
})
if err != nil {
t.Fatal(err)
}
taskID, err := st.StrmTasks.Create(ctx, &domain.StrmTask{
Name: "电视剧",
AccountID: accountID,
ParentID: "library",
Path: "/云影音/电视剧",
Status: domain.StrmStatusActive,
})
if err != nil {
t.Fatal(err)
}
branch, err := svc.CreateBranch(ctx, &domain.StrmBranch{
TaskID: taskID,
ParentID: "one-piece",
Path: "/云影音/电视剧/海贼王",
Recursive: true,
RetentionDays: 30,
BranchType: domain.StrmBranchTypeTemporary,
})
if err != nil {
t.Fatal(err)
}
if branch.ExpiresAt.IsZero() {
t.Fatal("监控分支创建后应按保留天数设置过期时间")
}
days := 90
updated, err := svc.UpdateBranch(ctx, taskID, branch.ID, BranchPatch{RetentionDays: &days})
if err != nil {
t.Fatal(err)
}
if updated.ParentID != branch.ParentID || updated.Path != branch.Path || updated.RelativePath != branch.RelativePath {
t.Fatalf("仅修改保留天数不应改变目录信息,更新前=%+v,更新后=%+v", branch, updated)
}
if !updated.Recursive || updated.BranchType != domain.StrmBranchTypeTemporary || updated.Source != "manual" {
t.Fatalf("仅修改保留天数不应改变其他分支属性: %+v", updated)
}
if updated.RetentionDays != days {
t.Fatalf("保留天数=%d,期望=%d", updated.RetentionDays, days)
}
expectedExpiry := branch.CreatedAt.Add(90 * 24 * time.Hour)
if !updated.ExpiresAt.Equal(expectedExpiry) {
t.Fatalf("过期时间=%v,期望按创建时间计算为 %v", updated.ExpiresAt, expectedExpiry)
}
permanent := 0
updated, err = svc.UpdateBranch(ctx, taskID, branch.ID, BranchPatch{RetentionDays: &permanent})
if err != nil {
t.Fatal(err)
}
if !updated.ExpiresAt.IsZero() {
t.Fatalf("永久保留应清空过期时间,实际=%v", updated.ExpiresAt)
}
if updated.Path != branch.Path {
t.Fatalf("改为永久保留后路径被改变为 %q", updated.Path)
}
}
func TestNormalizeTemporaryBranchExpiryUsesCreatedAt(t *testing.T) {
createdAt := time.Date(2026, 1, 1, 8, 0, 0, 0, time.Local)
branch := &domain.StrmBranch{
RetentionDays: 90,
CreatedAt: createdAt,
}
if err := normalizeTemporaryBranchExpiry(branch, true); err != nil {
t.Fatal(err)
}
expected := createdAt.Add(90 * 24 * time.Hour)
if !branch.ExpiresAt.Equal(expected) {
t.Fatalf("过期时间=%v,期望=%v", branch.ExpiresAt, expected)
}
}
+62 -2
View File
@@ -608,6 +608,15 @@ func (s *Service) ListBranches(ctx context.Context, taskID int64) ([]*domain.Str
return s.branches.ListByTask(ctx, taskID) return s.branches.ListByTask(ctx, taskID)
} }
type BranchPatch struct {
ParentID *string
Path *string
Recursive *bool
RetentionDays *int
BranchType *string
Status *string
}
func (s *Service) CreateBranch(ctx context.Context, branch *domain.StrmBranch) (*domain.StrmBranch, error) { func (s *Service) CreateBranch(ctx context.Context, branch *domain.StrmBranch) (*domain.StrmBranch, error) {
if s.branches == nil { if s.branches == nil {
return nil, domain.Errf(domain.CodeNotImplement) return nil, domain.Errf(domain.CodeNotImplement)
@@ -624,6 +633,9 @@ func (s *Service) CreateBranch(ctx context.Context, branch *domain.StrmBranch) (
if branch.BranchType == domain.StrmBranchTypeBase { if branch.BranchType == domain.StrmBranchTypeBase {
branch.Recursive = false branch.Recursive = false
branch.RetentionDays = 0 branch.RetentionDays = 0
branch.ExpiresAt = time.Time{}
} else if err := normalizeTemporaryBranchExpiry(branch, false); err != nil {
return nil, err
} }
if branch.Status == "" { if branch.Status == "" {
branch.Status = "running" branch.Status = "running"
@@ -638,17 +650,47 @@ func (s *Service) CreateBranch(ctx context.Context, branch *domain.StrmBranch) (
return s.branches.Get(ctx, id) return s.branches.Get(ctx, id)
} }
func (s *Service) UpdateBranch(ctx context.Context, branch *domain.StrmBranch) (*domain.StrmBranch, error) { func (s *Service) UpdateBranch(ctx context.Context, taskID, branchID int64, patch BranchPatch) (*domain.StrmBranch, error) {
if s.branches == nil { if s.branches == nil {
return nil, domain.Errf(domain.CodeNotImplement) return nil, domain.Errf(domain.CodeNotImplement)
} }
task, err := s.repo.Get(ctx, branch.TaskID) branch, err := s.branches.Get(ctx, branchID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if branch.TaskID != taskID {
return nil, domain.Errorf(domain.CodeNotFound, "STRM 分支不存在")
}
task, err := s.repo.Get(ctx, taskID)
if err != nil {
return nil, err
}
if patch.ParentID != nil {
branch.ParentID = *patch.ParentID
}
if patch.Path != nil {
branch.Path = *patch.Path
}
if patch.Recursive != nil {
branch.Recursive = *patch.Recursive
}
retentionChanged := patch.RetentionDays != nil
if retentionChanged {
branch.RetentionDays = *patch.RetentionDays
}
if patch.BranchType != nil {
branch.BranchType = *patch.BranchType
}
if patch.Status != nil {
branch.Status = *patch.Status
}
branch.RelativePath = branchRelativePath(task.Path, branch.Path) branch.RelativePath = branchRelativePath(task.Path, branch.Path)
if branch.BranchType == domain.StrmBranchTypeBase { if branch.BranchType == domain.StrmBranchTypeBase {
branch.Recursive = false branch.Recursive = false
branch.RetentionDays = 0
branch.ExpiresAt = time.Time{}
} else if err := normalizeTemporaryBranchExpiry(branch, retentionChanged); err != nil {
return nil, err
} }
if err := s.branches.Update(ctx, branch); err != nil { if err := s.branches.Update(ctx, branch); err != nil {
return nil, err return nil, err
@@ -656,6 +698,24 @@ func (s *Service) UpdateBranch(ctx context.Context, branch *domain.StrmBranch) (
return s.branches.Get(ctx, branch.ID) return s.branches.Get(ctx, branch.ID)
} }
func normalizeTemporaryBranchExpiry(branch *domain.StrmBranch, reset bool) error {
if branch.RetentionDays < 0 || branch.RetentionDays > 3650 {
return domain.Errorf(domain.CodeValidation, "监控分支保留天数需为 0 到 3650")
}
if branch.RetentionDays == 0 {
branch.ExpiresAt = time.Time{}
return nil
}
if reset || branch.ExpiresAt.IsZero() {
baseTime := branch.CreatedAt
if baseTime.IsZero() {
baseTime = time.Now()
}
branch.ExpiresAt = baseTime.Add(time.Duration(branch.RetentionDays) * 24 * time.Hour)
}
return nil
}
func (s *Service) DeleteBranch(ctx context.Context, id int64) error { func (s *Service) DeleteBranch(ctx context.Context, id int64) error {
if s.branches == nil { if s.branches == nil {
return domain.Errf(domain.CodeNotImplement) return domain.Errf(domain.CodeNotImplement)