183 lines
5.4 KiB
Go
183 lines
5.4 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"github.com/TopherMayor/unified-media-manager/internal/db"
|
|
)
|
|
|
|
type BlocklistItem struct {
|
|
ID int64 `json:"id"`
|
|
ReleaseTitle string `json:"release_title"`
|
|
SourceTitle *string `json:"source_title,omitempty"`
|
|
Quality json.RawMessage `json:"quality"`
|
|
Indexer *string `json:"indexer,omitempty"`
|
|
Protocol string `json:"protocol"`
|
|
TorrentHash *string `json:"torrent_hash,omitempty"`
|
|
Size *int64 `json:"size,omitempty"`
|
|
Message *string `json:"message,omitempty"`
|
|
MediaID *int64 `json:"media_id,omitempty"`
|
|
BlockReason string `json:"block_reason"`
|
|
AutoExpiresAt *time.Time `json:"auto_expires_at,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
type BlocklistFilters struct {
|
|
Page int
|
|
PageSize int
|
|
}
|
|
|
|
type AddBlocklistRequest struct {
|
|
ReleaseTitle string `json:"release_title"`
|
|
SourceTitle *string `json:"source_title,omitempty"`
|
|
Quality json.RawMessage `json:"quality,omitempty"`
|
|
Indexer *string `json:"indexer,omitempty"`
|
|
Protocol string `json:"protocol,omitempty"`
|
|
TorrentHash *string `json:"torrent_hash,omitempty"`
|
|
Size *int64 `json:"size,omitempty"`
|
|
Message *string `json:"message,omitempty"`
|
|
MediaID *int64 `json:"media_id,omitempty"`
|
|
BlockReason *string `json:"block_reason,omitempty"`
|
|
AutoExpiresAt *time.Time `json:"auto_expires_at,omitempty"`
|
|
}
|
|
|
|
const blocklistColumns = `id, release_title, source_title, quality, indexer, protocol,
|
|
torrent_hash, size, message, media_id, block_reason, auto_expires_at, created_at`
|
|
|
|
type BlocklistService struct {
|
|
db *db.DB
|
|
}
|
|
|
|
func NewBlocklistService(database *db.DB) *BlocklistService {
|
|
return &BlocklistService{db: database}
|
|
}
|
|
|
|
func scanBlocklistItem(scanner interface{ Scan(...interface{}) error }) (*BlocklistItem, error) {
|
|
var item BlocklistItem
|
|
var sourceTitle, indexer, torrentHash, message sql.NullString
|
|
var size, mediaID sql.NullInt64
|
|
var autoExpiresAt sql.NullTime
|
|
var quality []byte
|
|
|
|
err := scanner.Scan(&item.ID, &item.ReleaseTitle, &sourceTitle, &quality, &indexer,
|
|
&item.Protocol, &torrentHash, &size, &message, &mediaID, &item.BlockReason,
|
|
&autoExpiresAt, &item.CreatedAt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if sourceTitle.Valid {
|
|
item.SourceTitle = &sourceTitle.String
|
|
}
|
|
if indexer.Valid {
|
|
item.Indexer = &indexer.String
|
|
}
|
|
if torrentHash.Valid {
|
|
item.TorrentHash = &torrentHash.String
|
|
}
|
|
if message.Valid {
|
|
item.Message = &message.String
|
|
}
|
|
if size.Valid {
|
|
item.Size = &size.Int64
|
|
}
|
|
if mediaID.Valid {
|
|
item.MediaID = &mediaID.Int64
|
|
}
|
|
if autoExpiresAt.Valid {
|
|
item.AutoExpiresAt = &autoExpiresAt.Time
|
|
}
|
|
if quality != nil {
|
|
item.Quality = json.RawMessage(quality)
|
|
}
|
|
return &item, nil
|
|
}
|
|
|
|
func (s *BlocklistService) List(ctx context.Context, filters BlocklistFilters) ([]BlocklistItem, int, error) {
|
|
var total int
|
|
if err := s.db.Pool.QueryRow(ctx, "SELECT COUNT(*) FROM blocklist").Scan(&total); err != nil {
|
|
return nil, 0, fmt.Errorf("count blocklist: %w", err)
|
|
}
|
|
|
|
rows, err := s.db.Pool.Query(ctx,
|
|
fmt.Sprintf("SELECT %s FROM blocklist ORDER BY created_at DESC LIMIT $1 OFFSET $2", blocklistColumns),
|
|
filters.PageSize, (filters.Page-1)*filters.PageSize)
|
|
if err != nil {
|
|
return nil, 0, fmt.Errorf("list blocklist: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var items []BlocklistItem
|
|
for rows.Next() {
|
|
item, err := scanBlocklistItem(rows)
|
|
if err != nil {
|
|
slog.Error("failed to scan blocklist item", "error", err)
|
|
continue
|
|
}
|
|
items = append(items, *item)
|
|
}
|
|
|
|
return items, total, nil
|
|
}
|
|
|
|
func (s *BlocklistService) Add(ctx context.Context, req AddBlocklistRequest) (int64, error) {
|
|
protocol := req.Protocol
|
|
if protocol == "" {
|
|
protocol = "torrent"
|
|
}
|
|
blockReason := "manual"
|
|
if req.BlockReason != nil {
|
|
blockReason = *req.BlockReason
|
|
}
|
|
quality := req.Quality
|
|
if quality == nil {
|
|
quality = json.RawMessage("{}")
|
|
}
|
|
|
|
var id int64
|
|
err := s.db.Pool.QueryRow(ctx,
|
|
`INSERT INTO blocklist (release_title, source_title, quality, indexer, protocol,
|
|
torrent_hash, size, message, media_id, block_reason, auto_expires_at)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id`,
|
|
req.ReleaseTitle, req.SourceTitle, quality, req.Indexer, protocol,
|
|
req.TorrentHash, req.Size, req.Message, req.MediaID, blockReason, req.AutoExpiresAt).Scan(&id)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("create blocklist entry: %w", err)
|
|
}
|
|
|
|
return id, nil
|
|
}
|
|
|
|
func (s *BlocklistService) Delete(ctx context.Context, id int64) error {
|
|
tag, err := s.db.Pool.Exec(ctx, "DELETE FROM blocklist WHERE id = $1", id)
|
|
if err != nil {
|
|
return fmt.Errorf("delete blocklist item: %w", err)
|
|
}
|
|
if tag.RowsAffected() == 0 {
|
|
return fmt.Errorf("blocklist item not found")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *BlocklistService) Clear(ctx context.Context) (int64, error) {
|
|
tag, err := s.db.Pool.Exec(ctx, "DELETE FROM blocklist")
|
|
if err != nil {
|
|
return 0, fmt.Errorf("clear blocklist: %w", err)
|
|
}
|
|
return tag.RowsAffected(), nil
|
|
}
|
|
|
|
func (s *BlocklistService) ClearExpired(ctx context.Context) (int64, error) {
|
|
tag, err := s.db.Pool.Exec(ctx,
|
|
"DELETE FROM blocklist WHERE auto_expires_at IS NOT NULL AND auto_expires_at < NOW()")
|
|
if err != nil {
|
|
return 0, fmt.Errorf("clear expired blocklist: %w", err)
|
|
}
|
|
return tag.RowsAffected(), nil
|
|
}
|