mirror of https://gitee.com/answerdev/answer.git
Merge branch 'feat/1.1.0/report' into test
# Conflicts: # internal/migrations/v13.go
This commit is contained in:
commit
8c395bc306
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"github.copilot"
|
||||
]
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"eslint.workingDirectories": [
|
||||
"ui"
|
||||
],
|
||||
"commentTranslate.multiLineMerge": true
|
||||
]
|
||||
}
|
||||
|
|
|
@ -170,7 +170,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
|||
answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo)
|
||||
questionActivityRepo := activity.NewQuestionActivityRepo(dataData, activityRepo, userRankRepo)
|
||||
answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, questionActivityRepo)
|
||||
questionService := service.NewQuestionService(questionRepo, tagCommonService, questionCommon, userCommon, revisionService, metaService, collectionCommon, answerActivityService, dataData)
|
||||
questionService := service.NewQuestionService(questionRepo, tagCommonService, questionCommon, userCommon, userRepo, revisionService, metaService, collectionCommon, answerActivityService, dataData, emailService)
|
||||
answerService := service.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService)
|
||||
questionController := controller.NewQuestionController(questionService, answerService, rankService)
|
||||
dashboardService := dashboard.NewDashboardService(questionRepo, answerRepo, commentCommonRepo, voteRepo, userRepo, reportRepo, configRepo, siteInfoCommonService, serviceConf, dataData)
|
||||
|
|
166
docs/docs.go
166
docs/docs.go
|
@ -2841,6 +2841,19 @@ const docTemplate = `{
|
|||
"name": "type",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"all",
|
||||
"posts",
|
||||
"invites",
|
||||
"votes"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "inbox_type",
|
||||
"name": "inbox_type",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
@ -3626,6 +3639,81 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/question/invite": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "get question invite user info",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Question"
|
||||
],
|
||||
"summary": "get question invite user info",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "1",
|
||||
"description": "Question ID",
|
||||
"name": "id",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "update question invite user",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Question"
|
||||
],
|
||||
"summary": "update question invite user",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "question",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.QuestionUpdateInviteUser"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/question/operation": {
|
||||
"put": {
|
||||
"security": [
|
||||
|
@ -5025,6 +5113,55 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/user/info/search": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "SearchUserListByName",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "SearchUserListByName",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "username",
|
||||
"name": "username",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/schema.GetOtherUserInfoResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/user/interface": {
|
||||
"put": {
|
||||
"security": [
|
||||
|
@ -7511,6 +7648,12 @@ const docTemplate = `{
|
|||
"maxLength": 65535,
|
||||
"minLength": 6
|
||||
},
|
||||
"mention_username_list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"description": "tags",
|
||||
"type": "array",
|
||||
|
@ -7674,6 +7817,12 @@ const docTemplate = `{
|
|||
"description": "question id",
|
||||
"type": "string"
|
||||
},
|
||||
"invite_user": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"description": "tags",
|
||||
"type": "array",
|
||||
|
@ -7689,6 +7838,23 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"schema.QuestionUpdateInviteUser": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"invite_user": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.RemoveAnswerReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
|
@ -2829,6 +2829,19 @@
|
|||
"name": "type",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"all",
|
||||
"posts",
|
||||
"invites",
|
||||
"votes"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "inbox_type",
|
||||
"name": "inbox_type",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
@ -3614,6 +3627,81 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/question/invite": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "get question invite user info",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Question"
|
||||
],
|
||||
"summary": "get question invite user info",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "1",
|
||||
"description": "Question ID",
|
||||
"name": "id",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "update question invite user",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Question"
|
||||
],
|
||||
"summary": "update question invite user",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "question",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.QuestionUpdateInviteUser"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/question/operation": {
|
||||
"put": {
|
||||
"security": [
|
||||
|
@ -5013,6 +5101,55 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/user/info/search": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "SearchUserListByName",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "SearchUserListByName",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "username",
|
||||
"name": "username",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/schema.GetOtherUserInfoResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/user/interface": {
|
||||
"put": {
|
||||
"security": [
|
||||
|
@ -7499,6 +7636,12 @@
|
|||
"maxLength": 65535,
|
||||
"minLength": 6
|
||||
},
|
||||
"mention_username_list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"description": "tags",
|
||||
"type": "array",
|
||||
|
@ -7662,6 +7805,12 @@
|
|||
"description": "question id",
|
||||
"type": "string"
|
||||
},
|
||||
"invite_user": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"description": "tags",
|
||||
"type": "array",
|
||||
|
@ -7677,6 +7826,23 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"schema.QuestionUpdateInviteUser": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"invite_user": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.RemoveAnswerReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
|
@ -1197,6 +1197,10 @@ definitions:
|
|||
maxLength: 65535
|
||||
minLength: 6
|
||||
type: string
|
||||
mention_username_list:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
tags:
|
||||
description: tags
|
||||
items:
|
||||
|
@ -1313,6 +1317,10 @@ definitions:
|
|||
id:
|
||||
description: question id
|
||||
type: string
|
||||
invite_user:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
tags:
|
||||
description: tags
|
||||
items:
|
||||
|
@ -1329,6 +1337,17 @@ definitions:
|
|||
- tags
|
||||
- title
|
||||
type: object
|
||||
schema.QuestionUpdateInviteUser:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
invite_user:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
schema.RemoveAnswerReq:
|
||||
properties:
|
||||
id:
|
||||
|
@ -3957,6 +3976,16 @@ paths:
|
|||
name: type
|
||||
required: true
|
||||
type: string
|
||||
- description: inbox_type
|
||||
enum:
|
||||
- all
|
||||
- posts
|
||||
- invites
|
||||
- votes
|
||||
in: query
|
||||
name: inbox_type
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
|
@ -4439,6 +4468,53 @@ paths:
|
|||
summary: get question details
|
||||
tags:
|
||||
- Question
|
||||
/answer/api/v1/question/invite:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: get question invite user info
|
||||
parameters:
|
||||
- default: "1"
|
||||
description: Question ID
|
||||
in: query
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: get question invite user info
|
||||
tags:
|
||||
- Question
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: update question invite user
|
||||
parameters:
|
||||
- description: question
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/schema.QuestionUpdateInviteUser'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.RespBody'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: update question invite user
|
||||
tags:
|
||||
- Question
|
||||
/answer/api/v1/question/operation:
|
||||
put:
|
||||
consumes:
|
||||
|
@ -5291,6 +5367,34 @@ paths:
|
|||
summary: UserUpdateInfo update user info
|
||||
tags:
|
||||
- User
|
||||
/answer/api/v1/user/info/search:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: SearchUserListByName
|
||||
parameters:
|
||||
- description: username
|
||||
in: query
|
||||
name: username
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/handler.RespBody'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/schema.GetOtherUserInfoResp'
|
||||
type: object
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: SearchUserListByName
|
||||
tags:
|
||||
- User
|
||||
/answer/api/v1/user/interface:
|
||||
put:
|
||||
consumes:
|
||||
|
|
|
@ -388,6 +388,8 @@ backend:
|
|||
other: downvoted answer
|
||||
up_voted_comment:
|
||||
other: upvoted comment
|
||||
invited_you_to_answer:
|
||||
other: invited you to answer
|
||||
email_tpl:
|
||||
change_email:
|
||||
title:
|
||||
|
@ -399,6 +401,11 @@ backend:
|
|||
other: "[{{.SiteName}}] {{.DisplayName}} answered your question"
|
||||
body:
|
||||
other: "<strong><a href='{{.AnswerUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>{{.AnswerSummary}}</blockquote><br>\n<a href='{{.AnswerUrl}}'>View it on {{.SiteName}}</a><br><br>\n\n<small>You are receiving this because you authored the thread. <a href='{{.UnsubscribeUrl}}'>Unsubscribe</a></small>"
|
||||
invited_you_to_answer:
|
||||
title:
|
||||
other: "[{{.SiteName}}] {{.DisplayName}} invited you to answer"
|
||||
body:
|
||||
other: "<strong><a href='{{.InviteUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>I think you may know the answer.</blockquote><br>\n<a href='{{.InviteUrl}}'>View it on {{.SiteName}}</a><br><br>\n\n<small>You are receiving this because you authored the thread. <a href='{{.UnsubscribeUrl}}'>Unsubscribe</a></small>"
|
||||
new_comment:
|
||||
title:
|
||||
other: "[{{.SiteName}}] {{.DisplayName}} commented on your post"
|
||||
|
|
|
@ -378,6 +378,8 @@ backend:
|
|||
other: 踩了答案
|
||||
up_voted_comment:
|
||||
other: 赞了评论
|
||||
invited_you_to_answer:
|
||||
other: 邀请你回答问题
|
||||
email_tpl:
|
||||
change_email:
|
||||
title:
|
||||
|
@ -388,12 +390,17 @@ backend:
|
|||
title:
|
||||
other: "[{{.SiteName}}] {{.DisplayName}} 回答了您的问题"
|
||||
body:
|
||||
other: "<strong><a href='{{.AnswerUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>{{.AnswerSummary}}</blockquote><br>\n<a href='{{.AnswerUrl}}'>在 {{.SiteName}} 上查看</a><br><br>\n\n<small>您会收到此邮件是因为您是该讨论的作者。<a href='{{.UnsubscribeUrl}}'>取消订阅</a></small>"
|
||||
other: "<strong><a href='{{.AnswerUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>{{.AnswerSummary}}</blockquote><br>\n<a href='{{.AnswerUrl}}'>在 {{.SiteName}} 上查看</a><br><br>\n\n<small>您会收到此邮件是因为您开启了订阅。<a href='{{.UnsubscribeUrl}}'>取消订阅</a></small>"
|
||||
invited_you_to_answer:
|
||||
title:
|
||||
other: "[{{.SiteName}}] {{.DisplayName}} 邀请您回答问题"
|
||||
body:
|
||||
other: "<strong><a href='{{.InviteUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>我想你可能知道答案。</blockquote><br>\n<a href='{{.InviteUrl}}'>在 {{.SiteName}} 上查看</a><br><br>\n\n<small>您会收到此邮件是因为您开启了订阅. <a href='{{.UnsubscribeUrl}}'>取消订阅</a></small>"
|
||||
new_comment:
|
||||
title:
|
||||
other: "[{{.SiteName}}] {{.DisplayName}} 评论了您的帖子"
|
||||
body:
|
||||
other: "<strong><a href='{{.CommentUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>{{.CommentSummary}}</blockquote><br>\n<a href='{{.CommentUrl}}'>在 {{.SiteName}} 上查看</a><br><br>\n\n<small>您会收到此邮件是因为您是该讨论的作者。<a href='{{.UnsubscribeUrl}}'>取消订阅</a></small>"
|
||||
other: "<strong><a href='{{.CommentUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>{{.CommentSummary}}</blockquote><br>\n<a href='{{.CommentUrl}}'>在 {{.SiteName}} 上查看</a><br><br>\n\n<small>您会收到此邮件是因为您开启了订阅。<a href='{{.UnsubscribeUrl}}'>取消订阅</a></small>"
|
||||
pass_reset:
|
||||
title:
|
||||
other: "[{{.SiteName }}] 重置密码"
|
||||
|
|
|
@ -18,4 +18,7 @@ const (
|
|||
|
||||
EmailTplKeyTestTitle = "email_tpl.test.title"
|
||||
EmailTplKeyTestBody = "email_tpl.test.body"
|
||||
|
||||
EmailTplKeyInvitedAnswerTitle = "email_tpl.invited_you_to_answer.title"
|
||||
EmailTplKeyInvitedAnswerBody = "email_tpl.invited_you_to_answer.body"
|
||||
)
|
||||
|
|
|
@ -35,4 +35,6 @@ const (
|
|||
NotificationYourAnswerWasDeleted = "notification.action.your_answer_was_deleted"
|
||||
// NotificationYourCommentWasDeleted your comment was deleted
|
||||
NotificationYourCommentWasDeleted = "notification.action.your_comment_was_deleted"
|
||||
// NotificationInvitedYouToAnswer invited you to answer
|
||||
NotificationInvitedYouToAnswer = "notification.action.invited_you_to_answer"
|
||||
)
|
||||
|
|
|
@ -109,7 +109,7 @@ func (cc *ConnectorController) ConnectorRedirect(connector plugin.Connector) (fn
|
|||
commonRouterPrefix, ConnectorRedirectRouterPrefix, connector.ConnectorSlugName())
|
||||
userInfo, err := connector.ConnectorReceiver(ctx, receiverURL)
|
||||
if err != nil {
|
||||
log.Errorf("connector received failed: %v", err)
|
||||
log.Errorf("connector received failed, error info: %v, response data is: %s", err, userInfo.MetaInfo)
|
||||
ctx.Redirect(http.StatusFound, "/50x")
|
||||
return
|
||||
}
|
||||
|
|
|
@ -143,6 +143,7 @@ func (nc *NotificationController) ClearIDUnRead(ctx *gin.Context) {
|
|||
// @Param page query int false "page size"
|
||||
// @Param page_size query int false "page size"
|
||||
// @Param type query string true "type" Enums(inbox,achievement)
|
||||
// @Param inbox_type query string true "inbox_type" Enums(all,posts,invites,votes)
|
||||
// @Success 200 {object} handler.RespBody
|
||||
// @Router /answer/api/v1/notification/page [get]
|
||||
func (nc *NotificationController) GetList(ctx *gin.Context) {
|
||||
|
|
|
@ -221,6 +221,23 @@ func (qc *QuestionController) GetQuestion(ctx *gin.Context) {
|
|||
handler.HandleResponse(ctx, nil, info)
|
||||
}
|
||||
|
||||
// GetQuestionInviteUserInfo get question invite user info
|
||||
// @Summary get question invite user info
|
||||
// @Description get question invite user info
|
||||
// @Tags Question
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id query string true "Question ID" default(1)
|
||||
// @Success 200 {string} string ""
|
||||
// @Router /answer/api/v1/question/invite [get]
|
||||
func (qc *QuestionController) GetQuestionInviteUserInfo(ctx *gin.Context) {
|
||||
questionID := uid.DeShortID(ctx.Query("id"))
|
||||
resp, err := qc.questionService.InviteUserInfo(ctx, questionID)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
|
||||
}
|
||||
|
||||
// SimilarQuestion godoc
|
||||
// @Summary Search Similar Question
|
||||
// @Description Search Similar Question
|
||||
|
@ -500,6 +517,51 @@ func (qc *QuestionController) UpdateQuestion(ctx *gin.Context) {
|
|||
handler.HandleResponse(ctx, nil, &schema.UpdateQuestionResp{WaitForReview: !req.NoNeedReview})
|
||||
}
|
||||
|
||||
// UpdateQuestionInviteUser update question invite user
|
||||
// @Summary update question invite user
|
||||
// @Description update question invite user
|
||||
// @Tags Question
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param data body schema.QuestionUpdateInviteUser true "question"
|
||||
// @Success 200 {object} handler.RespBody
|
||||
// @Router /answer/api/v1/question/invite [put]
|
||||
func (qc *QuestionController) UpdateQuestionInviteUser(ctx *gin.Context) {
|
||||
req := &schema.QuestionUpdateInviteUser{}
|
||||
errFields := handler.BindAndCheckReturnErr(ctx, req)
|
||||
if ctx.IsAborted() {
|
||||
return
|
||||
}
|
||||
req.ID = uid.DeShortID(req.ID)
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
|
||||
canList, err := qc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
|
||||
permission.AnswerInviteSomeoneToAnswer,
|
||||
})
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
|
||||
objectOwner := qc.rankService.CheckOperationObjectOwner(ctx, req.UserID, req.ID)
|
||||
req.CanEdit = canList[0] || objectOwner
|
||||
if !req.CanEdit {
|
||||
handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil)
|
||||
return
|
||||
}
|
||||
if len(errFields) > 0 {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), errFields)
|
||||
return
|
||||
}
|
||||
err = qc.questionService.UpdateQuestionInviteUser(ctx, req)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
handler.HandleResponse(ctx, nil, nil)
|
||||
}
|
||||
|
||||
// SearchByTitleLike add question title like
|
||||
// @Summary add question title like
|
||||
// @Description add question title like
|
||||
|
|
|
@ -607,3 +607,22 @@ func (uc *UserController) UserUnsubscribeEmailNotification(ctx *gin.Context) {
|
|||
err := uc.userService.UserUnsubscribeEmailNotification(ctx, req)
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
// SearchUserListByName godoc
|
||||
// @Summary SearchUserListByName
|
||||
// @Description SearchUserListByName
|
||||
// @Tags User
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param username query string true "username"
|
||||
// @Success 200 {object} handler.RespBody{data=schema.GetOtherUserInfoResp}
|
||||
// @Router /answer/api/v1/user/info/search [get]
|
||||
func (uc *UserController) SearchUserListByName(ctx *gin.Context) {
|
||||
req := &schema.GetOtherUserInfoByUsernameReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
resp, err := uc.userService.SearchUserListByName(ctx, req.Username)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ type Question struct {
|
|||
CreatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP TIMESTAMP created_at"`
|
||||
UpdatedAt time.Time `xorm:"updated_at TIMESTAMP"`
|
||||
UserID string `xorm:"not null default 0 BIGINT(20) INDEX user_id"`
|
||||
InviteUserID string `xorm:"TEXT invite_user_id"`
|
||||
LastEditUserID string `xorm:"not null default 0 BIGINT(20) last_edit_user_id"`
|
||||
Title string `xorm:"not null default '' VARCHAR(150) title"`
|
||||
OriginalText string `xorm:"not null MEDIUMTEXT original_text"`
|
||||
|
|
|
@ -2,14 +2,44 @@ package migrations
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type QuestionPostTime struct {
|
||||
ID string `xorm:"not null pk BIGINT(20) id"`
|
||||
CreatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP TIMESTAMP created_at"`
|
||||
UpdatedAt time.Time `xorm:"updated_at TIMESTAMP"`
|
||||
UserID string `xorm:"not null default 0 BIGINT(20) INDEX user_id"`
|
||||
InviteUserID string `xorm:"TEXT invite_user_id"`
|
||||
LastEditUserID string `xorm:"not null default 0 BIGINT(20) last_edit_user_id"`
|
||||
Title string `xorm:"not null default '' VARCHAR(150) title"`
|
||||
OriginalText string `xorm:"not null MEDIUMTEXT original_text"`
|
||||
ParsedText string `xorm:"not null MEDIUMTEXT parsed_text"`
|
||||
Status int `xorm:"not null default 1 INT(11) status"`
|
||||
Pin int `xorm:"not null default 1 INT(11) pin"`
|
||||
Show int `xorm:"not null default 1 INT(11) show"`
|
||||
ViewCount int `xorm:"not null default 0 INT(11) view_count"`
|
||||
UniqueViewCount int `xorm:"not null default 0 INT(11) unique_view_count"`
|
||||
VoteCount int `xorm:"not null default 0 INT(11) vote_count"`
|
||||
AnswerCount int `xorm:"not null default 0 INT(11) answer_count"`
|
||||
CollectionCount int `xorm:"not null default 0 INT(11) collection_count"`
|
||||
FollowCount int `xorm:"not null default 0 INT(11) follow_count"`
|
||||
AcceptedAnswerID string `xorm:"not null default 0 BIGINT(20) accepted_answer_id"`
|
||||
LastAnswerID string `xorm:"not null default 0 BIGINT(20) last_answer_id"`
|
||||
PostUpdateTime time.Time `xorm:"post_update_time TIMESTAMP"`
|
||||
RevisionID string `xorm:"not null default 0 BIGINT(20) revision_id"`
|
||||
}
|
||||
|
||||
func (QuestionPostTime) TableName() string {
|
||||
return "question"
|
||||
}
|
||||
|
||||
func updateQuestionPostTime(x *xorm.Engine) error {
|
||||
questionList := make([]entity.Question, 0)
|
||||
questionList := make([]QuestionPostTime, 0)
|
||||
err := x.Find(&questionList, &entity.Question{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get questions failed: %w", err)
|
||||
|
@ -21,7 +51,7 @@ func updateQuestionPostTime(x *xorm.Engine) error {
|
|||
} else if !item.CreatedAt.IsZero() {
|
||||
item.PostUpdateTime = item.CreatedAt
|
||||
}
|
||||
if _, err = x.Update(item, &entity.Question{ID: item.ID}); err != nil {
|
||||
if _, err = x.Update(item, &QuestionPostTime{ID: item.ID}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", item, err)
|
||||
return fmt.Errorf("update question failed: %w", err)
|
||||
}
|
||||
|
|
|
@ -3,12 +3,13 @@ package migrations
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/constant"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"github.com/answerdev/answer/internal/service/permission"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
|
@ -19,6 +20,7 @@ func updateCount(x *xorm.Engine) error {
|
|||
updateTagCount(x)
|
||||
updateUserQuestionCount(x)
|
||||
updateUserAnswerCount(x)
|
||||
inviteAnswer(x)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -302,3 +304,36 @@ func updateUserAnswerCount(x *xorm.Engine) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func inviteAnswer(x *xorm.Engine) error {
|
||||
type Question struct {
|
||||
ID string `xorm:"not null pk BIGINT(20) id"`
|
||||
CreatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP TIMESTAMP created_at"`
|
||||
UpdatedAt time.Time `xorm:"updated_at TIMESTAMP"`
|
||||
UserID string `xorm:"not null default 0 BIGINT(20) INDEX user_id"`
|
||||
InviteUserID string `xorm:"TEXT invite_user_id"`
|
||||
LastEditUserID string `xorm:"not null default 0 BIGINT(20) last_edit_user_id"`
|
||||
Title string `xorm:"not null default '' VARCHAR(150) title"`
|
||||
OriginalText string `xorm:"not null MEDIUMTEXT original_text"`
|
||||
ParsedText string `xorm:"not null MEDIUMTEXT parsed_text"`
|
||||
Status int `xorm:"not null default 1 INT(11) status"`
|
||||
Pin int `xorm:"not null default 1 INT(11) pin"`
|
||||
Show int `xorm:"not null default 1 INT(11) show"`
|
||||
ViewCount int `xorm:"not null default 0 INT(11) view_count"`
|
||||
UniqueViewCount int `xorm:"not null default 0 INT(11) unique_view_count"`
|
||||
VoteCount int `xorm:"not null default 0 INT(11) vote_count"`
|
||||
AnswerCount int `xorm:"not null default 0 INT(11) answer_count"`
|
||||
CollectionCount int `xorm:"not null default 0 INT(11) collection_count"`
|
||||
FollowCount int `xorm:"not null default 0 INT(11) follow_count"`
|
||||
AcceptedAnswerID string `xorm:"not null default 0 BIGINT(20) accepted_answer_id"`
|
||||
LastAnswerID string `xorm:"not null default 0 BIGINT(20) last_answer_id"`
|
||||
PostUpdateTime time.Time `xorm:"post_update_time TIMESTAMP"`
|
||||
RevisionID string `xorm:"not null default 0 BIGINT(20) revision_id"`
|
||||
}
|
||||
err := x.Sync(new(Question))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -195,6 +195,17 @@ func (ur *userRepo) GetByUsername(ctx context.Context, username string) (userInf
|
|||
return
|
||||
}
|
||||
|
||||
func (ur *userRepo) GetByUsernames(ctx context.Context, usernames []string) ([]*entity.User, error) {
|
||||
list := make([]*entity.User, 0)
|
||||
err := ur.data.DB.Where("status =?", entity.UserStatusAvailable).In("username", usernames).Find(&list)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
return list, err
|
||||
}
|
||||
tryToDecorateUserListFromUserCenter(ctx, ur.data, list)
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// GetByEmail get user by email
|
||||
func (ur *userRepo) GetByEmail(ctx context.Context, email string) (userInfo *entity.User, exist bool, err error) {
|
||||
userInfo = &entity.User{}
|
||||
|
@ -215,6 +226,23 @@ func (ur *userRepo) GetUserCount(ctx context.Context) (count int64, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func (ur *userRepo) SearchUserListByName(ctx context.Context, name string) (userList []*entity.User, err error) {
|
||||
userList = make([]*entity.User, 0)
|
||||
if name == "" {
|
||||
return userList, nil
|
||||
}
|
||||
session := ur.data.DB.Where("")
|
||||
session.Where("username LIKE LOWER(?) or display_name LIKE ?", name+"%", name+"%").And("status =?", entity.UserStatusAvailable)
|
||||
session.Asc("username")
|
||||
session = session.Limit(5, 0)
|
||||
err = session.OrderBy("id desc").Find(&userList)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
tryToDecorateUserListFromUserCenter(ctx, ur.data, userList)
|
||||
return
|
||||
}
|
||||
|
||||
func tryToDecorateUserInfoFromUserCenter(ctx context.Context, data *data.Data, original *entity.User) (err error) {
|
||||
if original == nil {
|
||||
return nil
|
||||
|
|
|
@ -112,6 +112,7 @@ func (a *AnswerAPIRouter) RegisterMustUnAuthAnswerAPIRouter(r *gin.RouterGroup)
|
|||
routerGroup.POST("/user/password/reset", a.userController.RetrievePassWord)
|
||||
routerGroup.POST("/user/password/replacement", a.userController.UseRePassWord)
|
||||
routerGroup.PUT("/user/email/notification", a.userController.UserUnsubscribeEmailNotification)
|
||||
routerGroup.GET("/user/info/search", a.userController.SearchUserListByName)
|
||||
}
|
||||
|
||||
func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) {
|
||||
|
@ -129,6 +130,7 @@ func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) {
|
|||
|
||||
//question
|
||||
r.GET("/question/info", a.questionController.GetQuestion)
|
||||
r.GET("/question/invite", a.questionController.GetQuestionInviteUserInfo)
|
||||
r.GET("/question/page", a.questionController.QuestionPage)
|
||||
r.GET("/question/similar/tag", a.questionController.SimilarQuestion)
|
||||
r.GET("/personal/qa/top", a.questionController.UserTop)
|
||||
|
@ -193,6 +195,7 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
|
|||
r.POST("/question", a.questionController.AddQuestion)
|
||||
r.POST("/question/answer", a.questionController.AddQuestionByAnswer)
|
||||
r.PUT("/question", a.questionController.UpdateQuestion)
|
||||
r.PUT("/question/invite", a.questionController.UpdateQuestionInviteUser)
|
||||
r.DELETE("/question", a.questionController.RemoveQuestion)
|
||||
r.PUT("/question/status", a.questionController.CloseQuestion)
|
||||
r.PUT("/question/operation", a.questionController.OperationQuestion)
|
||||
|
|
|
@ -47,6 +47,21 @@ type NewAnswerTemplateData struct {
|
|||
UnsubscribeUrl string
|
||||
}
|
||||
|
||||
type NewInviteAnswerTemplateRawData struct {
|
||||
InviterDisplayName string
|
||||
QuestionTitle string
|
||||
QuestionID string
|
||||
UnsubscribeCode string
|
||||
}
|
||||
|
||||
type NewInviteAnswerTemplateData struct {
|
||||
SiteName string
|
||||
DisplayName string
|
||||
QuestionTitle string
|
||||
InviteUrl string
|
||||
UnsubscribeUrl string
|
||||
}
|
||||
|
||||
type NewCommentTemplateRawData struct {
|
||||
CommentUserDisplayName string
|
||||
QuestionTitle string
|
||||
|
|
|
@ -7,6 +7,10 @@ const (
|
|||
NotificationRead = 2
|
||||
NotificationStatusNormal = 1
|
||||
NotificationStatusDelete = 10
|
||||
NotificationInboxTypeAll = 1
|
||||
NotificationInboxTypePosts = 2
|
||||
NotificationInboxTypeInvites = 3
|
||||
NotificationInboxTypeVotes = 4
|
||||
)
|
||||
|
||||
var NotificationType = map[string]int{
|
||||
|
@ -14,6 +18,13 @@ var NotificationType = map[string]int{
|
|||
"achievement": NotificationTypeAchievement,
|
||||
}
|
||||
|
||||
var NotificationInboxType = map[string]int{
|
||||
"all": NotificationInboxTypeAll,
|
||||
"posts": NotificationInboxTypePosts,
|
||||
"invites": NotificationInboxTypeInvites,
|
||||
"votes": NotificationInboxTypeVotes,
|
||||
}
|
||||
|
||||
type NotificationContent struct {
|
||||
ID string `json:"id"`
|
||||
TriggerUserID string `json:"-"` //show userid
|
||||
|
@ -73,6 +84,8 @@ type NotificationSearch struct {
|
|||
PageSize int `json:"page_size" form:"page_size"` //Search page size
|
||||
Type int `json:"-" form:"-"`
|
||||
TypeStr string `json:"type" form:"type"` // inbox achievement
|
||||
InboxTypeStr string `json:"inbox_type" form:"inbox_type"` // inbox achievement
|
||||
InboxType int `json:"-" form:"-"` // inbox achievement
|
||||
UserID string `json:"-"`
|
||||
}
|
||||
|
||||
|
|
|
@ -88,6 +88,7 @@ type QuestionAddByAnswer struct {
|
|||
Tags []*TagItem `validate:"required,dive" json:"tags"`
|
||||
// user id
|
||||
UserID string `json:"-"`
|
||||
MentionUsernameList []string `validate:"omitempty" json:"mention_username_list"`
|
||||
QuestionPermission
|
||||
}
|
||||
|
||||
|
@ -140,6 +141,7 @@ type QuestionUpdate struct {
|
|||
Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"`
|
||||
// html
|
||||
HTML string `json:"-"`
|
||||
InviteUser []string `validate:"omitempty" json:"invite_user"`
|
||||
// tags
|
||||
Tags []*TagItem `validate:"required,dive" json:"tags"`
|
||||
// edit summary
|
||||
|
@ -150,6 +152,13 @@ type QuestionUpdate struct {
|
|||
QuestionPermission
|
||||
}
|
||||
|
||||
type QuestionUpdateInviteUser struct {
|
||||
ID string `validate:"required" json:"id"`
|
||||
InviteUser []string `validate:"omitempty" json:"invite_user"`
|
||||
UserID string `json:"-"`
|
||||
QuestionPermission
|
||||
}
|
||||
|
||||
func (req *QuestionUpdate) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
req.HTML = converter.Markdown2HTML(req.Content)
|
||||
return nil, nil
|
||||
|
|
|
@ -242,7 +242,6 @@ func (es *EmailService) NewAnswerTemplate(ctx context.Context, raw *schema.NewAn
|
|||
AnswerSummary: raw.AnswerSummary,
|
||||
UnsubscribeUrl: fmt.Sprintf("%s/users/unsubscribe?code=%s", siteInfo.SiteUrl, raw.UnsubscribeCode),
|
||||
}
|
||||
templateData.SiteName = siteInfo.Name
|
||||
|
||||
lang := handler.GetLangByCtx(ctx)
|
||||
title = translator.TrWithData(lang, constant.EmailTplKeyNewAnswerTitle, templateData)
|
||||
|
@ -250,6 +249,27 @@ func (es *EmailService) NewAnswerTemplate(ctx context.Context, raw *schema.NewAn
|
|||
return title, body, nil
|
||||
}
|
||||
|
||||
// NewInviteAnswerTemplate new invite answer template
|
||||
func (es *EmailService) NewInviteAnswerTemplate(ctx context.Context, raw *schema.NewInviteAnswerTemplateRawData) (
|
||||
title, body string, err error) {
|
||||
siteInfo, err := es.GetSiteGeneral(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
templateData := &schema.NewInviteAnswerTemplateData{
|
||||
SiteName: siteInfo.Name,
|
||||
DisplayName: raw.InviterDisplayName,
|
||||
QuestionTitle: raw.QuestionTitle,
|
||||
InviteUrl: fmt.Sprintf("%s/questions/%s", siteInfo.SiteUrl, raw.QuestionID),
|
||||
UnsubscribeUrl: fmt.Sprintf("%s/users/unsubscribe?code=%s", siteInfo.SiteUrl, raw.UnsubscribeCode),
|
||||
}
|
||||
|
||||
lang := handler.GetLangByCtx(ctx)
|
||||
title = translator.TrWithData(lang, constant.EmailTplKeyInvitedAnswerTitle, templateData)
|
||||
body = translator.TrWithData(lang, constant.EmailTplKeyInvitedAnswerBody, templateData)
|
||||
return title, body, nil
|
||||
}
|
||||
|
||||
// NewCommentTemplate new comment template
|
||||
func (es *EmailService) NewCommentTemplate(ctx context.Context, raw *schema.NewCommentTemplateRawData) (
|
||||
title, body string, err error) {
|
||||
|
@ -271,7 +291,6 @@ func (es *EmailService) NewCommentTemplate(ctx context.Context, raw *schema.NewC
|
|||
templateData.CommentUrl = fmt.Sprintf("%s/questions/%s?commentId=%s", siteInfo.SiteUrl,
|
||||
raw.QuestionID, raw.CommentID)
|
||||
}
|
||||
templateData.SiteName = siteInfo.Name
|
||||
|
||||
lang := handler.GetLangByCtx(ctx)
|
||||
title = translator.TrWithData(lang, constant.EmailTplKeyNewCommentTitle, templateData)
|
||||
|
|
|
@ -123,7 +123,12 @@ func (ns *NotificationService) GetNotificationPage(ctx context.Context, searchCo
|
|||
if !ok {
|
||||
return pager.NewPageModel(0, resp), nil
|
||||
}
|
||||
searchInboxType, ok := schema.NotificationInboxType[searchCond.InboxTypeStr]
|
||||
if !ok {
|
||||
return pager.NewPageModel(0, resp), nil
|
||||
}
|
||||
searchCond.Type = searchType
|
||||
searchCond.InboxType = searchInboxType
|
||||
notifications, total, err := ns.notificationRepo.GetNotificationPage(ctx, searchCond)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -150,6 +150,34 @@ func (qs *QuestionCommon) FindInfoByID(ctx context.Context, questionIDs []string
|
|||
return list, nil
|
||||
}
|
||||
|
||||
func (qs *QuestionCommon) InviteUserInfo(ctx context.Context, questionID string) (inviteList []*schema.UserBasicInfo, err error) {
|
||||
InviteUserInfo := make([]*schema.UserBasicInfo, 0)
|
||||
dbinfo, has, err := qs.questionRepo.GetQuestion(ctx, questionID)
|
||||
if err != nil {
|
||||
return InviteUserInfo, err
|
||||
}
|
||||
if !has {
|
||||
return InviteUserInfo, errors.NotFound(reason.QuestionNotFound)
|
||||
}
|
||||
//InviteUser
|
||||
if dbinfo.InviteUserID != "" {
|
||||
InviteUserIDs := make([]string, 0)
|
||||
err := json.Unmarshal([]byte(dbinfo.InviteUserID), &InviteUserIDs)
|
||||
if err == nil {
|
||||
inviteUserInfoMap, err := qs.userCommon.BatchUserBasicInfoByID(ctx, InviteUserIDs)
|
||||
if err == nil {
|
||||
for _, userid := range InviteUserIDs {
|
||||
_, ok := inviteUserInfoMap[userid]
|
||||
if ok {
|
||||
InviteUserInfo = append(InviteUserInfo, inviteUserInfoMap[userid])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return InviteUserInfo, nil
|
||||
}
|
||||
|
||||
func (qs *QuestionCommon) Info(ctx context.Context, questionID string, loginUserID string) (showinfo *schema.QuestionInfo, err error) {
|
||||
dbinfo, has, err := qs.questionRepo.GetQuestion(ctx, questionID)
|
||||
if err != nil {
|
||||
|
@ -186,9 +214,7 @@ func (qs *QuestionCommon) Info(ctx context.Context, questionID string, loginUser
|
|||
operation.Level = schema.OperationLevelInfo
|
||||
showinfo.Operation = operation
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/answerdev/answer/internal/service/activity"
|
||||
"github.com/answerdev/answer/internal/service/activity_queue"
|
||||
collectioncommon "github.com/answerdev/answer/internal/service/collection_common"
|
||||
"github.com/answerdev/answer/internal/service/export"
|
||||
"github.com/answerdev/answer/internal/service/meta"
|
||||
"github.com/answerdev/answer/internal/service/notice_queue"
|
||||
"github.com/answerdev/answer/internal/service/permission"
|
||||
|
@ -26,10 +27,12 @@ import (
|
|||
"github.com/answerdev/answer/internal/service/revision_common"
|
||||
tagcommon "github.com/answerdev/answer/internal/service/tag_common"
|
||||
usercommon "github.com/answerdev/answer/internal/service/user_common"
|
||||
"github.com/answerdev/answer/pkg/encryption"
|
||||
"github.com/answerdev/answer/pkg/htmltext"
|
||||
"github.com/answerdev/answer/pkg/uid"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/i18n"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
@ -42,11 +45,13 @@ type QuestionService struct {
|
|||
tagCommon *tagcommon.TagCommonService
|
||||
questioncommon *questioncommon.QuestionCommon
|
||||
userCommon *usercommon.UserCommon
|
||||
userRepo usercommon.UserRepo
|
||||
revisionService *revision_common.RevisionService
|
||||
metaService *meta.MetaService
|
||||
collectionCommon *collectioncommon.CollectionCommon
|
||||
answerActivityService *activity.AnswerActivityService
|
||||
data *data.Data
|
||||
emailService *export.EmailService
|
||||
}
|
||||
|
||||
func NewQuestionService(
|
||||
|
@ -54,23 +59,26 @@ func NewQuestionService(
|
|||
tagCommon *tagcommon.TagCommonService,
|
||||
questioncommon *questioncommon.QuestionCommon,
|
||||
userCommon *usercommon.UserCommon,
|
||||
userRepo usercommon.UserRepo,
|
||||
revisionService *revision_common.RevisionService,
|
||||
metaService *meta.MetaService,
|
||||
collectionCommon *collectioncommon.CollectionCommon,
|
||||
answerActivityService *activity.AnswerActivityService,
|
||||
data *data.Data,
|
||||
|
||||
emailService *export.EmailService,
|
||||
) *QuestionService {
|
||||
return &QuestionService{
|
||||
questionRepo: questionRepo,
|
||||
tagCommon: tagCommon,
|
||||
questioncommon: questioncommon,
|
||||
userCommon: userCommon,
|
||||
userRepo: userRepo,
|
||||
revisionService: revisionService,
|
||||
metaService: metaService,
|
||||
collectionCommon: collectionCommon,
|
||||
answerActivityService: answerActivityService,
|
||||
data: data,
|
||||
emailService: emailService,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -535,6 +543,115 @@ func (qs *QuestionService) UpdateQuestionCheckTags(ctx context.Context, req *sch
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (qs *QuestionService) UpdateQuestionInviteUser(ctx context.Context, req *schema.QuestionUpdateInviteUser) (err error) {
|
||||
originQuestion, exist, err := qs.questionRepo.GetQuestion(ctx, req.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exist {
|
||||
return errors.NotFound(reason.ObjectNotFound)
|
||||
}
|
||||
|
||||
//verify invite user
|
||||
inviteUserInfoList, err := qs.userCommon.BatchGetUserBasicInfoByUserNames(ctx, req.InviteUser)
|
||||
if err != nil {
|
||||
log.Error("BatchGetUserBasicInfoByUserNames error", err.Error())
|
||||
}
|
||||
inviteUserIDs := make([]string, 0)
|
||||
for _, item := range req.InviteUser {
|
||||
_, ok := inviteUserInfoList[item]
|
||||
if ok {
|
||||
inviteUserIDs = append(inviteUserIDs, inviteUserInfoList[item].ID)
|
||||
}
|
||||
}
|
||||
inviteUserStr := ""
|
||||
inviteUserByte, err := json.Marshal(inviteUserIDs)
|
||||
if err != nil {
|
||||
log.Error("json.Marshal error", err.Error())
|
||||
inviteUserStr = "[]"
|
||||
} else {
|
||||
inviteUserStr = string(inviteUserByte)
|
||||
}
|
||||
question := &entity.Question{}
|
||||
question.ID = uid.DeShortID(req.ID)
|
||||
question.InviteUserID = inviteUserStr
|
||||
|
||||
saveerr := qs.questionRepo.UpdateQuestion(ctx, question, []string{"invite_user_id"})
|
||||
if saveerr != nil {
|
||||
return saveerr
|
||||
}
|
||||
go qs.notificationInviteUser(ctx, inviteUserIDs, originQuestion.ID, originQuestion.Title, req.UserID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qs *QuestionService) notificationInviteUser(
|
||||
ctx context.Context, invitedUserIDs []string, questionID, questionTitle, questionUserID string) {
|
||||
inviter, exist, err := qs.userCommon.GetUserBasicInfoByID(ctx, questionUserID)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
if !exist {
|
||||
log.Warnf("user %s not found", questionUserID)
|
||||
return
|
||||
}
|
||||
|
||||
users, err := qs.userRepo.BatchGetByID(ctx, invitedUserIDs)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
invitee := make(map[string]*entity.User, len(users))
|
||||
for _, user := range users {
|
||||
invitee[user.ID] = user
|
||||
}
|
||||
for _, userID := range invitedUserIDs {
|
||||
msg := &schema.NotificationMsg{
|
||||
ReceiverUserID: userID,
|
||||
TriggerUserID: questionUserID,
|
||||
Type: schema.NotificationTypeInbox,
|
||||
ObjectID: questionID,
|
||||
}
|
||||
msg.ObjectType = constant.QuestionObjectType
|
||||
msg.NotificationAction = constant.NotificationInvitedYouToAnswer
|
||||
notice_queue.AddNotification(msg)
|
||||
|
||||
userInfo, ok := invitee[userID]
|
||||
if !ok {
|
||||
log.Warnf("user %s not found", userID)
|
||||
return
|
||||
}
|
||||
if userInfo.NoticeStatus == schema.NoticeStatusOff || len(userInfo.EMail) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
rawData := &schema.NewInviteAnswerTemplateRawData{
|
||||
InviterDisplayName: inviter.DisplayName,
|
||||
QuestionTitle: questionTitle,
|
||||
QuestionID: questionID,
|
||||
UnsubscribeCode: encryption.MD5(userInfo.Pass),
|
||||
}
|
||||
codeContent := &schema.EmailCodeContent{
|
||||
SourceType: schema.UnsubscribeSourceType,
|
||||
Email: userInfo.EMail,
|
||||
UserID: userInfo.ID,
|
||||
}
|
||||
|
||||
// If receiver has set language, use it to send email.
|
||||
if len(userInfo.Language) > 0 {
|
||||
ctx = context.WithValue(ctx, constant.AcceptLanguageFlag, i18n.Language(userInfo.Language))
|
||||
}
|
||||
title, body, err := qs.emailService.NewInviteAnswerTemplate(ctx, rawData)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
go qs.emailService.SendAndSaveCodeWithTime(
|
||||
ctx, userInfo.EMail, title, body, rawData.UnsubscribeCode, codeContent.ToJSONString(), 7*24*time.Hour)
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateQuestion update question
|
||||
func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.QuestionUpdate) (questionInfo any, err error) {
|
||||
var canUpdate bool
|
||||
|
@ -756,6 +873,10 @@ func (qs *QuestionService) GetQuestionAndAddPV(ctx context.Context, questionID,
|
|||
return qs.GetQuestion(ctx, questionID, loginUserID, per)
|
||||
}
|
||||
|
||||
func (qs *QuestionService) InviteUserInfo(ctx context.Context, questionID string) (inviteList []*schema.UserBasicInfo, err error) {
|
||||
return qs.questioncommon.InviteUserInfo(ctx, questionID)
|
||||
}
|
||||
|
||||
func (qs *QuestionService) ChangeTag(ctx context.Context, objectTagData *schema.TagChange) error {
|
||||
return qs.tagCommon.ObjectChangeTag(ctx, objectTagData)
|
||||
}
|
||||
|
|
|
@ -32,8 +32,10 @@ type UserRepo interface {
|
|||
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)
|
||||
GetByUsernames(ctx context.Context, usernames []string) ([]*entity.User, error)
|
||||
GetByEmail(ctx context.Context, email string) (userInfo *entity.User, exist bool, err error)
|
||||
GetUserCount(ctx context.Context) (count int64, err error)
|
||||
SearchUserListByName(ctx context.Context, name string) (userList []*entity.User, err error)
|
||||
}
|
||||
|
||||
// UserCommon user service
|
||||
|
@ -74,6 +76,19 @@ func (us *UserCommon) GetUserBasicInfoByUserName(ctx context.Context, username s
|
|||
return info, exist, nil
|
||||
}
|
||||
|
||||
func (us *UserCommon) BatchGetUserBasicInfoByUserNames(ctx context.Context, usernames []string) (map[string]*schema.UserBasicInfo, error) {
|
||||
infomap := make(map[string]*schema.UserBasicInfo)
|
||||
list, err := us.userRepo.GetByUsernames(ctx, usernames)
|
||||
if err != nil {
|
||||
return infomap, err
|
||||
}
|
||||
for _, user := range list {
|
||||
info := us.FormatUserBasicInfo(ctx, user)
|
||||
infomap[user.Username] = info
|
||||
}
|
||||
return infomap, nil
|
||||
}
|
||||
|
||||
func (us *UserCommon) UpdateAnswerCount(ctx context.Context, userID string, num int) error {
|
||||
return us.userRepo.UpdateAnswerCount(ctx, userID, num)
|
||||
}
|
||||
|
|
|
@ -814,6 +814,19 @@ func (us *UserService) getUserInfoMapping(ctx context.Context, userIDs []string)
|
|||
return userInfoMapping, nil
|
||||
}
|
||||
|
||||
func (us *UserService) SearchUserListByName(ctx context.Context, name string) ([]*schema.UserBasicInfo, error) {
|
||||
userinfolist := make([]*schema.UserBasicInfo, 0)
|
||||
list, err := us.userRepo.SearchUserListByName(ctx, name)
|
||||
if err != nil {
|
||||
return userinfolist, err
|
||||
}
|
||||
for _, user := range list {
|
||||
userinfo := us.userCommonService.FormatUserBasicInfo(ctx, user)
|
||||
userinfolist = append(userinfolist, userinfo)
|
||||
}
|
||||
return userinfolist, nil
|
||||
}
|
||||
|
||||
func (us *UserService) warpStatRankingResp(
|
||||
userInfoMapping map[string]*entity.User,
|
||||
rankStat []*entity.ActivityUserRankStat,
|
||||
|
|
Loading…
Reference in New Issue