ci / module boundaries / boundaries (push) Successful in 6s
ci / api / api (push) Successful in 47s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m2s
ci / db / db (push) Successful in 1m4s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 1m30s
ci / ts / ts (packages + web) (push) Successful in 2m54s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m14s
100 lines
3.9 KiB
Go
100 lines
3.9 KiB
Go
package uploads
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
// ChangeStatus deliberately does not expose pgx to use cases.
|
|
func (st *Store) ChangeStatus(ctx context.Context, id uuid.UUID, from []Status, to Status) error {
|
|
return st.Transition(ctx, id, from, to, nil)
|
|
}
|
|
|
|
// UpdateMetadata uses one guarded UPDATE; a concurrent decision is reported to the caller.
|
|
func (st *Store) UpdateMetadata(ctx context.Context, id uuid.UUID, m Meta, edited bool, pl placement) error {
|
|
raw, err := json.Marshal(m)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result, err := st.pool.Exec(ctx, `UPDATE upload_files SET metadata=$2, metadata_edited=$3, target_source_id=$4,
|
|
target_rel_path=$5, status_reason=$6, matched_artist_id=$7, matched_album_id=$8, updated_at=now()
|
|
WHERE id=$1 AND status IN ('pending_review','import_failed')`,
|
|
id, raw, edited, pl.SourceID, pl.RelPath, nullable(pl.Reason), pl.ArtistID, pl.AlbumID)
|
|
if err != nil {
|
|
return fmt.Errorf("uploads: update: %w", err)
|
|
}
|
|
if result.RowsAffected() != 1 {
|
|
return ErrWrongStatus
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Decide keeps quota release, status and reviewer metadata in the same transaction.
|
|
func (st *Store) Decide(ctx context.Context, id, reviewer uuid.UUID, approve bool, reason string) error {
|
|
from, to := []Status{StatusPendingReview, StatusImportFailed}, StatusRejected
|
|
if approve {
|
|
from, to, reason = []Status{StatusPendingReview}, StatusApproved, ""
|
|
}
|
|
return st.Transition(ctx, id, from, to, func(tx pgx.Tx) error {
|
|
_, err := tx.Exec(ctx, `UPDATE upload_files SET reviewed_by=$2, reviewed_at=now(), status_reason=$3 WHERE id=$1`, id, reviewer, nullable(reason))
|
|
return err
|
|
})
|
|
}
|
|
|
|
func (st *Store) FailImport(ctx context.Context, id uuid.UUID, reason string) error {
|
|
return st.Transition(ctx, id, []Status{StatusImporting}, StatusImportFailed, func(tx pgx.Tx) error {
|
|
_, err := tx.Exec(ctx, `UPDATE upload_files SET status_reason=$2 WHERE id=$1`, id, reason)
|
|
return err
|
|
})
|
|
}
|
|
|
|
func (st *Store) CompleteImport(ctx context.Context, id, sourceID uuid.UUID, rel string, mediaFileID uuid.UUID) error {
|
|
return st.Transition(ctx, id, []Status{StatusImporting}, StatusImported, func(tx pgx.Tx) error {
|
|
if _, err := tx.Exec(ctx, `UPDATE upload_files SET target_source_id=$2, target_rel_path=$3 WHERE id=$1`, id, sourceID, rel); err != nil {
|
|
return err
|
|
}
|
|
return MarkImported(ctx, tx, id, mediaFileID)
|
|
})
|
|
}
|
|
|
|
func (st *Store) ResetImporting(ctx context.Context) error {
|
|
_, err := st.pool.Exec(ctx, `UPDATE upload_files SET status='approved', updated_at=now() WHERE status='importing'`)
|
|
return err
|
|
}
|
|
|
|
func (st *Store) SaveOutcome(ctx context.Context, f File, o outcome) error {
|
|
return st.Transition(ctx, f.ID, []Status{StatusProcessing}, o.status, func(tx pgx.Tx) error {
|
|
var (
|
|
tags, metaJSON []byte
|
|
points []int32
|
|
)
|
|
if o.tags != nil {
|
|
tags, _ = json.Marshal(o.tags)
|
|
}
|
|
if o.meta != nil {
|
|
metaJSON, _ = json.Marshal(o.meta)
|
|
}
|
|
if o.print != nil {
|
|
points = make([]int32, len(o.print.Points))
|
|
for i, p := range o.print.Points {
|
|
points[i] = int32(p)
|
|
}
|
|
}
|
|
p := o.probe
|
|
_, err := tx.Exec(ctx, `
|
|
UPDATE upload_files SET sha256 = coalesce($2, sha256), format = NULLIF($3, ''), codec = NULLIF($4, ''),
|
|
bitrate = NULLIF($5, 0), sample_rate = NULLIF($6, 0), bit_depth = NULLIF($7, 0), duration_ms = NULLIF($8, 0),
|
|
tags = $9, metadata = $10, fingerprint = $11, duplicate_of_track_id = $12, duplicate_level = NULLIF($13, ''),
|
|
possible_duplicate = $14, quality_upgrade = $20, target_source_id = $15, target_rel_path = $16, status_reason = NULLIF($17, ''),
|
|
matched_artist_id = $18, matched_album_id = $19
|
|
WHERE id = $1`,
|
|
f.ID, o.sha, p.Format, p.Codec, p.Bitrate, p.SampleRate, p.BitDepth, p.DurationMs, tags, metaJSON, points,
|
|
o.dupTrack, o.dupLevel, o.possible, o.place.SourceID, o.place.RelPath, o.reason, o.place.ArtistID, o.place.AlbumID, o.quality)
|
|
return err
|
|
})
|
|
}
|