Skip to content

Commit e7ef017

Browse files
csg-pr-botDev Agent
andauthored
main__fix-personal-all-resource-filter (#1209)
Co-authored-by: Dev Agent <dev-agent@example.com>
1 parent 38ff1b0 commit e7ef017

8 files changed

Lines changed: 548 additions & 2 deletions

File tree

api/handler/user.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package handler
22

33
import (
44
"errors"
5+
"fmt"
56
"log/slog"
67
"net/http"
8+
"slices"
79
"strconv"
810

911
"opencsg.com/csghub-server/common/errorx"
@@ -30,6 +32,69 @@ type UserHandler struct {
3032
user component.UserComponent
3133
}
3234

35+
func hasUserRepoFilterQuery(ctx *gin.Context) bool {
36+
filterKeys := []string{
37+
"search", "sort", "source", "status", "xnet_migration_status",
38+
"dataset_type", "user_purchased", "list_serverless",
39+
"tag_category", "tag_name", "tag_group",
40+
"model_tree",
41+
"model_params_min", "model_params_max",
42+
"repo_size_min", "repo_size_max",
43+
}
44+
for _, key := range filterKeys {
45+
if _, ok := ctx.GetQuery(key); ok {
46+
return true
47+
}
48+
}
49+
return false
50+
}
51+
52+
func parseUserRepoFilter(ctx *gin.Context, owner, currentUser string) (*types.RepoFilter, error) {
53+
if !hasUserRepoFilterQuery(ctx) {
54+
return nil, nil
55+
}
56+
57+
filter := &types.RepoFilter{
58+
Tags: parseTagReqs(ctx),
59+
Owner: owner,
60+
Username: currentUser,
61+
}
62+
63+
tree, err := parseTreeReqs(ctx)
64+
if err != nil {
65+
return nil, err
66+
}
67+
filter.Tree = tree
68+
filter = getFilterFromContext(ctx, filter)
69+
filter.SpaceSDK = ctx.Query("sdk")
70+
71+
if listServerless, err := strconv.ParseBool(ctx.Query("list_serverless")); err == nil {
72+
filter.ListServerless = listServerless
73+
}
74+
75+
filter.ModelParamsMin, filter.ModelParamsMax, err = parseFloatRangeFromContext(ctx, "model_params_min", "model_params_max")
76+
if err != nil {
77+
return nil, errorx.ReqParamInvalid(err, errorx.Ctx().Set("query", "model_params_range"))
78+
}
79+
80+
filter.RepoSizeMin, filter.RepoSizeMax, err = parseInt64RangeFromContext(ctx, "repo_size_min", "repo_size_max")
81+
if err != nil {
82+
return nil, errorx.ReqParamInvalid(err, errorx.Ctx().Set("query", "repo_size_range"))
83+
}
84+
85+
if !slices.Contains(types.Sorts, filter.Sort) {
86+
err := fmt.Errorf("sort parameter must be one of %v", types.Sorts)
87+
return nil, errorx.ReqParamInvalid(err, errorx.Ctx().Set("query", "sort_filter"))
88+
}
89+
90+
if filter.Source != "" && !slices.Contains(types.Sources, filter.Source) {
91+
err := fmt.Errorf("source parameter must be one of %v", types.Sources)
92+
return nil, errorx.ReqParamInvalid(err, errorx.Ctx().Set("query", "source_filter"))
93+
}
94+
95+
return filter, nil
96+
}
97+
3398
// GetUserDatasets godoc
3499
// @Security ApiKey
35100
// @Summary Get user datasets
@@ -53,6 +118,12 @@ func (h *UserHandler) Datasets(ctx *gin.Context) {
53118

54119
req.Owner = ctx.Param("username")
55120
req.CurrentUser = httpbase.GetCurrentUser(ctx)
121+
req.Filter, err = parseUserRepoFilter(ctx, req.Owner, req.CurrentUser)
122+
if err != nil {
123+
slog.ErrorContext(ctx.Request.Context(), "Bad user datasets filter request format", "error", err)
124+
httpbase.BadRequestWithExt(ctx, err)
125+
return
126+
}
56127
req.Page = page
57128
req.PageSize = per
58129
ds, total, err := h.user.Datasets(ctx.Request.Context(), &req)
@@ -94,6 +165,12 @@ func (h *UserHandler) Models(ctx *gin.Context) {
94165

95166
req.Owner = ctx.Param("username")
96167
req.CurrentUser = httpbase.GetCurrentUser(ctx)
168+
req.Filter, err = parseUserRepoFilter(ctx, req.Owner, req.CurrentUser)
169+
if err != nil {
170+
slog.ErrorContext(ctx.Request.Context(), "Bad user models filter request format", "error", err)
171+
httpbase.BadRequestWithExt(ctx, err)
172+
return
173+
}
97174
req.Page = page
98175
req.PageSize = per
99176
ms, total, err := h.user.Models(ctx.Request.Context(), &req)
@@ -136,6 +213,12 @@ func (h *UserHandler) Codes(ctx *gin.Context) {
136213

137214
req.Owner = ctx.Param("username")
138215
req.CurrentUser = httpbase.GetCurrentUser(ctx)
216+
req.Filter, err = parseUserRepoFilter(ctx, req.Owner, req.CurrentUser)
217+
if err != nil {
218+
slog.ErrorContext(ctx.Request.Context(), "Bad user codes filter request format", "error", err)
219+
httpbase.BadRequestWithExt(ctx, err)
220+
return
221+
}
139222
req.Page = page
140223
req.PageSize = per
141224
ms, total, err := h.user.Codes(ctx.Request.Context(), &req)
@@ -178,6 +261,12 @@ func (h *UserHandler) Spaces(ctx *gin.Context) {
178261
req.SDK = ctx.Query("sdk")
179262
req.Owner = ctx.Param("username")
180263
req.CurrentUser = httpbase.GetCurrentUser(ctx)
264+
req.Filter, err = parseUserRepoFilter(ctx, req.Owner, req.CurrentUser)
265+
if err != nil {
266+
slog.ErrorContext(ctx.Request.Context(), "Bad user spaces filter request format", "error", err)
267+
httpbase.BadRequestWithExt(ctx, err)
268+
return
269+
}
181270
req.Page = page
182271
req.PageSize = per
183272
ms, total, err := h.user.Spaces(ctx.Request.Context(), &req)
@@ -950,6 +1039,12 @@ func (h *UserHandler) MCPServers(ctx *gin.Context) {
9501039

9511040
req.Owner = ctx.Param("username")
9521041
req.CurrentUser = httpbase.GetCurrentUser(ctx)
1042+
req.Filter, err = parseUserRepoFilter(ctx, req.Owner, req.CurrentUser)
1043+
if err != nil {
1044+
slog.ErrorContext(ctx.Request.Context(), "Bad user mcp servers filter request format", "error", err)
1045+
httpbase.BadRequestWithExt(ctx, err)
1046+
return
1047+
}
9531048
req.Page = page
9541049
req.PageSize = per
9551050
mcps, total, err := h.user.MCPServers(ctx.Request.Context(), &req)
@@ -992,6 +1087,12 @@ func (h *UserHandler) Skills(ctx *gin.Context) {
9921087

9931088
req.Owner = ctx.Param("username")
9941089
req.CurrentUser = httpbase.GetCurrentUser(ctx)
1090+
req.Filter, err = parseUserRepoFilter(ctx, req.Owner, req.CurrentUser)
1091+
if err != nil {
1092+
slog.ErrorContext(ctx.Request.Context(), "Bad user skills filter request format", "error", err)
1093+
httpbase.BadRequestWithExt(ctx, err)
1094+
return
1095+
}
9951096
req.Page = page
9961097
req.PageSize = per
9971098
skills, total, err := h.user.Skills(ctx.Request.Context(), &req)

api/handler/user_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,55 @@ func TestUserHandler_Models(t *testing.T) {
7676
})
7777
}
7878

79+
func TestUserHandler_Models_WithRepoFilters(t *testing.T) {
80+
tester := NewUserTester(t).WithHandleFunc(func(h *UserHandler) gin.HandlerFunc {
81+
return h.Models
82+
})
83+
84+
modelParamsMin := 1.5
85+
modelParamsMax := 9.5
86+
tester.mocks.user.EXPECT().Models(tester.Ctx(), &types.UserDatasetsReq{
87+
Owner: "go",
88+
CurrentUser: "u",
89+
Filter: &types.RepoFilter{
90+
Owner: "go",
91+
Username: "u",
92+
Search: "hello",
93+
Sort: "trending",
94+
Source: "local",
95+
ListServerless: true,
96+
Tags: []types.TagReq{{
97+
Category: "runtime_framework",
98+
Group: "inference",
99+
}},
100+
ModelParamsMin: &modelParamsMin,
101+
ModelParamsMax: &modelParamsMax,
102+
},
103+
PageOpts: types.PageOpts{
104+
Page: 1,
105+
PageSize: 10,
106+
},
107+
}).Return([]types.Model{{Name: "ds"}}, 100, nil)
108+
tester.AddPagination(1, 10).
109+
WithUser().
110+
WithParam("username", "go").
111+
WithQuery("search", "hello").
112+
WithQuery("sort", "trending").
113+
WithQuery("source", "local").
114+
WithQuery("list_serverless", "true").
115+
WithQuery("tag_category", "runtime_framework").
116+
WithQuery("tag_group", "inference").
117+
WithQuery("tag_name", "").
118+
WithQuery("model_params_min", "1.5").
119+
WithQuery("model_params_max", "9.5").
120+
Execute()
121+
tester.ResponseEqSimple(t, 200, gin.H{
122+
"message": "OK",
123+
"data": []types.Model{{Name: "ds"}},
124+
"total": 100,
125+
})
126+
}
127+
79128
func TestUserHandler_Codes(t *testing.T) {
80129
tester := NewUserTester(t).WithHandleFunc(func(h *UserHandler) gin.HandlerFunc {
81130
return h.Codes
@@ -97,6 +146,32 @@ func TestUserHandler_Codes(t *testing.T) {
97146
})
98147
}
99148

149+
func TestUserHandler_Spaces_WithSDKOnlyKeepsLegacyRequest(t *testing.T) {
150+
tester := NewUserTester(t).WithHandleFunc(func(h *UserHandler) gin.HandlerFunc {
151+
return h.Spaces
152+
})
153+
154+
tester.mocks.user.EXPECT().Spaces(tester.Ctx(), &types.UserSpacesReq{
155+
SDK: "gradio",
156+
Owner: "go",
157+
CurrentUser: "u",
158+
PageOpts: types.PageOpts{
159+
Page: 1,
160+
PageSize: 10,
161+
},
162+
}).Return([]types.Space{{Name: "ds"}}, 100, nil)
163+
tester.AddPagination(1, 10).
164+
WithUser().
165+
WithParam("username", "go").
166+
WithQuery("sdk", "gradio").
167+
Execute()
168+
tester.ResponseEqSimple(t, 200, gin.H{
169+
"message": "OK",
170+
"data": []types.Space{{Name: "ds"}},
171+
"total": 100,
172+
})
173+
}
174+
100175
func TestUserHandler_Spaces(t *testing.T) {
101176
tester := NewUserTester(t).WithHandleFunc(func(h *UserHandler) gin.HandlerFunc {
102177
return h.Spaces

builder/store/database/repository.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,17 @@ var (
3737
redisOnce sync.Once
3838
)
3939

40+
func escapeLikePattern(value string) string {
41+
var builder strings.Builder
42+
for _, r := range value {
43+
if r == '\\' || r == '%' || r == '_' {
44+
builder.WriteRune('\\')
45+
}
46+
builder.WriteRune(r)
47+
}
48+
return builder.String()
49+
}
50+
4051
type repoStoreImpl struct {
4152
config *config.Config
4253
db *DB
@@ -677,6 +688,9 @@ func (s *repoStoreImpl) PublicToUser(ctx context.Context, repoType types.Reposit
677688
Relation("Tags")
678689

679690
q.Where("repository.repository_type = ?", repoType)
691+
if filter.Owner != "" {
692+
q.Where("repository.path LIKE ? ESCAPE '\\'", fmt.Sprintf("%s/%%", escapeLikePattern(filter.Owner)))
693+
}
680694

681695
switch repoType {
682696
case types.ModelRepo:
@@ -842,6 +856,9 @@ func (s *repoStoreImpl) publicToUserTrending(ctx context.Context, repoType types
842856
Join("JOIN repositories AS r ON r.id = rrs.repository_id").
843857
Where("rrs.weight_name = ?", RecomWeightTotal).
844858
Where("r.repository_type = ?", repoType)
859+
if filter.Owner != "" {
860+
q.Where("r.path LIKE ? ESCAPE '\\'", fmt.Sprintf("%s/%%", escapeLikePattern(filter.Owner)))
861+
}
845862

846863
// Join with business table
847864
q.Join(fmt.Sprintf("INNER JOIN %s ON %s.repository_id = r.id", bizTable, bizTable))

builder/store/database/repository_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,44 @@ func TestRepoStore_PublicToUser(t *testing.T) {
798798
}
799799
}
800800

801+
func TestRepoStore_PublicToUserOwnerFilter(t *testing.T) {
802+
db := tests.InitTestDB()
803+
defer db.Close()
804+
ctx := context.TODO()
805+
806+
store := database.NewRepoStoreWithDB(db)
807+
repos := []database.Repository{
808+
{Name: "match", Path: "owner/match", GitPath: "owner/match", UserID: 123, RepositoryType: types.CodeRepo},
809+
{Name: "other", Path: "other/match", GitPath: "other/match", UserID: 123, RepositoryType: types.CodeRepo},
810+
{Name: "underscore-match", Path: "own_er/match", GitPath: "own_er/match", UserID: 123, RepositoryType: types.CodeRepo},
811+
{Name: "underscore-other", Path: "ownerX/match", GitPath: "ownerX/match", UserID: 123, RepositoryType: types.CodeRepo},
812+
}
813+
for _, repo := range repos {
814+
created, err := store.CreateRepo(ctx, repo)
815+
require.Nil(t, err)
816+
_, err = db.Core.NewInsert().Model(&database.Code{RepositoryID: created.ID}).Exec(ctx)
817+
require.Nil(t, err)
818+
}
819+
820+
rs, count, err := store.PublicToUser(ctx, types.CodeRepo, []int64{123}, &types.RepoFilter{
821+
Owner: "owner",
822+
Sort: "recently_update",
823+
}, 10, 1, false)
824+
require.Nil(t, err)
825+
require.Equal(t, 1, count)
826+
require.Len(t, rs, 1)
827+
require.Equal(t, "owner/match", rs[0].Path)
828+
829+
rs, count, err = store.PublicToUser(ctx, types.CodeRepo, []int64{123}, &types.RepoFilter{
830+
Owner: "own_er",
831+
Sort: "recently_update",
832+
}, 10, 1, false)
833+
require.Nil(t, err)
834+
require.Equal(t, 1, count)
835+
require.Len(t, rs, 1)
836+
require.Equal(t, "own_er/match", rs[0].Path)
837+
}
838+
801839
func TestRepoStore_PublicToUserRangeFilters(t *testing.T) {
802840
db := tests.InitTestDB()
803841
defer db.Close()

common/types/repo.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ type RepoFilter struct {
314314
Sort string
315315
Search string
316316
Source string
317+
Owner string
317318
Username string
318319
Tree *TreeReq
319320
ListServerless bool

common/types/user.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,13 +229,15 @@ type UpdateAPIKeyRequest struct {
229229
type UserDatasetsReq struct {
230230
Owner string `json:"owner"`
231231
CurrentUser string `json:"current_user"`
232+
Filter *RepoFilter
232233
PageOpts
233234
}
234235

235236
type UserSpacesReq struct {
236237
SDK string `json:"sdk"`
237238
Owner string `json:"owner"`
238239
CurrentUser string `json:"current_user"`
240+
Filter *RepoFilter
239241
PageOpts
240242
}
241243

0 commit comments

Comments
 (0)