feat(admin): allow admin resend activation email

This commit is contained in:
LinkinStars 2023-07-10 18:00:16 +08:00
parent 69f250ee04
commit 3283e09c9a
9 changed files with 396 additions and 10 deletions

View File

@ -188,7 +188,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
reportAdminService := report_admin.NewReportAdminService(reportRepo, userCommon, answerRepo, questionRepo, commentCommonRepo, reportHandle, configService, objService)
controller_adminReportController := controller_admin.NewReportController(reportAdminService)
userAdminRepo := user.NewUserAdminRepo(dataData, authRepo)
userAdminService := user_admin.NewUserAdminService(userAdminRepo, userRoleRelService, authService, userCommon, userActiveActivityRepo, siteInfoCommonService)
userAdminService := user_admin.NewUserAdminService(userAdminRepo, userRoleRelService, authService, userCommon, userActiveActivityRepo, siteInfoCommonService, emailService)
userAdminController := controller_admin.NewUserAdminController(userAdminService)
reasonRepo := reason.NewReasonRepo(configService)
reasonService := reason2.NewReasonService(reasonRepo)

View File

@ -1695,6 +1695,86 @@ const docTemplate = `{
}
}
},
"/answer/admin/api/users/activation": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "get user activation",
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "get user activation",
"parameters": [
{
"type": "string",
"description": "user id",
"name": "user_id",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/handler.RespBody"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/schema.GetUserActivationResp"
}
}
}
]
}
}
}
},
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "send user activation",
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "send user activation",
"parameters": [
{
"description": "SendUserActivationReq",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/schema.SendUserActivationReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.RespBody"
}
}
}
}
},
"/answer/admin/api/users/page": {
"get": {
"security": [
@ -3444,7 +3524,7 @@ const docTemplate = `{
"ApiKeyAuth": []
}
],
"description": "user's vote",
"description": "get user personal votes",
"consumes": [
"application/json"
],
@ -3454,7 +3534,7 @@ const docTemplate = `{
"tags": [
"Activity"
],
"summary": "user's votes",
"summary": "get user personal votes",
"parameters": [
{
"type": "integer",
@ -6345,7 +6425,8 @@ const docTemplate = `{
"properties": {
"display_name": {
"type": "string",
"maxLength": 30
"maxLength": 30,
"minLength": 4
},
"email": {
"type": "string",
@ -7397,6 +7478,14 @@ const docTemplate = `{
}
}
},
"schema.GetUserActivationResp": {
"type": "object",
"properties": {
"activation_url": {
"type": "string"
}
}
},
"schema.GetUserPageResp": {
"type": "object",
"properties": {
@ -8012,6 +8101,17 @@ const docTemplate = `{
}
}
},
"schema.SendUserActivationReq": {
"type": "object",
"required": [
"user_id"
],
"properties": {
"user_id": {
"type": "string"
}
}
},
"schema.SiteBrandingReq": {
"type": "object",
"properties": {

View File

@ -1683,6 +1683,86 @@
}
}
},
"/answer/admin/api/users/activation": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "get user activation",
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "get user activation",
"parameters": [
{
"type": "string",
"description": "user id",
"name": "user_id",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/handler.RespBody"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/schema.GetUserActivationResp"
}
}
}
]
}
}
}
},
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "send user activation",
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "send user activation",
"parameters": [
{
"description": "SendUserActivationReq",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/schema.SendUserActivationReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.RespBody"
}
}
}
}
},
"/answer/admin/api/users/page": {
"get": {
"security": [
@ -3432,7 +3512,7 @@
"ApiKeyAuth": []
}
],
"description": "user's vote",
"description": "get user personal votes",
"consumes": [
"application/json"
],
@ -3442,7 +3522,7 @@
"tags": [
"Activity"
],
"summary": "user's votes",
"summary": "get user personal votes",
"parameters": [
{
"type": "integer",
@ -6333,7 +6413,8 @@
"properties": {
"display_name": {
"type": "string",
"maxLength": 30
"maxLength": 30,
"minLength": 4
},
"email": {
"type": "string",
@ -7385,6 +7466,14 @@
}
}
},
"schema.GetUserActivationResp": {
"type": "object",
"properties": {
"activation_url": {
"type": "string"
}
}
},
"schema.GetUserPageResp": {
"type": "object",
"properties": {
@ -8000,6 +8089,17 @@
}
}
},
"schema.SendUserActivationReq": {
"type": "object",
"required": [
"user_id"
],
"properties": {
"user_id": {
"type": "string"
}
}
},
"schema.SiteBrandingReq": {
"type": "object",
"properties": {

View File

@ -206,6 +206,7 @@ definitions:
properties:
display_name:
maxLength: 30
minLength: 4
type: string
email:
maxLength: 500
@ -952,6 +953,11 @@ definitions:
unreviewed_info:
$ref: '#/definitions/schema.GetRevisionResp'
type: object
schema.GetUserActivationResp:
properties:
activation_url:
type: string
type: object
schema.GetUserPageResp:
properties:
avatar:
@ -1382,6 +1388,13 @@ definitions:
description: object_type
type: string
type: object
schema.SendUserActivationReq:
properties:
user_id:
type: string
required:
- user_id
type: object
schema.SiteBrandingReq:
properties:
favicon:
@ -3291,6 +3304,53 @@ paths:
summary: update user
tags:
- admin
/answer/admin/api/users/activation:
get:
description: get user activation
parameters:
- description: user id
in: query
name: user_id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/handler.RespBody'
- properties:
data:
$ref: '#/definitions/schema.GetUserActivationResp'
type: object
security:
- ApiKeyAuth: []
summary: get user activation
tags:
- admin
post:
description: send user activation
parameters:
- description: SendUserActivationReq
in: body
name: data
required: true
schema:
$ref: '#/definitions/schema.SendUserActivationReq'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.RespBody'
security:
- ApiKeyAuth: []
summary: send user activation
tags:
- admin
/answer/admin/api/users/page:
get:
description: get user page
@ -4364,7 +4424,7 @@ paths:
get:
consumes:
- application/json
description: user's vote
description: get user personal votes
parameters:
- description: page size
in: query
@ -4395,7 +4455,7 @@ paths:
type: object
security:
- ApiKeyAuth: []
summary: user's votes
summary: get user personal votes
tags:
- Activity
/answer/api/v1/post/render:

View File

@ -135,3 +135,41 @@ func (uc *UserAdminController) GetUserPage(ctx *gin.Context) {
resp, err := uc.userService.GetUserPage(ctx, req)
handler.HandleResponse(ctx, err, resp)
}
// GetUserActivation get user activation
// @Summary get user activation
// @Description get user activation
// @Security ApiKeyAuth
// @Tags admin
// @Produce json
// @Param user_id query string true "user id"
// @Success 200 {object} handler.RespBody{data=schema.GetUserActivationResp}
// @Router /answer/admin/api/users/activation [get]
func (uc *UserAdminController) GetUserActivation(ctx *gin.Context) {
req := &schema.GetUserActivationReq{}
if handler.BindAndCheck(ctx, req) {
return
}
resp, err := uc.userService.GetUserActivation(ctx, req)
handler.HandleResponse(ctx, err, resp)
}
// SendUserActivation send user activation
// @Summary send user activation
// @Description send user activation
// @Security ApiKeyAuth
// @Tags admin
// @Produce json
// @Param data body schema.SendUserActivationReq true "SendUserActivationReq"
// @Success 200 {object} handler.RespBody
// @Router /answer/admin/api/users/activation [post]
func (uc *UserAdminController) SendUserActivation(ctx *gin.Context) {
req := &schema.SendUserActivationReq{}
if handler.BindAndCheck(ctx, req) {
return
}
err := uc.userService.SendUserActivation(ctx, req)
handler.HandleResponse(ctx, err, nil)
}

View File

@ -257,6 +257,8 @@ func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) {
r.GET("/users/page", a.adminUserController.GetUserPage)
r.PUT("/user/status", a.adminUserController.UpdateUserStatus)
r.PUT("/user/role", a.adminUserController.UpdateUserRole)
r.GET("/user/activation", a.adminUserController.GetUserActivation)
r.POST("/user/activation", a.adminUserController.SendUserActivation)
r.POST("/user", a.adminUserController.AddUser)
r.PUT("/user/password", a.adminUserController.UpdateUserPassword)

View File

@ -86,7 +86,7 @@ type UpdateUserRoleReq struct {
// AddUserReq add user request
type AddUserReq struct {
DisplayName string `validate:"required,gt=4,lte=30" json:"display_name"`
DisplayName string `validate:"required,gte=4,lte=30" json:"display_name"`
Email string `validate:"required,email,gt=0,lte=500" json:"email"`
Password string `validate:"required,gte=8,lte=32" json:"password"`
LoginUserID string `json:"-"`
@ -98,3 +98,18 @@ type UpdateUserPasswordReq struct {
Password string `validate:"required,gte=8,lte=32" json:"password"`
LoginUserID string `json:"-"`
}
// GetUserActivationReq get user activation
type GetUserActivationReq struct {
UserID string `validate:"required" form:"user_id"`
}
// GetUserActivationResp get user activation
type GetUserActivationResp struct {
ActivationURL string `json:"activation_url"`
}
// SendUserActivationReq send user activation
type SendUserActivationReq struct {
UserID string `validate:"required" json:"user_id"`
}

View File

@ -79,6 +79,14 @@ type TestTemplateData struct {
SiteName string
}
// SaveCode save code
func (es *EmailService) SaveCode(ctx context.Context, code, codeContent string) {
err := es.emailRepo.SetCode(ctx, code, codeContent, 10*time.Minute)
if err != nil {
log.Error(err)
}
}
// SendAndSaveCode send email and save code
func (es *EmailService) SendAndSaveCode(ctx context.Context, toEmailAddr, subject, body, code, codeContent string) {
es.Send(ctx, toEmailAddr, subject, body)

View File

@ -3,6 +3,8 @@ package user_admin
import (
"context"
"fmt"
"github.com/answerdev/answer/internal/service/export"
"github.com/google/uuid"
"net/mail"
"strings"
"time"
@ -42,6 +44,7 @@ type UserAdminService struct {
userCommonService *usercommon.UserCommon
userActivity activity.UserActiveActivityRepo
siteInfoCommonService siteinfo_common.SiteInfoCommonService
emailService *export.EmailService
}
// NewUserAdminService new user admin service
@ -52,6 +55,7 @@ func NewUserAdminService(
userCommonService *usercommon.UserCommon,
userActivity activity.UserActiveActivityRepo,
siteInfoCommonService siteinfo_common.SiteInfoCommonService,
emailService *export.EmailService,
) *UserAdminService {
return &UserAdminService{
userRepo: userRepo,
@ -60,6 +64,7 @@ func NewUserAdminService(
userCommonService: userCommonService,
userActivity: userActivity,
siteInfoCommonService: siteInfoCommonService,
emailService: emailService,
}
}
@ -293,3 +298,61 @@ func (us *UserAdminService) setUserRoleInfo(ctx context.Context, resp []*schema.
u.RoleName = r.Name
}
}
func (us *UserAdminService) GetUserActivation(ctx context.Context, req *schema.GetUserActivationReq) (
resp *schema.GetUserActivationResp, err error) {
user, exist, err := us.userRepo.GetUserInfo(ctx, req.UserID)
if err != nil {
return nil, err
}
if !exist {
return nil, errors.BadRequest(reason.UserNotFound)
}
general, err := us.siteInfoCommonService.GetSiteGeneral(ctx)
if err != nil {
return nil, err
}
data := &schema.EmailCodeContent{
Email: user.EMail,
UserID: user.ID,
}
code := uuid.NewString()
us.emailService.SaveCode(ctx, code, data.ToJSONString())
resp = &schema.GetUserActivationResp{
ActivationURL: fmt.Sprintf("%s/users/account-activation?code=%s", general.SiteUrl, code),
}
return resp, nil
}
// SendUserActivation send user activation email
func (us *UserAdminService) SendUserActivation(ctx context.Context, req *schema.SendUserActivationReq) (err error) {
user, exist, err := us.userRepo.GetUserInfo(ctx, req.UserID)
if err != nil {
return err
}
if !exist {
return errors.BadRequest(reason.UserNotFound)
}
general, err := us.siteInfoCommonService.GetSiteGeneral(ctx)
if err != nil {
return err
}
data := &schema.EmailCodeContent{
Email: user.EMail,
UserID: user.ID,
}
code := uuid.NewString()
us.emailService.SaveCode(ctx, code, data.ToJSONString())
verifyEmailURL := fmt.Sprintf("%s/users/account-activation?code=%s", general.SiteUrl, code)
title, body, err := us.emailService.RegisterTemplate(ctx, verifyEmailURL)
if err != nil {
return err
}
go us.emailService.SendAndSaveCode(ctx, user.EMail, title, body, code, data.ToJSONString())
return nil
}