8b098dadb208d7009c1bdce50b28e2874d9741fa

Author
Pavle Portic <git@theedgeofrage.com>
Committer
Pavle Portic <git@theedgeofrage.com>
Date

Message

Add unsubscribe functionality

Diff

This diff is truncated to protect this page.

  1diff --git a/db/channels.go b/db/channels.go
  2index 999ba1c706e1d3c32cf4f84f3f1569306985a9e2..00cd8f20cbd2891711f900333b4e7015533eddfb 100644
  3--- a/db/channels.go
  4+++ b/db/channels.go
  5@@ -90,3 +90,19 @@ func (d *postgresDB) SubscribeUserToChannel(ctx context.Context, username string
  6 
  7 	return nil
  8 }
  9+
 10+var unsubscribeUserFromChannelQuery = `DELETE FROM user_subscriptions WHERE username = $1 AND channel_id = $2`
 11+
 12+func (d *postgresDB) UnsubscribeUserFromChannel(ctx context.Context, username string, channelID string) error {
 13+	resp, err := d.db.ExecContext(ctx, unsubscribeUserFromChannelQuery, username, channelID)
 14+	if err != nil {
 15+		d.l.Log("level", "ERROR", "function", "db.SubscribeUserToChannel", "error", err)
 16+		return err
 17+	}
 18+
 19+	if affected, err := resp.RowsAffected(); err != nil || affected != 1 {
 20+		return ErrChannelNotFound
 21+	}
 22+
 23+	return nil
 24+}
 25diff --git a/db/db.go b/db/db.go
 26index 41d8c14b200b7b6964f18fae1237ec7fc5dca737..c79f451b06277152a15ca1e7788ac2abaf351dfa 100644
 27--- a/db/db.go
 28+++ b/db/db.go
 29@@ -10,6 +10,7 @@ import (
 30 
 31 var (
 32 	ErrChannelExists     = errors.New("channel already exists")
 33+	ErrChannelNotFound   = errors.New("no channel with that ID found")
 34 	ErrAlreadySubscribed = errors.New("already subscribed to channel")
 35 	ErrVideoExists       = errors.New("video already exists")
 36 	ErrUserExists        = errors.New("user already exists")
 37@@ -30,8 +31,10 @@ type DB interface {
 38 	ListChannels(ctx context.Context) ([]models.Channel, error)
 39 	// GetChannelSubscribers lists all channels from the database
 40 	GetChannelSubscribers(ctx context.Context, channelID string) ([]string, error)
 41-	// SubscribeUserToChannel will start showing new videos for that channel to the user
 42+	// SubscribeUserToChannel will start adding new videos from that channel to the user
 43 	SubscribeUserToChannel(ctx context.Context, username string, channelID string) error
 44+	// SubscribeUserToChannel will stop adding videos from that channel to the user
 45+	UnsubscribeUserFromChannel(ctx context.Context, username string, channelID string) error
 46 
 47 	// GetNewVideos returns a list of unwatched videos from all subscribed channels
 48 	GetNewVideos(ctx context.Context, username string) ([]models.Video, error)
 49diff --git a/handler/channels.go b/handler/channels.go
 50index eebceadf8c8e933f42ff519919196545cce3ef29..0b2b787e0146ea5988f25ba2ee7bbe3e70d710cd 100644
 51--- a/handler/channels.go
 52+++ b/handler/channels.go
 53@@ -27,3 +27,7 @@ func (h *handler) SubscribeToChannel(ctx context.Context, username string, chann
 54 
 55 	return h.db.SubscribeUserToChannel(ctx, username, channelID)
 56 }
 57+
 58+func (h *handler) UnsubscribeFromChannel(ctx context.Context, username string, channelID string) error {
 59+	return h.db.UnsubscribeUserFromChannel(ctx, username, channelID)
 60+}
 61diff --git a/handler/handler.go b/handler/handler.go
 62index f8f82ae13e8d41093448a5763c0b844b3b241853..8c95bf75ce8fb719695a057b0b3474103c9fb629 100644
 63--- a/handler/handler.go
 64+++ b/handler/handler.go
 65@@ -11,6 +11,7 @@ import (
 66 type Handler interface {
 67 	CreateUser(ctx context.Context, user models.User) error
 68 	SubscribeToChannel(ctx context.Context, username string, channelID string) error
 69+	UnsubscribeFromChannel(ctx context.Context, username string, channelID string) error
 70 	GetNewVideos(ctx context.Context, username string) ([]models.Video, error)
 71 	GetWatchedVideos(ctx context.Context, username string) ([]models.Video, error)
 72 	FetchVideos(ctx context.Context) error
 73diff --git a/httpserver/ytrssil/channels.go b/httpserver/ytrssil/channels.go
 74index 457677cdfd7932810feb72baddfa096b64c6fd34..78645179129ccce462b16aff7f0f22c5c7dd44c8 100644
 75--- a/httpserver/ytrssil/channels.go
 76+++ b/httpserver/ytrssil/channels.go
 77@@ -37,3 +37,26 @@ func (s *server) SubscribeToChannel(c *gin.Context) {
 78 
 79 	c.JSON(http.StatusOK, gin.H{"msg": "subscribed to channel successfully"})
 80 }
 81+
 82+func (s *server) UnsubscribeFromChannel(c *gin.Context) {
 83+	var channel models.Channel
 84+	err := c.ShouldBindUri(&channel)
 85+	if err != nil {
 86+		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
 87+		return
 88+	}
 89+	username := c.GetString("username")
 90+
 91+	err = s.handler.UnsubscribeFromChannel(c.Request.Context(), username, channel.ID)
 92+	if err != nil {
 93+		if errors.Is(err, db.ErrChannelNotFound) {
 94+			c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": err.Error()})
 95+			return
 96+		}
 97+
 98+		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
 99+		return
100+	}
101+
102+	c.JSON(http.StatusOK, gin.H{"msg": "unsubscribed from channel successfully"})
103+}
104diff --git a/httpserver/ytrssil/server.go b/httpserver/ytrssil/server.go
105index 668a8c5ae337b90d9b1d134d625bb02b7441564c..548d4e985c29b2645bc2440310e6c418f8de3aa0 100644
106--- a/httpserver/ytrssil/server.go
107+++ b/httpserver/ytrssil/server.go
108@@ -44,6 +44,7 @@ func SetupGinRouter(l log.Logger, handler handler.Handler, authMiddleware func(c
109 	api.Use(authMiddleware)
110 	{
111 		api.POST("channels/:channel_id/subscribe", srv.SubscribeToChannel)
112+		api.POST("channels/:channel_id/unsubscribe", srv.UnsubscribeFromChannel)
113 		api.GET("videos/new", srv.GetNewVideos)
114 		api.GET("videos/watched", srv.GetWatchedVideos)
115 		api.POST("videos/:video_id/watch", srv.MarkVideoAsWatched)
116diff --git a/mocks/db/db.go b/mocks/db/db.go
117index 0bcbb02b6dcd1d54ce61279dddbdfa4d011ed3c5..4291a0015ebf421af8769d0942d6331f60cbee96 100644
118--- a/mocks/db/db.go
119+++ b/mocks/db/db.go
120@@ -57,6 +57,9 @@ var _ db.DB = &DBMock{}
121 //			SubscribeUserToChannelFunc: func(ctx context.Context, username string, channelID string) error {
122 //				panic("mock out the SubscribeUserToChannel method")
123 //			},
124+//			UnsubscribeUserFromChannelFunc: func(ctx context.Context, username string, channelID string) error {
125+//				panic("mock out the UnsubscribeUserFromChannel method")
126+//			},
127 //		}
128 //
129 //		// use mockedDB in code that requires db.DB
130@@ -100,6 +103,9 @@ type DBMock struct {
131 	// SubscribeUserToChannelFunc mocks the SubscribeUserToChannel method.
132 	SubscribeUserToChannelFunc func(ctx context.Context, username string, channelID string) error
133 
134+	// UnsubscribeUserFromChannelFunc mocks the UnsubscribeUserFromChannel method.
135+	UnsubscribeUserFromChannelFunc func(ctx context.Context, username string, channelID string) error
136+
137 	// calls tracks calls to the methods.
138 	calls struct {
139 		// AddVideo holds details about calls to the AddVideo method.
140@@ -194,19 +200,29 @@ type DBMock struct {
141 			// ChannelID is the channelID argument value.
142 			ChannelID string
143 		}
144+		// UnsubscribeUserFromChannel holds details about calls to the UnsubscribeUserFromChannel method.
145+		UnsubscribeUserFromChannel []struct {
146+			// Ctx is the ctx argument value.
147+			Ctx context.Context
148+			// Username is the username argument value.
149+			Username string
150+			// ChannelID is the channelID argument value.
151+			ChannelID string
152+		}
153 	}
154-	lockAddVideo               sync.RWMutex
155-	lockAddVideoToUser         sync.RWMutex
156-	lockAuthenticateUser       sync.RWMutex
157-	lockCreateChannel          sync.RWMutex
158-	lockCreateUser             sync.RWMutex
159-	lockDeleteUser             sync.RWMutex
160-	lockGetChannelSubscribers  sync.RWMutex
161-	lockGetNewVideos           sync.RWMutex
162-	lockGetWatchedVideos       sync.RWMutex
163-	lockListChannels           sync.RWMutex
164-	lockSetVideoWatchTime      sync.RWMutex
165-	lockSubscribeUserToChannel sync.RWMutex
166+	lockAddVideo                   sync.RWMutex
167+	lockAddVideoToUser             sync.RWMutex
168+	lockAuthenticateUser           sync.RWMutex
169+	lockCreateChannel              sync.RWMutex
170+	lockCreateUser                 sync.RWMutex
171+	lockDeleteUser                 sync.RWMutex
172+	lockGetChannelSubscribers      sync.RWMutex
173+	lockGetNewVideos               sync.RWMutex
174+	lockGetWatchedVideos           sync.RWMutex
175+	lockListChannels               sync.RWMutex
176+	lockSetVideoWatchTime          sync.RWMutex
177+	lockSubscribeUserToChannel     sync.RWMutex
178+	lockUnsubscribeUserFromChannel sync.RWMutex
179 }
180 
181 // AddVideo calls AddVideoFunc.
182@@ -656,3 +672,43 @@ func (mock *DBMock) SubscribeUserToChannelCalls() []struct {
183 	mock.lockSubscribeUserToChannel.RUnlock()
184 	return calls
185 }
186+
187+// UnsubscribeUserFromChannel calls UnsubscribeUserFromChannelFunc.
188+func (mock *DBMock) UnsubscribeUserFromChannel(ctx context.Context, username string, channelID string) error {
189+	if mock.UnsubscribeUserFromChannelFunc == nil {
190+		panic("DBMock.UnsubscribeUserFromChannelFunc: method is nil but DB.UnsubscribeUserFromChannel was just called")
191+	}
192+	callInfo := struct {
193+		Ctx       context.Context
194+		Username  string
195+		ChannelID string
196+	}{
197+		Ctx:       ctx,
198+		Username:  username,
199+		ChannelID: channelID,
200+	}
201+	mock.lockUnsubscribeUserFromChannel.Lock()
202+	mock.calls.UnsubscribeUserFromChannel = append(mock.calls.UnsubscribeUserFromChannel, callInfo)
203+	mock.lockUnsubscribeUserFromChannel.Unlock()
204+	return mock.UnsubscribeUserFromChannelFunc(ctx, username, channelID)
205+}
206+
207+// UnsubscribeUserFromChannelCalls gets all the calls that were made to UnsubscribeUserFromChannel.
208+// Check the length with:
209+//
210+//	len(mockedDB.UnsubscribeUserFromChannelCalls())
211+func (mock *DBMock) UnsubscribeUserFromChannelCalls() []struct {
212+	Ctx       context.Context
213+	Username  string
214+	ChannelID string
215+} {
216+	var calls []struct {
217+		Ctx       context.Context
218+		Username  string
219+		ChannelID string