@@ -11,6 +11,7 @@ import (
1111 "log"
1212 "log/slog"
1313 "net/http"
14+ "regexp"
1415 "strconv"
1516 "strings"
1617 "sync"
@@ -151,12 +152,16 @@ type BulkUpdateAccountsRequest struct {
151152}
152153
153154type BulkUpdateAccountFilters struct {
154- Platform string `json:"platform"`
155- Type string `json:"type"`
156- Status string `json:"status"`
157- Group string `json:"group"`
158- Search string `json:"search"`
159- PrivacyMode string `json:"privacy_mode"`
155+ Platform string `json:"platform"`
156+ Type string `json:"type"`
157+ Status string `json:"status"`
158+ Schedulable string `json:"schedulable"`
159+ Group string `json:"group"`
160+ Search string `json:"search"`
161+ PrivacyMode string `json:"privacy_mode"`
162+ DisplayGroup string `json:"display_group"`
163+ NamePrefix string `json:"name_prefix"`
164+ SearchRegex string `json:"search_regex"`
160165}
161166
162167// CheckMixedChannelRequest represents check mixed channel risk request
@@ -178,6 +183,90 @@ type AccountWithConcurrency struct {
178183
179184const accountListGroupUngroupedQueryValue = "ungrouped"
180185
186+ type accountListQueryFilters struct {
187+ platform string
188+ accountType string
189+ status string
190+ schedulable string
191+ search string
192+ groupID int64
193+ privacyMode string
194+ displayGroup string
195+ namePrefix string
196+ searchRegex string
197+ sortBy string
198+ sortOrder string
199+ }
200+
201+ func normalizeSchedulableFilter (raw string ) (string , error ) {
202+ switch strings .ToLower (strings .TrimSpace (raw )) {
203+ case "" , "all" :
204+ return "" , nil
205+ case "true" , "1" , "enabled" , "on" :
206+ return "true" , nil
207+ case "false" , "0" , "disabled" , "off" :
208+ return "false" , nil
209+ default :
210+ return "" , infraerrors .BadRequest ("INVALID_SCHEDULABLE_FILTER" , "invalid schedulable filter" )
211+ }
212+ }
213+
214+ func parseAccountListQueryFilters (c * gin.Context ) (* accountListQueryFilters , error ) {
215+ search := strings .TrimSpace (c .Query ("search" ))
216+ if len (search ) > 100 {
217+ search = search [:100 ]
218+ }
219+ displayGroup := strings .TrimSpace (c .Query ("display_group" ))
220+ if len (displayGroup ) > 100 {
221+ displayGroup = displayGroup [:100 ]
222+ }
223+ namePrefix := strings .TrimSpace (c .Query ("name_prefix" ))
224+ if len (namePrefix ) > 100 {
225+ namePrefix = namePrefix [:100 ]
226+ }
227+ searchRegex := strings .TrimSpace (c .Query ("search_regex" ))
228+ if len (searchRegex ) > 512 {
229+ return nil , infraerrors .BadRequest ("INVALID_SEARCH_REGEX" , "search regex is too long" )
230+ }
231+ if searchRegex != "" {
232+ if _ , err := regexp .Compile (searchRegex ); err != nil {
233+ return nil , infraerrors .BadRequest ("INVALID_SEARCH_REGEX" , "invalid search regex" )
234+ }
235+ }
236+ schedulable , err := normalizeSchedulableFilter (c .Query ("schedulable" ))
237+ if err != nil {
238+ return nil , err
239+ }
240+
241+ groupID := int64 (0 )
242+ if groupIDStr := c .Query ("group" ); groupIDStr != "" {
243+ if groupIDStr == accountListGroupUngroupedQueryValue {
244+ groupID = service .AccountListGroupUngrouped
245+ } else {
246+ parsedGroupID , parseErr := strconv .ParseInt (groupIDStr , 10 , 64 )
247+ if parseErr != nil || parsedGroupID < 0 {
248+ return nil , infraerrors .BadRequest ("INVALID_GROUP_FILTER" , "invalid group filter" )
249+ }
250+ groupID = parsedGroupID
251+ }
252+ }
253+
254+ return & accountListQueryFilters {
255+ platform : c .Query ("platform" ),
256+ accountType : c .Query ("type" ),
257+ status : c .Query ("status" ),
258+ schedulable : schedulable ,
259+ search : search ,
260+ groupID : groupID ,
261+ privacyMode : strings .TrimSpace (c .Query ("privacy_mode" )),
262+ displayGroup : displayGroup ,
263+ namePrefix : namePrefix ,
264+ searchRegex : searchRegex ,
265+ sortBy : c .DefaultQuery ("sort_by" , "name" ),
266+ sortOrder : c .DefaultQuery ("sort_order" , "asc" ),
267+ }, nil
268+ }
269+
181270func (h * AccountHandler ) buildAccountResponseWithRuntime (ctx context.Context , account * service.Account ) AccountWithConcurrency {
182271 item := AccountWithConcurrency {
183272 Account : dto .AccountFromService (account ),
@@ -226,39 +315,13 @@ func (h *AccountHandler) buildAccountResponseWithRuntime(ctx context.Context, ac
226315// GET /api/v1/admin/accounts
227316func (h * AccountHandler ) List (c * gin.Context ) {
228317 page , pageSize := response .ParsePagination (c )
229- platform := c .Query ("platform" )
230- accountType := c .Query ("type" )
231- status := c .Query ("status" )
232- search := c .Query ("search" )
233- privacyMode := strings .TrimSpace (c .Query ("privacy_mode" ))
234- sortBy := c .DefaultQuery ("sort_by" , "name" )
235- sortOrder := c .DefaultQuery ("sort_order" , "asc" )
236- // 标准化和验证 search 参数
237- search = strings .TrimSpace (search )
238- if len (search ) > 100 {
239- search = search [:100 ]
318+ filters , err := parseAccountListQueryFilters (c )
319+ if err != nil {
320+ response .ErrorFrom (c , err )
321+ return
240322 }
241323 lite := parseBoolQueryWithDefault (c .Query ("lite" ), false )
242-
243- var groupID int64
244- if groupIDStr := c .Query ("group" ); groupIDStr != "" {
245- if groupIDStr == accountListGroupUngroupedQueryValue {
246- groupID = service .AccountListGroupUngrouped
247- } else {
248- parsedGroupID , parseErr := strconv .ParseInt (groupIDStr , 10 , 64 )
249- if parseErr != nil {
250- response .ErrorFrom (c , infraerrors .BadRequest ("INVALID_GROUP_FILTER" , "invalid group filter" ))
251- return
252- }
253- if parsedGroupID < 0 {
254- response .ErrorFrom (c , infraerrors .BadRequest ("INVALID_GROUP_FILTER" , "invalid group filter" ))
255- return
256- }
257- groupID = parsedGroupID
258- }
259- }
260-
261- accounts , total , err := h .adminService .ListAccounts (c .Request .Context (), page , pageSize , platform , accountType , status , search , groupID , privacyMode , sortBy , sortOrder )
324+ accounts , total , err := h .adminService .ListAccounts (c .Request .Context (), page , pageSize , filters .platform , filters .accountType , filters .status , filters .schedulable , filters .search , filters .groupID , filters .privacyMode , filters .displayGroup , filters .namePrefix , filters .searchRegex , filters .sortBy , filters .sortOrder )
262325 if err != nil {
263326 response .ErrorFrom (c , err )
264327 return
@@ -380,7 +443,7 @@ func (h *AccountHandler) List(c *gin.Context) {
380443 result [i ] = item
381444 }
382445
383- etag := buildAccountsListETag (result , total , page , pageSize , platform , accountType , status , search , lite )
446+ etag := buildAccountsListETag (result , total , page , pageSize , filters . platform , filters . accountType , filters . status , filters . schedulable , filters . search , lite )
384447 if etag != "" {
385448 c .Header ("ETag" , etag )
386449 c .Header ("Vary" , "If-None-Match" )
@@ -397,7 +460,7 @@ func buildAccountsListETag(
397460 items []AccountWithConcurrency ,
398461 total int64 ,
399462 page , pageSize int ,
400- platform , accountType , status , search string ,
463+ platform , accountType , status , schedulable , search string ,
401464 lite bool ,
402465) string {
403466 payload := struct {
@@ -407,6 +470,7 @@ func buildAccountsListETag(
407470 Platform string `json:"platform"`
408471 AccountType string `json:"type"`
409472 Status string `json:"status"`
473+ Schedulable string `json:"schedulable"`
410474 Search string `json:"search"`
411475 Lite bool `json:"lite"`
412476 Items []AccountWithConcurrency `json:"items"`
@@ -417,6 +481,7 @@ func buildAccountsListETag(
417481 Platform : platform ,
418482 AccountType : accountType ,
419483 Status : status ,
484+ Schedulable : schedulable ,
420485 Search : search ,
421486 Lite : lite ,
422487 Items : items ,
@@ -1489,12 +1554,16 @@ func toServiceBulkUpdateAccountFilters(filters *BulkUpdateAccountFilters) *servi
14891554 return nil
14901555 }
14911556 return & service.BulkUpdateAccountFilters {
1492- Platform : filters .Platform ,
1493- Type : filters .Type ,
1494- Status : filters .Status ,
1495- Group : filters .Group ,
1496- Search : filters .Search ,
1497- PrivacyMode : filters .PrivacyMode ,
1557+ Platform : filters .Platform ,
1558+ Type : filters .Type ,
1559+ Status : filters .Status ,
1560+ Schedulable : filters .Schedulable ,
1561+ Group : filters .Group ,
1562+ Search : filters .Search ,
1563+ PrivacyMode : filters .PrivacyMode ,
1564+ DisplayGroup : filters .DisplayGroup ,
1565+ NamePrefix : filters .NamePrefix ,
1566+ SearchRegex : filters .SearchRegex ,
14981567 }
14991568}
15001569
@@ -2107,7 +2176,7 @@ func (h *AccountHandler) BatchRefreshTier(c *gin.Context) {
21072176 accounts := make ([]* service.Account , 0 )
21082177
21092178 if len (req .AccountIDs ) == 0 {
2110- allAccounts , _ , err := h .adminService .ListAccounts (ctx , 1 , 10000 , "gemini" , "oauth" , "" , "" , 0 , "" , "name" , "asc" )
2179+ allAccounts , _ , err := h .adminService .ListAccounts (ctx , 1 , 10000 , "gemini" , "oauth" , "" , "" , "" , 0 , "" , "" , "" , "" , "name" , "asc" )
21112180 if err != nil {
21122181 response .ErrorFrom (c , err )
21132182 return
0 commit comments