Sync from /srv/compose/unified-media-manager
This commit is contained in:
182
internal/service/blocklist.go
Normal file
182
internal/service/blocklist.go
Normal file
@@ -0,0 +1,182 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user