Skip to content

Commit 83b63d2

Browse files
authored
Merge branch 'main' into enhancement/transaction-improvements
2 parents 35bb080 + 64e8149 commit 83b63d2

31 files changed

+1714
-24
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ The CLI currently supports the following products:
5858
- [api](./docs/api.md)
5959
- [configure](./docs/configure.md)
6060
- [event](docs/event.md)
61+
- [mock-api](docs/mock-api.md)
6162
- [token](docs/token.md)
6263
- [version](docs/version.md)
6364

internal/database/_schema.sql

+16-1
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,8 @@ create table drops_entitlements(
204204
benefit_id text not null,
205205
timestamp text not null,
206206
user_id text not null,
207-
game_id text not null,
207+
game_id text not null,
208+
status text not null default 'CLAIMED',
208209
foreign key (user_id) references users(id),
209210
foreign key (game_id) references categories(id)
210211
);
@@ -289,3 +290,17 @@ create table clips (
289290
foreign key (broadcaster_id) references users(id),
290291
foreign key (creator_id) references users(id)
291292
);
293+
create table stream_schedule(
294+
id text not null primary key,
295+
broadcaster_id text not null,
296+
starttime text not null,
297+
endtime text not null,
298+
timezone text not null,
299+
is_vacation boolean not null default false,
300+
is_recurring boolean not null default false,
301+
is_canceled boolean not null default false,
302+
title text,
303+
category_id text,
304+
foreign key (broadcaster_id) references users(id),
305+
foreign key (category_id) references categories(id)
306+
);

internal/database/drops.go

+6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type DropsEntitlement struct {
1010
BenefitID string `db:"benefit_id" json:"benefit_id"`
1111
GameID string `db:"game_id" json:"game_id"`
1212
Timestamp string `db:"timestamp" json:"timestamp"`
13+
Status string `db:"status" json:"fulfillment_status"`
1314
}
1415

1516
func (q *Query) GetDropsEntitlements(de DropsEntitlement) (*DBResponse, error) {
@@ -51,3 +52,8 @@ func (q *Query) InsertDropsEntitlement(d DropsEntitlement) error {
5152
_, err := q.DB.NamedExec(stmt, d)
5253
return err
5354
}
55+
56+
func (q *Query) UpdateDropsEntitlement(d DropsEntitlement) error {
57+
_, err := q.DB.NamedExec(generateUpdateSQL("drops_entitlements", []string{"id"}, d), d)
58+
return err
59+
}

internal/database/init.go

+5-1
Large diffs are not rendered by default.

internal/database/schedule.go

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package database
4+
5+
import (
6+
"database/sql"
7+
"errors"
8+
"time"
9+
)
10+
11+
type Schedule struct {
12+
Segments []ScheduleSegment `json:"segments"`
13+
UserID string `db:"broadcaster_id" json:"broadcaster_id"`
14+
UserLogin string `db:"broadcaster_login" json:"broadcaster_login" dbi:"false"`
15+
UserName string `db:"broadcaster_name" json:"broadcaster_name" dbi:"false"`
16+
Vacation *ScheduleVacation `json:"vacation"`
17+
}
18+
19+
type ScheduleSegment struct {
20+
ID string `db:"id" json:"id" dbs:"s.id"`
21+
Title string `db:"title" json:"title"`
22+
StartTime string `db:"starttime" json:"start_time"`
23+
EndTime string `db:"endtime" json:"end_time"`
24+
IsRecurring bool `db:"is_recurring" json:"is_recurring"`
25+
IsVacation bool `db:"is_vacation" json:"-"`
26+
Category *SegmentCategory `json:"category"`
27+
UserID string `db:"broadcaster_id" json:"-"`
28+
Timezone string `db:"timezone" json:"timezone"`
29+
CategoryID *string `db:"category_id" json:"-"`
30+
CategoryName *string `db:"category_name" dbi:"false" json:"-"`
31+
IsCanceled *bool `db:"is_canceled" json:"-"`
32+
CanceledUntil *string `json:"canceled_until"`
33+
}
34+
type ScheduleVacation struct {
35+
ID string `db:"id" json:"-"`
36+
StartTime string `db:"starttime" json:"start_time"`
37+
EndTime string `db:"endtime" json:"end_time"`
38+
}
39+
40+
type SegmentCategory struct {
41+
ID *string `db:"category_id" json:"id" dbs:"category_id"`
42+
CategoryName *string `db:"category_name" json:"name" dbi:"false"`
43+
}
44+
45+
func (q *Query) GetSchedule(p ScheduleSegment, startTime time.Time) (*DBResponse, error) {
46+
r := Schedule{}
47+
48+
u, err := q.GetUser(User{ID: p.UserID})
49+
if err != nil {
50+
return nil, err
51+
}
52+
r.UserID = u.ID
53+
r.UserLogin = u.UserLogin
54+
r.UserName = u.DisplayName
55+
56+
sql := generateSQL("select s.*, c.category_name from stream_schedule s left join categories c on s.category_id = c.id", p, SEP_AND)
57+
p.StartTime = startTime.Format(time.RFC3339)
58+
sql += " and datetime(starttime) >= datetime(:starttime) " + q.SQL
59+
rows, err := q.DB.NamedQuery(sql, p)
60+
if err != nil {
61+
return nil, err
62+
}
63+
64+
for rows.Next() {
65+
var s ScheduleSegment
66+
err := rows.StructScan(&s)
67+
if err != nil {
68+
return nil, err
69+
}
70+
if s.CategoryID != nil {
71+
s.Category = &SegmentCategory{
72+
ID: s.CategoryID,
73+
CategoryName: s.CategoryName,
74+
}
75+
}
76+
if s.IsVacation {
77+
r.Vacation = &ScheduleVacation{
78+
StartTime: s.StartTime,
79+
EndTime: s.EndTime,
80+
}
81+
} else {
82+
r.Segments = append(r.Segments, s)
83+
}
84+
}
85+
v, err := q.GetVacations(ScheduleSegment{UserID: p.UserID})
86+
if err != nil {
87+
return nil, err
88+
}
89+
r.Vacation = &v
90+
dbr := DBResponse{
91+
Data: r,
92+
Limit: q.Limit,
93+
Total: len(r.Segments),
94+
}
95+
96+
if len(r.Segments) != q.Limit {
97+
q.PaginationCursor = ""
98+
}
99+
100+
dbr.Cursor = q.PaginationCursor
101+
102+
return &dbr, err
103+
}
104+
105+
func (q *Query) InsertSchedule(p ScheduleSegment) error {
106+
tx := q.DB.MustBegin()
107+
_, err := tx.NamedExec(generateInsertSQL("stream_schedule", "id", p, false), p)
108+
if err != nil {
109+
return err
110+
}
111+
return tx.Commit()
112+
}
113+
114+
func (q *Query) DeleteSegment(id string, broadcasterID string) error {
115+
_, err := q.DB.Exec("delete from stream_schedule where id=$1 and broadcaster_id=$2", id, broadcasterID)
116+
return err
117+
}
118+
119+
func (q *Query) UpdateSegment(p ScheduleSegment) error {
120+
_, err := q.DB.NamedExec(generateUpdateSQL("stream_schedule", []string{"id"}, p), p)
121+
return err
122+
}
123+
124+
func (q *Query) GetVacations(p ScheduleSegment) (ScheduleVacation, error) {
125+
v := ScheduleVacation{}
126+
err := q.DB.Get(&v, "select id,starttime,endtime from stream_schedule where is_vacation=true and datetime(endtime) > datetime('now') and broadcaster_id= $1 limit 1", p.UserID)
127+
if errors.As(err, &sql.ErrNoRows) {
128+
return v, nil
129+
}
130+
return v, err
131+
}

internal/events/trigger/trigger_event.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package trigger
44

55
import (
66
"fmt"
7+
"log"
78
"time"
89

910
"github.com/twitchdev/twitch-cli/internal/database"
@@ -111,7 +112,7 @@ func Fire(p TriggerParameters) (string, error) {
111112
}
112113
defer resp.Body.Close()
113114

114-
println(fmt.Sprintf(`[%v] Request Sent`, resp.StatusCode))
115+
log.Println(fmt.Sprintf(`[%v] Request Sent`, resp.StatusCode))
115116
}
116117

117118
return string(resp.JSON), nil

internal/mock_api/authentication/authentication.go

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type UserAuthentication struct {
2424

2525
func AuthenticationMiddleware(next mock_api.MockEndpoint) http.Handler {
2626
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
27+
w.Header().Set("Content-Type", "application/json")
2728
db := r.Context().Value("db").(database.CLIDatabase)
2829

2930
// skip auth check for unsupported methods
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package chat
4+
5+
import (
6+
"encoding/json"
7+
"fmt"
8+
"net/http"
9+
10+
"github.com/twitchdev/twitch-cli/internal/database"
11+
"github.com/twitchdev/twitch-cli/internal/mock_api/mock_errors"
12+
"github.com/twitchdev/twitch-cli/internal/models"
13+
"github.com/twitchdev/twitch-cli/internal/util"
14+
)
15+
16+
var channelEmotesMethodsSupported = map[string]bool{
17+
http.MethodGet: true,
18+
http.MethodPost: false,
19+
http.MethodDelete: false,
20+
http.MethodPatch: false,
21+
http.MethodPut: false,
22+
}
23+
24+
var channelEmotesScopesByMethod = map[string][]string{
25+
http.MethodGet: {},
26+
http.MethodPost: {},
27+
http.MethodDelete: {},
28+
http.MethodPatch: {},
29+
http.MethodPut: {},
30+
}
31+
32+
type ChannelEmotes struct{}
33+
34+
func (e ChannelEmotes) Path() string { return "/chat/emotes/channel" }
35+
36+
func (e ChannelEmotes) GetRequiredScopes(method string) []string {
37+
return channelEmotesScopesByMethod[method]
38+
}
39+
40+
func (e ChannelEmotes) ValidMethod(method string) bool {
41+
return channelEmotesMethodsSupported[method]
42+
}
43+
44+
func (e ChannelEmotes) ServeHTTP(w http.ResponseWriter, r *http.Request) {
45+
db = r.Context().Value("db").(database.CLIDatabase)
46+
47+
switch r.Method {
48+
case http.MethodGet:
49+
getChannelEmotes(w, r)
50+
break
51+
default:
52+
w.WriteHeader(http.StatusMethodNotAllowed)
53+
}
54+
}
55+
func getChannelEmotes(w http.ResponseWriter, r *http.Request) {
56+
emotes := []EmotesResponse{}
57+
broadcaster := r.URL.Query().Get("broadcaster_id")
58+
if broadcaster == "" {
59+
mock_errors.WriteBadRequest(w, "Missing required parameter broadcaster_id")
60+
return
61+
}
62+
63+
setID := fmt.Sprint(util.RandomInt(10 * 1000))
64+
ownerID := util.RandomUserID()
65+
for _, v := range defaultEmoteTypes {
66+
emoteType := v
67+
for i := 0; i < 5; i++ {
68+
id := util.RandomInt(10 * 1000)
69+
name := util.RandomGUID()
70+
er := EmotesResponse{
71+
ID: fmt.Sprint(id),
72+
Name: name,
73+
Images: EmotesImages{
74+
ImageURL1X: fmt.Sprintf("https://static-cdn.jtvnw.net/emoticons/v1/%v/1.0", id),
75+
ImageURL2X: fmt.Sprintf("https://static-cdn.jtvnw.net/emoticons/v1/%v/2.0", id),
76+
ImageURL4X: fmt.Sprintf("https://static-cdn.jtvnw.net/emoticons/v1/%v/4.0", id),
77+
},
78+
EmoteType: &emoteType,
79+
EmoteSetID: &setID,
80+
OwnerID: &ownerID,
81+
}
82+
if emoteType == "subscription" {
83+
thousand := "1000"
84+
er.Tier = &thousand
85+
} else {
86+
es := ""
87+
er.Tier = &es
88+
}
89+
90+
emotes = append(emotes, er)
91+
}
92+
}
93+
94+
bytes, _ := json.Marshal(models.APIResponse{Data: emotes})
95+
w.Write(bytes)
96+
}

internal/mock_api/endpoints/chat/chat_test.go

+51
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,54 @@ func TestChannelBadges(t *testing.T) {
4141
a.Nil(err)
4242
a.Equal(200, resp.StatusCode)
4343
}
44+
45+
func TestGlobalEmotes(t *testing.T) {
46+
a := test_setup.SetupTestEnv(t)
47+
ts := test_server.SetupTestServer(GlobalEmotes{})
48+
49+
// get
50+
req, _ := http.NewRequest(http.MethodGet, ts.URL+GlobalEmotes{}.Path(), nil)
51+
q := req.URL.Query()
52+
req.URL.RawQuery = q.Encode()
53+
resp, err := http.DefaultClient.Do(req)
54+
a.Nil(err)
55+
a.Equal(200, resp.StatusCode)
56+
}
57+
58+
func TestChannelEmotes(t *testing.T) {
59+
a := test_setup.SetupTestEnv(t)
60+
ts := test_server.SetupTestServer(ChannelEmotes{})
61+
62+
// get
63+
req, _ := http.NewRequest(http.MethodGet, ts.URL+ChannelEmotes{}.Path(), nil)
64+
q := req.URL.Query()
65+
req.URL.RawQuery = q.Encode()
66+
resp, err := http.DefaultClient.Do(req)
67+
a.Nil(err)
68+
a.Equal(400, resp.StatusCode)
69+
70+
q.Set("broadcaster_id", "1")
71+
req.URL.RawQuery = q.Encode()
72+
resp, err = http.DefaultClient.Do(req)
73+
a.Nil(err)
74+
a.Equal(200, resp.StatusCode)
75+
}
76+
77+
func TestEmoteSets(t *testing.T) {
78+
a := test_setup.SetupTestEnv(t)
79+
ts := test_server.SetupTestServer(EmoteSets{})
80+
81+
// get
82+
req, _ := http.NewRequest(http.MethodGet, ts.URL+EmoteSets{}.Path(), nil)
83+
q := req.URL.Query()
84+
req.URL.RawQuery = q.Encode()
85+
resp, err := http.DefaultClient.Do(req)
86+
a.Nil(err)
87+
a.Equal(400, resp.StatusCode)
88+
89+
q.Set("emote_set_id", "1")
90+
req.URL.RawQuery = q.Encode()
91+
resp, err = http.DefaultClient.Do(req)
92+
a.Nil(err)
93+
a.Equal(200, resp.StatusCode)
94+
}

0 commit comments

Comments
 (0)