127 lines
3.7 KiB
Go
127 lines
3.7 KiB
Go
package catalog
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// UserRating represents a user's rating for a media item.
|
|
type UserRating struct {
|
|
UserID int `json:"user_id"`
|
|
ProfileID string `json:"profile_id"`
|
|
MediaItemID string `json:"media_item_id"`
|
|
Rating int `json:"rating"`
|
|
RatedAt time.Time `json:"rated_at"`
|
|
}
|
|
|
|
// RatingsRepo provides access to the user_ratings table.
|
|
type RatingsRepo struct {
|
|
pool *pgxpool.Pool
|
|
}
|
|
|
|
// NewRatingsRepo creates a new RatingsRepo.
|
|
func NewRatingsRepo(pool *pgxpool.Pool) *RatingsRepo {
|
|
return &RatingsRepo{pool: pool}
|
|
}
|
|
|
|
// Set creates or updates a user's rating for an item.
|
|
func (r *RatingsRepo) Set(ctx context.Context, userID int, profileID, mediaItemID string, rating int) error {
|
|
_, err := r.pool.Exec(ctx, `
|
|
INSERT INTO user_ratings (user_id, profile_id, media_item_id, rating, rated_at)
|
|
VALUES ($1, $2, $3, $4, NOW())
|
|
ON CONFLICT (user_id, profile_id, media_item_id)
|
|
DO UPDATE SET rating = EXCLUDED.rating, rated_at = EXCLUDED.rated_at`,
|
|
userID, profileID, mediaItemID, rating,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("set rating: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Get retrieves a user's rating for an item. Returns nil if not rated.
|
|
func (r *RatingsRepo) Get(ctx context.Context, userID int, profileID, mediaItemID string) (*UserRating, error) {
|
|
var ur UserRating
|
|
err := r.pool.QueryRow(ctx, `
|
|
SELECT user_id, profile_id, media_item_id, rating, rated_at
|
|
FROM user_ratings
|
|
WHERE user_id = $1 AND profile_id = $2 AND media_item_id = $3`,
|
|
userID, profileID, mediaItemID,
|
|
).Scan(&ur.UserID, &ur.ProfileID, &ur.MediaItemID, &ur.Rating, &ur.RatedAt)
|
|
if err == pgx.ErrNoRows {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get rating: %w", err)
|
|
}
|
|
return &ur, nil
|
|
}
|
|
|
|
// Delete removes a user's rating for an item.
|
|
func (r *RatingsRepo) Delete(ctx context.Context, userID int, profileID, mediaItemID string) error {
|
|
_, err := r.pool.Exec(ctx, `
|
|
DELETE FROM user_ratings
|
|
WHERE user_id = $1 AND profile_id = $2 AND media_item_id = $3`,
|
|
userID, profileID, mediaItemID,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("delete rating: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// List returns all ratings for a user+profile with pagination.
|
|
func (r *RatingsRepo) List(ctx context.Context, userID int, profileID string, limit, offset int) ([]UserRating, error) {
|
|
rows, err := r.pool.Query(ctx, `
|
|
SELECT user_id, profile_id, media_item_id, rating, rated_at
|
|
FROM user_ratings
|
|
WHERE user_id = $1 AND profile_id = $2
|
|
ORDER BY rated_at DESC
|
|
LIMIT $3 OFFSET $4`,
|
|
userID, profileID, limit, offset,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list ratings: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var ratings []UserRating
|
|
for rows.Next() {
|
|
var ur UserRating
|
|
if err := rows.Scan(&ur.UserID, &ur.ProfileID, &ur.MediaItemID, &ur.Rating, &ur.RatedAt); err != nil {
|
|
return nil, fmt.Errorf("scan rating: %w", err)
|
|
}
|
|
ratings = append(ratings, ur)
|
|
}
|
|
return ratings, rows.Err()
|
|
}
|
|
|
|
// ListForItems returns ratings for a specific set of item IDs (used by recommendation filtering).
|
|
func (r *RatingsRepo) ListForItems(ctx context.Context, userID int, profileID string, itemIDs []string) (map[string]int, error) {
|
|
rows, err := r.pool.Query(ctx, `
|
|
SELECT media_item_id, rating
|
|
FROM user_ratings
|
|
WHERE user_id = $1 AND profile_id = $2 AND media_item_id = ANY($3)`,
|
|
userID, profileID, itemIDs,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list ratings for items: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
result := make(map[string]int, len(itemIDs))
|
|
for rows.Next() {
|
|
var itemID string
|
|
var rating int
|
|
if err := rows.Scan(&itemID, &rating); err != nil {
|
|
return nil, fmt.Errorf("scan rating: %w", err)
|
|
}
|
|
result[itemID] = rating
|
|
}
|
|
return result, rows.Err()
|
|
}
|