Skip to content

Commit

Permalink
Removed /users/follows and added /channels/followers and channels/fol…
Browse files Browse the repository at this point in the history
…lowed
  • Loading branch information
Xemdo committed Nov 22, 2023
1 parent aef868d commit d359902
Show file tree
Hide file tree
Showing 10 changed files with 344 additions and 132 deletions.
21 changes: 14 additions & 7 deletions internal/api/resources.go
Expand Up @@ -171,33 +171,40 @@ var endpointMethodSupports = map[string]map[string]bool{
"PATCH": true,
"DELETE": false,
},
"/subscriptions": {
"/channels/followed": {
"GET": true,
"POST": false,
"PUT": false,
"PATCH": false,
"DELETE": false,
},
"/tags/streams": {
"/channels/followers": {
"GET": true,
"POST": false,
"PUT": false,
"PATCH": false,
"DELETE": false,
},
"/streams/tags": {
"/subscriptions": {
"GET": true,
"POST": false,
"PUT": true,
"PUT": false,
"PATCH": false,
"DELETE": false,
},
"/users/follows": {
"/tags/streams": {
"GET": true,
"POST": true,
"POST": false,
"PUT": false,
"PATCH": false,
"DELETE": true,
"DELETE": false,
},
"/streams/tags": {
"GET": true,
"POST": false,
"PUT": true,
"PATCH": false,
"DELETE": false,
},
"/users/extensions/list": {
"GET": true,
Expand Down
2 changes: 1 addition & 1 deletion internal/database/database_test.go
Expand Up @@ -273,7 +273,7 @@ func TestUsers(t *testing.T) {
err = q.AddFollow(urp)
a.Nil(err)

dbr, err = q.GetFollows(urp)
dbr, err = q.GetFollows(urp, false)
a.Nil(err)
follows := dbr.Data.([]Follow)
a.GreaterOrEqual(len(follows), 1)
Expand Down
29 changes: 20 additions & 9 deletions internal/database/user.go
Expand Up @@ -38,13 +38,13 @@ type User struct {
}

type Follow struct {
BroadcasterID string `db:"to_id" json:"to_id"`
BroadcasterLogin string `db:"to_login" json:"to_login"`
BroadcasterName string `db:"to_name" json:"to_name"`
ViewerID string `db:"from_id" json:"from_id"`
ViewerLogin string `db:"from_login" json:"from_login"`
ViewerName string `db:"from_name" json:"from_name"`
FollowedAt string `db:"created_at" json:"followed_at"`
BroadcasterID string `db:"to_id"`
BroadcasterLogin string `db:"to_login"`
BroadcasterName string `db:"to_name"`
ViewerID string `db:"from_id"`
ViewerLogin string `db:"from_login"`
ViewerName string `db:"from_name"`
FollowedAt string `db:"created_at"`
}

type UserRequestParams struct {
Expand Down Expand Up @@ -185,7 +185,10 @@ func (q *Query) AddFollow(p UserRequestParams) error {
return err
}

func (q *Query) GetFollows(p UserRequestParams) (*DBResponse, error) {
// "Total" returned depends on totalsFromUser bool.
// "true" will return the number of people the user from p.UserID currently follows.
// "false" will return the number of people the user from p.BroadcasterID currently follows.
func (q *Query) GetFollows(p UserRequestParams, totalsFromUser bool) (*DBResponse, error) {
db := q.DB
var r []Follow
var f Follow
Expand All @@ -203,8 +206,16 @@ func (q *Query) GetFollows(p UserRequestParams) (*DBResponse, error) {
}
r = append(r, f)
}

totalsP := UserRequestParams{}
if totalsFromUser {
totalsP.UserID = p.UserID
} else {
totalsP.BroadcasterID = p.BroadcasterID
}

var total int
rows, err = q.DB.NamedQuery(generateSQL("select count(*) from follows", p, SEP_AND), p)
rows, err = q.DB.NamedQuery(generateSQL("select count(*) from follows", totalsP, SEP_AND), totalsP)
for rows.Next() {
err := rows.Scan(&total)
if err != nil {
Expand Down
50 changes: 50 additions & 0 deletions internal/mock_api/endpoints/channels/channels_test.go
Expand Up @@ -189,3 +189,53 @@ func TestInformation(t *testing.T) {
a.Nil(err)
a.Equal(400, resp.StatusCode)
}

func TestFollowed(t *testing.T) {
a := test_setup.SetupTestEnv(t)
ts := test_server.SetupTestServer(FollowedEndpoint{})

// get
req, _ := http.NewRequest(http.MethodGet, ts.URL+FollowedEndpoint{}.Path(), nil)
q := req.URL.Query()
req.URL.RawQuery = q.Encode()
resp, err := http.DefaultClient.Do(req)
a.Nil(err)
a.Equal(400, resp.StatusCode)

q.Set("user_id", "1")
req.URL.RawQuery = q.Encode()
resp, err = http.DefaultClient.Do(req)
a.Nil(err)
a.Equal(200, resp.StatusCode)

q.Set("broadcaster_id", "2")
req.URL.RawQuery = q.Encode()
resp, err = http.DefaultClient.Do(req)
a.Nil(err)
a.Equal(200, resp.StatusCode)
}

func TestFollowers(t *testing.T) {
a := test_setup.SetupTestEnv(t)
ts := test_server.SetupTestServer(FollowersEndpoint{})

// get
req, _ := http.NewRequest(http.MethodGet, ts.URL+FollowersEndpoint{}.Path(), nil)
q := req.URL.Query()
req.URL.RawQuery = q.Encode()
resp, err := http.DefaultClient.Do(req)
a.Nil(err)
a.Equal(400, resp.StatusCode)

q.Set("broadcaster_id", "1")
req.URL.RawQuery = q.Encode()
resp, err = http.DefaultClient.Do(req)
a.Nil(err)
a.Equal(200, resp.StatusCode)

q.Set("user_id", "2")
req.URL.RawQuery = q.Encode()
resp, err = http.DefaultClient.Do(req)
a.Nil(err)
a.Equal(200, resp.StatusCode)
}
112 changes: 112 additions & 0 deletions internal/mock_api/endpoints/channels/followed.go
@@ -0,0 +1,112 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package channels

import (
"encoding/json"
"net/http"

"github.com/twitchdev/twitch-cli/internal/database"
"github.com/twitchdev/twitch-cli/internal/mock_api/authentication"
"github.com/twitchdev/twitch-cli/internal/mock_api/mock_errors"
"github.com/twitchdev/twitch-cli/internal/models"
)

var followedMethodsSupported = map[string]bool{
http.MethodGet: true,
http.MethodPost: false,
http.MethodDelete: false,
http.MethodPatch: false,
http.MethodPut: false,
}

var followedScopesByMethod = map[string][]string{
http.MethodGet: {"user:read:follows"},
http.MethodPost: {},
http.MethodDelete: {},
http.MethodPatch: {},
http.MethodPut: {},
}

type FollowedEndpoint struct{}

type GetFollowedEndpointResponseData struct {
BroadcasterID string `json:"broadcaster_id"`
BroadcasterLogin string `json:"broadcaster_login"`
BroadcasterName string `json:"broadcaster_name"`
FollowedAt string `json:"followed_at"`
}

func (e FollowedEndpoint) Path() string { return "/channels/followed" }

func (e FollowedEndpoint) GetRequiredScopes(method string) []string {
return followedScopesByMethod[method]
}

func (e FollowedEndpoint) ValidMethod(method string) bool {
return followedMethodsSupported[method]
}

func (e FollowedEndpoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
db = r.Context().Value("db").(database.CLIDatabase)

switch r.Method {
case http.MethodGet:
getFollowed(w, r)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
}

func getFollowed(w http.ResponseWriter, r *http.Request) {
user_id := r.URL.Query().Get("user_id")
broadcaster_id := r.URL.Query().Get("broadcaster_id")

userCtx := r.Context().Value("auth").(authentication.UserAuthentication)

if user_id == "" {
mock_errors.WriteBadRequest(w, "The user_id query parameter is required")
return
}

if user_id != userCtx.UserID {
mock_errors.WriteUnauthorized(w, "user_id does not match User ID in the access token")
return
}

req := database.UserRequestParams{
UserID: user_id,
BroadcasterID: broadcaster_id,
}

dbr, err := db.NewQuery(r, 100).GetFollows(req, true)
if dbr == nil {
mock_errors.WriteServerError(w, err.Error())
return
}

// Build list of who the user is following
follows := []GetFollowedEndpointResponseData{}
for _, f := range dbr.Data.([]database.Follow) {
follows = append(follows, GetFollowedEndpointResponseData{
BroadcasterID: f.BroadcasterID,
BroadcasterLogin: f.BroadcasterLogin,
BroadcasterName: f.BroadcasterName,
FollowedAt: f.FollowedAt,
})
}

body := models.APIResponse{
Data: follows,
Total: &dbr.Total,
}
if dbr != nil && dbr.Cursor != "" {
body.Pagination = &models.APIPagination{
Cursor: dbr.Cursor,
}
}

bytes, _ := json.Marshal(body)
w.Write(bytes)
}

0 comments on commit d359902

Please sign in to comment.