Skip to content

Commit

Permalink
Add admin api/v1/users api
Browse files Browse the repository at this point in the history
  • Loading branch information
泽华 authored and Rader committed Oct 16, 2024
1 parent 25ebc25 commit 297a5e1
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 1 deletion.
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

0 comments on commit 297a5e1

Please sign in to comment.