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": [
|
"eslint.workingDirectories": [
|
||||||
"ui"
|
"ui"
|
||||||
],
|
]
|
||||||
"commentTranslate.multiLineMerge": true
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,7 +170,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||||
answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo)
|
answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo)
|
||||||
questionActivityRepo := activity.NewQuestionActivityRepo(dataData, activityRepo, userRankRepo)
|
questionActivityRepo := activity.NewQuestionActivityRepo(dataData, activityRepo, userRankRepo)
|
||||||
answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, questionActivityRepo)
|
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)
|
answerService := service.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService)
|
||||||
questionController := controller.NewQuestionController(questionService, answerService, rankService)
|
questionController := controller.NewQuestionController(questionService, answerService, rankService)
|
||||||
dashboardService := dashboard.NewDashboardService(questionRepo, answerRepo, commentCommonRepo, voteRepo, userRepo, reportRepo, configRepo, siteInfoCommonService, serviceConf, dataData)
|
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",
|
"name": "type",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
"required": true
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"all",
|
||||||
|
"posts",
|
||||||
|
"invites",
|
||||||
|
"votes"
|
||||||
|
],
|
||||||
|
"type": "string",
|
||||||
|
"description": "inbox_type",
|
||||||
|
"name": "inbox_type",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"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": {
|
"/answer/api/v1/question/operation": {
|
||||||
"put": {
|
"put": {
|
||||||
"security": [
|
"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": {
|
"/answer/api/v1/user/interface": {
|
||||||
"put": {
|
"put": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -7511,6 +7648,12 @@ const docTemplate = `{
|
||||||
"maxLength": 65535,
|
"maxLength": 65535,
|
||||||
"minLength": 6
|
"minLength": 6
|
||||||
},
|
},
|
||||||
|
"mention_username_list": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"tags": {
|
"tags": {
|
||||||
"description": "tags",
|
"description": "tags",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
@ -7674,6 +7817,12 @@ const docTemplate = `{
|
||||||
"description": "question id",
|
"description": "question id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"invite_user": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"tags": {
|
"tags": {
|
||||||
"description": "tags",
|
"description": "tags",
|
||||||
"type": "array",
|
"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": {
|
"schema.RemoveAnswerReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
@ -2829,6 +2829,19 @@
|
||||||
"name": "type",
|
"name": "type",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
"required": true
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"all",
|
||||||
|
"posts",
|
||||||
|
"invites",
|
||||||
|
"votes"
|
||||||
|
],
|
||||||
|
"type": "string",
|
||||||
|
"description": "inbox_type",
|
||||||
|
"name": "inbox_type",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"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": {
|
"/answer/api/v1/question/operation": {
|
||||||
"put": {
|
"put": {
|
||||||
"security": [
|
"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": {
|
"/answer/api/v1/user/interface": {
|
||||||
"put": {
|
"put": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -7499,6 +7636,12 @@
|
||||||
"maxLength": 65535,
|
"maxLength": 65535,
|
||||||
"minLength": 6
|
"minLength": 6
|
||||||
},
|
},
|
||||||
|
"mention_username_list": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"tags": {
|
"tags": {
|
||||||
"description": "tags",
|
"description": "tags",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
@ -7662,6 +7805,12 @@
|
||||||
"description": "question id",
|
"description": "question id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"invite_user": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"tags": {
|
"tags": {
|
||||||
"description": "tags",
|
"description": "tags",
|
||||||
"type": "array",
|
"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": {
|
"schema.RemoveAnswerReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
@ -1197,6 +1197,10 @@ definitions:
|
||||||
maxLength: 65535
|
maxLength: 65535
|
||||||
minLength: 6
|
minLength: 6
|
||||||
type: string
|
type: string
|
||||||
|
mention_username_list:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
tags:
|
tags:
|
||||||
description: tags
|
description: tags
|
||||||
items:
|
items:
|
||||||
|
@ -1313,6 +1317,10 @@ definitions:
|
||||||
id:
|
id:
|
||||||
description: question id
|
description: question id
|
||||||
type: string
|
type: string
|
||||||
|
invite_user:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
tags:
|
tags:
|
||||||
description: tags
|
description: tags
|
||||||
items:
|
items:
|
||||||
|
@ -1329,6 +1337,17 @@ definitions:
|
||||||
- tags
|
- tags
|
||||||
- title
|
- title
|
||||||
type: object
|
type: object
|
||||||
|
schema.QuestionUpdateInviteUser:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
invite_user:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
type: object
|
||||||
schema.RemoveAnswerReq:
|
schema.RemoveAnswerReq:
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
|
@ -3957,6 +3976,16 @@ paths:
|
||||||
name: type
|
name: type
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
- description: inbox_type
|
||||||
|
enum:
|
||||||
|
- all
|
||||||
|
- posts
|
||||||
|
- invites
|
||||||
|
- votes
|
||||||
|
in: query
|
||||||
|
name: inbox_type
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
|
@ -4439,6 +4468,53 @@ paths:
|
||||||
summary: get question details
|
summary: get question details
|
||||||
tags:
|
tags:
|
||||||
- Question
|
- 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:
|
/answer/api/v1/question/operation:
|
||||||
put:
|
put:
|
||||||
consumes:
|
consumes:
|
||||||
|
@ -5291,6 +5367,34 @@ paths:
|
||||||
summary: UserUpdateInfo update user info
|
summary: UserUpdateInfo update user info
|
||||||
tags:
|
tags:
|
||||||
- User
|
- 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:
|
/answer/api/v1/user/interface:
|
||||||
put:
|
put:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
|
@ -388,6 +388,8 @@ backend:
|
||||||
other: downvoted answer
|
other: downvoted answer
|
||||||
up_voted_comment:
|
up_voted_comment:
|
||||||
other: upvoted comment
|
other: upvoted comment
|
||||||
|
invited_you_to_answer:
|
||||||
|
other: invited you to answer
|
||||||
email_tpl:
|
email_tpl:
|
||||||
change_email:
|
change_email:
|
||||||
title:
|
title:
|
||||||
|
@ -399,6 +401,11 @@ backend:
|
||||||
other: "[{{.SiteName}}] {{.DisplayName}} answered your question"
|
other: "[{{.SiteName}}] {{.DisplayName}} answered your question"
|
||||||
body:
|
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>"
|
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:
|
new_comment:
|
||||||
title:
|
title:
|
||||||
other: "[{{.SiteName}}] {{.DisplayName}} commented on your post"
|
other: "[{{.SiteName}}] {{.DisplayName}} commented on your post"
|
||||||
|
|
|
@ -378,6 +378,8 @@ backend:
|
||||||
other: 踩了答案
|
other: 踩了答案
|
||||||
up_voted_comment:
|
up_voted_comment:
|
||||||
other: 赞了评论
|
other: 赞了评论
|
||||||
|
invited_you_to_answer:
|
||||||
|
other: 邀请你回答问题
|
||||||
email_tpl:
|
email_tpl:
|
||||||
change_email:
|
change_email:
|
||||||
title:
|
title:
|
||||||
|
@ -388,12 +390,17 @@ backend:
|
||||||
title:
|
title:
|
||||||
other: "[{{.SiteName}}] {{.DisplayName}} 回答了您的问题"
|
other: "[{{.SiteName}}] {{.DisplayName}} 回答了您的问题"
|
||||||
body:
|
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:
|
new_comment:
|
||||||
title:
|
title:
|
||||||
other: "[{{.SiteName}}] {{.DisplayName}} 评论了您的帖子"
|
other: "[{{.SiteName}}] {{.DisplayName}} 评论了您的帖子"
|
||||||
body:
|
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:
|
pass_reset:
|
||||||
title:
|
title:
|
||||||
other: "[{{.SiteName }}] 重置密码"
|
other: "[{{.SiteName }}] 重置密码"
|
||||||
|
|
|
@ -18,4 +18,7 @@ const (
|
||||||
|
|
||||||
EmailTplKeyTestTitle = "email_tpl.test.title"
|
EmailTplKeyTestTitle = "email_tpl.test.title"
|
||||||
EmailTplKeyTestBody = "email_tpl.test.body"
|
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"
|
NotificationYourAnswerWasDeleted = "notification.action.your_answer_was_deleted"
|
||||||
// NotificationYourCommentWasDeleted your comment was deleted
|
// NotificationYourCommentWasDeleted your comment was deleted
|
||||||
NotificationYourCommentWasDeleted = "notification.action.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())
|
commonRouterPrefix, ConnectorRedirectRouterPrefix, connector.ConnectorSlugName())
|
||||||
userInfo, err := connector.ConnectorReceiver(ctx, receiverURL)
|
userInfo, err := connector.ConnectorReceiver(ctx, receiverURL)
|
||||||
if err != nil {
|
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")
|
ctx.Redirect(http.StatusFound, "/50x")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,6 +143,7 @@ func (nc *NotificationController) ClearIDUnRead(ctx *gin.Context) {
|
||||||
// @Param page query int false "page size"
|
// @Param page query int false "page size"
|
||||||
// @Param page_size query int false "page size"
|
// @Param page_size query int false "page size"
|
||||||
// @Param type query string true "type" Enums(inbox,achievement)
|
// @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
|
// @Success 200 {object} handler.RespBody
|
||||||
// @Router /answer/api/v1/notification/page [get]
|
// @Router /answer/api/v1/notification/page [get]
|
||||||
func (nc *NotificationController) GetList(ctx *gin.Context) {
|
func (nc *NotificationController) GetList(ctx *gin.Context) {
|
||||||
|
|
|
@ -221,6 +221,23 @@ func (qc *QuestionController) GetQuestion(ctx *gin.Context) {
|
||||||
handler.HandleResponse(ctx, nil, info)
|
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
|
// SimilarQuestion godoc
|
||||||
// @Summary Search Similar Question
|
// @Summary Search Similar Question
|
||||||
// @Description 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})
|
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
|
// SearchByTitleLike add question title like
|
||||||
// @Summary add question title like
|
// @Summary add question title like
|
||||||
// @Description 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)
|
err := uc.userService.UserUnsubscribeEmailNotification(ctx, req)
|
||||||
handler.HandleResponse(ctx, err, nil)
|
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"`
|
CreatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP TIMESTAMP created_at"`
|
||||||
UpdatedAt time.Time `xorm:"updated_at TIMESTAMP"`
|
UpdatedAt time.Time `xorm:"updated_at TIMESTAMP"`
|
||||||
UserID string `xorm:"not null default 0 BIGINT(20) INDEX user_id"`
|
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"`
|
LastEditUserID string `xorm:"not null default 0 BIGINT(20) last_edit_user_id"`
|
||||||
Title string `xorm:"not null default '' VARCHAR(150) title"`
|
Title string `xorm:"not null default '' VARCHAR(150) title"`
|
||||||
OriginalText string `xorm:"not null MEDIUMTEXT original_text"`
|
OriginalText string `xorm:"not null MEDIUMTEXT original_text"`
|
||||||
|
|
|
@ -2,14 +2,44 @@ package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/entity"
|
"github.com/answerdev/answer/internal/entity"
|
||||||
"github.com/segmentfault/pacman/log"
|
"github.com/segmentfault/pacman/log"
|
||||||
"xorm.io/xorm"
|
"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 {
|
func updateQuestionPostTime(x *xorm.Engine) error {
|
||||||
questionList := make([]entity.Question, 0)
|
questionList := make([]QuestionPostTime, 0)
|
||||||
err := x.Find(&questionList, &entity.Question{})
|
err := x.Find(&questionList, &entity.Question{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get questions failed: %w", err)
|
return fmt.Errorf("get questions failed: %w", err)
|
||||||
|
@ -21,7 +51,7 @@ func updateQuestionPostTime(x *xorm.Engine) error {
|
||||||
} else if !item.CreatedAt.IsZero() {
|
} else if !item.CreatedAt.IsZero() {
|
||||||
item.PostUpdateTime = item.CreatedAt
|
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)
|
log.Errorf("update %+v config failed: %s", item, err)
|
||||||
return fmt.Errorf("update question failed: %w", err)
|
return fmt.Errorf("update question failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,13 @@ package migrations
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/constant"
|
"github.com/answerdev/answer/internal/base/constant"
|
||||||
"github.com/answerdev/answer/internal/entity"
|
"github.com/answerdev/answer/internal/entity"
|
||||||
"github.com/answerdev/answer/internal/schema"
|
"github.com/answerdev/answer/internal/schema"
|
||||||
"github.com/segmentfault/pacman/log"
|
|
||||||
"github.com/answerdev/answer/internal/service/permission"
|
"github.com/answerdev/answer/internal/service/permission"
|
||||||
|
"github.com/segmentfault/pacman/log"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,6 +20,7 @@ func updateCount(x *xorm.Engine) error {
|
||||||
updateTagCount(x)
|
updateTagCount(x)
|
||||||
updateUserQuestionCount(x)
|
updateUserQuestionCount(x)
|
||||||
updateUserAnswerCount(x)
|
updateUserAnswerCount(x)
|
||||||
|
inviteAnswer(x)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,3 +304,36 @@ func updateUserAnswerCount(x *xorm.Engine) error {
|
||||||
}
|
}
|
||||||
return nil
|
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
|
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
|
// GetByEmail get user by email
|
||||||
func (ur *userRepo) GetByEmail(ctx context.Context, email string) (userInfo *entity.User, exist bool, err error) {
|
func (ur *userRepo) GetByEmail(ctx context.Context, email string) (userInfo *entity.User, exist bool, err error) {
|
||||||
userInfo = &entity.User{}
|
userInfo = &entity.User{}
|
||||||
|
@ -215,6 +226,23 @@ func (ur *userRepo) GetUserCount(ctx context.Context) (count int64, err error) {
|
||||||
return
|
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) {
|
func tryToDecorateUserInfoFromUserCenter(ctx context.Context, data *data.Data, original *entity.User) (err error) {
|
||||||
if original == nil {
|
if original == nil {
|
||||||
return 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/reset", a.userController.RetrievePassWord)
|
||||||
routerGroup.POST("/user/password/replacement", a.userController.UseRePassWord)
|
routerGroup.POST("/user/password/replacement", a.userController.UseRePassWord)
|
||||||
routerGroup.PUT("/user/email/notification", a.userController.UserUnsubscribeEmailNotification)
|
routerGroup.PUT("/user/email/notification", a.userController.UserUnsubscribeEmailNotification)
|
||||||
|
routerGroup.GET("/user/info/search", a.userController.SearchUserListByName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) {
|
func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) {
|
||||||
|
@ -129,6 +130,7 @@ func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) {
|
||||||
|
|
||||||
//question
|
//question
|
||||||
r.GET("/question/info", a.questionController.GetQuestion)
|
r.GET("/question/info", a.questionController.GetQuestion)
|
||||||
|
r.GET("/question/invite", a.questionController.GetQuestionInviteUserInfo)
|
||||||
r.GET("/question/page", a.questionController.QuestionPage)
|
r.GET("/question/page", a.questionController.QuestionPage)
|
||||||
r.GET("/question/similar/tag", a.questionController.SimilarQuestion)
|
r.GET("/question/similar/tag", a.questionController.SimilarQuestion)
|
||||||
r.GET("/personal/qa/top", a.questionController.UserTop)
|
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", a.questionController.AddQuestion)
|
||||||
r.POST("/question/answer", a.questionController.AddQuestionByAnswer)
|
r.POST("/question/answer", a.questionController.AddQuestionByAnswer)
|
||||||
r.PUT("/question", a.questionController.UpdateQuestion)
|
r.PUT("/question", a.questionController.UpdateQuestion)
|
||||||
|
r.PUT("/question/invite", a.questionController.UpdateQuestionInviteUser)
|
||||||
r.DELETE("/question", a.questionController.RemoveQuestion)
|
r.DELETE("/question", a.questionController.RemoveQuestion)
|
||||||
r.PUT("/question/status", a.questionController.CloseQuestion)
|
r.PUT("/question/status", a.questionController.CloseQuestion)
|
||||||
r.PUT("/question/operation", a.questionController.OperationQuestion)
|
r.PUT("/question/operation", a.questionController.OperationQuestion)
|
||||||
|
|
|
@ -47,6 +47,21 @@ type NewAnswerTemplateData struct {
|
||||||
UnsubscribeUrl string
|
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 {
|
type NewCommentTemplateRawData struct {
|
||||||
CommentUserDisplayName string
|
CommentUserDisplayName string
|
||||||
QuestionTitle string
|
QuestionTitle string
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
package schema
|
package schema
|
||||||
|
|
||||||
const (
|
const (
|
||||||
NotificationTypeInbox = 1
|
NotificationTypeInbox = 1
|
||||||
NotificationTypeAchievement = 2
|
NotificationTypeAchievement = 2
|
||||||
NotificationNotRead = 1
|
NotificationNotRead = 1
|
||||||
NotificationRead = 2
|
NotificationRead = 2
|
||||||
NotificationStatusNormal = 1
|
NotificationStatusNormal = 1
|
||||||
NotificationStatusDelete = 10
|
NotificationStatusDelete = 10
|
||||||
|
NotificationInboxTypeAll = 1
|
||||||
|
NotificationInboxTypePosts = 2
|
||||||
|
NotificationInboxTypeInvites = 3
|
||||||
|
NotificationInboxTypeVotes = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
var NotificationType = map[string]int{
|
var NotificationType = map[string]int{
|
||||||
|
@ -14,6 +18,13 @@ var NotificationType = map[string]int{
|
||||||
"achievement": NotificationTypeAchievement,
|
"achievement": NotificationTypeAchievement,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var NotificationInboxType = map[string]int{
|
||||||
|
"all": NotificationInboxTypeAll,
|
||||||
|
"posts": NotificationInboxTypePosts,
|
||||||
|
"invites": NotificationInboxTypeInvites,
|
||||||
|
"votes": NotificationInboxTypeVotes,
|
||||||
|
}
|
||||||
|
|
||||||
type NotificationContent struct {
|
type NotificationContent struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
TriggerUserID string `json:"-"` //show userid
|
TriggerUserID string `json:"-"` //show userid
|
||||||
|
@ -69,11 +80,13 @@ type RedDot struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type NotificationSearch struct {
|
type NotificationSearch struct {
|
||||||
Page int `json:"page" form:"page"` //Query number of pages
|
Page int `json:"page" form:"page"` //Query number of pages
|
||||||
PageSize int `json:"page_size" form:"page_size"` //Search page size
|
PageSize int `json:"page_size" form:"page_size"` //Search page size
|
||||||
Type int `json:"-" form:"-"`
|
Type int `json:"-" form:"-"`
|
||||||
TypeStr string `json:"type" form:"type"` // inbox achievement
|
TypeStr string `json:"type" form:"type"` // inbox achievement
|
||||||
UserID string `json:"-"`
|
InboxTypeStr string `json:"inbox_type" form:"inbox_type"` // inbox achievement
|
||||||
|
InboxType int `json:"-" form:"-"` // inbox achievement
|
||||||
|
UserID string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NotificationClearRequest struct {
|
type NotificationClearRequest struct {
|
||||||
|
|
|
@ -87,7 +87,8 @@ type QuestionAddByAnswer struct {
|
||||||
// tags
|
// tags
|
||||||
Tags []*TagItem `validate:"required,dive" json:"tags"`
|
Tags []*TagItem `validate:"required,dive" json:"tags"`
|
||||||
// user id
|
// user id
|
||||||
UserID string `json:"-"`
|
UserID string `json:"-"`
|
||||||
|
MentionUsernameList []string `validate:"omitempty" json:"mention_username_list"`
|
||||||
QuestionPermission
|
QuestionPermission
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +140,8 @@ type QuestionUpdate struct {
|
||||||
// content
|
// content
|
||||||
Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"`
|
Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"`
|
||||||
// html
|
// html
|
||||||
HTML string `json:"-"`
|
HTML string `json:"-"`
|
||||||
|
InviteUser []string `validate:"omitempty" json:"invite_user"`
|
||||||
// tags
|
// tags
|
||||||
Tags []*TagItem `validate:"required,dive" json:"tags"`
|
Tags []*TagItem `validate:"required,dive" json:"tags"`
|
||||||
// edit summary
|
// edit summary
|
||||||
|
@ -150,6 +152,13 @@ type QuestionUpdate struct {
|
||||||
QuestionPermission
|
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) {
|
func (req *QuestionUpdate) Check() (errFields []*validator.FormErrorField, err error) {
|
||||||
req.HTML = converter.Markdown2HTML(req.Content)
|
req.HTML = converter.Markdown2HTML(req.Content)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -242,7 +242,6 @@ func (es *EmailService) NewAnswerTemplate(ctx context.Context, raw *schema.NewAn
|
||||||
AnswerSummary: raw.AnswerSummary,
|
AnswerSummary: raw.AnswerSummary,
|
||||||
UnsubscribeUrl: fmt.Sprintf("%s/users/unsubscribe?code=%s", siteInfo.SiteUrl, raw.UnsubscribeCode),
|
UnsubscribeUrl: fmt.Sprintf("%s/users/unsubscribe?code=%s", siteInfo.SiteUrl, raw.UnsubscribeCode),
|
||||||
}
|
}
|
||||||
templateData.SiteName = siteInfo.Name
|
|
||||||
|
|
||||||
lang := handler.GetLangByCtx(ctx)
|
lang := handler.GetLangByCtx(ctx)
|
||||||
title = translator.TrWithData(lang, constant.EmailTplKeyNewAnswerTitle, templateData)
|
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
|
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
|
// NewCommentTemplate new comment template
|
||||||
func (es *EmailService) NewCommentTemplate(ctx context.Context, raw *schema.NewCommentTemplateRawData) (
|
func (es *EmailService) NewCommentTemplate(ctx context.Context, raw *schema.NewCommentTemplateRawData) (
|
||||||
title, body string, err error) {
|
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,
|
templateData.CommentUrl = fmt.Sprintf("%s/questions/%s?commentId=%s", siteInfo.SiteUrl,
|
||||||
raw.QuestionID, raw.CommentID)
|
raw.QuestionID, raw.CommentID)
|
||||||
}
|
}
|
||||||
templateData.SiteName = siteInfo.Name
|
|
||||||
|
|
||||||
lang := handler.GetLangByCtx(ctx)
|
lang := handler.GetLangByCtx(ctx)
|
||||||
title = translator.TrWithData(lang, constant.EmailTplKeyNewCommentTitle, templateData)
|
title = translator.TrWithData(lang, constant.EmailTplKeyNewCommentTitle, templateData)
|
||||||
|
|
|
@ -123,7 +123,12 @@ func (ns *NotificationService) GetNotificationPage(ctx context.Context, searchCo
|
||||||
if !ok {
|
if !ok {
|
||||||
return pager.NewPageModel(0, resp), nil
|
return pager.NewPageModel(0, resp), nil
|
||||||
}
|
}
|
||||||
|
searchInboxType, ok := schema.NotificationInboxType[searchCond.InboxTypeStr]
|
||||||
|
if !ok {
|
||||||
|
return pager.NewPageModel(0, resp), nil
|
||||||
|
}
|
||||||
searchCond.Type = searchType
|
searchCond.Type = searchType
|
||||||
|
searchCond.InboxType = searchInboxType
|
||||||
notifications, total, err := ns.notificationRepo.GetNotificationPage(ctx, searchCond)
|
notifications, total, err := ns.notificationRepo.GetNotificationPage(ctx, searchCond)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -150,6 +150,34 @@ func (qs *QuestionCommon) FindInfoByID(ctx context.Context, questionIDs []string
|
||||||
return list, nil
|
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) {
|
func (qs *QuestionCommon) Info(ctx context.Context, questionID string, loginUserID string) (showinfo *schema.QuestionInfo, err error) {
|
||||||
dbinfo, has, err := qs.questionRepo.GetQuestion(ctx, questionID)
|
dbinfo, has, err := qs.questionRepo.GetQuestion(ctx, questionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -186,9 +214,7 @@ func (qs *QuestionCommon) Info(ctx context.Context, questionID string, loginUser
|
||||||
operation.Level = schema.OperationLevelInfo
|
operation.Level = schema.OperationLevelInfo
|
||||||
showinfo.Operation = operation
|
showinfo.Operation = operation
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"github.com/answerdev/answer/internal/service/activity"
|
"github.com/answerdev/answer/internal/service/activity"
|
||||||
"github.com/answerdev/answer/internal/service/activity_queue"
|
"github.com/answerdev/answer/internal/service/activity_queue"
|
||||||
collectioncommon "github.com/answerdev/answer/internal/service/collection_common"
|
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/meta"
|
||||||
"github.com/answerdev/answer/internal/service/notice_queue"
|
"github.com/answerdev/answer/internal/service/notice_queue"
|
||||||
"github.com/answerdev/answer/internal/service/permission"
|
"github.com/answerdev/answer/internal/service/permission"
|
||||||
|
@ -26,10 +27,12 @@ import (
|
||||||
"github.com/answerdev/answer/internal/service/revision_common"
|
"github.com/answerdev/answer/internal/service/revision_common"
|
||||||
tagcommon "github.com/answerdev/answer/internal/service/tag_common"
|
tagcommon "github.com/answerdev/answer/internal/service/tag_common"
|
||||||
usercommon "github.com/answerdev/answer/internal/service/user_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/htmltext"
|
||||||
"github.com/answerdev/answer/pkg/uid"
|
"github.com/answerdev/answer/pkg/uid"
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
"github.com/segmentfault/pacman/errors"
|
"github.com/segmentfault/pacman/errors"
|
||||||
|
"github.com/segmentfault/pacman/i18n"
|
||||||
"github.com/segmentfault/pacman/log"
|
"github.com/segmentfault/pacman/log"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
@ -42,11 +45,13 @@ type QuestionService struct {
|
||||||
tagCommon *tagcommon.TagCommonService
|
tagCommon *tagcommon.TagCommonService
|
||||||
questioncommon *questioncommon.QuestionCommon
|
questioncommon *questioncommon.QuestionCommon
|
||||||
userCommon *usercommon.UserCommon
|
userCommon *usercommon.UserCommon
|
||||||
|
userRepo usercommon.UserRepo
|
||||||
revisionService *revision_common.RevisionService
|
revisionService *revision_common.RevisionService
|
||||||
metaService *meta.MetaService
|
metaService *meta.MetaService
|
||||||
collectionCommon *collectioncommon.CollectionCommon
|
collectionCommon *collectioncommon.CollectionCommon
|
||||||
answerActivityService *activity.AnswerActivityService
|
answerActivityService *activity.AnswerActivityService
|
||||||
data *data.Data
|
data *data.Data
|
||||||
|
emailService *export.EmailService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewQuestionService(
|
func NewQuestionService(
|
||||||
|
@ -54,23 +59,26 @@ func NewQuestionService(
|
||||||
tagCommon *tagcommon.TagCommonService,
|
tagCommon *tagcommon.TagCommonService,
|
||||||
questioncommon *questioncommon.QuestionCommon,
|
questioncommon *questioncommon.QuestionCommon,
|
||||||
userCommon *usercommon.UserCommon,
|
userCommon *usercommon.UserCommon,
|
||||||
|
userRepo usercommon.UserRepo,
|
||||||
revisionService *revision_common.RevisionService,
|
revisionService *revision_common.RevisionService,
|
||||||
metaService *meta.MetaService,
|
metaService *meta.MetaService,
|
||||||
collectionCommon *collectioncommon.CollectionCommon,
|
collectionCommon *collectioncommon.CollectionCommon,
|
||||||
answerActivityService *activity.AnswerActivityService,
|
answerActivityService *activity.AnswerActivityService,
|
||||||
data *data.Data,
|
data *data.Data,
|
||||||
|
emailService *export.EmailService,
|
||||||
) *QuestionService {
|
) *QuestionService {
|
||||||
return &QuestionService{
|
return &QuestionService{
|
||||||
questionRepo: questionRepo,
|
questionRepo: questionRepo,
|
||||||
tagCommon: tagCommon,
|
tagCommon: tagCommon,
|
||||||
questioncommon: questioncommon,
|
questioncommon: questioncommon,
|
||||||
userCommon: userCommon,
|
userCommon: userCommon,
|
||||||
|
userRepo: userRepo,
|
||||||
revisionService: revisionService,
|
revisionService: revisionService,
|
||||||
metaService: metaService,
|
metaService: metaService,
|
||||||
collectionCommon: collectionCommon,
|
collectionCommon: collectionCommon,
|
||||||
answerActivityService: answerActivityService,
|
answerActivityService: answerActivityService,
|
||||||
data: data,
|
data: data,
|
||||||
|
emailService: emailService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -535,6 +543,115 @@ func (qs *QuestionService) UpdateQuestionCheckTags(ctx context.Context, req *sch
|
||||||
return nil, nil
|
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
|
// UpdateQuestion update question
|
||||||
func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.QuestionUpdate) (questionInfo any, err error) {
|
func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.QuestionUpdate) (questionInfo any, err error) {
|
||||||
var canUpdate bool
|
var canUpdate bool
|
||||||
|
@ -756,6 +873,10 @@ func (qs *QuestionService) GetQuestionAndAddPV(ctx context.Context, questionID,
|
||||||
return qs.GetQuestion(ctx, questionID, loginUserID, per)
|
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 {
|
func (qs *QuestionService) ChangeTag(ctx context.Context, objectTagData *schema.TagChange) error {
|
||||||
return qs.tagCommon.ObjectChangeTag(ctx, objectTagData)
|
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)
|
GetByUserID(ctx context.Context, userID string) (userInfo *entity.User, exist bool, err error)
|
||||||
BatchGetByID(ctx context.Context, ids []string) ([]*entity.User, error)
|
BatchGetByID(ctx context.Context, ids []string) ([]*entity.User, error)
|
||||||
GetByUsername(ctx context.Context, username string) (userInfo *entity.User, exist bool, err 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)
|
GetByEmail(ctx context.Context, email string) (userInfo *entity.User, exist bool, err error)
|
||||||
GetUserCount(ctx context.Context) (count int64, err error)
|
GetUserCount(ctx context.Context) (count int64, err error)
|
||||||
|
SearchUserListByName(ctx context.Context, name string) (userList []*entity.User, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserCommon user service
|
// UserCommon user service
|
||||||
|
@ -74,6 +76,19 @@ func (us *UserCommon) GetUserBasicInfoByUserName(ctx context.Context, username s
|
||||||
return info, exist, nil
|
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 {
|
func (us *UserCommon) UpdateAnswerCount(ctx context.Context, userID string, num int) error {
|
||||||
return us.userRepo.UpdateAnswerCount(ctx, userID, num)
|
return us.userRepo.UpdateAnswerCount(ctx, userID, num)
|
||||||
}
|
}
|
||||||
|
|
|
@ -814,6 +814,19 @@ func (us *UserService) getUserInfoMapping(ctx context.Context, userIDs []string)
|
||||||
return userInfoMapping, nil
|
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(
|
func (us *UserService) warpStatRankingResp(
|
||||||
userInfoMapping map[string]*entity.User,
|
userInfoMapping map[string]*entity.User,
|
||||||
rankStat []*entity.ActivityUserRankStat,
|
rankStat []*entity.ActivityUserRankStat,
|
||||||
|
|
Loading…
Reference in New Issue