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
|
||||
}
|
||||
|
||||
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 (
|
||||
"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
|
||||
}
|
||||
|
|
52
db/users.go
52
db/users.go
|
@ -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
|
||||
}
|
||||
|
|
80
db/videos.go
80
db/videos.go
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 (
|
||||
"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
|
||||
}
|
||||
|
|
|
@ -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.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
|
||||
|
|
|
@ -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"})
|
||||
}
|
||||
|
|
|
@ -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"})
|
||||
}
|
||||
|
|
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/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
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue