Add unsubscribe functionality

This commit is contained in:
Pavle Portic 2022-10-30 19:23:40 +01:00
parent 337b4af251
commit 8b098dadb2
Signed by: TheEdgeOfRage
GPG Key ID: 66AD4BA646FBC0D2
7 changed files with 117 additions and 13 deletions

View File

@ -90,3 +90,19 @@ func (d *postgresDB) SubscribeUserToChannel(ctx context.Context, username string
return nil
}
var unsubscribeUserFromChannelQuery = `DELETE FROM user_subscriptions WHERE username = $1 AND channel_id = $2`
func (d *postgresDB) UnsubscribeUserFromChannel(ctx context.Context, username string, channelID string) error {
resp, err := d.db.ExecContext(ctx, unsubscribeUserFromChannelQuery, username, channelID)
if err != nil {
d.l.Log("level", "ERROR", "function", "db.SubscribeUserToChannel", "error", err)
return err
}
if affected, err := resp.RowsAffected(); err != nil || affected != 1 {
return ErrChannelNotFound
}
return nil
}

View File

@ -10,6 +10,7 @@ import (
var (
ErrChannelExists = errors.New("channel already exists")
ErrChannelNotFound = errors.New("no channel with that ID found")
ErrAlreadySubscribed = errors.New("already subscribed to channel")
ErrVideoExists = errors.New("video already exists")
ErrUserExists = errors.New("user already exists")
@ -30,8 +31,10 @@ 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)
// SubscribeUserToChannel will start showing new videos for that channel to the user
// SubscribeUserToChannel will start adding new videos from that channel to the user
SubscribeUserToChannel(ctx context.Context, username string, channelID string) error
// SubscribeUserToChannel will stop adding videos from that channel to the user
UnsubscribeUserFromChannel(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)

View File

@ -27,3 +27,7 @@ func (h *handler) SubscribeToChannel(ctx context.Context, username string, chann
return h.db.SubscribeUserToChannel(ctx, username, channelID)
}
func (h *handler) UnsubscribeFromChannel(ctx context.Context, username string, channelID string) error {
return h.db.UnsubscribeUserFromChannel(ctx, username, channelID)
}

View File

@ -11,6 +11,7 @@ import (
type Handler interface {
CreateUser(ctx context.Context, user models.User) error
SubscribeToChannel(ctx context.Context, username string, channelID string) error
UnsubscribeFromChannel(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

View File

@ -37,3 +37,26 @@ func (s *server) SubscribeToChannel(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "subscribed to channel successfully"})
}
func (s *server) UnsubscribeFromChannel(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.UnsubscribeFromChannel(c.Request.Context(), username, channel.ID)
if err != nil {
if errors.Is(err, db.ErrChannelNotFound) {
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": "unsubscribed from channel successfully"})
}

View File

@ -44,6 +44,7 @@ func SetupGinRouter(l log.Logger, handler handler.Handler, authMiddleware func(c
api.Use(authMiddleware)
{
api.POST("channels/:channel_id/subscribe", srv.SubscribeToChannel)
api.POST("channels/:channel_id/unsubscribe", srv.UnsubscribeFromChannel)
api.GET("videos/new", srv.GetNewVideos)
api.GET("videos/watched", srv.GetWatchedVideos)
api.POST("videos/:video_id/watch", srv.MarkVideoAsWatched)

View File

@ -57,6 +57,9 @@ var _ db.DB = &DBMock{}
// SubscribeUserToChannelFunc: func(ctx context.Context, username string, channelID string) error {
// panic("mock out the SubscribeUserToChannel method")
// },
// UnsubscribeUserFromChannelFunc: func(ctx context.Context, username string, channelID string) error {
// panic("mock out the UnsubscribeUserFromChannel method")
// },
// }
//
// // use mockedDB in code that requires db.DB
@ -100,6 +103,9 @@ type DBMock struct {
// SubscribeUserToChannelFunc mocks the SubscribeUserToChannel method.
SubscribeUserToChannelFunc func(ctx context.Context, username string, channelID string) error
// UnsubscribeUserFromChannelFunc mocks the UnsubscribeUserFromChannel method.
UnsubscribeUserFromChannelFunc func(ctx context.Context, username string, channelID string) error
// calls tracks calls to the methods.
calls struct {
// AddVideo holds details about calls to the AddVideo method.
@ -194,6 +200,15 @@ type DBMock struct {
// ChannelID is the channelID argument value.
ChannelID string
}
// UnsubscribeUserFromChannel holds details about calls to the UnsubscribeUserFromChannel method.
UnsubscribeUserFromChannel []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// Username is the username argument value.
Username string
// ChannelID is the channelID argument value.
ChannelID string
}
}
lockAddVideo sync.RWMutex
lockAddVideoToUser sync.RWMutex
@ -207,6 +222,7 @@ type DBMock struct {
lockListChannels sync.RWMutex
lockSetVideoWatchTime sync.RWMutex
lockSubscribeUserToChannel sync.RWMutex
lockUnsubscribeUserFromChannel sync.RWMutex
}
// AddVideo calls AddVideoFunc.
@ -656,3 +672,43 @@ func (mock *DBMock) SubscribeUserToChannelCalls() []struct {
mock.lockSubscribeUserToChannel.RUnlock()
return calls
}
// UnsubscribeUserFromChannel calls UnsubscribeUserFromChannelFunc.
func (mock *DBMock) UnsubscribeUserFromChannel(ctx context.Context, username string, channelID string) error {
if mock.UnsubscribeUserFromChannelFunc == nil {
panic("DBMock.UnsubscribeUserFromChannelFunc: method is nil but DB.UnsubscribeUserFromChannel was just called")
}
callInfo := struct {
Ctx context.Context
Username string
ChannelID string
}{
Ctx: ctx,
Username: username,
ChannelID: channelID,
}
mock.lockUnsubscribeUserFromChannel.Lock()
mock.calls.UnsubscribeUserFromChannel = append(mock.calls.UnsubscribeUserFromChannel, callInfo)
mock.lockUnsubscribeUserFromChannel.Unlock()
return mock.UnsubscribeUserFromChannelFunc(ctx, username, channelID)
}
// UnsubscribeUserFromChannelCalls gets all the calls that were made to UnsubscribeUserFromChannel.
// Check the length with:
//
// len(mockedDB.UnsubscribeUserFromChannelCalls())
func (mock *DBMock) UnsubscribeUserFromChannelCalls() []struct {
Ctx context.Context
Username string
ChannelID string
} {
var calls []struct {
Ctx context.Context
Username string
ChannelID string
}
mock.lockUnsubscribeUserFromChannel.RLock()
calls = mock.calls.UnsubscribeUserFromChannel
mock.lockUnsubscribeUserFromChannel.RUnlock()
return calls
}