diff --git a/api/handler/user.go b/api/handler/user.go index 44cf7c648..42d85830a 100644 --- a/api/handler/user.go +++ b/api/handler/user.go @@ -2,8 +2,10 @@ package handler import ( "errors" + "fmt" "log/slog" "net/http" + "slices" "strconv" "opencsg.com/csghub-server/common/errorx" @@ -30,6 +32,69 @@ type UserHandler struct { user component.UserComponent } +func hasUserRepoFilterQuery(ctx *gin.Context) bool { + filterKeys := []string{ + "search", "sort", "source", "status", "xnet_migration_status", + "dataset_type", "user_purchased", "list_serverless", + "tag_category", "tag_name", "tag_group", + "model_tree", + "model_params_min", "model_params_max", + "repo_size_min", "repo_size_max", + } + for _, key := range filterKeys { + if _, ok := ctx.GetQuery(key); ok { + return true + } + } + return false +} + +func parseUserRepoFilter(ctx *gin.Context, owner, currentUser string) (*types.RepoFilter, error) { + if !hasUserRepoFilterQuery(ctx) { + return nil, nil + } + + filter := &types.RepoFilter{ + Tags: parseTagReqs(ctx), + Owner: owner, + Username: currentUser, + } + + tree, err := parseTreeReqs(ctx) + if err != nil { + return nil, err + } + filter.Tree = tree + filter = getFilterFromContext(ctx, filter) + filter.SpaceSDK = ctx.Query("sdk") + + if listServerless, err := strconv.ParseBool(ctx.Query("list_serverless")); err == nil { + filter.ListServerless = listServerless + } + + filter.ModelParamsMin, filter.ModelParamsMax, err = parseFloatRangeFromContext(ctx, "model_params_min", "model_params_max") + if err != nil { + return nil, errorx.ReqParamInvalid(err, errorx.Ctx().Set("query", "model_params_range")) + } + + filter.RepoSizeMin, filter.RepoSizeMax, err = parseInt64RangeFromContext(ctx, "repo_size_min", "repo_size_max") + if err != nil { + return nil, errorx.ReqParamInvalid(err, errorx.Ctx().Set("query", "repo_size_range")) + } + + if !slices.Contains(types.Sorts, filter.Sort) { + err := fmt.Errorf("sort parameter must be one of %v", types.Sorts) + return nil, errorx.ReqParamInvalid(err, errorx.Ctx().Set("query", "sort_filter")) + } + + if filter.Source != "" && !slices.Contains(types.Sources, filter.Source) { + err := fmt.Errorf("source parameter must be one of %v", types.Sources) + return nil, errorx.ReqParamInvalid(err, errorx.Ctx().Set("query", "source_filter")) + } + + return filter, nil +} + // GetUserDatasets godoc // @Security ApiKey // @Summary Get user datasets @@ -53,6 +118,12 @@ func (h *UserHandler) Datasets(ctx *gin.Context) { req.Owner = ctx.Param("username") req.CurrentUser = httpbase.GetCurrentUser(ctx) + req.Filter, err = parseUserRepoFilter(ctx, req.Owner, req.CurrentUser) + if err != nil { + slog.ErrorContext(ctx.Request.Context(), "Bad user datasets filter request format", "error", err) + httpbase.BadRequestWithExt(ctx, err) + return + } req.Page = page req.PageSize = per ds, total, err := h.user.Datasets(ctx.Request.Context(), &req) @@ -94,6 +165,12 @@ func (h *UserHandler) Models(ctx *gin.Context) { req.Owner = ctx.Param("username") req.CurrentUser = httpbase.GetCurrentUser(ctx) + req.Filter, err = parseUserRepoFilter(ctx, req.Owner, req.CurrentUser) + if err != nil { + slog.ErrorContext(ctx.Request.Context(), "Bad user models filter request format", "error", err) + httpbase.BadRequestWithExt(ctx, err) + return + } req.Page = page req.PageSize = per ms, total, err := h.user.Models(ctx.Request.Context(), &req) @@ -136,6 +213,12 @@ func (h *UserHandler) Codes(ctx *gin.Context) { req.Owner = ctx.Param("username") req.CurrentUser = httpbase.GetCurrentUser(ctx) + req.Filter, err = parseUserRepoFilter(ctx, req.Owner, req.CurrentUser) + if err != nil { + slog.ErrorContext(ctx.Request.Context(), "Bad user codes filter request format", "error", err) + httpbase.BadRequestWithExt(ctx, err) + return + } req.Page = page req.PageSize = per ms, total, err := h.user.Codes(ctx.Request.Context(), &req) @@ -178,6 +261,12 @@ func (h *UserHandler) Spaces(ctx *gin.Context) { req.SDK = ctx.Query("sdk") req.Owner = ctx.Param("username") req.CurrentUser = httpbase.GetCurrentUser(ctx) + req.Filter, err = parseUserRepoFilter(ctx, req.Owner, req.CurrentUser) + if err != nil { + slog.ErrorContext(ctx.Request.Context(), "Bad user spaces filter request format", "error", err) + httpbase.BadRequestWithExt(ctx, err) + return + } req.Page = page req.PageSize = per ms, total, err := h.user.Spaces(ctx.Request.Context(), &req) @@ -950,6 +1039,12 @@ func (h *UserHandler) MCPServers(ctx *gin.Context) { req.Owner = ctx.Param("username") req.CurrentUser = httpbase.GetCurrentUser(ctx) + req.Filter, err = parseUserRepoFilter(ctx, req.Owner, req.CurrentUser) + if err != nil { + slog.ErrorContext(ctx.Request.Context(), "Bad user mcp servers filter request format", "error", err) + httpbase.BadRequestWithExt(ctx, err) + return + } req.Page = page req.PageSize = per mcps, total, err := h.user.MCPServers(ctx.Request.Context(), &req) @@ -992,6 +1087,12 @@ func (h *UserHandler) Skills(ctx *gin.Context) { req.Owner = ctx.Param("username") req.CurrentUser = httpbase.GetCurrentUser(ctx) + req.Filter, err = parseUserRepoFilter(ctx, req.Owner, req.CurrentUser) + if err != nil { + slog.ErrorContext(ctx.Request.Context(), "Bad user skills filter request format", "error", err) + httpbase.BadRequestWithExt(ctx, err) + return + } req.Page = page req.PageSize = per skills, total, err := h.user.Skills(ctx.Request.Context(), &req) diff --git a/api/handler/user_test.go b/api/handler/user_test.go index 32aaf293c..ae8e61064 100644 --- a/api/handler/user_test.go +++ b/api/handler/user_test.go @@ -76,6 +76,55 @@ func TestUserHandler_Models(t *testing.T) { }) } +func TestUserHandler_Models_WithRepoFilters(t *testing.T) { + tester := NewUserTester(t).WithHandleFunc(func(h *UserHandler) gin.HandlerFunc { + return h.Models + }) + + modelParamsMin := 1.5 + modelParamsMax := 9.5 + tester.mocks.user.EXPECT().Models(tester.Ctx(), &types.UserDatasetsReq{ + Owner: "go", + CurrentUser: "u", + Filter: &types.RepoFilter{ + Owner: "go", + Username: "u", + Search: "hello", + Sort: "trending", + Source: "local", + ListServerless: true, + Tags: []types.TagReq{{ + Category: "runtime_framework", + Group: "inference", + }}, + ModelParamsMin: &modelParamsMin, + ModelParamsMax: &modelParamsMax, + }, + PageOpts: types.PageOpts{ + Page: 1, + PageSize: 10, + }, + }).Return([]types.Model{{Name: "ds"}}, 100, nil) + tester.AddPagination(1, 10). + WithUser(). + WithParam("username", "go"). + WithQuery("search", "hello"). + WithQuery("sort", "trending"). + WithQuery("source", "local"). + WithQuery("list_serverless", "true"). + WithQuery("tag_category", "runtime_framework"). + WithQuery("tag_group", "inference"). + WithQuery("tag_name", ""). + WithQuery("model_params_min", "1.5"). + WithQuery("model_params_max", "9.5"). + Execute() + tester.ResponseEqSimple(t, 200, gin.H{ + "message": "OK", + "data": []types.Model{{Name: "ds"}}, + "total": 100, + }) +} + func TestUserHandler_Codes(t *testing.T) { tester := NewUserTester(t).WithHandleFunc(func(h *UserHandler) gin.HandlerFunc { return h.Codes @@ -97,6 +146,32 @@ func TestUserHandler_Codes(t *testing.T) { }) } +func TestUserHandler_Spaces_WithSDKOnlyKeepsLegacyRequest(t *testing.T) { + tester := NewUserTester(t).WithHandleFunc(func(h *UserHandler) gin.HandlerFunc { + return h.Spaces + }) + + tester.mocks.user.EXPECT().Spaces(tester.Ctx(), &types.UserSpacesReq{ + SDK: "gradio", + Owner: "go", + CurrentUser: "u", + PageOpts: types.PageOpts{ + Page: 1, + PageSize: 10, + }, + }).Return([]types.Space{{Name: "ds"}}, 100, nil) + tester.AddPagination(1, 10). + WithUser(). + WithParam("username", "go"). + WithQuery("sdk", "gradio"). + Execute() + tester.ResponseEqSimple(t, 200, gin.H{ + "message": "OK", + "data": []types.Space{{Name: "ds"}}, + "total": 100, + }) +} + func TestUserHandler_Spaces(t *testing.T) { tester := NewUserTester(t).WithHandleFunc(func(h *UserHandler) gin.HandlerFunc { return h.Spaces diff --git a/builder/store/database/repository.go b/builder/store/database/repository.go index ac0b84b7a..a736158d1 100644 --- a/builder/store/database/repository.go +++ b/builder/store/database/repository.go @@ -37,6 +37,17 @@ var ( redisOnce sync.Once ) +func escapeLikePattern(value string) string { + var builder strings.Builder + for _, r := range value { + if r == '\\' || r == '%' || r == '_' { + builder.WriteRune('\\') + } + builder.WriteRune(r) + } + return builder.String() +} + type repoStoreImpl struct { config *config.Config db *DB @@ -677,6 +688,9 @@ func (s *repoStoreImpl) PublicToUser(ctx context.Context, repoType types.Reposit Relation("Tags") q.Where("repository.repository_type = ?", repoType) + if filter.Owner != "" { + q.Where("repository.path LIKE ? ESCAPE '\\'", fmt.Sprintf("%s/%%", escapeLikePattern(filter.Owner))) + } switch repoType { case types.ModelRepo: @@ -842,6 +856,9 @@ func (s *repoStoreImpl) publicToUserTrending(ctx context.Context, repoType types Join("JOIN repositories AS r ON r.id = rrs.repository_id"). Where("rrs.weight_name = ?", RecomWeightTotal). Where("r.repository_type = ?", repoType) + if filter.Owner != "" { + q.Where("r.path LIKE ? ESCAPE '\\'", fmt.Sprintf("%s/%%", escapeLikePattern(filter.Owner))) + } // Join with business table q.Join(fmt.Sprintf("INNER JOIN %s ON %s.repository_id = r.id", bizTable, bizTable)) diff --git a/builder/store/database/repository_test.go b/builder/store/database/repository_test.go index 32e008c77..c4dbe6d82 100644 --- a/builder/store/database/repository_test.go +++ b/builder/store/database/repository_test.go @@ -798,6 +798,44 @@ func TestRepoStore_PublicToUser(t *testing.T) { } } +func TestRepoStore_PublicToUserOwnerFilter(t *testing.T) { + db := tests.InitTestDB() + defer db.Close() + ctx := context.TODO() + + store := database.NewRepoStoreWithDB(db) + repos := []database.Repository{ + {Name: "match", Path: "owner/match", GitPath: "owner/match", UserID: 123, RepositoryType: types.CodeRepo}, + {Name: "other", Path: "other/match", GitPath: "other/match", UserID: 123, RepositoryType: types.CodeRepo}, + {Name: "underscore-match", Path: "own_er/match", GitPath: "own_er/match", UserID: 123, RepositoryType: types.CodeRepo}, + {Name: "underscore-other", Path: "ownerX/match", GitPath: "ownerX/match", UserID: 123, RepositoryType: types.CodeRepo}, + } + for _, repo := range repos { + created, err := store.CreateRepo(ctx, repo) + require.Nil(t, err) + _, err = db.Core.NewInsert().Model(&database.Code{RepositoryID: created.ID}).Exec(ctx) + require.Nil(t, err) + } + + rs, count, err := store.PublicToUser(ctx, types.CodeRepo, []int64{123}, &types.RepoFilter{ + Owner: "owner", + Sort: "recently_update", + }, 10, 1, false) + require.Nil(t, err) + require.Equal(t, 1, count) + require.Len(t, rs, 1) + require.Equal(t, "owner/match", rs[0].Path) + + rs, count, err = store.PublicToUser(ctx, types.CodeRepo, []int64{123}, &types.RepoFilter{ + Owner: "own_er", + Sort: "recently_update", + }, 10, 1, false) + require.Nil(t, err) + require.Equal(t, 1, count) + require.Len(t, rs, 1) + require.Equal(t, "own_er/match", rs[0].Path) +} + func TestRepoStore_PublicToUserRangeFilters(t *testing.T) { db := tests.InitTestDB() defer db.Close() diff --git a/common/types/repo.go b/common/types/repo.go index 5193932f7..ffd0684a4 100644 --- a/common/types/repo.go +++ b/common/types/repo.go @@ -314,6 +314,7 @@ type RepoFilter struct { Sort string Search string Source string + Owner string Username string Tree *TreeReq ListServerless bool diff --git a/common/types/user.go b/common/types/user.go index 9e57f2fd2..190ee9fe9 100644 --- a/common/types/user.go +++ b/common/types/user.go @@ -229,6 +229,7 @@ type UpdateAPIKeyRequest struct { type UserDatasetsReq struct { Owner string `json:"owner"` CurrentUser string `json:"current_user"` + Filter *RepoFilter PageOpts } @@ -236,6 +237,7 @@ type UserSpacesReq struct { SDK string `json:"sdk"` Owner string `json:"owner"` CurrentUser string `json:"current_user"` + Filter *RepoFilter PageOpts } diff --git a/component/user.go b/component/user.go index 6fcc8c98d..39dc268bd 100644 --- a/component/user.go +++ b/component/user.go @@ -125,6 +125,24 @@ type userComponentImpl struct { skillStore database.SkillStore } +func (c *userComponentImpl) filteredUserRepos(ctx context.Context, repoType types.RepositoryType, owner, currentUser string, filter *types.RepoFilter, per, page int) ([]*database.Repository, int, error) { + if filter == nil { + return nil, 0, nil + } + appliedFilter := *filter + appliedFilter.Owner = owner + appliedFilter.Username = currentUser + return c.repoComponent.PublicToUser(ctx, repoType, currentUser, &appliedFilter, per, page) +} + +func repoIDsFromRepositories(repos []*database.Repository) []int64 { + repoIDs := make([]int64, 0, len(repos)) + for _, repo := range repos { + repoIDs = append(repoIDs, repo.ID) + } + return repoIDs +} + func (c *userComponentImpl) Datasets(ctx context.Context, req *types.UserDatasetsReq) ([]types.Dataset, int, error) { var resDatasets []types.Dataset userExists, err := c.userStore.IsExist(ctx, req.Owner) @@ -151,6 +169,44 @@ func (c *userComponentImpl) Datasets(ctx context.Context, req *types.UserDataset } } + if req.Filter != nil { + repos, total, err := c.filteredUserRepos(ctx, types.DatasetRepo, req.Owner, req.CurrentUser, req.Filter, req.PageSize, req.Page) + if err != nil { + return nil, 0, fmt.Errorf("failed to get filtered user dataset repos,error:%w", err) + } + if len(repos) == 0 { + return []types.Dataset{}, total, nil + } + ds, err := c.datasetStore.ByRepoIDs(ctx, repoIDsFromRepositories(repos)) + if err != nil { + return nil, 0, fmt.Errorf("failed to get filtered user datasets,error:%w", err) + } + datasetByRepoID := make(map[int64]database.Dataset, len(ds)) + for _, data := range ds { + datasetByRepoID[data.RepositoryID] = data + } + for _, repo := range repos { + data, ok := datasetByRepoID[repo.ID] + if !ok { + continue + } + resDatasets = append(resDatasets, types.Dataset{ + ID: data.ID, + Name: repo.Name, + Nickname: repo.Nickname, + Description: repo.Description, + Likes: repo.Likes, + Downloads: repo.DownloadCount, + Path: repo.Path, + RepositoryID: data.RepositoryID, + Private: repo.Private, + CreatedAt: data.CreatedAt, + UpdatedAt: repo.UpdatedAt, + }) + } + return resDatasets, total, nil + } + onlyPublic := req.Owner != req.CurrentUser ds, total, err := c.datasetStore.ByUsername(ctx, req.Owner, req.PageSize, req.Page, onlyPublic) if err != nil { @@ -204,6 +260,44 @@ func (c *userComponentImpl) Models(ctx context.Context, req *types.UserModelsReq } } + if req.Filter != nil { + repos, total, err := c.filteredUserRepos(ctx, types.ModelRepo, req.Owner, req.CurrentUser, req.Filter, req.PageSize, req.Page) + if err != nil { + return nil, 0, fmt.Errorf("failed to get filtered user model repos,error:%w", err) + } + if len(repos) == 0 { + return []types.Model{}, total, nil + } + ms, err := c.modelStore.ByRepoIDs(ctx, repoIDsFromRepositories(repos)) + if err != nil { + return nil, 0, fmt.Errorf("failed to get filtered user models,error:%w", err) + } + modelByRepoID := make(map[int64]database.Model, len(ms)) + for _, data := range ms { + modelByRepoID[data.RepositoryID] = data + } + for _, repo := range repos { + data, ok := modelByRepoID[repo.ID] + if !ok { + continue + } + resModels = append(resModels, types.Model{ + ID: data.ID, + Name: repo.Name, + Nickname: repo.Nickname, + Description: repo.Description, + Likes: repo.Likes, + Downloads: repo.DownloadCount, + Path: repo.Path, + RepositoryID: data.RepositoryID, + Private: repo.Private, + CreatedAt: data.CreatedAt, + UpdatedAt: repo.UpdatedAt, + }) + } + return resModels, total, nil + } + onlyPublic := req.Owner != req.CurrentUser ms, total, err := c.modelStore.ByUsername(ctx, req.Owner, req.PageSize, req.Page, onlyPublic) if err != nil { @@ -257,6 +351,44 @@ func (c *userComponentImpl) Codes(ctx context.Context, req *types.UserModelsReq) } } + if req.Filter != nil { + repos, total, err := c.filteredUserRepos(ctx, types.CodeRepo, req.Owner, req.CurrentUser, req.Filter, req.PageSize, req.Page) + if err != nil { + return nil, 0, fmt.Errorf("failed to get filtered user code repos,error:%w", err) + } + if len(repos) == 0 { + return []types.Code{}, total, nil + } + codes, err := c.codeStore.ByRepoIDs(ctx, repoIDsFromRepositories(repos)) + if err != nil { + return nil, 0, fmt.Errorf("failed to get filtered user codes,error:%w", err) + } + codeByRepoID := make(map[int64]database.Code, len(codes)) + for _, data := range codes { + codeByRepoID[data.RepositoryID] = data + } + for _, repo := range repos { + data, ok := codeByRepoID[repo.ID] + if !ok { + continue + } + resCodes = append(resCodes, types.Code{ + ID: data.ID, + Name: repo.Name, + Nickname: repo.Nickname, + Description: repo.Description, + Likes: repo.Likes, + Downloads: repo.DownloadCount, + Path: repo.Path, + RepositoryID: data.RepositoryID, + Private: repo.Private, + CreatedAt: data.CreatedAt, + UpdatedAt: repo.UpdatedAt, + }) + } + return resCodes, total, nil + } + onlyPublic := req.Owner != req.CurrentUser ms, total, err := c.codeStore.ByUsername(ctx, req.Owner, req.PageSize, req.Page, onlyPublic) if err != nil { @@ -309,6 +441,27 @@ func (c *userComponentImpl) Spaces(ctx context.Context, req *types.UserSpacesReq } } + if req.Filter != nil { + appliedFilter := *req.Filter + appliedFilter.Owner = req.Owner + appliedFilter.Username = req.CurrentUser + if appliedFilter.SpaceSDK == "" { + appliedFilter.SpaceSDK = req.SDK + } + spaces, total, err := c.spaceComponent.Index(ctx, &appliedFilter, req.PageSize, req.Page, false) + if err != nil { + return nil, 0, fmt.Errorf("failed to get filtered user spaces,error:%w", err) + } + var resSpaces []types.Space + for _, space := range spaces { + if space == nil { + continue + } + resSpaces = append(resSpaces, *space) + } + return resSpaces, total, nil + } + return c.spaceComponent.UserSpaces(ctx, req) } @@ -789,6 +942,51 @@ func (c *userComponentImpl) MCPServers(ctx context.Context, req *types.UserMCPsR } } + if req.Filter != nil { + repos, total, err := c.filteredUserRepos(ctx, types.MCPServerRepo, req.Owner, req.CurrentUser, req.Filter, req.PageSize, req.Page) + if err != nil { + return nil, 0, fmt.Errorf("failed to get filtered user mcp server repos, error: %w", err) + } + if len(repos) == 0 { + return []types.MCPServer{}, total, nil + } + mcpServers, err := c.mcpServerStore.ByRepoIDs(ctx, repoIDsFromRepositories(repos)) + if err != nil { + return nil, 0, fmt.Errorf("failed to get filtered user mcp servers, error: %w", err) + } + mcpByRepoID := make(map[int64]database.MCPServer, len(mcpServers)) + for _, mcpServer := range mcpServers { + mcpByRepoID[mcpServer.RepositoryID] = mcpServer + } + var resMCPs []types.MCPServer + for _, repo := range repos { + mcpServer, ok := mcpByRepoID[repo.ID] + if !ok { + continue + } + resMCPs = append(resMCPs, types.MCPServer{ + ID: mcpServer.ID, + Name: repo.Name, + Nickname: repo.Nickname, + Description: repo.Description, + Likes: repo.Likes, + Downloads: repo.DownloadCount, + Path: repo.Path, + RepositoryID: mcpServer.RepositoryID, + Private: repo.Private, + CreatedAt: mcpServer.CreatedAt, + UpdatedAt: repo.UpdatedAt, + Source: repo.Source, + SyncStatus: repo.SyncStatus, + License: repo.License, + GithubPath: repo.GithubPath, + ToolsNum: mcpServer.ToolsNum, + StarNum: repo.StarCount, + }) + } + return resMCPs, total, nil + } + onlyPublic := req.Owner != req.CurrentUser mcpServers, total, err := c.mcpServerStore.ByUsername(ctx, req.Owner, req.PageSize, req.Page, onlyPublic) if err != nil { @@ -839,6 +1037,49 @@ func (c *userComponentImpl) Skills(ctx context.Context, req *types.UserSkillsReq } } + if req.Filter != nil { + onlyPublic := req.Owner != req.CurrentUser + repos, total, err := c.filteredUserRepos(ctx, types.SkillRepo, req.Owner, req.CurrentUser, req.Filter, req.PageSize, req.Page) + if err != nil { + return nil, 0, fmt.Errorf("failed to get filtered user skill repos, error: %w", err) + } + if len(repos) == 0 { + return []types.Skill{}, total, nil + } + skills, err := c.skillStore.ByRepoIDs(ctx, repoIDsFromRepositories(repos), onlyPublic) + if err != nil { + return nil, 0, fmt.Errorf("failed to get filtered user skills, error: %w", err) + } + skillByRepoID := make(map[int64]database.Skill, len(skills)) + for _, skill := range skills { + skillByRepoID[skill.RepositoryID] = skill + } + var resSkills []types.Skill + for _, repo := range repos { + skill, ok := skillByRepoID[repo.ID] + if !ok { + continue + } + resSkills = append(resSkills, types.Skill{ + ID: skill.ID, + Name: repo.Name, + Nickname: repo.Nickname, + Description: repo.Description, + Likes: repo.Likes, + Downloads: repo.DownloadCount, + Path: repo.Path, + RepositoryID: skill.RepositoryID, + Private: repo.Private, + CreatedAt: skill.CreatedAt, + UpdatedAt: repo.UpdatedAt, + Source: repo.Source, + SyncStatus: repo.SyncStatus, + License: repo.License, + }) + } + return resSkills, total, nil + } + onlyPublic := req.Owner != req.CurrentUser skills, total, err := c.skillStore.ByUsername(ctx, req.Owner, req.PageSize, req.Page, onlyPublic) if err != nil { diff --git a/component/user_test.go b/component/user_test.go index 9b7462061..15faed58d 100644 --- a/component/user_test.go +++ b/component/user_test.go @@ -61,6 +61,42 @@ func TestUserComponent_Models(t *testing.T) { }, data) } +func TestUserComponent_Models_WithRepoFilter(t *testing.T) { + ctx := context.TODO() + uc := initializeTestUserComponent(ctx, t) + + requestFilter := &types.RepoFilter{Search: "hello", Sort: "trending"} + expectedFilter := &types.RepoFilter{ + Owner: "owner", + Username: "user", + Search: "hello", + Sort: "trending", + } + uc.mocks.stores.UserMock().EXPECT().IsExist(ctx, "owner").Return(true, nil) + uc.mocks.stores.UserMock().EXPECT().IsExist(ctx, "user").Return(true, nil) + uc.mocks.components.repo.EXPECT().PublicToUser(ctx, types.ModelRepo, "user", expectedFilter, 10, 1).Return([]*database.Repository{ + {ID: 11, Name: "foo", Path: "owner/foo"}, + }, 1, nil) + uc.mocks.stores.ModelMock().EXPECT().ByRepoIDs(ctx, []int64{11}).Return([]database.Model{ + {ID: 1, RepositoryID: 11}, + }, nil) + + data, total, err := uc.Models(ctx, &types.UserDatasetsReq{ + Owner: "owner", + CurrentUser: "user", + Filter: requestFilter, + PageOpts: types.PageOpts{ + Page: 1, + PageSize: 10, + }, + }) + require.Nil(t, err) + require.Equal(t, 1, total) + require.Equal(t, []types.Model{ + {ID: 1, Name: "foo", Path: "owner/foo", RepositoryID: 11}, + }, data) +} + func TestUserComponent_Codes(t *testing.T) { ctx := context.TODO() uc := initializeTestUserComponent(ctx, t) @@ -119,6 +155,41 @@ func TestUserComponent_Spaces(t *testing.T) { } +func TestUserComponent_Spaces_WithRepoFilter(t *testing.T) { + ctx := context.TODO() + uc := initializeTestUserComponent(ctx, t) + + requestFilter := &types.RepoFilter{Search: "hello", Status: "Running"} + expectedFilter := &types.RepoFilter{ + Owner: "owner", + Username: "user", + Search: "hello", + Status: "Running", + SpaceSDK: "gradio", + } + uc.mocks.stores.UserMock().EXPECT().IsExist(ctx, "owner").Return(true, nil) + uc.mocks.stores.UserMock().EXPECT().IsExist(ctx, "user").Return(true, nil) + uc.mocks.components.space.EXPECT().Index(ctx, expectedFilter, 10, 1, false).Return([]*types.Space{ + {ID: 1, Name: "foo", Status: "Running", Sdk: "gradio"}, + }, 1, nil) + + data, total, err := uc.Spaces(ctx, &types.UserSpacesReq{ + Owner: "owner", + CurrentUser: "user", + SDK: "gradio", + Filter: requestFilter, + PageOpts: types.PageOpts{ + Page: 1, + PageSize: 10, + }, + }) + require.Nil(t, err) + require.Equal(t, 1, total) + require.Equal(t, []types.Space{ + {ID: 1, Name: "foo", Status: "Running", Sdk: "gradio"}, + }, data) +} + func TestUserComponent_Skills(t *testing.T) { testCases := []struct { name string @@ -232,11 +303,11 @@ func TestUserComponent_Skills(t *testing.T) { uc := initializeTestUserComponent(ctx, t) uc.mocks.stores.UserMock().EXPECT().IsExist(ctx, "owner").Return(tc.ownerExists, tc.ownerError) - + // Only expect current user check if owner exists and has no error if tc.ownerExists && tc.ownerError == nil { uc.mocks.stores.UserMock().EXPECT().IsExist(ctx, "user").Return(tc.userExists, tc.userError) - + // Only expect skill retrieval if both users exist and have no errors if tc.userExists && tc.userError == nil { uc.mocks.stores.SkillMock().EXPECT().ByUsername(ctx, "owner", 10, 1, true).Return(tc.skills, tc.total, tc.skillsError)