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,19 +200,29 @@ 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
lockAuthenticateUser sync.RWMutex
lockCreateChannel sync.RWMutex
lockCreateUser sync.RWMutex
lockDeleteUser sync.RWMutex
lockGetChannelSubscribers sync.RWMutex
lockGetNewVideos sync.RWMutex
lockGetWatchedVideos sync.RWMutex
lockListChannels sync.RWMutex
lockSetVideoWatchTime sync.RWMutex
lockSubscribeUserToChannel sync.RWMutex
lockAddVideo sync.RWMutex
lockAddVideoToUser sync.RWMutex
lockAuthenticateUser sync.RWMutex
lockCreateChannel sync.RWMutex
lockCreateUser sync.RWMutex
lockDeleteUser sync.RWMutex
lockGetChannelSubscribers sync.RWMutex
lockGetNewVideos sync.RWMutex
lockGetWatchedVideos sync.RWMutex
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
}