Skip to content

Commit d359902

Browse files
committed
Removed /users/follows and added /channels/followers and channels/followed
1 parent aef868d commit d359902

File tree

10 files changed

+344
-132
lines changed

10 files changed

+344
-132
lines changed

internal/api/resources.go

+14-7
Original file line numberDiff line numberDiff line change
@@ -171,33 +171,40 @@ var endpointMethodSupports = map[string]map[string]bool{
171171
"PATCH": true,
172172
"DELETE": false,
173173
},
174-
"/subscriptions": {
174+
"/channels/followed": {
175175
"GET": true,
176176
"POST": false,
177177
"PUT": false,
178178
"PATCH": false,
179179
"DELETE": false,
180180
},
181-
"/tags/streams": {
181+
"/channels/followers": {
182182
"GET": true,
183183
"POST": false,
184184
"PUT": false,
185185
"PATCH": false,
186186
"DELETE": false,
187187
},
188-
"/streams/tags": {
188+
"/subscriptions": {
189189
"GET": true,
190190
"POST": false,
191-
"PUT": true,
191+
"PUT": false,
192192
"PATCH": false,
193193
"DELETE": false,
194194
},
195-
"/users/follows": {
195+
"/tags/streams": {
196196
"GET": true,
197-
"POST": true,
197+
"POST": false,
198198
"PUT": false,
199199
"PATCH": false,
200-
"DELETE": true,
200+
"DELETE": false,
201+
},
202+
"/streams/tags": {
203+
"GET": true,
204+
"POST": false,
205+
"PUT": true,
206+
"PATCH": false,
207+
"DELETE": false,
201208
},
202209
"/users/extensions/list": {
203210
"GET": true,

internal/database/database_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ func TestUsers(t *testing.T) {
273273
err = q.AddFollow(urp)
274274
a.Nil(err)
275275

276-
dbr, err = q.GetFollows(urp)
276+
dbr, err = q.GetFollows(urp, false)
277277
a.Nil(err)
278278
follows := dbr.Data.([]Follow)
279279
a.GreaterOrEqual(len(follows), 1)

internal/database/user.go

+20-9
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ type User struct {
3838
}
3939

4040
type Follow struct {
41-
BroadcasterID string `db:"to_id" json:"to_id"`
42-
BroadcasterLogin string `db:"to_login" json:"to_login"`
43-
BroadcasterName string `db:"to_name" json:"to_name"`
44-
ViewerID string `db:"from_id" json:"from_id"`
45-
ViewerLogin string `db:"from_login" json:"from_login"`
46-
ViewerName string `db:"from_name" json:"from_name"`
47-
FollowedAt string `db:"created_at" json:"followed_at"`
41+
BroadcasterID string `db:"to_id"`
42+
BroadcasterLogin string `db:"to_login"`
43+
BroadcasterName string `db:"to_name"`
44+
ViewerID string `db:"from_id"`
45+
ViewerLogin string `db:"from_login"`
46+
ViewerName string `db:"from_name"`
47+
FollowedAt string `db:"created_at"`
4848
}
4949

5050
type UserRequestParams struct {
@@ -185,7 +185,10 @@ func (q *Query) AddFollow(p UserRequestParams) error {
185185
return err
186186
}
187187

188-
func (q *Query) GetFollows(p UserRequestParams) (*DBResponse, error) {
188+
// "Total" returned depends on totalsFromUser bool.
189+
// "true" will return the number of people the user from p.UserID currently follows.
190+
// "false" will return the number of people the user from p.BroadcasterID currently follows.
191+
func (q *Query) GetFollows(p UserRequestParams, totalsFromUser bool) (*DBResponse, error) {
189192
db := q.DB
190193
var r []Follow
191194
var f Follow
@@ -203,8 +206,16 @@ func (q *Query) GetFollows(p UserRequestParams) (*DBResponse, error) {
203206
}
204207
r = append(r, f)
205208
}
209+
210+
totalsP := UserRequestParams{}
211+
if totalsFromUser {
212+
totalsP.UserID = p.UserID
213+
} else {
214+
totalsP.BroadcasterID = p.BroadcasterID
215+
}
216+
206217
var total int
207-
rows, err = q.DB.NamedQuery(generateSQL("select count(*) from follows", p, SEP_AND), p)
218+
rows, err = q.DB.NamedQuery(generateSQL("select count(*) from follows", totalsP, SEP_AND), totalsP)
208219
for rows.Next() {
209220
err := rows.Scan(&total)
210221
if err != nil {

internal/mock_api/endpoints/channels/channels_test.go

+50
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,53 @@ func TestInformation(t *testing.T) {
189189
a.Nil(err)
190190
a.Equal(400, resp.StatusCode)
191191
}
192+
193+
func TestFollowed(t *testing.T) {
194+
a := test_setup.SetupTestEnv(t)
195+
ts := test_server.SetupTestServer(FollowedEndpoint{})
196+
197+
// get
198+
req, _ := http.NewRequest(http.MethodGet, ts.URL+FollowedEndpoint{}.Path(), nil)
199+
q := req.URL.Query()
200+
req.URL.RawQuery = q.Encode()
201+
resp, err := http.DefaultClient.Do(req)
202+
a.Nil(err)
203+
a.Equal(400, resp.StatusCode)
204+
205+
q.Set("user_id", "1")
206+
req.URL.RawQuery = q.Encode()
207+
resp, err = http.DefaultClient.Do(req)
208+
a.Nil(err)
209+
a.Equal(200, resp.StatusCode)
210+
211+
q.Set("broadcaster_id", "2")
212+
req.URL.RawQuery = q.Encode()
213+
resp, err = http.DefaultClient.Do(req)
214+
a.Nil(err)
215+
a.Equal(200, resp.StatusCode)
216+
}
217+
218+
func TestFollowers(t *testing.T) {
219+
a := test_setup.SetupTestEnv(t)
220+
ts := test_server.SetupTestServer(FollowersEndpoint{})
221+
222+
// get
223+
req, _ := http.NewRequest(http.MethodGet, ts.URL+FollowersEndpoint{}.Path(), nil)
224+
q := req.URL.Query()
225+
req.URL.RawQuery = q.Encode()
226+
resp, err := http.DefaultClient.Do(req)
227+
a.Nil(err)
228+
a.Equal(400, resp.StatusCode)
229+
230+
q.Set("broadcaster_id", "1")
231+
req.URL.RawQuery = q.Encode()
232+
resp, err = http.DefaultClient.Do(req)
233+
a.Nil(err)
234+
a.Equal(200, resp.StatusCode)
235+
236+
q.Set("user_id", "2")
237+
req.URL.RawQuery = q.Encode()
238+
resp, err = http.DefaultClient.Do(req)
239+
a.Nil(err)
240+
a.Equal(200, resp.StatusCode)
241+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package channels
4+
5+
import (
6+
"encoding/json"
7+
"net/http"
8+
9+
"github.com/twitchdev/twitch-cli/internal/database"
10+
"github.com/twitchdev/twitch-cli/internal/mock_api/authentication"
11+
"github.com/twitchdev/twitch-cli/internal/mock_api/mock_errors"
12+
"github.com/twitchdev/twitch-cli/internal/models"
13+
)
14+
15+
var followedMethodsSupported = map[string]bool{
16+
http.MethodGet: true,
17+
http.MethodPost: false,
18+
http.MethodDelete: false,
19+
http.MethodPatch: false,
20+
http.MethodPut: false,
21+
}
22+
23+
var followedScopesByMethod = map[string][]string{
24+
http.MethodGet: {"user:read:follows"},
25+
http.MethodPost: {},
26+
http.MethodDelete: {},
27+
http.MethodPatch: {},
28+
http.MethodPut: {},
29+
}
30+
31+
type FollowedEndpoint struct{}
32+
33+
type GetFollowedEndpointResponseData struct {
34+
BroadcasterID string `json:"broadcaster_id"`
35+
BroadcasterLogin string `json:"broadcaster_login"`
36+
BroadcasterName string `json:"broadcaster_name"`
37+
FollowedAt string `json:"followed_at"`
38+
}
39+
40+
func (e FollowedEndpoint) Path() string { return "/channels/followed" }
41+
42+
func (e FollowedEndpoint) GetRequiredScopes(method string) []string {
43+
return followedScopesByMethod[method]
44+
}
45+
46+
func (e FollowedEndpoint) ValidMethod(method string) bool {
47+
return followedMethodsSupported[method]
48+
}
49+
50+
func (e FollowedEndpoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
51+
db = r.Context().Value("db").(database.CLIDatabase)
52+
53+
switch r.Method {
54+
case http.MethodGet:
55+
getFollowed(w, r)
56+
default:
57+
w.WriteHeader(http.StatusMethodNotAllowed)
58+
return
59+
}
60+
}
61+
62+
func getFollowed(w http.ResponseWriter, r *http.Request) {
63+
user_id := r.URL.Query().Get("user_id")
64+
broadcaster_id := r.URL.Query().Get("broadcaster_id")
65+
66+
userCtx := r.Context().Value("auth").(authentication.UserAuthentication)
67+
68+
if user_id == "" {
69+
mock_errors.WriteBadRequest(w, "The user_id query parameter is required")
70+
return
71+
}
72+
73+
if user_id != userCtx.UserID {
74+
mock_errors.WriteUnauthorized(w, "user_id does not match User ID in the access token")
75+
return
76+
}
77+
78+
req := database.UserRequestParams{
79+
UserID: user_id,
80+
BroadcasterID: broadcaster_id,
81+
}
82+
83+
dbr, err := db.NewQuery(r, 100).GetFollows(req, true)
84+
if dbr == nil {
85+
mock_errors.WriteServerError(w, err.Error())
86+
return
87+
}
88+
89+
// Build list of who the user is following
90+
follows := []GetFollowedEndpointResponseData{}
91+
for _, f := range dbr.Data.([]database.Follow) {
92+
follows = append(follows, GetFollowedEndpointResponseData{
93+
BroadcasterID: f.BroadcasterID,
94+
BroadcasterLogin: f.BroadcasterLogin,
95+
BroadcasterName: f.BroadcasterName,
96+
FollowedAt: f.FollowedAt,
97+
})
98+
}
99+
100+
body := models.APIResponse{
101+
Data: follows,
102+
Total: &dbr.Total,
103+
}
104+
if dbr != nil && dbr.Cursor != "" {
105+
body.Pagination = &models.APIPagination{
106+
Cursor: dbr.Cursor,
107+
}
108+
}
109+
110+
bytes, _ := json.Marshal(body)
111+
w.Write(bytes)
112+
}

0 commit comments

Comments
 (0)