feat(uploads): preview storage paths, detect templates, attach covers and withdraw batches
ci / module boundaries / boundaries (push) Successful in 7s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 55s
ci / ts / ts (packages + web) (push) Failing after 4m4s
ci / api / api (push) Successful in 55s
ci / db / db (push) Failing after 1m14s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m11s

This commit is contained in:
mrleo1nid committed 2026-10-29 10:28:29 +03:00
1 parent 5e2d1e902b
commit 64dd8ed05e
29 files changed
+1219 -471

No files matched your search

+27 -7
View File
@@ -178,7 +178,7 @@ type CreateFile struct {
}
// Create создаёт пачку и резервирует место; не поместившиеся и дубли отклоняются с кодом.
func (s *Service) Create(ctx context.Context, comment *string, files []CreateFile) (uuid.UUID, []Reserved, error) {
func (s *Service) Create(ctx context.Context, comment *string, files []CreateFile, cover []byte) (uuid.UUID, []Reserved, error) {
p, err := principal(ctx, auth.PermSuggestTracks)
if err != nil {
return uuid.Nil, nil, err
@@ -209,10 +209,25 @@ func (s *Service) Create(ctx context.Context, comment *string, files []CreateFil
}
accepted = append(accepted, NewFile{ClientID: f.ClientID, Name: name, RelPath: cleanClientPath(f.RelPath), Size: f.Size, SHA256: sum})
}
batch, res, err := s.store.CreateBatch(ctx, p.UserID, comment, accepted, st)
coverPath, err := s.createCover(cover)
if err != nil {
return uuid.Nil, nil, err
}
keepCover := false
defer func() {
if coverPath != nil && !keepCover {
_ = os.Remove(s.stagingAbs(*coverPath))
}
}()
batch, res, err := s.store.CreateBatch(ctx, p.UserID, comment, accepted, st, coverPath)
if err != nil {
return uuid.Nil, nil, err
}
for _, r := range res {
if r.Rejected == "" {
keepCover = true
}
}
return batch, append(res, rejected...), nil
}
@@ -240,12 +255,12 @@ func (s *Service) Complete(ctx context.Context, f File) error {
// --- Списки ---
// ListMine — предложения текущего пользователя.
func (s *Service) ListMine(ctx context.Context, statuses []Status, cursor string, limit int) ([]File, string, error) {
func (s *Service) ListMine(ctx context.Context, statuses []Status, cursor string, limit int, batch *uuid.UUID) ([]File, string, error) {
p, err := principal(ctx, auth.PermSuggestTracks)
if err != nil {
return nil, "", err
}
return s.list(ctx, ListFilter{AuthorID: &p.UserID, Statuses: statuses, Cursor: cursor, Limit: limit})
return s.list(ctx, ListFilter{AuthorID: &p.UserID, BatchID: batch, Statuses: statuses, Cursor: cursor, Limit: limit})
}
// ListAll — вся очередь (админ).
@@ -279,7 +294,7 @@ func (s *Service) Withdraw(ctx context.Context, id uuid.UUID) error {
if err := s.store.ChangeStatus(ctx, id, []Status{StatusUploading, StatusProcessing, StatusPendingReview}, StatusWithdrawn); err != nil {
return notFound(err)
}
s.removeStaging(f)
s.removeStaging(ctx, f)
return nil
}
@@ -380,7 +395,7 @@ func (s *Service) reviewOne(ctx context.Context, reviewer, id uuid.UUID, approve
if err := s.store.Decide(ctx, id, reviewer, false, reason); err != nil {
return notFound(err)
}
s.removeStaging(f)
s.removeStaging(ctx, f)
s.store.Audit(ctx, &reviewer, "upload.reject", id, map[string]any{"reason": reason})
return nil
}
@@ -542,7 +557,7 @@ func reuseExisting(root, rel string) string {
}
// removeStaging удаляет файл staging (и временные копии с тегами).
func (s *Service) removeStaging(f File) {
func (s *Service) removeStaging(ctx context.Context, f File) {
if f.StagingPath == "" {
return
}
@@ -552,6 +567,11 @@ func (s *Service) removeStaging(f File) {
_ = os.Remove(m)
}
_ = os.Remove(filepath.Dir(p)) // каталог пачки, если опустел
if f.CoverPath != nil {
if cover, err := s.store.UnusedBatchCover(ctx, f.BatchID); err == nil && cover != nil {
_ = os.Remove(s.stagingAbs(*cover))
}
}
}
// TusURL — адрес tus для файла.