package database import ( "context" "fmt" "time" "github.com/jackc/pgx/v5/pgxpool" ) type Torrent struct { ID string AlbumID string InfoHash string Tracker string Title string Format string Quality string Source string BitDepth int SampleRate int Seeders int Peers int Size int64 TrackCount int HasCoverArt bool HasCueSheet bool HasRipLog bool DownloadLink string TorrentFile []byte CreatedAt time.Time UpdatedAt time.Time } type TorrentRepository struct { pool *pgxpool.Pool } func NewTorrentRepository(pool *pgxpool.Pool) *TorrentRepository { return &TorrentRepository{pool: pool} } func (r *TorrentRepository) Create(ctx context.Context, t *Torrent) error { _, err := r.pool.Exec(ctx, `INSERT INTO torrents (album_id, info_hash, tracker, title, format, quality, source, bit_depth, sample_rate, seeders, peers, size, track_count, has_cover_art, has_cue_sheet, has_rip_log, download_link, torrent_file) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) ON CONFLICT (info_hash) DO UPDATE SET seeders = EXCLUDED.seeders, peers = EXCLUDED.peers, updated_at = NOW()`, t.AlbumID, t.InfoHash, t.Tracker, t.Title, t.Format, t.Quality, t.Source, t.BitDepth, t.SampleRate, t.Seeders, t.Peers, t.Size, t.TrackCount, t.HasCoverArt, t.HasCueSheet, t.HasRipLog, t.DownloadLink, t.TorrentFile, ) if err != nil { return fmt.Errorf("creating torrent: %w", err) } return nil } func (r *TorrentRepository) GetByInfoHash(ctx context.Context, infoHash string) (*Torrent, error) { t := &Torrent{} err := r.pool.QueryRow(ctx, `SELECT id, album_id, info_hash, tracker, title, format, quality, source, bit_depth, sample_rate, seeders, peers, size, track_count, has_cover_art, has_cue_sheet, has_rip_log, download_link, torrent_file, created_at, updated_at FROM torrents WHERE info_hash = $1`, infoHash, ).Scan(&t.ID, &t.AlbumID, &t.InfoHash, &t.Tracker, &t.Title, &t.Format, &t.Quality, &t.Source, &t.BitDepth, &t.SampleRate, &t.Seeders, &t.Peers, &t.Size, &t.TrackCount, &t.HasCoverArt, &t.HasCueSheet, &t.HasRipLog, &t.DownloadLink, &t.TorrentFile, &t.CreatedAt, &t.UpdatedAt) if err != nil { return nil, fmt.Errorf("getting torrent: %w", err) } return t, nil } func (r *TorrentRepository) GetByAlbumID(ctx context.Context, albumID string) ([]*Torrent, error) { rows, err := r.pool.Query(ctx, `SELECT id, album_id, info_hash, tracker, title, format, quality, source, bit_depth, sample_rate, seeders, peers, size, track_count, has_cover_art, has_cue_sheet, has_rip_log, download_link, torrent_file, created_at, updated_at FROM torrents WHERE album_id = $1 ORDER BY seeders DESC`, albumID, ) if err != nil { return nil, fmt.Errorf("listing torrents: %w", err) } defer rows.Close() var torrents []*Torrent for rows.Next() { t := &Torrent{} if err := rows.Scan(&t.ID, &t.AlbumID, &t.InfoHash, &t.Tracker, &t.Title, &t.Format, &t.Quality, &t.Source, &t.BitDepth, &t.SampleRate, &t.Seeders, &t.Peers, &t.Size, &t.TrackCount, &t.HasCoverArt, &t.HasCueSheet, &t.HasRipLog, &t.DownloadLink, &t.TorrentFile, &t.CreatedAt, &t.UpdatedAt); err != nil { return nil, fmt.Errorf("scanning torrent: %w", err) } torrents = append(torrents, t) } return torrents, nil } func (r *TorrentRepository) HasAlbumInFormat(ctx context.Context, albumID string, format string) (bool, error) { var exists bool err := r.pool.QueryRow(ctx, `SELECT EXISTS( SELECT 1 FROM downloads WHERE album_id = $1 AND format = $2 AND state IN ('completed', 'seeding') )`, albumID, format, ).Scan(&exists) if err != nil { return false, fmt.Errorf("checking album format: %w", err) } return exists, nil }