Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add admin api/v1/users api to allow search user by name or email #148

Merged
merged 1 commit into from
Oct 16, 2024
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
1 change: 1 addition & 0 deletions api/router/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ func NewRouter(config *config.Config, enableSwagger bool) (*gin.Engine, error) {
// JWT token
apiGroup.POST("/jwt/token", needAPIKey, userProxyHandler.Proxy)
apiGroup.GET("/jwt/:token", needAPIKey, userProxyHandler.ProxyToApi("/api/v1/jwt/%s", "token"))
apiGroup.GET("/users", userProxyHandler.Proxy)

// callback
callbackCtrl, err := callback.NewGitCallbackHandler(config)
Expand Down
19 changes: 19 additions & 0 deletions builder/store/database/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,25 @@ func (s *UserStore) Index(ctx context.Context) (users []User, err error) {
return
}

func (s *UserStore) IndexWithSearch(ctx context.Context, search string, per, page int) (users []User, count int, err error) {
search = strings.ToLower(search)
query := s.db.Operator.Core.NewSelect().
Model(&users)
if search != "" {
query.Where("LOWER(username) like ? OR LOWER(email) like ?", fmt.Sprintf("%%%s%%", search), fmt.Sprintf("%%%s%%", search))
}
count, err = query.Count(ctx)
if err != nil {
return
}
query.Order("id asc").Limit(per).Offset((page - 1) * per)
err = query.Scan(ctx, &users)
if err != nil {
return
}
return
}

func (s *UserStore) FindByUsername(ctx context.Context, username string) (user User, err error) {
user.Username = username
err = s.db.Operator.Core.NewSelect().Model(&user).Where("username = ?", username).Scan(ctx)
Expand Down
2 changes: 1 addition & 1 deletion common/types/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ type User struct {
Roles []string `json:"roles,omitempty"`
LastLoginAt string `json:"last_login_at,omitempty"`
Orgs []Organization `json:"orgs,omitempty"`
CanChangeUserName bool `json:"can_change_username"`
CanChangeUserName bool `json:"can_change_username,omitempty"`
}

type UserLikesRequest struct {
Expand Down
66 changes: 66 additions & 0 deletions docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -10078,6 +10078,72 @@ const docTemplate = `{
}
}
},
"/users": {
"get": {
"security": [
{
"ApiKey": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "Get users info. Only Admin",
"parameters": [
{
"type": "string",
"description": "search",
"name": "search",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/types.Response"
},
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/types.User"
}
},
"total": {
"type": "integer"
}
}
}
]
}
},
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/types.APIBadRequest"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/types.APIInternalServerError"
}
}
}
}
},
"/{repo_type}/{namespace}/{name}/blob/{file_path}": {
"get": {
"security": [
Expand Down
66 changes: 66 additions & 0 deletions docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -10067,6 +10067,72 @@
}
}
},
"/users": {
"get": {
"security": [
{
"ApiKey": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "Get users info. Only Admin",
"parameters": [
{
"type": "string",
"description": "search",
"name": "search",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/types.Response"
},
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/types.User"
}
},
"total": {
"type": "integer"
}
}
}
]
}
},
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/types.APIBadRequest"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/types.APIInternalServerError"
}
}
}
}
},
"/{repo_type}/{namespace}/{name}/blob/{file_path}": {
"get": {
"security": [
Expand Down
39 changes: 39 additions & 0 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10211,6 +10211,45 @@ paths:
summary: '[Deprecated: use DELETE:/token/{app}/{token_name} instead]'
tags:
- Access token
/users:
get:
consumes:
- application/json
parameters:
- description: search
in: query
name: search
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/types.Response'
- properties:
data:
items:
$ref: '#/definitions/types.User'
type: array
total:
type: integer
type: object
"400":
description: Bad request
schema:
$ref: '#/definitions/types.APIBadRequest'
"500":
description: Internal server error
schema:
$ref: '#/definitions/types.APIInternalServerError'
security:
- ApiKey: []
summary: Get users info. Only Admin
tags:
- User
securityDefinitions:
ApiKey:
description: Bearer token
Expand Down
41 changes: 41 additions & 0 deletions user/component/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,47 @@ func (c *UserComponent) Get(ctx context.Context, userName, visitorName string) (
return &u, nil
}

func (c *UserComponent) Index(ctx context.Context, visitorName, search string, per, page int) ([]*types.User, int, error) {
var (
respUsers []*types.User
onlyBasicInfo bool
)
canAdmin, err := c.CanAdmin(ctx, visitorName)
if err != nil {
return nil, 0, fmt.Errorf("failed to check visitor user permission, visitor: %s, error: %w", visitorName, err)
}
if !canAdmin {
onlyBasicInfo = true
}

dbusers, count, err := c.us.IndexWithSearch(ctx, search, per, page)
if err != nil {
newError := fmt.Errorf("failed to find user by name in db,error:%w", err)
return nil, count, newError
}

for _, dbuser := range dbusers {
user := &types.User{
Username: dbuser.Username,
Nickname: dbuser.NickName,
Avatar: dbuser.Avatar,
}

if !onlyBasicInfo {
user.Email = dbuser.Email
user.UUID = dbuser.UUID
user.Bio = dbuser.Bio
user.Homepage = dbuser.Homepage
user.Phone = dbuser.Phone
user.Roles = dbuser.Roles()
}

respUsers = append(respUsers, user)
}

return respUsers, count, nil
}

func (c *UserComponent) Signin(ctx context.Context, code, state string) (*types.JWTClaims, string, error) {
c.lazyInit()

Expand Down
36 changes: 36 additions & 0 deletions user/handler/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"opencsg.com/csghub-server/api/httpbase"
"opencsg.com/csghub-server/common/config"
"opencsg.com/csghub-server/common/types"
"opencsg.com/csghub-server/common/utils/common"
apicomponent "opencsg.com/csghub-server/component"
"opencsg.com/csghub-server/user/component"
)
Expand Down Expand Up @@ -173,6 +174,41 @@ func (h *UserHandler) Get(ctx *gin.Context) {
httpbase.OK(ctx, user)
}

// GetUsers godoc
// @Security ApiKey
// @Summary Get users info. Only Admin
// @Tags User
// @Accept json
// @Produce json
// @Param search query string true "search"
// @Success 200 {object} types.Response{data=[]types.User,total=int} "OK"
// @Failure 400 {object} types.APIBadRequest "Bad request"
// @Failure 500 {object} types.APIInternalServerError "Internal server error"
// @Router /users [get]
func (h *UserHandler) Index(ctx *gin.Context) {
visitorName := httpbase.GetCurrentUser(ctx)
search := ctx.Query("search")
per, page, err := common.GetPerAndPageFromContext(ctx)
if err != nil {
slog.Error("Failed to get per and page", slog.Any("error", err))
httpbase.BadRequest(ctx, err.Error())
return
}
users, count, err := h.c.Index(ctx, visitorName, search, per, page)
if err != nil {
slog.Error("Failed to get user", slog.Any("error", err))
httpbase.ServerError(ctx, err)
return
}
respData := gin.H{
"data": users,
"total": count,
}

slog.Info("Get users succeed")
httpbase.OK(ctx, respData)
}

func (h *UserHandler) Casdoor(ctx *gin.Context) {
code := ctx.Query("code")
state := ctx.Query("state")
Expand Down
2 changes: 2 additions & 0 deletions user/router/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ func NewRouter(config *config.Config) (*gin.Engine, error) {
// userGroup.DELETE("/:username", userMatch, userHandler.Delete)
// get user's all tokens
userGroup.GET("/:username/tokens", userMatch, acHandler.GetUserTokens)
// get user list
apiV1Group.GET("/users", mustLogin(), userHandler.Index)

}
// routers for organizations
Expand Down