Skip to content

Commit f5f33ca

Browse files
committed
fix: adjust classroom topic scan + manage upgrade scopes (#73) (thanks @salmonumbrella)
1 parent ce44ca2 commit f5f33ca

8 files changed

Lines changed: 320 additions & 84 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
- Gmail: include `gmail.settings.sharing` scope for filter operations to avoid 403 insufficientPermissions. (#69) — thanks @ryanh-ai.
88
- Gmail: resync on stale history 404s and skip missing message fetches without masking non-404 failures. (#70) — thanks @antons.
9+
- Auth: account manager upgrade respects managed services and skips Keep OAuth scopes. (#73) — thanks @salmonumbrella.
910
- Classroom: normalize assignee updates + fix grade update masks. (#74) — thanks @salmonumbrella.
11+
- Classroom: scan pages when filtering coursework/materials by topic. (#73) — thanks @salmonumbrella.
1012

1113
### Build
1214

docs/spec.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,13 +185,13 @@ Flag aliases:
185185
- `gog classroom teachers add <courseId> <userId>`
186186
- `gog classroom teachers remove <courseId> <userId>`
187187
- `gog classroom roster <courseId> [--students] [--teachers]`
188-
- `gog classroom coursework <courseId> [--state ...] [--topic TOPIC_ID] [--max N] [--page TOKEN]`
188+
- `gog classroom coursework <courseId> [--state ...] [--topic TOPIC_ID] [--scan-pages N] [--max N] [--page TOKEN]`
189189
- `gog classroom coursework get <courseId> <courseworkId>`
190190
- `gog classroom coursework create <courseId> --title TITLE [--type ASSIGNMENT|...]`
191191
- `gog classroom coursework update <courseId> <courseworkId> [--title ...]`
192192
- `gog classroom coursework delete <courseId> <courseworkId>`
193193
- `gog classroom coursework assignees <courseId> <courseworkId> [--mode ...] [--add-student ...]`
194-
- `gog classroom materials <courseId> [--state ...] [--topic TOPIC_ID] [--max N] [--page TOKEN]`
194+
- `gog classroom materials <courseId> [--state ...] [--topic TOPIC_ID] [--scan-pages N] [--max N] [--page TOKEN]`
195195
- `gog classroom materials get <courseId> <materialId>`
196196
- `gog classroom materials create <courseId> --title TITLE`
197197
- `gog classroom materials update <courseId> <materialId> [--title ...]`

internal/cmd/classroom_coursework.go

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ type ClassroomCourseworkCmd struct {
2222
}
2323

2424
type ClassroomCourseworkListCmd struct {
25-
CourseID string `arg:"" name:"courseId" help:"Course ID or alias"`
26-
States string `name:"state" help:"Coursework states filter (comma-separated: DRAFT,PUBLISHED,DELETED)"`
27-
Topic string `name:"topic" help:"Filter by topic ID"`
28-
OrderBy string `name:"order-by" help:"Order by (e.g., updateTime desc, dueDate desc)"`
29-
Max int64 `name:"max" aliases:"limit" help:"Max results" default:"100"`
30-
Page string `name:"page" help:"Page token"`
25+
CourseID string `arg:"" name:"courseId" help:"Course ID or alias"`
26+
States string `name:"state" help:"Coursework states filter (comma-separated: DRAFT,PUBLISHED,DELETED)"`
27+
Topic string `name:"topic" help:"Filter by topic ID"`
28+
OrderBy string `name:"order-by" help:"Order by (e.g., updateTime desc, dueDate desc)"`
29+
Max int64 `name:"max" aliases:"limit" help:"Max results" default:"100"`
30+
Page string `name:"page" help:"Page token"`
31+
ScanPages int `name:"scan-pages" help:"Pages to scan when filtering by topic" default:"3"`
3132
}
3233

3334
func (c *ClassroomCourseworkListCmd) Run(ctx context.Context, flags *RootFlags) error {
@@ -46,45 +47,71 @@ func (c *ClassroomCourseworkListCmd) Run(ctx context.Context, flags *RootFlags)
4647
return wrapClassroomError(err)
4748
}
4849

49-
call := svc.Courses.CourseWork.List(courseID).PageSize(c.Max).PageToken(c.Page).Context(ctx)
50-
if states := splitCSV(c.States); len(states) > 0 {
51-
upper := make([]string, 0, len(states))
52-
for _, state := range states {
53-
upper = append(upper, strings.ToUpper(state))
50+
makeCall := func(page string) *classroom.CoursesCourseWorkListCall {
51+
call := svc.Courses.CourseWork.List(courseID).PageSize(c.Max).PageToken(page).Context(ctx)
52+
if states := splitCSV(c.States); len(states) > 0 {
53+
upper := make([]string, 0, len(states))
54+
for _, state := range states {
55+
upper = append(upper, strings.ToUpper(state))
56+
}
57+
call.CourseWorkStates(upper...)
5458
}
55-
call.CourseWorkStates(upper...)
56-
}
57-
if v := strings.TrimSpace(c.OrderBy); v != "" {
58-
call.OrderBy(v)
59-
}
60-
61-
resp, err := call.Do()
62-
if err != nil {
63-
return wrapClassroomError(err)
59+
if v := strings.TrimSpace(c.OrderBy); v != "" {
60+
call.OrderBy(v)
61+
}
62+
return call
6463
}
6564

66-
// Client-side filter by topic (API doesn't support server-side topic filter)
6765
topicFilter := strings.TrimSpace(c.Topic)
68-
coursework := resp.CourseWork
69-
if topicFilter != "" {
70-
filtered := make([]*classroom.CourseWork, 0, len(coursework))
71-
for _, work := range coursework {
66+
pageToken := c.Page
67+
scanPages := c.ScanPages
68+
if scanPages <= 0 {
69+
scanPages = 1
70+
}
71+
72+
var (
73+
coursework []*classroom.CourseWork
74+
nextPageToken string
75+
)
76+
for page := 0; ; page++ {
77+
resp, err := makeCall(pageToken).Do()
78+
if err != nil {
79+
return wrapClassroomError(err)
80+
}
81+
nextPageToken = resp.NextPageToken
82+
83+
if topicFilter == "" {
84+
coursework = resp.CourseWork
85+
break
86+
}
87+
88+
filtered := make([]*classroom.CourseWork, 0, len(resp.CourseWork))
89+
for _, work := range resp.CourseWork {
7290
if work != nil && work.TopicId == topicFilter {
7391
filtered = append(filtered, work)
7492
}
7593
}
76-
coursework = filtered
94+
if len(filtered) > 0 {
95+
coursework = filtered
96+
break
97+
}
98+
if nextPageToken == "" || page+1 >= scanPages {
99+
coursework = filtered
100+
break
101+
}
102+
pageToken = nextPageToken
77103
}
78104

79105
if outfmt.IsJSON(ctx) {
80106
return outfmt.WriteJSON(os.Stdout, map[string]any{
81107
"coursework": coursework,
82-
"nextPageToken": resp.NextPageToken,
108+
"nextPageToken": nextPageToken,
83109
})
84110
}
85111

86112
if len(coursework) == 0 {
87113
u.Err().Println("No coursework")
114+
printNextPageHint(u, nextPageToken)
88115
return nil
89116
}
90117

@@ -104,7 +131,7 @@ func (c *ClassroomCourseworkListCmd) Run(ctx context.Context, flags *RootFlags)
104131
formatFloatValue(work.MaxPoints),
105132
)
106133
}
107-
printNextPageHint(u, resp.NextPageToken)
134+
printNextPageHint(u, nextPageToken)
108135
return nil
109136
}
110137

internal/cmd/classroom_materials.go

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ type ClassroomMaterialsCmd struct {
2121
}
2222

2323
type ClassroomMaterialsListCmd struct {
24-
CourseID string `arg:"" name:"courseId" help:"Course ID or alias"`
25-
States string `name:"state" help:"Material states filter (comma-separated: PUBLISHED,DRAFT,DELETED)"`
26-
Topic string `name:"topic" help:"Filter by topic ID"`
27-
OrderBy string `name:"order-by" help:"Order by (e.g., updateTime desc)"`
28-
Max int64 `name:"max" aliases:"limit" help:"Max results" default:"100"`
29-
Page string `name:"page" help:"Page token"`
24+
CourseID string `arg:"" name:"courseId" help:"Course ID or alias"`
25+
States string `name:"state" help:"Material states filter (comma-separated: PUBLISHED,DRAFT,DELETED)"`
26+
Topic string `name:"topic" help:"Filter by topic ID"`
27+
OrderBy string `name:"order-by" help:"Order by (e.g., updateTime desc)"`
28+
Max int64 `name:"max" aliases:"limit" help:"Max results" default:"100"`
29+
Page string `name:"page" help:"Page token"`
30+
ScanPages int `name:"scan-pages" help:"Pages to scan when filtering by topic" default:"3"`
3031
}
3132

3233
func (c *ClassroomMaterialsListCmd) Run(ctx context.Context, flags *RootFlags) error {
@@ -45,45 +46,71 @@ func (c *ClassroomMaterialsListCmd) Run(ctx context.Context, flags *RootFlags) e
4546
return wrapClassroomError(err)
4647
}
4748

48-
call := svc.Courses.CourseWorkMaterials.List(courseID).PageSize(c.Max).PageToken(c.Page).Context(ctx)
49-
if states := splitCSV(c.States); len(states) > 0 {
50-
upper := make([]string, 0, len(states))
51-
for _, state := range states {
52-
upper = append(upper, strings.ToUpper(state))
49+
makeCall := func(page string) *classroom.CoursesCourseWorkMaterialsListCall {
50+
call := svc.Courses.CourseWorkMaterials.List(courseID).PageSize(c.Max).PageToken(page).Context(ctx)
51+
if states := splitCSV(c.States); len(states) > 0 {
52+
upper := make([]string, 0, len(states))
53+
for _, state := range states {
54+
upper = append(upper, strings.ToUpper(state))
55+
}
56+
call.CourseWorkMaterialStates(upper...)
5357
}
54-
call.CourseWorkMaterialStates(upper...)
55-
}
56-
if v := strings.TrimSpace(c.OrderBy); v != "" {
57-
call.OrderBy(v)
58-
}
59-
60-
resp, err := call.Do()
61-
if err != nil {
62-
return wrapClassroomError(err)
58+
if v := strings.TrimSpace(c.OrderBy); v != "" {
59+
call.OrderBy(v)
60+
}
61+
return call
6362
}
6463

65-
// Client-side filter by topic (API doesn't support server-side topic filter)
6664
topicFilter := strings.TrimSpace(c.Topic)
67-
materials := resp.CourseWorkMaterial
68-
if topicFilter != "" {
69-
filtered := make([]*classroom.CourseWorkMaterial, 0, len(materials))
70-
for _, material := range materials {
65+
pageToken := c.Page
66+
scanPages := c.ScanPages
67+
if scanPages <= 0 {
68+
scanPages = 1
69+
}
70+
71+
var (
72+
materials []*classroom.CourseWorkMaterial
73+
nextPageToken string
74+
)
75+
for page := 0; ; page++ {
76+
resp, err := makeCall(pageToken).Do()
77+
if err != nil {
78+
return wrapClassroomError(err)
79+
}
80+
nextPageToken = resp.NextPageToken
81+
82+
if topicFilter == "" {
83+
materials = resp.CourseWorkMaterial
84+
break
85+
}
86+
87+
filtered := make([]*classroom.CourseWorkMaterial, 0, len(resp.CourseWorkMaterial))
88+
for _, material := range resp.CourseWorkMaterial {
7189
if material != nil && material.TopicId == topicFilter {
7290
filtered = append(filtered, material)
7391
}
7492
}
75-
materials = filtered
93+
if len(filtered) > 0 {
94+
materials = filtered
95+
break
96+
}
97+
if nextPageToken == "" || page+1 >= scanPages {
98+
materials = filtered
99+
break
100+
}
101+
pageToken = nextPageToken
76102
}
77103

78104
if outfmt.IsJSON(ctx) {
79105
return outfmt.WriteJSON(os.Stdout, map[string]any{
80106
"materials": materials,
81-
"nextPageToken": resp.NextPageToken,
107+
"nextPageToken": nextPageToken,
82108
})
83109
}
84110

85111
if len(materials) == 0 {
86112
u.Err().Println("No materials")
113+
printNextPageHint(u, nextPageToken)
87114
return nil
88115
}
89116

@@ -101,7 +128,7 @@ func (c *ClassroomMaterialsListCmd) Run(ctx context.Context, flags *RootFlags) e
101128
sanitizeTab(material.UpdateTime),
102129
)
103130
}
104-
printNextPageHint(u, resp.NextPageToken)
131+
printNextPageHint(u, nextPageToken)
105132
return nil
106133
}
107134

0 commit comments

Comments
 (0)