Mark videos as watched

This commit is contained in:
Pavle Portic 2022-10-30 01:08:07 +02:00
parent fa2a3cccec
commit 3147d3dd4c
Signed by: TheEdgeOfRage
GPG Key ID: 66AD4BA646FBC0D2
14 changed files with 539 additions and 305 deletions

View File

@ -73,3 +73,20 @@ func (d *postgresDB) GetChannelSubscribers(ctx context.Context, channelID string
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
}

View File

@ -3,6 +3,7 @@ package db
import (
"context"
"errors"
"time"
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/models"
)
@ -11,14 +12,17 @@ var (
ErrChannelExists = errors.New("channel already exists")
ErrAlreadySubscribed = errors.New("already subscribed to channel")
ErrVideoExists = errors.New("video already exists")
ErrUserExists = errors.New("user already exists")
)
// DB represents a database layer for getting video and channel data
type DB interface {
// GetNewVideos returns unwatched videos from all channels
GetNewVideos(ctx context.Context, username string) ([]models.Video, error)
// CreateVideo adds a newly published video to the database
CreateVideo(ctx context.Context, video models.Video, channelID 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
// CreateChannel starts tracking a new channel and fetch new videos for it
CreateChannel(ctx context.Context, channel models.Channel) error
@ -26,17 +30,17 @@ type DB interface {
ListChannels(ctx context.Context) ([]models.Channel, error)
// GetChannelSubscribers lists all channels from the database
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(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(ctx context.Context, username string, videoID string) error
// WatchVideo marks a video as watched so it no longer shows in the feed
WatchVideo(ctx context.Context, username string, videoID string) error
// SetVideoWatchTime sets or unsets the watch timestamp of a user's video
SetVideoWatchTime(ctx context.Context, username string, videoID string, watchTime *time.Time) error
}

View File

@ -2,6 +2,8 @@ package db
import (
"context"
"database/sql"
"errors"
"github.com/alexedwards/argon2id"
"github.com/lib/pq"
@ -16,6 +18,10 @@ func (d *postgresDB) AuthenticateUser(ctx context.Context, user models.User) (bo
var hashedPassword string
err := row.Scan(&hashedPassword)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return false, nil
}
d.l.Log("level", "ERROR", "function", "db.AuthenticateUser", "error", 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 {
_, err := d.db.ExecContext(ctx, createUserQuery, user.Username, user.Password)
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)
return err
}
@ -52,44 +63,3 @@ func (d *postgresDB) DeleteUser(ctx context.Context, username string) error {
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
}

View File

@ -2,6 +2,7 @@ package db
import (
"context"
"time"
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/models"
"github.com/lib/pq"
@ -52,17 +53,88 @@ func (d *postgresDB) GetNewVideos(ctx context.Context, username string) ([]model
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 {
_, err := d.db.ExecContext(ctx, createVideoQuery, video.ID, video.Title, video.PublishedTime, channelID)
func (d *postgresDB) GetWatchedVideos(ctx context.Context, username string) ([]models.Video, error) {
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 pgerr, ok := err.(*pq.Error); ok {
if pgerr.Code == "23505" {
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
}

29
handler/channels.go Normal file
View File

@ -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)
}

View File

@ -2,13 +2,8 @@ package handler
import (
"context"
"errors"
"strings"
"github.com/alexedwards/argon2id"
"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/models"
)
@ -17,7 +12,10 @@ type Handler interface {
CreateUser(ctx context.Context, user models.User) error
SubscribeToChannel(ctx context.Context, username string, channelID string) error
GetNewVideos(ctx context.Context, username string) ([]models.Video, error)
GetWatchedVideos(ctx context.Context, username string) ([]models.Video, 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 {
@ -28,100 +26,3 @@ type handler struct {
func New(log log.Logger, db db.DB) *handler {
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
}

19
handler/users.go Normal file
View File

@ -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)
}

93
handler/videos.go Normal file
View File

@ -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)
}

View File

@ -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"})
}

View File

@ -43,8 +43,11 @@ func SetupGinRouter(l log.Logger, handler handler.Handler, authMiddleware func(c
api := engine.Group("/api")
api.Use(authMiddleware)
{
api.GET("videos/new", srv.GetNewVideos)
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

View File

@ -7,13 +7,12 @@ import (
"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) CreateUser(c *gin.Context) {
var user models.User
err := c.BindJSON(&user)
err := c.ShouldBindJSON(&user)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
@ -21,36 +20,14 @@ func (s *server) CreateUser(c *gin.Context) {
err = s.handler.CreateUser(c.Request.Context(), user)
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()})
return
}
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"})
}

View File

@ -8,14 +8,27 @@ import (
)
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)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
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,
})
}
@ -29,3 +42,39 @@ func (s *server) FetchVideos(c *gin.Context) {
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"})
}

View File

@ -8,6 +8,7 @@ import (
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/db"
"gitea.theedgeofrage.com/TheEdgeOfRage/ytrssil-api/models"
"sync"
"time"
)
// Ensure, that DBMock does implement db.DB.
@ -20,6 +21,9 @@ var _ db.DB = &DBMock{}
//
// // make and configure a mocked db.DB
// 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 {
// panic("mock out the AddVideoToUser method")
// },
@ -32,9 +36,6 @@ var _ db.DB = &DBMock{}
// CreateUserFunc: func(ctx context.Context, user models.User) error {
// 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 {
// panic("mock out the DeleteUser method")
// },
@ -44,15 +45,18 @@ var _ db.DB = &DBMock{}
// GetNewVideosFunc: func(ctx context.Context, username string) ([]models.Video, error) {
// 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) {
// 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 {
// 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
@ -60,6 +64,9 @@ var _ db.DB = &DBMock{}
//
// }
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 func(ctx context.Context, username string, videoID string) error
@ -72,9 +79,6 @@ type DBMock struct {
// CreateUserFunc mocks the CreateUser method.
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 func(ctx context.Context, username string) error
@ -84,17 +88,29 @@ type DBMock struct {
// GetNewVideosFunc mocks the GetNewVideos method.
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 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 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 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 []struct {
// Ctx is the ctx argument value.
@ -125,15 +141,6 @@ type DBMock struct {
// User is the user argument value.
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 []struct {
// Ctx is the ctx argument value.
@ -155,11 +162,29 @@ type DBMock struct {
// Username is the username argument value.
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 []struct {
// Ctx is the ctx argument value.
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 []struct {
// Ctx is the ctx argument value.
@ -169,27 +194,59 @@ type DBMock struct {
// ChannelID is the channelID argument value.
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
lockAuthenticateUser sync.RWMutex
lockCreateChannel sync.RWMutex
lockCreateUser sync.RWMutex
lockCreateVideo sync.RWMutex
lockDeleteUser sync.RWMutex
lockGetChannelSubscribers sync.RWMutex
lockGetNewVideos sync.RWMutex
lockGetWatchedVideos sync.RWMutex
lockListChannels sync.RWMutex
lockSetVideoWatchTime 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.
@ -340,46 +397,6 @@ func (mock *DBMock) CreateUserCalls() []struct {
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.
func (mock *DBMock) DeleteUser(ctx context.Context, username string) error {
if mock.DeleteUserFunc == nil {
@ -488,6 +505,42 @@ func (mock *DBMock) GetNewVideosCalls() []struct {
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.
func (mock *DBMock) ListChannels(ctx context.Context) ([]models.Channel, error) {
if mock.ListChannelsFunc == nil {
@ -520,6 +573,50 @@ func (mock *DBMock) ListChannelsCalls() []struct {
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.
func (mock *DBMock) SubscribeUserToChannel(ctx context.Context, username string, channelID string) error {
if mock.SubscribeUserToChannelFunc == nil {
@ -559,43 +656,3 @@ func (mock *DBMock) SubscribeUserToChannelCalls() []struct {
mock.lockSubscribeUserToChannel.RUnlock()
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
}

View File

@ -1,5 +1,9 @@
package models
type GetNewVideosResponse struct {
type VideosResponse struct {
Videos []Video `json:"videos"`
}
type VideoURIRequest struct {
VideoID string `uri:"video_id" binding:"required"`
}