Merge remote-tracking branch 'origin/dev' into test
This commit is contained in:
+199
-2
@@ -17,8 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
// Code generated by swaggo/swag. DO NOT EDIT.
|
||||
|
||||
// Package docs Code generated by swaggo/swag. DO NOT EDIT
|
||||
package docs
|
||||
|
||||
import "github.com/swaggo/swag"
|
||||
@@ -1573,6 +1572,45 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/user/profile": {
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "edit user profile",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "edit user profile",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "user",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.EditUserProfileReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/user/role": {
|
||||
"put": {
|
||||
"security": [
|
||||
@@ -2908,6 +2946,92 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/meta/reaction": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "get reaction for an object",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Meta"
|
||||
],
|
||||
"summary": "get reaction",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "object_id",
|
||||
"name": "object_id",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/schema.ReactionResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "update reaction. if not exist, add one",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Meta"
|
||||
],
|
||||
"summary": "add or update reaction",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "reaction",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.UpdateReactionReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/notification/page": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -7355,6 +7479,26 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.EditUserProfileReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"email",
|
||||
"user_id"
|
||||
],
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"maxLength": 500
|
||||
},
|
||||
"user_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string",
|
||||
"maxLength": 30
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.ExternalLoginBindingUserSendEmailReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -8891,6 +9035,31 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.ReactionItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"is_active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"tooltip": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.ReactionResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"reaction_summary": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/schema.ReactionItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.ReasonItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -9952,6 +10121,34 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.UpdateReactionReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"emoji",
|
||||
"object_id",
|
||||
"reaction"
|
||||
],
|
||||
"properties": {
|
||||
"emoji": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"heart",
|
||||
"smile",
|
||||
"frown"
|
||||
]
|
||||
},
|
||||
"object_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"reaction": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"activate",
|
||||
"deactivate"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.UpdateReviewReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
@@ -1542,6 +1542,45 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/user/profile": {
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "edit user profile",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "edit user profile",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "user",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.EditUserProfileReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/user/role": {
|
||||
"put": {
|
||||
"security": [
|
||||
@@ -2877,6 +2916,92 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/meta/reaction": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "get reaction for an object",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Meta"
|
||||
],
|
||||
"summary": "get reaction",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "object_id",
|
||||
"name": "object_id",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/schema.ReactionResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "update reaction. if not exist, add one",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Meta"
|
||||
],
|
||||
"summary": "add or update reaction",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "reaction",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.UpdateReactionReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/notification/page": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -7324,6 +7449,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.EditUserProfileReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"email",
|
||||
"user_id"
|
||||
],
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"maxLength": 500
|
||||
},
|
||||
"user_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string",
|
||||
"maxLength": 30
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.ExternalLoginBindingUserSendEmailReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -8860,6 +9005,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.ReactionItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"is_active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"tooltip": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.ReactionResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"reaction_summary": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/schema.ReactionItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.ReasonItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -9921,6 +10091,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.UpdateReactionReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"emoji",
|
||||
"object_id",
|
||||
"reaction"
|
||||
],
|
||||
"properties": {
|
||||
"emoji": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"heart",
|
||||
"smile",
|
||||
"frown"
|
||||
]
|
||||
},
|
||||
"object_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"reaction": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"activate",
|
||||
"deactivate"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.UpdateReviewReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
@@ -425,6 +425,20 @@ definitions:
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
schema.EditUserProfileReq:
|
||||
properties:
|
||||
email:
|
||||
maxLength: 500
|
||||
type: string
|
||||
user_id:
|
||||
type: string
|
||||
username:
|
||||
maxLength: 30
|
||||
type: string
|
||||
required:
|
||||
- email
|
||||
- user_id
|
||||
type: object
|
||||
schema.ExternalLoginBindingUserSendEmailReq:
|
||||
properties:
|
||||
binding_key:
|
||||
@@ -1506,6 +1520,22 @@ definitions:
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
schema.ReactionItem:
|
||||
properties:
|
||||
count:
|
||||
type: integer
|
||||
is_active:
|
||||
type: boolean
|
||||
tooltip:
|
||||
type: string
|
||||
type: object
|
||||
schema.ReactionResp:
|
||||
properties:
|
||||
reaction_summary:
|
||||
additionalProperties:
|
||||
$ref: '#/definitions/schema.ReactionItem'
|
||||
type: object
|
||||
type: object
|
||||
schema.ReasonItem:
|
||||
properties:
|
||||
content_type:
|
||||
@@ -2227,6 +2257,26 @@ definitions:
|
||||
required:
|
||||
- level
|
||||
type: object
|
||||
schema.UpdateReactionReq:
|
||||
properties:
|
||||
emoji:
|
||||
enum:
|
||||
- heart
|
||||
- smile
|
||||
- frown
|
||||
type: string
|
||||
object_id:
|
||||
type: string
|
||||
reaction:
|
||||
enum:
|
||||
- activate
|
||||
- deactivate
|
||||
type: string
|
||||
required:
|
||||
- emoji
|
||||
- object_id
|
||||
- reaction
|
||||
type: object
|
||||
schema.UpdateReviewReq:
|
||||
properties:
|
||||
review_id:
|
||||
@@ -3581,6 +3631,30 @@ paths:
|
||||
summary: update user password
|
||||
tags:
|
||||
- admin
|
||||
/answer/admin/api/user/profile:
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: edit user profile
|
||||
parameters:
|
||||
- description: user
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/schema.EditUserProfileReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.RespBody'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: edit user profile
|
||||
tags:
|
||||
- admin
|
||||
/answer/admin/api/user/role:
|
||||
put:
|
||||
consumes:
|
||||
@@ -4384,6 +4458,57 @@ paths:
|
||||
summary: Get language options
|
||||
tags:
|
||||
- Lang
|
||||
/answer/api/v1/meta/reaction:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: get reaction for an object
|
||||
parameters:
|
||||
- description: object_id
|
||||
in: query
|
||||
name: object_id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/handler.RespBody'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/schema.ReactionResp'
|
||||
type: object
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: get reaction
|
||||
tags:
|
||||
- Meta
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: update reaction. if not exist, add one
|
||||
parameters:
|
||||
- description: reaction
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/schema.UpdateReactionReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.RespBody'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: add or update reaction
|
||||
tags:
|
||||
- Meta
|
||||
/answer/api/v1/notification/page:
|
||||
get:
|
||||
consumes:
|
||||
|
||||
@@ -152,6 +152,8 @@ backend:
|
||||
admin:
|
||||
cannot_update_their_password:
|
||||
other: You cannot modify your password.
|
||||
cannot_edit_their_profile:
|
||||
other: You cannot modify your profile.
|
||||
cannot_modify_self_status:
|
||||
other: You cannot modify your status.
|
||||
email_or_password_wrong:
|
||||
@@ -1535,6 +1537,19 @@ ui:
|
||||
msg: Password must be at 8-32 characters in length.
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
edit_profile_modal:
|
||||
title: Edit profile
|
||||
form:
|
||||
fields:
|
||||
username:
|
||||
label: Username
|
||||
msg_range: Username up to 30 characters.
|
||||
email:
|
||||
label: Email
|
||||
msg_invalid: Invalid Email Address.
|
||||
edit_success: Edited successfully
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
user_modal:
|
||||
title: Add new user
|
||||
form:
|
||||
@@ -1580,6 +1595,7 @@ ui:
|
||||
filter:
|
||||
placeholder: "Filter by name, user:id"
|
||||
set_new_password: Set new password
|
||||
edit_profile: Edit profile
|
||||
change_status: Change status
|
||||
change_role: Change role
|
||||
show_logs: Show logs
|
||||
|
||||
@@ -98,6 +98,7 @@ const (
|
||||
NotAllowedLoginViaPassword = "error.user.not_allowed_login_via_password"
|
||||
SMTPConfigFromNameCannotBeEmail = "error.smtp.config_from_name_cannot_be_email"
|
||||
AdminCannotUpdateTheirPassword = "error.admin.cannot_update_their_password"
|
||||
AdminCannotEditTheirProfile = "error.admin.cannot_edit_their_profile"
|
||||
AdminCannotModifySelfStatus = "error.admin.cannot_modify_self_status"
|
||||
UserAccessDenied = "error.user.access_denied"
|
||||
UserPageAccessDenied = "error.user.page_access_denied"
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/apache/incubator-answer/internal/base/handler"
|
||||
"github.com/apache/incubator-answer/internal/base/middleware"
|
||||
"github.com/apache/incubator-answer/internal/base/reason"
|
||||
"github.com/apache/incubator-answer/internal/base/translator"
|
||||
"github.com/apache/incubator-answer/internal/schema"
|
||||
"github.com/apache/incubator-answer/internal/service/user_admin"
|
||||
"github.com/apache/incubator-answer/plugin"
|
||||
@@ -152,6 +153,35 @@ func (uc *UserAdminController) UpdateUserPassword(ctx *gin.Context) {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
// EditUserProfile edit user profile
|
||||
// @Summary edit user profile
|
||||
// @Description edit user profile
|
||||
// @Security ApiKeyAuth
|
||||
// @Tags admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param data body schema.EditUserProfileReq true "user"
|
||||
// @Success 200 {object} handler.RespBody
|
||||
// @Router /answer/admin/api/user/profile [put]
|
||||
func (uc *UserAdminController) EditUserProfile(ctx *gin.Context) {
|
||||
req := &schema.EditUserProfileReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.IsAdmin = middleware.GetUserIsAdminModerator(ctx)
|
||||
if !req.IsAdmin {
|
||||
handler.HandleResponse(ctx, errors.Forbidden(reason.ForbiddenError), nil)
|
||||
return
|
||||
}
|
||||
|
||||
errFields, err := uc.userService.EditUserProfile(ctx, req)
|
||||
for _, field := range errFields {
|
||||
field.ErrorMsg = translator.Tr(handler.GetLang(ctx), field.ErrorMsg)
|
||||
}
|
||||
handler.HandleResponse(ctx, err, errFields)
|
||||
}
|
||||
|
||||
// GetUserPage get user page
|
||||
// @Summary get user page
|
||||
// @Description get user page
|
||||
|
||||
@@ -375,6 +375,10 @@ func (qr *questionRepo) GetQuestionPage(ctx context.Context, page, pageSize int,
|
||||
case "newest":
|
||||
session.OrderBy("question.pin desc,question.created_at DESC")
|
||||
case "active":
|
||||
if inDays == 0 {
|
||||
session.And("question.created_at > ?", time.Now().AddDate(0, 0, -180))
|
||||
}
|
||||
session.And("question.post_update_time > ?", time.Now().AddDate(0, 0, -90))
|
||||
session.OrderBy("question.pin desc,question.post_update_time DESC, question.updated_at DESC")
|
||||
case "frequent":
|
||||
session.OrderBy("question.pin desc,question.view_count DESC")
|
||||
|
||||
@@ -174,6 +174,16 @@ func (ur *userRepo) UpdateInfo(ctx context.Context, userInfo *entity.User) (err
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateUserProfile update user profile
|
||||
func (ur *userRepo) UpdateUserProfile(ctx context.Context, userInfo *entity.User) (err error) {
|
||||
_, err = ur.data.DB.Context(ctx).Where("id = ?", userInfo.ID).
|
||||
Cols("username", "e_mail", "mail_status").Update(userInfo)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetByUserID get user info by user id
|
||||
func (ur *userRepo) GetByUserID(ctx context.Context, userID string) (userInfo *entity.User, exist bool, err error) {
|
||||
userInfo = &entity.User{}
|
||||
|
||||
@@ -311,6 +311,7 @@ func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) {
|
||||
r.POST("/user", a.adminUserController.AddUser)
|
||||
r.POST("/users", a.adminUserController.AddUsers)
|
||||
r.PUT("/user/password", a.adminUserController.UpdateUserPassword)
|
||||
r.PUT("/user/profile", a.adminUserController.EditUserProfile)
|
||||
|
||||
// reason
|
||||
r.GET("/reasons", a.reasonController.Reasons)
|
||||
|
||||
@@ -108,6 +108,15 @@ type UpdateUserRoleReq struct {
|
||||
LoginUserID string `json:"-"`
|
||||
}
|
||||
|
||||
// EditUserProfileReq edit user profile request
|
||||
type EditUserProfileReq struct {
|
||||
UserID string `validate:"required" json:"user_id"`
|
||||
Username string `validate:"omitempty,gt=3,lte=30" json:"username"`
|
||||
Email string `validate:"required,email,gt=0,lte=500" json:"email"`
|
||||
LoginUserID string `json:"-"`
|
||||
IsAdmin bool `json:"-"`
|
||||
}
|
||||
|
||||
// AddUserReq add user request
|
||||
type AddUserReq struct {
|
||||
DisplayName string `validate:"required,gte=4,lte=30" json:"display_name"`
|
||||
|
||||
@@ -45,6 +45,7 @@ import (
|
||||
"github.com/apache/incubator-answer/internal/service/role"
|
||||
"github.com/apache/incubator-answer/internal/service/siteinfo_common"
|
||||
usercommon "github.com/apache/incubator-answer/internal/service/user_common"
|
||||
"github.com/apache/incubator-answer/pkg/checker"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
@@ -345,6 +346,61 @@ func (us *UserAdminService) UpdateUserPassword(ctx context.Context, req *schema.
|
||||
return
|
||||
}
|
||||
|
||||
// EditUserProfile edit user profile
|
||||
func (us *UserAdminService) EditUserProfile(ctx context.Context, req *schema.EditUserProfileReq) (
|
||||
errFields []*validator.FormErrorField, err error) {
|
||||
if req.UserID == req.LoginUserID {
|
||||
return nil, errors.BadRequest(reason.AdminCannotEditTheirProfile)
|
||||
}
|
||||
userInfo, exist, err := us.userRepo.GetUserInfo(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exist {
|
||||
return nil, errors.BadRequest(reason.UserNotFound)
|
||||
}
|
||||
|
||||
if checker.IsInvalidUsername(req.Username) || checker.IsUsersIgnorePath(req.Username) {
|
||||
return append(errFields, &validator.FormErrorField{
|
||||
ErrorField: "username",
|
||||
ErrorMsg: reason.UsernameInvalid,
|
||||
}), errors.BadRequest(reason.UsernameInvalid)
|
||||
}
|
||||
|
||||
userInfo, exist, err = us.userCommonService.GetByUsername(ctx, req.Username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exist && userInfo.ID != req.UserID {
|
||||
return append(errFields, &validator.FormErrorField{
|
||||
ErrorField: "username",
|
||||
ErrorMsg: reason.UsernameDuplicate,
|
||||
}), errors.BadRequest(reason.UsernameDuplicate)
|
||||
}
|
||||
|
||||
userInfo, exist, err = us.userCommonService.GetByEmail(ctx, req.Email)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exist && userInfo.ID != req.UserID {
|
||||
return append(errFields, &validator.FormErrorField{
|
||||
ErrorField: "email",
|
||||
ErrorMsg: reason.EmailDuplicate,
|
||||
}), errors.BadRequest(reason.EmailDuplicate)
|
||||
}
|
||||
|
||||
user := &entity.User{}
|
||||
user.ID = req.UserID
|
||||
user.Username = req.Username
|
||||
user.EMail = req.Email
|
||||
user.MailStatus = entity.EmailStatusAvailable
|
||||
err = us.userCommonService.UpdateUserProfile(ctx, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetUserInfo get user one
|
||||
func (us *UserAdminService) GetUserInfo(ctx context.Context, userID string) (resp *schema.GetUserInfoResp, err error) {
|
||||
user, exist, err := us.userRepo.GetUserInfo(ctx, userID)
|
||||
|
||||
@@ -52,6 +52,7 @@ type UserRepo interface {
|
||||
UpdateUserInterface(ctx context.Context, userID, language, colorSchema string) (err error)
|
||||
UpdatePass(ctx context.Context, userID, pass string) error
|
||||
UpdateInfo(ctx context.Context, userInfo *entity.User) (err error)
|
||||
UpdateUserProfile(ctx context.Context, userInfo *entity.User) (err error)
|
||||
GetByUserID(ctx context.Context, userID string) (userInfo *entity.User, exist bool, err error)
|
||||
BatchGetByID(ctx context.Context, ids []string) ([]*entity.User, error)
|
||||
GetByUsername(ctx context.Context, username string) (userInfo *entity.User, exist bool, err error)
|
||||
@@ -119,6 +120,18 @@ func (us *UserCommon) BatchGetUserBasicInfoByUserNames(ctx context.Context, user
|
||||
return infomap, nil
|
||||
}
|
||||
|
||||
func (us *UserCommon) GetByEmail(ctx context.Context, email string) (userInfo *entity.User, exist bool, err error) {
|
||||
return us.userRepo.GetByEmail(ctx, email)
|
||||
}
|
||||
|
||||
func (us *UserCommon) GetByUsername(ctx context.Context, username string) (userInfo *entity.User, exist bool, err error) {
|
||||
return us.userRepo.GetByUsername(ctx, username)
|
||||
}
|
||||
|
||||
func (us *UserCommon) UpdateUserProfile(ctx context.Context, userInfo *entity.User) (err error) {
|
||||
return us.userRepo.UpdateUserProfile(ctx, userInfo)
|
||||
}
|
||||
|
||||
func (us *UserCommon) UpdateAnswerCount(ctx context.Context, userID string, num int) error {
|
||||
return us.userRepo.UpdateAnswerCount(ctx, userID, num)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import usePageUsers from './usePageUsers';
|
||||
import useChangeUserRoleModal from './useChangeUserRoleModal';
|
||||
import useUserModal from './useUserModal';
|
||||
import useChangePasswordModal from './useChangePasswordModal';
|
||||
import useChangeProfileModal from './useChangeProfileModal';
|
||||
import usePageTags from './usePageTags';
|
||||
import useLoginRedirect from './useLoginRedirect';
|
||||
import usePromptWithUnload from './usePrompt';
|
||||
@@ -39,6 +40,7 @@ export {
|
||||
useChangeUserRoleModal,
|
||||
useUserModal,
|
||||
useChangePasswordModal,
|
||||
useChangeProfileModal,
|
||||
usePageTags,
|
||||
useLoginRedirect,
|
||||
usePromptWithUnload,
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { useLayoutEffect, useState, useRef } from 'react';
|
||||
import { Modal, Button } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ReactDOM from 'react-dom/client';
|
||||
|
||||
import type * as Type from '@/common/interface';
|
||||
import { SchemaForm, JSONSchema, UISchema, initFormData } from '@/components';
|
||||
import { handleFormError } from '@/utils';
|
||||
import pattern from '@/common/pattern';
|
||||
|
||||
const div = document.createElement('div');
|
||||
const root = ReactDOM.createRoot(div);
|
||||
|
||||
interface IProps {
|
||||
title?: string;
|
||||
onConfirm?: (formData: any) => Promise<any>;
|
||||
}
|
||||
const useChangeProfileModal = (props: IProps = {}, userData) => {
|
||||
const { t } = useTranslation('translation', {
|
||||
keyPrefix: 'admin.edit_profile_modal',
|
||||
});
|
||||
|
||||
const { title = t('title'), onConfirm } = props;
|
||||
const [visible, setVisibleState] = useState(false);
|
||||
const [userId, setUserId] = useState('');
|
||||
const schema: JSONSchema = {
|
||||
title: t('title'),
|
||||
required: ['username', 'email'],
|
||||
properties: {
|
||||
username: {
|
||||
type: 'string',
|
||||
title: t('form.fields.username.label'),
|
||||
default: userData.username,
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
title: t('form.fields.email.label'),
|
||||
default: userData.e_mail,
|
||||
},
|
||||
},
|
||||
};
|
||||
const uiSchema: UISchema = {
|
||||
username: {
|
||||
'ui:options': {
|
||||
inputType: 'text',
|
||||
validator: (value) => {
|
||||
const MIN_LENGTH = 3;
|
||||
const MAX_LENGTH = 30;
|
||||
if (value.length < MIN_LENGTH || value.length > MAX_LENGTH) {
|
||||
return t('form.fields.username.msg_range');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
},
|
||||
email: {
|
||||
'ui:options': {
|
||||
inputType: 'email',
|
||||
validator: (value) => {
|
||||
if (value && !pattern.email.test(value)) {
|
||||
return t('form.fields.email.msg_invalid');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const [formData, setFormData] = useState<Type.FormDataType>(
|
||||
initFormData(schema),
|
||||
);
|
||||
|
||||
const formRef = useRef<{
|
||||
validator: () => Promise<boolean>;
|
||||
}>(null);
|
||||
|
||||
const onClose = () => {
|
||||
setVisibleState(false);
|
||||
};
|
||||
|
||||
const onShow = (user_id: string) => {
|
||||
setUserId(user_id);
|
||||
setVisibleState(true);
|
||||
};
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const isValid = await formRef.current?.validator();
|
||||
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (onConfirm instanceof Function) {
|
||||
onConfirm({
|
||||
username: formData.username.value,
|
||||
email: formData.email.value,
|
||||
user_id: userId,
|
||||
})
|
||||
.then(() => {
|
||||
setUserId('');
|
||||
onClose();
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.isError) {
|
||||
const data = handleFormError(err, formData);
|
||||
setFormData({ ...data });
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleOnChange = (data) => {
|
||||
setFormData(data);
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
root.render(
|
||||
<Modal show={visible} title={title} onHide={onClose}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title as="h5">{title}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<SchemaForm
|
||||
ref={formRef}
|
||||
schema={schema}
|
||||
uiSchema={uiSchema}
|
||||
formData={formData}
|
||||
onChange={handleOnChange}
|
||||
hiddenSubmit
|
||||
/>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="link" onClick={() => onClose()}>
|
||||
{t('btn_cancel')}
|
||||
</Button>
|
||||
<Button variant="primary" onClick={handleSubmit}>
|
||||
{t('btn_submit')}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>,
|
||||
);
|
||||
});
|
||||
return {
|
||||
onClose,
|
||||
onShow,
|
||||
};
|
||||
};
|
||||
|
||||
export default useChangeProfileModal;
|
||||
@@ -23,11 +23,16 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Modal, Icon } from '@/components';
|
||||
import {
|
||||
useChangeUserRoleModal,
|
||||
useChangeProfileModal,
|
||||
useChangePasswordModal,
|
||||
useActivationEmailModal,
|
||||
useToast,
|
||||
} from '@/hooks';
|
||||
import { updateUserPassword, changeUserStatus } from '@/services';
|
||||
import {
|
||||
updateUserPassword,
|
||||
changeUserStatus,
|
||||
updateUserProfile,
|
||||
} from '@/services';
|
||||
|
||||
interface Props {
|
||||
showActionPassword?: boolean;
|
||||
@@ -71,6 +76,29 @@ const UserOperation = ({
|
||||
});
|
||||
},
|
||||
});
|
||||
const changeProfileModal = useChangeProfileModal(
|
||||
{
|
||||
onConfirm: (rd) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
updateUserProfile(rd)
|
||||
.then(() => {
|
||||
Toast.onShow({
|
||||
msg: t('edit_success', {
|
||||
keyPrefix: 'admin.edit_profile_modal',
|
||||
}),
|
||||
variant: 'success',
|
||||
});
|
||||
resolve(true);
|
||||
refreshUsers?.();
|
||||
})
|
||||
.catch((e) => {
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
userData,
|
||||
);
|
||||
|
||||
const activationEmailModal = useActivationEmailModal();
|
||||
|
||||
@@ -105,6 +133,10 @@ const UserOperation = ({
|
||||
changePasswordModal.onShow(user_id);
|
||||
}
|
||||
|
||||
if (type === 'profile') {
|
||||
changeProfileModal.onShow(user_id);
|
||||
}
|
||||
|
||||
if (type === 'activation') {
|
||||
activationEmailModal.onShow(user_id);
|
||||
}
|
||||
@@ -166,6 +198,9 @@ const UserOperation = ({
|
||||
{t('set_new_password')}
|
||||
</Dropdown.Item>
|
||||
) : null}
|
||||
<Dropdown.Item onClick={() => handleAction('profile')}>
|
||||
{t('edit_profile')}
|
||||
</Dropdown.Item>
|
||||
{showActionRole ? (
|
||||
<Dropdown.Item onClick={() => handleAction('role')}>
|
||||
{t('change_role')}
|
||||
|
||||
@@ -68,6 +68,14 @@ export const updateUserPassword = (params: {
|
||||
return request.put('/answer/admin/api/user/password', params);
|
||||
};
|
||||
|
||||
export const updateUserProfile = (params: {
|
||||
username: string;
|
||||
email: string;
|
||||
user_id: string;
|
||||
}) => {
|
||||
return request.put('/answer/admin/api/user/profile', params);
|
||||
};
|
||||
|
||||
export const getUserActivation = (userId: string) => {
|
||||
const apiUrl = `/answer/admin/api/user/activation`;
|
||||
return request.get<{
|
||||
|
||||
Reference in New Issue
Block a user