Mark videos as watched
This commit is contained in:
parent
fa2a3cccec
commit
3147d3dd4c
|
@ -73,3 +73,20 @@ func (d *postgresDB) GetChannelSubscribers(ctx context.Context, channelID string
|
||||||
|
|
||||||
return subs, nil
|
return subs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var subscribeUserToChannelQuery = `INSERT INTO user_subscriptions (username, channel_id) VALUES ($1, $2)`
|
||||||
|
|
||||||
|
func (d *postgresDB) SubscribeUserToChannel(ctx context.Context, username string, channelID string) error {
|
||||||
|
_, err := d.db.ExecContext(ctx, subscribeUserToChannelQuery, username, channelID)
|
||||||
|
if err != nil {
|
||||||
|
if pgerr, ok := err.(*pq.Error); ok {
|
||||||
|
if pgerr.Code == "23505" {
|
||||||
|
return ErrAlreadySubscribed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.l.Log("level", "ERROR", "function", "db.SubscribeUserToChannel", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
30
db/db.go
30
db/db.go
|
@ -3,6 +3,7 @@ package db
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/models"
|
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/models"
|
||||||
)
|
)
|
||||||
|
@ -11,14 +12,17 @@ var (
|
||||||
ErrChannelExists = errors.New("channel already exists")
|
ErrChannelExists = errors.New("channel already exists")
|
||||||
ErrAlreadySubscribed = errors.New("already subscribed to channel")
|
ErrAlreadySubscribed = errors.New("already subscribed to channel")
|
||||||
ErrVideoExists = errors.New("video already exists")
|
ErrVideoExists = errors.New("video already exists")
|
||||||
|
ErrUserExists = errors.New("user already exists")
|
||||||
)
|
)
|
||||||
|
|
||||||
// DB represents a database layer for getting video and channel data
|
// DB represents a database layer for getting video and channel data
|
||||||
type DB interface {
|
type DB interface {
|
||||||
// GetNewVideos returns unwatched videos from all channels
|
// AuthenticateUser verifies a user's password against a hashed value
|
||||||
GetNewVideos(ctx context.Context, username string) ([]models.Video, error)
|
AuthenticateUser(ctx context.Context, user models.User) (bool, error)
|
||||||
// CreateVideo adds a newly published video to the database
|
// CreateUser registers a new user in the database
|
||||||
CreateVideo(ctx context.Context, video models.Video, channelID string) error
|
CreateUser(ctx context.Context, user models.User) error
|
||||||
|
// DeleteUser registers a new user in the database
|
||||||
|
DeleteUser(ctx context.Context, username string) error
|
||||||
|
|
||||||
// CreateChannel starts tracking a new channel and fetch new videos for it
|
// CreateChannel starts tracking a new channel and fetch new videos for it
|
||||||
CreateChannel(ctx context.Context, channel models.Channel) error
|
CreateChannel(ctx context.Context, channel models.Channel) error
|
||||||
|
@ -26,17 +30,17 @@ type DB interface {
|
||||||
ListChannels(ctx context.Context) ([]models.Channel, error)
|
ListChannels(ctx context.Context) ([]models.Channel, error)
|
||||||
// GetChannelSubscribers lists all channels from the database
|
// GetChannelSubscribers lists all channels from the database
|
||||||
GetChannelSubscribers(ctx context.Context, channelID string) ([]string, error)
|
GetChannelSubscribers(ctx context.Context, channelID string) ([]string, error)
|
||||||
|
|
||||||
// AuthenticateUser verifies a user's password against a hashed value
|
|
||||||
AuthenticateUser(ctx context.Context, user models.User) (bool, error)
|
|
||||||
// CreateUser registers a new user in the database
|
|
||||||
CreateUser(ctx context.Context, user models.User) error
|
|
||||||
// DeleteUser registers a new user in the database
|
|
||||||
DeleteUser(ctx context.Context, username string) error
|
|
||||||
// SubscribeUserToChannel will start showing new videos for that channel to the user
|
// SubscribeUserToChannel will start showing new videos for that channel to the user
|
||||||
SubscribeUserToChannel(ctx context.Context, username string, channelID string) error
|
SubscribeUserToChannel(ctx context.Context, username string, channelID string) error
|
||||||
|
|
||||||
|
// GetNewVideos returns a list of unwatched videos from all subscribed channels
|
||||||
|
GetNewVideos(ctx context.Context, username string) ([]models.Video, error)
|
||||||
|
// GetWatchedVideos returns a list of all watched videos for a user
|
||||||
|
GetWatchedVideos(ctx context.Context, username string) ([]models.Video, error)
|
||||||
|
// AddVideo adds a newly published video to the database
|
||||||
|
AddVideo(ctx context.Context, video models.Video, channelID string) error
|
||||||
// AddVideoToUser will list the video in the users feed
|
// AddVideoToUser will list the video in the users feed
|
||||||
AddVideoToUser(ctx context.Context, username string, videoID string) error
|
AddVideoToUser(ctx context.Context, username string, videoID string) error
|
||||||
// WatchVideo marks a video as watched so it no longer shows in the feed
|
// SetVideoWatchTime sets or unsets the watch timestamp of a user's video
|
||||||
WatchVideo(ctx context.Context, username string, videoID string) error
|
SetVideoWatchTime(ctx context.Context, username string, videoID string, watchTime *time.Time) error
|
||||||
}
|
}
|
||||||
|
|
52
db/users.go
52
db/users.go
|
@ -2,6 +2,8 @@ package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/alexedwards/argon2id"
|
"github.com/alexedwards/argon2id"
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
|
@ -16,6 +18,10 @@ func (d *postgresDB) AuthenticateUser(ctx context.Context, user models.User) (bo
|
||||||
var hashedPassword string
|
var hashedPassword string
|
||||||
err := row.Scan(&hashedPassword)
|
err := row.Scan(&hashedPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
d.l.Log("level", "ERROR", "function", "db.AuthenticateUser", "error", err)
|
d.l.Log("level", "ERROR", "function", "db.AuthenticateUser", "error", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -34,6 +40,11 @@ var createUserQuery = `INSERT INTO users (username, password) VALUES ($1, $2)`
|
||||||
func (d *postgresDB) CreateUser(ctx context.Context, user models.User) error {
|
func (d *postgresDB) CreateUser(ctx context.Context, user models.User) error {
|
||||||
_, err := d.db.ExecContext(ctx, createUserQuery, user.Username, user.Password)
|
_, err := d.db.ExecContext(ctx, createUserQuery, user.Username, user.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if pgerr, ok := err.(*pq.Error); ok {
|
||||||
|
if pgerr.Code == "23505" {
|
||||||
|
return ErrUserExists
|
||||||
|
}
|
||||||
|
}
|
||||||
d.l.Log("level", "ERROR", "function", "db.CreateUser", "error", err)
|
d.l.Log("level", "ERROR", "function", "db.CreateUser", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -52,44 +63,3 @@ func (d *postgresDB) DeleteUser(ctx context.Context, username string) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var subscribeUserToChannelQuery = `INSERT INTO user_subscriptions (username, channel_id) VALUES ($1, $2)`
|
|
||||||
|
|
||||||
func (d *postgresDB) SubscribeUserToChannel(ctx context.Context, username string, channelID string) error {
|
|
||||||
_, err := d.db.ExecContext(ctx, subscribeUserToChannelQuery, username, channelID)
|
|
||||||
if err != nil {
|
|
||||||
if pgerr, ok := err.(*pq.Error); ok {
|
|
||||||
if pgerr.Code == "23505" {
|
|
||||||
return ErrAlreadySubscribed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.l.Log("level", "ERROR", "function", "db.SubscribeUserToChannel", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var addVideoToUserQuery = `INSERT INTO user_videos (username, video_id) VALUES ($1, $2)`
|
|
||||||
|
|
||||||
func (d *postgresDB) AddVideoToUser(ctx context.Context, username string, videoID string) error {
|
|
||||||
_, err := d.db.ExecContext(ctx, addVideoToUserQuery, username, videoID)
|
|
||||||
if err != nil {
|
|
||||||
d.l.Log("level", "ERROR", "function", "db.AddVideoToUser", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var watchVideoQuery = `UPDATE user_videos SET watch_timestamp = NOW() WHERE username = $2 AND video_id = $3`
|
|
||||||
|
|
||||||
func (d *postgresDB) WatchVideo(ctx context.Context, username string, videoID string) error {
|
|
||||||
_, err := d.db.ExecContext(ctx, watchVideoQuery, username, videoID)
|
|
||||||
if err != nil {
|
|
||||||
d.l.Log("level", "ERROR", "function", "db.WatchVideo", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
80
db/videos.go
80
db/videos.go
|
@ -2,6 +2,7 @@ package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/models"
|
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/models"
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
|
@ -52,17 +53,88 @@ func (d *postgresDB) GetNewVideos(ctx context.Context, username string) ([]model
|
||||||
return videos, nil
|
return videos, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var createVideoQuery = `INSERT INTO videos (id, title, published_timestamp, channel_id) VALUES ($1, $2, $3, $4)`
|
var getWatchedVideosQuery = `
|
||||||
|
SELECT
|
||||||
|
video_id
|
||||||
|
, title
|
||||||
|
, published_timestamp
|
||||||
|
, watch_timestamp
|
||||||
|
, name as channel_name
|
||||||
|
FROM user_videos
|
||||||
|
LEFT JOIN videos ON video_id=videos.id
|
||||||
|
LEFT JOIN channels ON channel_id=channels.id
|
||||||
|
WHERE
|
||||||
|
1=1
|
||||||
|
AND watch_timestamp IS NOT NULL
|
||||||
|
AND username=$1
|
||||||
|
ORDER BY watch_timestamp ASC
|
||||||
|
`
|
||||||
|
|
||||||
func (d *postgresDB) CreateVideo(ctx context.Context, video models.Video, channelID string) error {
|
func (d *postgresDB) GetWatchedVideos(ctx context.Context, username string) ([]models.Video, error) {
|
||||||
_, err := d.db.ExecContext(ctx, createVideoQuery, video.ID, video.Title, video.PublishedTime, channelID)
|
rows, err := d.db.QueryContext(ctx, getWatchedVideosQuery, username)
|
||||||
|
if err != nil {
|
||||||
|
d.l.Log("level", "ERROR", "function", "db.GetWatchedVideos", "call", "sql.QueryContext", "error", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
videos := make([]models.Video, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var video models.Video
|
||||||
|
err = rows.Scan(
|
||||||
|
&video.ID,
|
||||||
|
&video.Title,
|
||||||
|
&video.PublishedTime,
|
||||||
|
&video.WatchTime,
|
||||||
|
&video.ChannelName,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
d.l.Log("level", "ERROR", "function", "db.GetWatchedVideos", "call", "sql.Scan", "error", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
videos = append(videos, video)
|
||||||
|
}
|
||||||
|
|
||||||
|
return videos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var addVideoQuery = `INSERT INTO videos (id, title, published_timestamp, channel_id) VALUES ($1, $2, $3, $4)`
|
||||||
|
|
||||||
|
func (d *postgresDB) AddVideo(ctx context.Context, video models.Video, channelID string) error {
|
||||||
|
_, err := d.db.ExecContext(ctx, addVideoQuery, video.ID, video.Title, video.PublishedTime, channelID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if pgerr, ok := err.(*pq.Error); ok {
|
if pgerr, ok := err.(*pq.Error); ok {
|
||||||
if pgerr.Code == "23505" {
|
if pgerr.Code == "23505" {
|
||||||
return ErrVideoExists
|
return ErrVideoExists
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d.l.Log("level", "ERROR", "function", "db.CreateVideo", "call", "sql.Exec", "error", err)
|
d.l.Log("level", "ERROR", "function", "db.AddVideo", "call", "sql.Exec", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var addVideoToUserQuery = `INSERT INTO user_videos (username, video_id) VALUES ($1, $2)`
|
||||||
|
|
||||||
|
func (d *postgresDB) AddVideoToUser(ctx context.Context, username string, videoID string) error {
|
||||||
|
_, err := d.db.ExecContext(ctx, addVideoToUserQuery, username, videoID)
|
||||||
|
if err != nil {
|
||||||
|
d.l.Log("level", "ERROR", "function", "db.AddVideoToUser", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var setVideoWatchTimeQuery = `UPDATE user_videos SET watch_timestamp = $1 WHERE username = $2 AND video_id = $3`
|
||||||
|
|
||||||
|
func (d *postgresDB) SetVideoWatchTime(
|
||||||
|
ctx context.Context, username string, videoID string, watchTime *time.Time,
|
||||||
|
) error {
|
||||||
|
_, err := d.db.ExecContext(ctx, setVideoWatchTimeQuery, watchTime, username, videoID)
|
||||||
|
if err != nil {
|
||||||
|
d.l.Log("level", "ERROR", "function", "db.WatchVideo", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/db"
|
||||||
|
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/feedparser"
|
||||||
|
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *handler) SubscribeToChannel(ctx context.Context, username string, channelID string) error {
|
||||||
|
parsedChannel, err := feedparser.Parse(h.log, channelID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
channel := models.Channel{
|
||||||
|
ID: channelID,
|
||||||
|
Name: parsedChannel.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.db.CreateChannel(ctx, channel)
|
||||||
|
if err != nil && !errors.Is(err, db.ErrChannelExists) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.db.SubscribeUserToChannel(ctx, username, channelID)
|
||||||
|
}
|
|
@ -2,13 +2,8 @@ package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/alexedwards/argon2id"
|
|
||||||
|
|
||||||
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/db"
|
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/db"
|
||||||
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/feedparser"
|
|
||||||
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/lib/log"
|
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/lib/log"
|
||||||
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/models"
|
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/models"
|
||||||
)
|
)
|
||||||
|
@ -17,7 +12,10 @@ type Handler interface {
|
||||||
CreateUser(ctx context.Context, user models.User) error
|
CreateUser(ctx context.Context, user models.User) error
|
||||||
SubscribeToChannel(ctx context.Context, username string, channelID string) error
|
SubscribeToChannel(ctx context.Context, username string, channelID string) error
|
||||||
GetNewVideos(ctx context.Context, username string) ([]models.Video, error)
|
GetNewVideos(ctx context.Context, username string) ([]models.Video, error)
|
||||||
|
GetWatchedVideos(ctx context.Context, username string) ([]models.Video, error)
|
||||||
FetchVideos(ctx context.Context) error
|
FetchVideos(ctx context.Context) error
|
||||||
|
MarkVideoAsWatched(ctx context.Context, username string, videoID string) error
|
||||||
|
MarkVideoAsUnwatched(ctx context.Context, username string, videoID string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
|
@ -28,100 +26,3 @@ type handler struct {
|
||||||
func New(log log.Logger, db db.DB) *handler {
|
func New(log log.Logger, db db.DB) *handler {
|
||||||
return &handler{log: log, db: db}
|
return &handler{log: log, db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) CreateUser(ctx context.Context, user models.User) error {
|
|
||||||
hashedPassword, err := argon2id.CreateHash(user.Password, argon2id.DefaultParams)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
user.Password = hashedPassword
|
|
||||||
|
|
||||||
return h.db.CreateUser(ctx, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) SubscribeToChannel(ctx context.Context, username string, channelID string) error {
|
|
||||||
parsedChannel, err := feedparser.Parse(h.log, channelID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
channel := models.Channel{
|
|
||||||
ID: channelID,
|
|
||||||
Name: parsedChannel.Name,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = h.db.CreateChannel(ctx, channel)
|
|
||||||
if !errors.Is(err, db.ErrChannelExists) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return h.db.SubscribeUserToChannel(ctx, username, channelID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) GetNewVideos(ctx context.Context, username string) ([]models.Video, error) {
|
|
||||||
return h.db.GetNewVideos(ctx, username)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) addVideoToAllSubscribers(ctx context.Context, channelID string, videoID string) error {
|
|
||||||
subs, err := h.db.GetChannelSubscribers(ctx, channelID)
|
|
||||||
if err != nil {
|
|
||||||
h.log.Log("level", "ERROR", "call", "db.GetChannelSubscribers", "err", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, sub := range subs {
|
|
||||||
err = h.db.AddVideoToUser(ctx, sub, videoID)
|
|
||||||
if err != nil {
|
|
||||||
h.log.Log("level", "ERROR", "call", "db.AddVideoToUser", "err", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) fetchVideosForChannel(ctx context.Context, channelID string, parsedChannel *feedparser.Channel) {
|
|
||||||
for _, parsedVideo := range parsedChannel.Videos {
|
|
||||||
date, err := parsedVideo.Published.Parse()
|
|
||||||
if err != nil {
|
|
||||||
h.log.Log("level", "WARNING", "call", "feedparser.Parse", "err", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
id := strings.Split(parsedVideo.ID, ":")[2]
|
|
||||||
video := models.Video{
|
|
||||||
ID: id,
|
|
||||||
Title: parsedVideo.Title,
|
|
||||||
PublishedTime: date,
|
|
||||||
}
|
|
||||||
err = h.db.CreateVideo(ctx, video, channelID)
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, db.ErrVideoExists) {
|
|
||||||
h.log.Log("level", "WARNING", "call", "db.CreateVideo", "err", err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err = h.addVideoToAllSubscribers(ctx, channelID, id)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) FetchVideos(ctx context.Context) error {
|
|
||||||
channels, err := h.db.ListChannels(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, channel := range channels {
|
|
||||||
parsedChannel, err := feedparser.Parse(h.log, channel.ID)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
h.fetchVideosForChannel(ctx, channel.ID, parsedChannel)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/alexedwards/argon2id"
|
||||||
|
|
||||||
|
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *handler) CreateUser(ctx context.Context, user models.User) error {
|
||||||
|
hashedPassword, err := argon2id.CreateHash(user.Password, argon2id.DefaultParams)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user.Password = hashedPassword
|
||||||
|
|
||||||
|
return h.db.CreateUser(ctx, user)
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/db"
|
||||||
|
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/feedparser"
|
||||||
|
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *handler) GetNewVideos(ctx context.Context, username string) ([]models.Video, error) {
|
||||||
|
return h.db.GetNewVideos(ctx, username)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) GetWatchedVideos(ctx context.Context, username string) ([]models.Video, error) {
|
||||||
|
return h.db.GetWatchedVideos(ctx, username)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) addVideoToAllSubscribers(ctx context.Context, channelID string, videoID string) error {
|
||||||
|
subs, err := h.db.GetChannelSubscribers(ctx, channelID)
|
||||||
|
if err != nil {
|
||||||
|
h.log.Log("level", "ERROR", "call", "db.GetChannelSubscribers", "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sub := range subs {
|
||||||
|
err = h.db.AddVideoToUser(ctx, sub, videoID)
|
||||||
|
if err != nil {
|
||||||
|
h.log.Log("level", "ERROR", "call", "db.AddVideoToUser", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) fetchVideosForChannel(ctx context.Context, channelID string, parsedChannel *feedparser.Channel) {
|
||||||
|
for _, parsedVideo := range parsedChannel.Videos {
|
||||||
|
date, err := parsedVideo.Published.Parse()
|
||||||
|
if err != nil {
|
||||||
|
h.log.Log("level", "WARNING", "call", "feedparser.Parse", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
id := strings.Split(parsedVideo.ID, ":")[2]
|
||||||
|
video := models.Video{
|
||||||
|
ID: id,
|
||||||
|
Title: parsedVideo.Title,
|
||||||
|
PublishedTime: date,
|
||||||
|
}
|
||||||
|
err = h.db.AddVideo(ctx, video, channelID)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, db.ErrVideoExists) {
|
||||||
|
h.log.Log("level", "WARNING", "call", "db.AddVideo", "err", err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = h.addVideoToAllSubscribers(ctx, channelID, id)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) FetchVideos(ctx context.Context) error {
|
||||||
|
channels, err := h.db.ListChannels(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, channel := range channels {
|
||||||
|
parsedChannel, err := feedparser.Parse(h.log, channel.ID)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
h.fetchVideosForChannel(ctx, channel.ID, parsedChannel)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) MarkVideoAsWatched(ctx context.Context, username string, videoID string) error {
|
||||||
|
watchTime := time.Now()
|
||||||
|
return h.db.SetVideoWatchTime(ctx, username, videoID, &watchTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) MarkVideoAsUnwatched(ctx context.Context, username string, videoID string) error {
|
||||||
|
return h.db.SetVideoWatchTime(ctx, username, videoID, nil)
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package ytrssil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/db"
|
||||||
|
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/feedparser"
|
||||||
|
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *server) SubscribeToChannel(c *gin.Context) {
|
||||||
|
var channel models.Channel
|
||||||
|
err := c.ShouldBindUri(&channel)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
username := c.GetString("username")
|
||||||
|
|
||||||
|
err = s.handler.SubscribeToChannel(c.Request.Context(), username, channel.ID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, db.ErrAlreadySubscribed) {
|
||||||
|
c.AbortWithStatusJSON(http.StatusConflict, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errors.Is(err, feedparser.ErrInvalidChannelID) {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"msg": "subscribed to channel successfully"})
|
||||||
|
}
|
|
@ -43,8 +43,11 @@ func SetupGinRouter(l log.Logger, handler handler.Handler, authMiddleware func(c
|
||||||
api := engine.Group("/api")
|
api := engine.Group("/api")
|
||||||
api.Use(authMiddleware)
|
api.Use(authMiddleware)
|
||||||
{
|
{
|
||||||
api.GET("videos/new", srv.GetNewVideos)
|
|
||||||
api.POST("channels/:channel_id/subscribe", srv.SubscribeToChannel)
|
api.POST("channels/:channel_id/subscribe", srv.SubscribeToChannel)
|
||||||
|
api.GET("videos/new", srv.GetNewVideos)
|
||||||
|
api.GET("videos/watched", srv.GetWatchedVideos)
|
||||||
|
api.POST("videos/:video_id/watch", srv.MarkVideoAsWatched)
|
||||||
|
api.POST("videos/:video_id/unwatch", srv.MarkVideoAsUnwatched)
|
||||||
}
|
}
|
||||||
|
|
||||||
return engine, nil
|
return engine, nil
|
||||||
|
|
|
@ -7,13 +7,12 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/db"
|
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/db"
|
||||||
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/feedparser"
|
|
||||||
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/models"
|
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *server) CreateUser(c *gin.Context) {
|
func (s *server) CreateUser(c *gin.Context) {
|
||||||
var user models.User
|
var user models.User
|
||||||
err := c.BindJSON(&user)
|
err := c.ShouldBindJSON(&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
|
@ -21,36 +20,14 @@ func (s *server) CreateUser(c *gin.Context) {
|
||||||
|
|
||||||
err = s.handler.CreateUser(c.Request.Context(), user)
|
err = s.handler.CreateUser(c.Request.Context(), user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, db.ErrUserExists) {
|
||||||
|
c.AbortWithStatusJSON(http.StatusConflict, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"msg": "user created"})
|
c.JSON(http.StatusOK, gin.H{"msg": "user created"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) SubscribeToChannel(c *gin.Context) {
|
|
||||||
var channel models.Channel
|
|
||||||
err := c.BindUri(&channel)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
username := c.GetString("username")
|
|
||||||
|
|
||||||
err = s.handler.SubscribeToChannel(c.Request.Context(), username, channel.ID)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, db.ErrAlreadySubscribed) {
|
|
||||||
c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if errors.Is(err, feedparser.ErrInvalidChannelID) {
|
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"msg": "subscribed to channel successfully"})
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,14 +8,27 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *server) GetNewVideos(c *gin.Context) {
|
func (s *server) GetNewVideos(c *gin.Context) {
|
||||||
username := c.MustGet("username").(string)
|
username := c.GetString("username")
|
||||||
videos, err := s.handler.GetNewVideos(c.Request.Context(), username)
|
videos, err := s.handler.GetNewVideos(c.Request.Context(), username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, models.GetNewVideosResponse{
|
c.JSON(http.StatusOK, models.VideosResponse{
|
||||||
|
Videos: videos,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) GetWatchedVideos(c *gin.Context) {
|
||||||
|
username := c.GetString("username")
|
||||||
|
videos, err := s.handler.GetWatchedVideos(c.Request.Context(), username)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, models.VideosResponse{
|
||||||
Videos: videos,
|
Videos: videos,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -29,3 +42,39 @@ func (s *server) FetchVideos(c *gin.Context) {
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"msg": "videos fetched successfully"})
|
c.JSON(http.StatusOK, gin.H{"msg": "videos fetched successfully"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *server) MarkVideoAsWatched(c *gin.Context) {
|
||||||
|
username := c.GetString("username")
|
||||||
|
var req models.VideoURIRequest
|
||||||
|
err := c.ShouldBindUri(&req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.handler.MarkVideoAsWatched(c.Request.Context(), username, req.VideoID)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"msg": "marked video as watched"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) MarkVideoAsUnwatched(c *gin.Context) {
|
||||||
|
username := c.GetString("username")
|
||||||
|
var req models.VideoURIRequest
|
||||||
|
err := c.ShouldBindUri(&req)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.handler.MarkVideoAsUnwatched(c.Request.Context(), username, req.VideoID)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"msg": "cleared video from watch history"})
|
||||||
|
}
|
||||||
|
|
281
mocks/db/db.go
281
mocks/db/db.go
|
@ -8,6 +8,7 @@ import (
|
||||||
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/db"
|
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/db"
|
||||||
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/models"
|
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/models"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure, that DBMock does implement db.DB.
|
// Ensure, that DBMock does implement db.DB.
|
||||||
|
@ -20,6 +21,9 @@ var _ db.DB = &DBMock{}
|
||||||
//
|
//
|
||||||
// // make and configure a mocked db.DB
|
// // make and configure a mocked db.DB
|
||||||
// mockedDB := &DBMock{
|
// mockedDB := &DBMock{
|
||||||
|
// AddVideoFunc: func(ctx context.Context, video models.Video, channelID string) error {
|
||||||
|
// panic("mock out the AddVideo method")
|
||||||
|
// },
|
||||||
// AddVideoToUserFunc: func(ctx context.Context, username string, videoID string) error {
|
// AddVideoToUserFunc: func(ctx context.Context, username string, videoID string) error {
|
||||||
// panic("mock out the AddVideoToUser method")
|
// panic("mock out the AddVideoToUser method")
|
||||||
// },
|
// },
|
||||||
|
@ -32,9 +36,6 @@ var _ db.DB = &DBMock{}
|
||||||
// CreateUserFunc: func(ctx context.Context, user models.User) error {
|
// CreateUserFunc: func(ctx context.Context, user models.User) error {
|
||||||
// panic("mock out the CreateUser method")
|
// panic("mock out the CreateUser method")
|
||||||
// },
|
// },
|
||||||
// CreateVideoFunc: func(ctx context.Context, video models.Video, channelID string) error {
|
|
||||||
// panic("mock out the CreateVideo method")
|
|
||||||
// },
|
|
||||||
// DeleteUserFunc: func(ctx context.Context, username string) error {
|
// DeleteUserFunc: func(ctx context.Context, username string) error {
|
||||||
// panic("mock out the DeleteUser method")
|
// panic("mock out the DeleteUser method")
|
||||||
// },
|
// },
|
||||||
|
@ -44,15 +45,18 @@ var _ db.DB = &DBMock{}
|
||||||
// GetNewVideosFunc: func(ctx context.Context, username string) ([]models.Video, error) {
|
// GetNewVideosFunc: func(ctx context.Context, username string) ([]models.Video, error) {
|
||||||
// panic("mock out the GetNewVideos method")
|
// panic("mock out the GetNewVideos method")
|
||||||
// },
|
// },
|
||||||
|
// GetWatchedVideosFunc: func(ctx context.Context, username string) ([]models.Video, error) {
|
||||||
|
// panic("mock out the GetWatchedVideos method")
|
||||||
|
// },
|
||||||
// ListChannelsFunc: func(ctx context.Context) ([]models.Channel, error) {
|
// ListChannelsFunc: func(ctx context.Context) ([]models.Channel, error) {
|
||||||
// panic("mock out the ListChannels method")
|
// panic("mock out the ListChannels method")
|
||||||
// },
|
// },
|
||||||
|
// SetVideoWatchTimeFunc: func(ctx context.Context, username string, videoID string, watchTime *time.Time) error {
|
||||||
|
// panic("mock out the SetVideoWatchTime method")
|
||||||
|
// },
|
||||||
// SubscribeUserToChannelFunc: func(ctx context.Context, username string, channelID string) error {
|
// SubscribeUserToChannelFunc: func(ctx context.Context, username string, channelID string) error {
|
||||||
// panic("mock out the SubscribeUserToChannel method")
|
// panic("mock out the SubscribeUserToChannel method")
|
||||||
// },
|
// },
|
||||||
// WatchVideoFunc: func(ctx context.Context, username string, videoID string) error {
|
|
||||||
// panic("mock out the WatchVideo method")
|
|
||||||
// },
|
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// // use mockedDB in code that requires db.DB
|
// // use mockedDB in code that requires db.DB
|
||||||
|
@ -60,6 +64,9 @@ var _ db.DB = &DBMock{}
|
||||||
//
|
//
|
||||||
// }
|
// }
|
||||||
type DBMock struct {
|
type DBMock struct {
|
||||||
|
// AddVideoFunc mocks the AddVideo method.
|
||||||
|
AddVideoFunc func(ctx context.Context, video models.Video, channelID string) error
|
||||||
|
|
||||||
// AddVideoToUserFunc mocks the AddVideoToUser method.
|
// AddVideoToUserFunc mocks the AddVideoToUser method.
|
||||||
AddVideoToUserFunc func(ctx context.Context, username string, videoID string) error
|
AddVideoToUserFunc func(ctx context.Context, username string, videoID string) error
|
||||||
|
|
||||||
|
@ -72,9 +79,6 @@ type DBMock struct {
|
||||||
// CreateUserFunc mocks the CreateUser method.
|
// CreateUserFunc mocks the CreateUser method.
|
||||||
CreateUserFunc func(ctx context.Context, user models.User) error
|
CreateUserFunc func(ctx context.Context, user models.User) error
|
||||||
|
|
||||||
// CreateVideoFunc mocks the CreateVideo method.
|
|
||||||
CreateVideoFunc func(ctx context.Context, video models.Video, channelID string) error
|
|
||||||
|
|
||||||
// DeleteUserFunc mocks the DeleteUser method.
|
// DeleteUserFunc mocks the DeleteUser method.
|
||||||
DeleteUserFunc func(ctx context.Context, username string) error
|
DeleteUserFunc func(ctx context.Context, username string) error
|
||||||
|
|
||||||
|
@ -84,17 +88,29 @@ type DBMock struct {
|
||||||
// GetNewVideosFunc mocks the GetNewVideos method.
|
// GetNewVideosFunc mocks the GetNewVideos method.
|
||||||
GetNewVideosFunc func(ctx context.Context, username string) ([]models.Video, error)
|
GetNewVideosFunc func(ctx context.Context, username string) ([]models.Video, error)
|
||||||
|
|
||||||
|
// GetWatchedVideosFunc mocks the GetWatchedVideos method.
|
||||||
|
GetWatchedVideosFunc func(ctx context.Context, username string) ([]models.Video, error)
|
||||||
|
|
||||||
// ListChannelsFunc mocks the ListChannels method.
|
// ListChannelsFunc mocks the ListChannels method.
|
||||||
ListChannelsFunc func(ctx context.Context) ([]models.Channel, error)
|
ListChannelsFunc func(ctx context.Context) ([]models.Channel, error)
|
||||||
|
|
||||||
|
// SetVideoWatchTimeFunc mocks the SetVideoWatchTime method.
|
||||||
|
SetVideoWatchTimeFunc func(ctx context.Context, username string, videoID string, watchTime *time.Time) error
|
||||||
|
|
||||||
// SubscribeUserToChannelFunc mocks the SubscribeUserToChannel method.
|
// SubscribeUserToChannelFunc mocks the SubscribeUserToChannel method.
|
||||||
SubscribeUserToChannelFunc func(ctx context.Context, username string, channelID string) error
|
SubscribeUserToChannelFunc func(ctx context.Context, username string, channelID string) error
|
||||||
|
|
||||||
// WatchVideoFunc mocks the WatchVideo method.
|
|
||||||
WatchVideoFunc func(ctx context.Context, username string, videoID string) error
|
|
||||||
|
|
||||||
// calls tracks calls to the methods.
|
// calls tracks calls to the methods.
|
||||||
calls struct {
|
calls struct {
|
||||||
|
// AddVideo holds details about calls to the AddVideo method.
|
||||||
|
AddVideo []struct {
|
||||||
|
// Ctx is the ctx argument value.
|
||||||
|
Ctx context.Context
|
||||||
|
// Video is the video argument value.
|
||||||
|
Video models.Video
|
||||||
|
// ChannelID is the channelID argument value.
|
||||||
|
ChannelID string
|
||||||
|
}
|
||||||
// AddVideoToUser holds details about calls to the AddVideoToUser method.
|
// AddVideoToUser holds details about calls to the AddVideoToUser method.
|
||||||
AddVideoToUser []struct {
|
AddVideoToUser []struct {
|
||||||
// Ctx is the ctx argument value.
|
// Ctx is the ctx argument value.
|
||||||
|
@ -125,15 +141,6 @@ type DBMock struct {
|
||||||
// User is the user argument value.
|
// User is the user argument value.
|
||||||
User models.User
|
User models.User
|
||||||
}
|
}
|
||||||
// CreateVideo holds details about calls to the CreateVideo method.
|
|
||||||
CreateVideo []struct {
|
|
||||||
// Ctx is the ctx argument value.
|
|
||||||
Ctx context.Context
|
|
||||||
// Video is the video argument value.
|
|
||||||
Video models.Video
|
|
||||||
// ChannelID is the channelID argument value.
|
|
||||||
ChannelID string
|
|
||||||
}
|
|
||||||
// DeleteUser holds details about calls to the DeleteUser method.
|
// DeleteUser holds details about calls to the DeleteUser method.
|
||||||
DeleteUser []struct {
|
DeleteUser []struct {
|
||||||
// Ctx is the ctx argument value.
|
// Ctx is the ctx argument value.
|
||||||
|
@ -155,11 +162,29 @@ type DBMock struct {
|
||||||
// Username is the username argument value.
|
// Username is the username argument value.
|
||||||
Username string
|
Username string
|
||||||
}
|
}
|
||||||
|
// GetWatchedVideos holds details about calls to the GetWatchedVideos method.
|
||||||
|
GetWatchedVideos []struct {
|
||||||
|
// Ctx is the ctx argument value.
|
||||||
|
Ctx context.Context
|
||||||
|
// Username is the username argument value.
|
||||||
|
Username string
|
||||||
|
}
|
||||||
// ListChannels holds details about calls to the ListChannels method.
|
// ListChannels holds details about calls to the ListChannels method.
|
||||||
ListChannels []struct {
|
ListChannels []struct {
|
||||||
// Ctx is the ctx argument value.
|
// Ctx is the ctx argument value.
|
||||||
Ctx context.Context
|
Ctx context.Context
|
||||||
}
|
}
|
||||||
|
// SetVideoWatchTime holds details about calls to the SetVideoWatchTime method.
|
||||||
|
SetVideoWatchTime []struct {
|
||||||
|
// Ctx is the ctx argument value.
|
||||||
|
Ctx context.Context
|
||||||
|
// Username is the username argument value.
|
||||||
|
Username string
|
||||||
|
// VideoID is the videoID argument value.
|
||||||
|
VideoID string
|
||||||
|
// WatchTime is the watchTime argument value.
|
||||||
|
WatchTime *time.Time
|
||||||
|
}
|
||||||
// SubscribeUserToChannel holds details about calls to the SubscribeUserToChannel method.
|
// SubscribeUserToChannel holds details about calls to the SubscribeUserToChannel method.
|
||||||
SubscribeUserToChannel []struct {
|
SubscribeUserToChannel []struct {
|
||||||
// Ctx is the ctx argument value.
|
// Ctx is the ctx argument value.
|
||||||
|
@ -169,27 +194,59 @@ type DBMock struct {
|
||||||
// ChannelID is the channelID argument value.
|
// ChannelID is the channelID argument value.
|
||||||
ChannelID string
|
ChannelID string
|
||||||
}
|
}
|
||||||
// WatchVideo holds details about calls to the WatchVideo method.
|
|
||||||
WatchVideo []struct {
|
|
||||||
// Ctx is the ctx argument value.
|
|
||||||
Ctx context.Context
|
|
||||||
// Username is the username argument value.
|
|
||||||
Username string
|
|
||||||
// VideoID is the videoID argument value.
|
|
||||||
VideoID string
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
lockAddVideo sync.RWMutex
|
||||||
lockAddVideoToUser sync.RWMutex
|
lockAddVideoToUser sync.RWMutex
|
||||||
lockAuthenticateUser sync.RWMutex
|
lockAuthenticateUser sync.RWMutex
|
||||||
lockCreateChannel sync.RWMutex
|
lockCreateChannel sync.RWMutex
|
||||||
lockCreateUser sync.RWMutex
|
lockCreateUser sync.RWMutex
|
||||||
lockCreateVideo sync.RWMutex
|
|
||||||
lockDeleteUser sync.RWMutex
|
lockDeleteUser sync.RWMutex
|
||||||
lockGetChannelSubscribers sync.RWMutex
|
lockGetChannelSubscribers sync.RWMutex
|
||||||
lockGetNewVideos sync.RWMutex
|
lockGetNewVideos sync.RWMutex
|
||||||
|
lockGetWatchedVideos sync.RWMutex
|
||||||
lockListChannels sync.RWMutex
|
lockListChannels sync.RWMutex
|
||||||
|
lockSetVideoWatchTime sync.RWMutex
|
||||||
lockSubscribeUserToChannel sync.RWMutex
|
lockSubscribeUserToChannel sync.RWMutex
|
||||||
lockWatchVideo sync.RWMutex
|
}
|
||||||
|
|
||||||
|
// AddVideo calls AddVideoFunc.
|
||||||
|
func (mock *DBMock) AddVideo(ctx context.Context, video models.Video, channelID string) error {
|
||||||
|
if mock.AddVideoFunc == nil {
|
||||||
|
panic("DBMock.AddVideoFunc: method is nil but DB.AddVideo was just called")
|
||||||
|
}
|
||||||
|
callInfo := struct {
|
||||||
|
Ctx context.Context
|
||||||
|
Video models.Video
|
||||||
|
ChannelID string
|
||||||
|
}{
|
||||||
|
Ctx: ctx,
|
||||||
|
Video: video,
|
||||||
|
ChannelID: channelID,
|
||||||
|
}
|
||||||
|
mock.lockAddVideo.Lock()
|
||||||
|
mock.calls.AddVideo = append(mock.calls.AddVideo, callInfo)
|
||||||
|
mock.lockAddVideo.Unlock()
|
||||||
|
return mock.AddVideoFunc(ctx, video, channelID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddVideoCalls gets all the calls that were made to AddVideo.
|
||||||
|
// Check the length with:
|
||||||
|
//
|
||||||
|
// len(mockedDB.AddVideoCalls())
|
||||||
|
func (mock *DBMock) AddVideoCalls() []struct {
|
||||||
|
Ctx context.Context
|
||||||
|
Video models.Video
|
||||||
|
ChannelID string
|
||||||
|
} {
|
||||||
|
var calls []struct {
|
||||||
|
Ctx context.Context
|
||||||
|
Video models.Video
|
||||||
|
ChannelID string
|
||||||
|
}
|
||||||
|
mock.lockAddVideo.RLock()
|
||||||
|
calls = mock.calls.AddVideo
|
||||||
|
mock.lockAddVideo.RUnlock()
|
||||||
|
return calls
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddVideoToUser calls AddVideoToUserFunc.
|
// AddVideoToUser calls AddVideoToUserFunc.
|
||||||
|
@ -340,46 +397,6 @@ func (mock *DBMock) CreateUserCalls() []struct {
|
||||||
return calls
|
return calls
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateVideo calls CreateVideoFunc.
|
|
||||||
func (mock *DBMock) CreateVideo(ctx context.Context, video models.Video, channelID string) error {
|
|
||||||
if mock.CreateVideoFunc == nil {
|
|
||||||
panic("DBMock.CreateVideoFunc: method is nil but DB.CreateVideo was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
Ctx context.Context
|
|
||||||
Video models.Video
|
|
||||||
ChannelID string
|
|
||||||
}{
|
|
||||||
Ctx: ctx,
|
|
||||||
Video: video,
|
|
||||||
ChannelID: channelID,
|
|
||||||
}
|
|
||||||
mock.lockCreateVideo.Lock()
|
|
||||||
mock.calls.CreateVideo = append(mock.calls.CreateVideo, callInfo)
|
|
||||||
mock.lockCreateVideo.Unlock()
|
|
||||||
return mock.CreateVideoFunc(ctx, video, channelID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateVideoCalls gets all the calls that were made to CreateVideo.
|
|
||||||
// Check the length with:
|
|
||||||
//
|
|
||||||
// len(mockedDB.CreateVideoCalls())
|
|
||||||
func (mock *DBMock) CreateVideoCalls() []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
Video models.Video
|
|
||||||
ChannelID string
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
Video models.Video
|
|
||||||
ChannelID string
|
|
||||||
}
|
|
||||||
mock.lockCreateVideo.RLock()
|
|
||||||
calls = mock.calls.CreateVideo
|
|
||||||
mock.lockCreateVideo.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteUser calls DeleteUserFunc.
|
// DeleteUser calls DeleteUserFunc.
|
||||||
func (mock *DBMock) DeleteUser(ctx context.Context, username string) error {
|
func (mock *DBMock) DeleteUser(ctx context.Context, username string) error {
|
||||||
if mock.DeleteUserFunc == nil {
|
if mock.DeleteUserFunc == nil {
|
||||||
|
@ -488,6 +505,42 @@ func (mock *DBMock) GetNewVideosCalls() []struct {
|
||||||
return calls
|
return calls
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetWatchedVideos calls GetWatchedVideosFunc.
|
||||||
|
func (mock *DBMock) GetWatchedVideos(ctx context.Context, username string) ([]models.Video, error) {
|
||||||
|
if mock.GetWatchedVideosFunc == nil {
|
||||||
|
panic("DBMock.GetWatchedVideosFunc: method is nil but DB.GetWatchedVideos was just called")
|
||||||
|
}
|
||||||
|
callInfo := struct {
|
||||||
|
Ctx context.Context
|
||||||
|
Username string
|
||||||
|
}{
|
||||||
|
Ctx: ctx,
|
||||||
|
Username: username,
|
||||||
|
}
|
||||||
|
mock.lockGetWatchedVideos.Lock()
|
||||||
|
mock.calls.GetWatchedVideos = append(mock.calls.GetWatchedVideos, callInfo)
|
||||||
|
mock.lockGetWatchedVideos.Unlock()
|
||||||
|
return mock.GetWatchedVideosFunc(ctx, username)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWatchedVideosCalls gets all the calls that were made to GetWatchedVideos.
|
||||||
|
// Check the length with:
|
||||||
|
//
|
||||||
|
// len(mockedDB.GetWatchedVideosCalls())
|
||||||
|
func (mock *DBMock) GetWatchedVideosCalls() []struct {
|
||||||
|
Ctx context.Context
|
||||||
|
Username string
|
||||||
|
} {
|
||||||
|
var calls []struct {
|
||||||
|
Ctx context.Context
|
||||||
|
Username string
|
||||||
|
}
|
||||||
|
mock.lockGetWatchedVideos.RLock()
|
||||||
|
calls = mock.calls.GetWatchedVideos
|
||||||
|
mock.lockGetWatchedVideos.RUnlock()
|
||||||
|
return calls
|
||||||
|
}
|
||||||
|
|
||||||
// ListChannels calls ListChannelsFunc.
|
// ListChannels calls ListChannelsFunc.
|
||||||
func (mock *DBMock) ListChannels(ctx context.Context) ([]models.Channel, error) {
|
func (mock *DBMock) ListChannels(ctx context.Context) ([]models.Channel, error) {
|
||||||
if mock.ListChannelsFunc == nil {
|
if mock.ListChannelsFunc == nil {
|
||||||
|
@ -520,6 +573,50 @@ func (mock *DBMock) ListChannelsCalls() []struct {
|
||||||
return calls
|
return calls
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetVideoWatchTime calls SetVideoWatchTimeFunc.
|
||||||
|
func (mock *DBMock) SetVideoWatchTime(ctx context.Context, username string, videoID string, watchTime *time.Time) error {
|
||||||
|
if mock.SetVideoWatchTimeFunc == nil {
|
||||||
|
panic("DBMock.SetVideoWatchTimeFunc: method is nil but DB.SetVideoWatchTime was just called")
|
||||||
|
}
|
||||||
|
callInfo := struct {
|
||||||
|
Ctx context.Context
|
||||||
|
Username string
|
||||||
|
VideoID string
|
||||||
|
WatchTime *time.Time
|
||||||
|
}{
|
||||||
|
Ctx: ctx,
|
||||||
|
Username: username,
|
||||||
|
VideoID: videoID,
|
||||||
|
WatchTime: watchTime,
|
||||||
|
}
|
||||||
|
mock.lockSetVideoWatchTime.Lock()
|
||||||
|
mock.calls.SetVideoWatchTime = append(mock.calls.SetVideoWatchTime, callInfo)
|
||||||
|
mock.lockSetVideoWatchTime.Unlock()
|
||||||
|
return mock.SetVideoWatchTimeFunc(ctx, username, videoID, watchTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetVideoWatchTimeCalls gets all the calls that were made to SetVideoWatchTime.
|
||||||
|
// Check the length with:
|
||||||
|
//
|
||||||
|
// len(mockedDB.SetVideoWatchTimeCalls())
|
||||||
|
func (mock *DBMock) SetVideoWatchTimeCalls() []struct {
|
||||||
|
Ctx context.Context
|
||||||
|
Username string
|
||||||
|
VideoID string
|
||||||
|
WatchTime *time.Time
|
||||||
|
} {
|
||||||
|
var calls []struct {
|
||||||
|
Ctx context.Context
|
||||||
|
Username string
|
||||||
|
VideoID string
|
||||||
|
WatchTime *time.Time
|
||||||
|
}
|
||||||
|
mock.lockSetVideoWatchTime.RLock()
|
||||||
|
calls = mock.calls.SetVideoWatchTime
|
||||||
|
mock.lockSetVideoWatchTime.RUnlock()
|
||||||
|
return calls
|
||||||
|
}
|
||||||
|
|
||||||
// SubscribeUserToChannel calls SubscribeUserToChannelFunc.
|
// SubscribeUserToChannel calls SubscribeUserToChannelFunc.
|
||||||
func (mock *DBMock) SubscribeUserToChannel(ctx context.Context, username string, channelID string) error {
|
func (mock *DBMock) SubscribeUserToChannel(ctx context.Context, username string, channelID string) error {
|
||||||
if mock.SubscribeUserToChannelFunc == nil {
|
if mock.SubscribeUserToChannelFunc == nil {
|
||||||
|
@ -559,43 +656,3 @@ func (mock *DBMock) SubscribeUserToChannelCalls() []struct {
|
||||||
mock.lockSubscribeUserToChannel.RUnlock()
|
mock.lockSubscribeUserToChannel.RUnlock()
|
||||||
return calls
|
return calls
|
||||||
}
|
}
|
||||||
|
|
||||||
// WatchVideo calls WatchVideoFunc.
|
|
||||||
func (mock *DBMock) WatchVideo(ctx context.Context, username string, videoID string) error {
|
|
||||||
if mock.WatchVideoFunc == nil {
|
|
||||||
panic("DBMock.WatchVideoFunc: method is nil but DB.WatchVideo was just called")
|
|
||||||
}
|
|
||||||
callInfo := struct {
|
|
||||||
Ctx context.Context
|
|
||||||
Username string
|
|
||||||
VideoID string
|
|
||||||
}{
|
|
||||||
Ctx: ctx,
|
|
||||||
Username: username,
|
|
||||||
VideoID: videoID,
|
|
||||||
}
|
|
||||||
mock.lockWatchVideo.Lock()
|
|
||||||
mock.calls.WatchVideo = append(mock.calls.WatchVideo, callInfo)
|
|
||||||
mock.lockWatchVideo.Unlock()
|
|
||||||
return mock.WatchVideoFunc(ctx, username, videoID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchVideoCalls gets all the calls that were made to WatchVideo.
|
|
||||||
// Check the length with:
|
|
||||||
//
|
|
||||||
// len(mockedDB.WatchVideoCalls())
|
|
||||||
func (mock *DBMock) WatchVideoCalls() []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
Username string
|
|
||||||
VideoID string
|
|
||||||
} {
|
|
||||||
var calls []struct {
|
|
||||||
Ctx context.Context
|
|
||||||
Username string
|
|
||||||
VideoID string
|
|
||||||
}
|
|
||||||
mock.lockWatchVideo.RLock()
|
|
||||||
calls = mock.calls.WatchVideo
|
|
||||||
mock.lockWatchVideo.RUnlock()
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
type GetNewVideosResponse struct {
|
type VideosResponse struct {
|
||||||
Videos []Video `json:"videos"`
|
Videos []Video `json:"videos"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VideoURIRequest struct {
|
||||||
|
VideoID string `uri:"video_id" binding:"required"`
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue