200 lines
5.6 KiB
Go
200 lines
5.6 KiB
Go
package userdb
|
|
|
|
import (
|
|
"database/sql"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Silo-Server/silo-server/internal/userstore"
|
|
)
|
|
|
|
// Favorite is an alias for the canonical type in userstore.
|
|
type Favorite = userstore.Favorite
|
|
|
|
// WatchlistEntry is an alias for the canonical type in userstore.
|
|
type WatchlistEntry = userstore.WatchlistEntry
|
|
|
|
// ---------- Favorites ----------
|
|
|
|
// AddFavorite adds a media item to a profile's favorites.
|
|
// If the item is already a favorite, the operation is a no-op.
|
|
func AddFavorite(db *sql.DB, profileID, mediaItemID string) error {
|
|
_, err := db.Exec(
|
|
`INSERT OR IGNORE INTO favorites (profile_id, media_item_id, added_at) VALUES (?, ?, ?)`,
|
|
profileID, mediaItemID, nowUTC(),
|
|
)
|
|
return err
|
|
}
|
|
|
|
func AddFavoriteAt(db *sql.DB, profileID, mediaItemID string, addedAt time.Time) error {
|
|
_, err := db.Exec(
|
|
`INSERT OR IGNORE INTO favorites (profile_id, media_item_id, added_at) VALUES (?, ?, ?)`,
|
|
profileID, mediaItemID, addedAt.UTC().Format(time.RFC3339),
|
|
)
|
|
return err
|
|
}
|
|
|
|
// RemoveFavorite removes a media item from a profile's favorites.
|
|
func RemoveFavorite(db *sql.DB, profileID, mediaItemID string) error {
|
|
_, err := db.Exec(
|
|
`DELETE FROM favorites WHERE profile_id = ? AND media_item_id = ?`,
|
|
profileID, mediaItemID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
// ListFavorites returns a paginated list of favorites for a profile,
|
|
// ordered by most-recently-added first.
|
|
func ListFavorites(db *sql.DB, profileID string, limit, offset int) ([]Favorite, error) {
|
|
rows, err := db.Query(
|
|
`SELECT profile_id, media_item_id, added_at FROM favorites
|
|
WHERE profile_id = ? ORDER BY added_at DESC LIMIT ? OFFSET ?`,
|
|
profileID, limit, offset,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var favorites []Favorite
|
|
for rows.Next() {
|
|
var f Favorite
|
|
if err := rows.Scan(&f.ProfileID, &f.MediaItemID, &f.AddedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
favorites = append(favorites, f)
|
|
}
|
|
return favorites, rows.Err()
|
|
}
|
|
|
|
// IsFavorite checks whether a media item is in a profile's favorites.
|
|
func IsFavorite(db *sql.DB, profileID, mediaItemID string) (bool, error) {
|
|
var count int
|
|
err := db.QueryRow(
|
|
`SELECT COUNT(*) FROM favorites WHERE profile_id = ? AND media_item_id = ?`,
|
|
profileID, mediaItemID,
|
|
).Scan(&count)
|
|
return count > 0, err
|
|
}
|
|
|
|
func ListFavoritesByMediaItems(db *sql.DB, profileID string, mediaItemIDs []string) (map[string]bool, error) {
|
|
result := make(map[string]bool, len(mediaItemIDs))
|
|
if len(mediaItemIDs) == 0 {
|
|
return result, nil
|
|
}
|
|
|
|
placeholders := make([]string, len(mediaItemIDs))
|
|
args := make([]any, 0, len(mediaItemIDs)+1)
|
|
args = append(args, profileID)
|
|
for i, mediaItemID := range mediaItemIDs {
|
|
placeholders[i] = "?"
|
|
args = append(args, mediaItemID)
|
|
}
|
|
|
|
rows, err := db.Query(
|
|
`SELECT media_item_id FROM favorites WHERE profile_id = ? AND media_item_id IN (`+strings.Join(placeholders, ",")+`)`,
|
|
args...,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
var mediaItemID string
|
|
if err := rows.Scan(&mediaItemID); err != nil {
|
|
return nil, err
|
|
}
|
|
result[mediaItemID] = true
|
|
}
|
|
return result, rows.Err()
|
|
}
|
|
|
|
// ---------- Watchlist ----------
|
|
|
|
// AddToWatchlist adds a media item to a profile's watchlist.
|
|
// If the item is already on the watchlist, the operation is a no-op.
|
|
func AddToWatchlist(db *sql.DB, profileID, mediaItemID string) error {
|
|
_, err := db.Exec(
|
|
`INSERT OR IGNORE INTO watchlist (profile_id, media_item_id, added_at) VALUES (?, ?, ?)`,
|
|
profileID, mediaItemID, nowUTC(),
|
|
)
|
|
return err
|
|
}
|
|
|
|
// RemoveFromWatchlist removes a media item from a profile's watchlist.
|
|
func RemoveFromWatchlist(db *sql.DB, profileID, mediaItemID string) error {
|
|
_, err := db.Exec(
|
|
`DELETE FROM watchlist WHERE profile_id = ? AND media_item_id = ?`,
|
|
profileID, mediaItemID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
// ListWatchlist returns a paginated list of watchlist entries for a profile,
|
|
// ordered by most-recently-added first.
|
|
func ListWatchlist(db *sql.DB, profileID string, limit, offset int) ([]WatchlistEntry, error) {
|
|
rows, err := db.Query(
|
|
`SELECT profile_id, media_item_id, added_at FROM watchlist
|
|
WHERE profile_id = ? ORDER BY added_at DESC LIMIT ? OFFSET ?`,
|
|
profileID, limit, offset,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var entries []WatchlistEntry
|
|
for rows.Next() {
|
|
var w WatchlistEntry
|
|
if err := rows.Scan(&w.ProfileID, &w.MediaItemID, &w.AddedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
entries = append(entries, w)
|
|
}
|
|
return entries, rows.Err()
|
|
}
|
|
|
|
// InWatchlist checks whether a media item is on a profile's watchlist.
|
|
func InWatchlist(db *sql.DB, profileID, mediaItemID string) (bool, error) {
|
|
var count int
|
|
err := db.QueryRow(
|
|
`SELECT COUNT(*) FROM watchlist WHERE profile_id = ? AND media_item_id = ?`,
|
|
profileID, mediaItemID,
|
|
).Scan(&count)
|
|
return count > 0, err
|
|
}
|
|
|
|
func ListWatchlistByMediaItems(db *sql.DB, profileID string, mediaItemIDs []string) (map[string]bool, error) {
|
|
result := make(map[string]bool, len(mediaItemIDs))
|
|
if len(mediaItemIDs) == 0 {
|
|
return result, nil
|
|
}
|
|
|
|
placeholders := make([]string, len(mediaItemIDs))
|
|
args := make([]any, 0, len(mediaItemIDs)+1)
|
|
args = append(args, profileID)
|
|
for i, mediaItemID := range mediaItemIDs {
|
|
placeholders[i] = "?"
|
|
args = append(args, mediaItemID)
|
|
}
|
|
|
|
rows, err := db.Query(
|
|
`SELECT media_item_id FROM watchlist WHERE profile_id = ? AND media_item_id IN (`+strings.Join(placeholders, ",")+`)`,
|
|
args...,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
var mediaItemID string
|
|
if err := rows.Scan(&mediaItemID); err != nil {
|
|
return nil, err
|
|
}
|
|
result[mediaItemID] = true
|
|
}
|
|
return result, rows.Err()
|
|
}
|