Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/Life-USTC/CLI
go 1.26.0

require (
github.com/Life-USTC/client-core v0.1.0
github.com/Life-USTC/client-core v0.3.0
github.com/PuerkitoBio/goquery v1.13.0
github.com/charmbracelet/bubbles v1.0.0
github.com/charmbracelet/bubbletea v1.3.10
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/Life-USTC/client-core v0.1.0 h1:kAtPq/m/W7SEMiR9KG/m2F0ZRyBXnzkNbfoKa4mMDmc=
github.com/Life-USTC/client-core v0.1.0/go.mod h1:2SgYKZ3hbEFEigNGPdbfFS2m149evVw2t/mEjA/S/LM=
github.com/Life-USTC/client-core v0.3.0 h1:hpTnY1x6CIVQDIH0PoZdg7/9K+BEpaSss3BlaVdCHWs=
github.com/Life-USTC/client-core v0.3.0/go.mod h1:2SgYKZ3hbEFEigNGPdbfFS2m149evVw2t/mEjA/S/LM=
github.com/PuerkitoBio/goquery v1.13.0 h1:mqHbjD7Jmnul4DTR24LKTjo1uUmHUh072kteGV+xpFM=
github.com/PuerkitoBio/goquery v1.13.0/go.mod h1:Hip5mdBL8K2wEGKJdr27sRaNwIdDajmCwB/ExUPwW+g=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
Expand Down
67 changes: 44 additions & 23 deletions internal/cmd/young_event/young_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/Life-USTC/CLI/internal/cmd/cmdutil"
"github.com/Life-USTC/CLI/internal/cmd/youngutil"
"github.com/Life-USTC/CLI/internal/output"
"github.com/Life-USTC/client-core/young"
)

type listOpts struct {
Expand Down Expand Up @@ -166,47 +167,53 @@ func buildListParams(opts listOpts) (url.Values, error) {
if err != nil {
return nil, err
}
active, err := normalizeActive(opts.active)
query, err := buildEventQuery(opts)
if err != nil {
return nil, err
}
for key, values := range query.Parameters(nil, nil) {
params[key] = values
}
return params, nil
}

func buildEventQuery(opts listOpts) (young.EventQuery, error) {
query := young.EventQuery{
Category: opts.category, Search: opts.search, OrganizerID: opts.organizerID,
}
active, err := normalizeActive(opts.active)
if err != nil {
return query, err
}
if active != "" {
params.Set("active", active)
value := active == "true"
query.Active = &value
}
dateUnknown, err := normalizeBooleanFilter("--date-unknown", opts.dateUnknown)
if err != nil {
return nil, err
return query, err
}
from := strings.TrimSpace(opts.dateFrom)
to := strings.TrimSpace(opts.dateTo)
if (from == "") != (to == "") {
return nil, fmt.Errorf("--date-from and --date-to must be provided together")
return query, fmt.Errorf("--date-from and --date-to must be provided together")
}
if dateUnknown != "" && (from != "" || to != "") {
return nil, fmt.Errorf("--date-unknown cannot be combined with --date-from or --date-to")
return query, fmt.Errorf("--date-unknown cannot be combined with --date-from or --date-to")
}
if dateUnknown != "" {
params.Set("dateUnknown", dateUnknown)
}
for key, value := range map[string]string{
"category": opts.category,
"search": opts.search,
"organizerId": opts.organizerID,
"dateFrom": from,
"dateTo": to,
} {
if value = strings.TrimSpace(value); value != "" {
params.Set(key, value)
}
value := dateUnknown == "true"
query.DateUnknown = &value
}
query.DateFrom, query.DateTo = from, to
timeBasis := strings.TrimSpace(opts.timeBasis)
if timeBasis != "" {
if timeBasis != "activity" && timeBasis != "registration" {
return nil, fmt.Errorf("--time-basis must be activity or registration")
return query, fmt.Errorf("--time-basis must be activity or registration")
}
params.Set("timeBasis", timeBasis)
query.TimeBasis = young.TimeBasis(timeBasis)
}
return params, nil
return query, nil
}

func normalizeActive(value string) (string, error) {
Expand Down Expand Up @@ -360,15 +367,15 @@ func runDateView(cmd *cobra.Command, view youngutil.DateView, anchor string, opt
dateTo: dateTo,
timeBasis: opts.timeBasis,
}
query, err := buildListParams(params)
query, err := buildEventQuery(params)
if err != nil {
return err
}
client, err := api.NewTypedClient(cmdutil.ServerFromCmd(cmd), false)
if err != nil {
return err
}
data, err := fetchDateEvents(cmd.Context(), client, query)
data, err := fetchDateEvents(cmd.Context(), client, query.Parameters(nil, nil))
if err != nil {
return err
}
Expand All @@ -379,7 +386,7 @@ func runDateView(cmd *cobra.Command, view youngutil.DateView, anchor string, opt
{Key: "Last synced", Value: output.Resolve(meta, "source.lastSyncedAt")},
{Key: "Unknown dates", Value: output.Resolve(meta, "unknownDateCount")},
}, "Calendar source")
fmt.Println("Unknown-date activities: life-ustc catalog young-event list --date-unknown true --time-basis " + opts.timeBasis)
fmt.Println("Unknown-date activities: " + unknownDateCommand(query))
}
list := cmdutil.NewListResult(data, "data")
return output.OutputList(list.Raw, list.Rows, []output.Column{
Expand All @@ -392,6 +399,20 @@ func runDateView(cmd *cobra.Command, view youngutil.DateView, anchor string, opt
}, list.Total, list.Page)
}

func unknownDateCommand(query young.EventQuery) string {
params := query.ForUnknownDates().Parameters(nil, nil)
command := "life-ustc catalog young-event list --date-unknown " + params.Get("dateUnknown")
for _, field := range [][2]string{
{"active", "active"}, {"category", "category"}, {"search", "search"},
{"organizer-id", "organizerId"}, {"time-basis", "timeBasis"},
} {
if value := params.Get(field[1]); value != "" {
command += " --" + field[0] + " '" + strings.ReplaceAll(value, "'", "'\\''") + "'"
}
}
return command
}

func dateStartKey(timeBasis string) string {
if strings.TrimSpace(timeBasis) == "registration" {
return "applyStartAt"
Expand Down
48 changes: 48 additions & 0 deletions internal/cmd/young_event/young_event_http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@ import (
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"reflect"
"strings"
"testing"

"github.com/Life-USTC/CLI/internal/api"
"github.com/Life-USTC/CLI/internal/cmd/youngutil"
"github.com/Life-USTC/CLI/internal/output"
"github.com/spf13/cobra"
)

func TestFetchListMapsAllServerFilters(t *testing.T) {
Expand Down Expand Up @@ -46,3 +53,44 @@ func TestFetchListMapsAllServerFilters(t *testing.T) {
t.Fatal(err)
}
}

func TestDateViewUnknownDateHintPreservesFilters(t *testing.T) {
t.Setenv("LIFE_USTC_CONFIG_DIR", t.TempDir())
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
want := map[string][]string{
"active": {"false"}, "category": {"Science & Tech"}, "search": {"freshmen's fair"},
"organizerId": {"org/科大+1"}, "timeBasis": {"registration"},
"dateFrom": {"2026-09-15"}, "dateTo": {"2026-09-15"}, "page": {"1"}, "pageSize": {"100"},
}
if r.URL.Path != "/api/catalog/young-events" || !reflect.DeepEqual(map[string][]string(r.URL.Query()), want) {
t.Errorf("request = %s, want filters %#v", r.URL, want)
}
w.Header().Set("Content-Type", "application/json")
_, _ = io.WriteString(w, `{"data":[],"pagination":{"page":1,"pageSize":100,"total":0,"totalPages":1},"meta":{"unknownDateCount":2,"source":{"status":"unknown","lastSyncedAt":null}}}`)
}))
defer server.Close()
cmd := &cobra.Command{}
cmd.SetContext(context.Background())
cmd.PersistentFlags().String("server", server.URL, "")
file, err := os.Create(filepath.Join(t.TempDir(), "output.txt"))
if err != nil {
t.Fatal(err)
}
oldStdout, oldOutput := os.Stdout, output.Current
os.Stdout, output.Current = file, &output.Opts{Format: "table", NoColor: true}
t.Cleanup(func() { os.Stdout, output.Current = oldStdout, oldOutput; _ = file.Close() })
if err := runDateView(cmd, youngutil.DateDay, "2026-09-15", dateOpts{
active: "false", category: "Science & Tech", search: "freshmen's fair",
organizerID: "org/科大+1", timeBasis: "registration",
}); err != nil {
t.Fatal(err)
}
printed, err := os.ReadFile(file.Name())
if err != nil {
t.Fatal(err)
}
want := "Unknown-date activities: life-ustc catalog young-event list --date-unknown true --active 'false' --category 'Science & Tech' --search 'freshmen'\\''s fair' --organizer-id 'org/科大+1' --time-basis 'registration'\n"
if !strings.Contains(string(printed), want) {
t.Fatalf("output = %s\nwant hint = %s", printed, want)
}
}
17 changes: 17 additions & 0 deletions internal/cmd/young_event/young_event_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,23 @@ func TestBuildListParamsSupportsUnknownDateFilter(t *testing.T) {
}
}

func TestBuildListParamsKeepsFalseDistinctFromAbsent(t *testing.T) {
params, err := buildListParams(listOpts{active: " false ", dateUnknown: "false", search: " 科大 & club "})
if err != nil {
t.Fatal(err)
}
if len(params) != 3 || params.Get("active") != "false" || params.Get("dateUnknown") != "false" || params.Get("search") != "科大 & club" {
t.Fatalf("params = %#v", params)
}
absent, err := buildListParams(listOpts{})
if err != nil || len(absent) != 0 {
t.Fatalf("absent filters = %#v, %v", absent, err)
}
if _, err := buildListParams(listOpts{dateUnknown: "false", dateFrom: "2026-09-01", dateTo: "2026-09-30"}); err == nil {
t.Fatal("CLI accepted explicit dateUnknown=false with date bounds")
}
}

func TestDateRegistrationUsesRegistrationColumns(t *testing.T) {
if got := dateStartKey("registration"); got != "applyStartAt" {
t.Fatalf("registration start key = %q", got)
Expand Down
Loading