feat(answer): support recover answer, question and tag

This commit is contained in:
LinkinStars 2023-09-26 16:15:25 +08:00
parent 4825f991e7
commit fd0b62c1f1
29 changed files with 1189 additions and 282 deletions

View File

@ -103,7 +103,7 @@ const docTemplate = `{
"ApiKeyAuth": []
}
],
"description": "Status:[available,deleted]",
"description": "update answer status",
"consumes": [
"application/json"
],
@ -113,15 +113,15 @@ const docTemplate = `{
"tags": [
"admin"
],
"summary": "AdminSetAnswerStatus",
"summary": "update answer status",
"parameters": [
{
"description": "AdminSetAnswerStatusRequest",
"description": "AdminUpdateAnswerStatusReq",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/schema.AdminSetAnswerStatusRequest"
"$ref": "#/definitions/schema.AdminUpdateAnswerStatusReq"
}
}
],
@ -428,7 +428,7 @@ const docTemplate = `{
"ApiKeyAuth": []
}
],
"description": "Status:[available,closed,deleted]",
"description": "update question status",
"consumes": [
"application/json"
],
@ -438,15 +438,15 @@ const docTemplate = `{
"tags": [
"admin"
],
"summary": "AdminSetQuestionStatus",
"summary": "update question status",
"parameters": [
{
"description": "AdminSetQuestionStatusRequest",
"description": "AdminUpdateQuestionStatusReq",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/schema.AdminSetQuestionStatusRequest"
"$ref": "#/definitions/schema.AdminUpdateQuestionStatusReq"
}
}
],
@ -2142,12 +2142,12 @@ const docTemplate = `{
"summary": "Accepted",
"parameters": [
{
"description": "AnswerAcceptedReq",
"description": "AcceptAnswerReq",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/schema.AnswerAcceptedReq"
"$ref": "#/definitions/schema.AcceptAnswerReq"
}
}
],
@ -2252,6 +2252,45 @@ const docTemplate = `{
}
}
},
"/answer/api/v1/answer/recover": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "recover deleted answer",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Answer"
],
"summary": "recover answer",
"parameters": [
{
"description": "answer",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/schema.RecoverAnswerReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.RespBody"
}
}
}
}
},
"/answer/api/v1/collection/switch": {
"post": {
"security": [
@ -4031,6 +4070,45 @@ const docTemplate = `{
}
}
},
"/answer/api/v1/question/recover": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "recover deleted question",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Question"
],
"summary": "recover deleted question",
"parameters": [
{
"description": "question",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/schema.QuestionRecoverReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.RespBody"
}
}
}
}
},
"/answer/api/v1/question/reopen": {
"put": {
"security": [
@ -4077,7 +4155,7 @@ const docTemplate = `{
"ApiKeyAuth": []
}
],
"description": "add question title like",
"description": "fuzzy query similar questions based on title",
"consumes": [
"application/json"
],
@ -4087,7 +4165,7 @@ const docTemplate = `{
"tags": [
"Question"
],
"summary": "add question title like",
"summary": "fuzzy query similar questions based on title",
"parameters": [
{
"type": "string",
@ -4820,6 +4898,40 @@ const docTemplate = `{
}
}
},
"/answer/api/v1/tag/recover": {
"post": {
"description": "recover delete tag",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Tag"
],
"summary": "recover delete tag",
"parameters": [
{
"description": "tag",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/schema.RecoverTagReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.RespBody"
}
}
}
}
},
"/answer/api/v1/tag/synonym": {
"put": {
"description": "update tag",
@ -6378,6 +6490,21 @@ const docTemplate = `{
"list": {}
}
},
"schema.AcceptAnswerReq": {
"type": "object",
"required": [
"question_id"
],
"properties": {
"answer_id": {
"type": "string"
},
"question_id": {
"type": "string",
"maxLength": 30
}
}
},
"schema.ActObjectInfo": {
"type": "object",
"properties": {
@ -6425,9 +6552,6 @@ const docTemplate = `{
"created_at": {
"type": "integer"
},
"id": {
"type": "string"
},
"object_id": {
"type": "string"
},
@ -6437,11 +6561,8 @@ const docTemplate = `{
"revision_id": {
"type": "string"
},
"user_display_name": {
"type": "string"
},
"username": {
"type": "string"
"user_info": {
"$ref": "#/definitions/schema.UserBasicInfo"
}
}
},
@ -6584,36 +6705,42 @@ const docTemplate = `{
}
}
},
"schema.AdminSetAnswerStatusRequest": {
"schema.AdminUpdateAnswerStatusReq": {
"type": "object",
"required": [
"answer_id",
"status"
],
"properties": {
"answer_id": {
"type": "string"
},
"status": {
"type": "string"
"type": "string",
"enum": [
"available",
"deleted"
]
}
}
},
"schema.AdminSetQuestionStatusRequest": {
"schema.AdminUpdateQuestionStatusReq": {
"type": "object",
"required": [
"question_id",
"status"
],
"properties": {
"question_id": {
"type": "string"
},
"status": {
"type": "string"
}
}
},
"schema.AnswerAcceptedReq": {
"type": "object",
"properties": {
"answer_id": {
"type": "string"
},
"question_id": {
"type": "string"
"type": "string",
"enum": [
"available",
"closed",
"deleted"
]
}
}
},
@ -7088,10 +7215,6 @@ const docTemplate = `{
"description": "user id",
"type": "string"
},
"ip_info": {
"description": "ip info",
"type": "string"
},
"language": {
"description": "language",
"type": "string"
@ -8088,11 +8211,25 @@ const docTemplate = `{
"rank": {
"type": "integer"
},
"status": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"schema.QuestionRecoverReq": {
"type": "object",
"required": [
"question_id"
],
"properties": {
"question_id": {
"type": "string"
}
}
},
"schema.QuestionUpdate": {
"type": "object",
"required": [
@ -8168,6 +8305,28 @@ const docTemplate = `{
}
}
},
"schema.RecoverAnswerReq": {
"type": "object",
"required": [
"answer_id"
],
"properties": {
"answer_id": {
"type": "string"
}
}
},
"schema.RecoverTagReq": {
"type": "object",
"required": [
"tag_id"
],
"properties": {
"tag_id": {
"type": "string"
}
}
},
"schema.RemoveAnswerReq": {
"type": "object",
"required": [
@ -8317,7 +8476,7 @@ const docTemplate = `{
"description": "user info",
"allOf": [
{
"$ref": "#/definitions/schema.UserBasicInfo"
"$ref": "#/definitions/schema.SearchObjectUser"
}
]
},
@ -8326,6 +8485,26 @@ const docTemplate = `{
}
}
},
"schema.SearchObjectUser": {
"type": "object",
"properties": {
"display_name": {
"type": "string"
},
"id": {
"type": "string"
},
"rank": {
"type": "integer"
},
"status": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"schema.SearchResp": {
"type": "object",
"properties": {
@ -8967,7 +9146,7 @@ const docTemplate = `{
"type": "string"
},
"captcha_id": {
"description": "captcha_id",
"description": "whether user can delete it",
"type": "string"
},
"comment_id": {
@ -8998,35 +9177,25 @@ const docTemplate = `{
"type": "object",
"properties": {
"avatar": {
"description": "avatar",
"allOf": [
{
"$ref": "#/definitions/schema.AvatarInfo"
}
]
"$ref": "#/definitions/schema.AvatarInfo"
},
"bio": {
"description": "bio",
"type": "string",
"maxLength": 4096
},
"display_name": {
"description": "display_name",
"type": "string",
"maxLength": 30
},
"location": {
"description": "location",
"type": "string",
"maxLength": 100
},
"username": {
"description": "username",
"type": "string",
"maxLength": 30
},
"website": {
"description": "website",
"type": "string",
"maxLength": 500
}
@ -9250,6 +9419,9 @@ const docTemplate = `{
"user_id"
],
"properties": {
"remove_all_content": {
"type": "boolean"
},
"status": {
"type": "string",
"enum": [
@ -9276,9 +9448,6 @@ const docTemplate = `{
"id": {
"type": "string"
},
"ip_info": {
"type": "string"
},
"location": {
"type": "string"
},
@ -9408,10 +9577,6 @@ const docTemplate = `{
"description": "user id",
"type": "string"
},
"ip_info": {
"description": "ip info",
"type": "string"
},
"language": {
"description": "language",
"type": "string"

View File

@ -91,7 +91,7 @@
"ApiKeyAuth": []
}
],
"description": "Status:[available,deleted]",
"description": "update answer status",
"consumes": [
"application/json"
],
@ -101,15 +101,15 @@
"tags": [
"admin"
],
"summary": "AdminSetAnswerStatus",
"summary": "update answer status",
"parameters": [
{
"description": "AdminSetAnswerStatusRequest",
"description": "AdminUpdateAnswerStatusReq",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/schema.AdminSetAnswerStatusRequest"
"$ref": "#/definitions/schema.AdminUpdateAnswerStatusReq"
}
}
],
@ -416,7 +416,7 @@
"ApiKeyAuth": []
}
],
"description": "Status:[available,closed,deleted]",
"description": "update question status",
"consumes": [
"application/json"
],
@ -426,15 +426,15 @@
"tags": [
"admin"
],
"summary": "AdminSetQuestionStatus",
"summary": "update question status",
"parameters": [
{
"description": "AdminSetQuestionStatusRequest",
"description": "AdminUpdateQuestionStatusReq",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/schema.AdminSetQuestionStatusRequest"
"$ref": "#/definitions/schema.AdminUpdateQuestionStatusReq"
}
}
],
@ -2130,12 +2130,12 @@
"summary": "Accepted",
"parameters": [
{
"description": "AnswerAcceptedReq",
"description": "AcceptAnswerReq",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/schema.AnswerAcceptedReq"
"$ref": "#/definitions/schema.AcceptAnswerReq"
}
}
],
@ -2240,6 +2240,45 @@
}
}
},
"/answer/api/v1/answer/recover": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "recover deleted answer",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Answer"
],
"summary": "recover answer",
"parameters": [
{
"description": "answer",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/schema.RecoverAnswerReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.RespBody"
}
}
}
}
},
"/answer/api/v1/collection/switch": {
"post": {
"security": [
@ -4019,6 +4058,45 @@
}
}
},
"/answer/api/v1/question/recover": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "recover deleted question",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Question"
],
"summary": "recover deleted question",
"parameters": [
{
"description": "question",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/schema.QuestionRecoverReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.RespBody"
}
}
}
}
},
"/answer/api/v1/question/reopen": {
"put": {
"security": [
@ -4065,7 +4143,7 @@
"ApiKeyAuth": []
}
],
"description": "add question title like",
"description": "fuzzy query similar questions based on title",
"consumes": [
"application/json"
],
@ -4075,7 +4153,7 @@
"tags": [
"Question"
],
"summary": "add question title like",
"summary": "fuzzy query similar questions based on title",
"parameters": [
{
"type": "string",
@ -4808,6 +4886,40 @@
}
}
},
"/answer/api/v1/tag/recover": {
"post": {
"description": "recover delete tag",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Tag"
],
"summary": "recover delete tag",
"parameters": [
{
"description": "tag",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/schema.RecoverTagReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.RespBody"
}
}
}
}
},
"/answer/api/v1/tag/synonym": {
"put": {
"description": "update tag",
@ -6366,6 +6478,21 @@
"list": {}
}
},
"schema.AcceptAnswerReq": {
"type": "object",
"required": [
"question_id"
],
"properties": {
"answer_id": {
"type": "string"
},
"question_id": {
"type": "string",
"maxLength": 30
}
}
},
"schema.ActObjectInfo": {
"type": "object",
"properties": {
@ -6413,9 +6540,6 @@
"created_at": {
"type": "integer"
},
"id": {
"type": "string"
},
"object_id": {
"type": "string"
},
@ -6425,11 +6549,8 @@
"revision_id": {
"type": "string"
},
"user_display_name": {
"type": "string"
},
"username": {
"type": "string"
"user_info": {
"$ref": "#/definitions/schema.UserBasicInfo"
}
}
},
@ -6572,36 +6693,42 @@
}
}
},
"schema.AdminSetAnswerStatusRequest": {
"schema.AdminUpdateAnswerStatusReq": {
"type": "object",
"required": [
"answer_id",
"status"
],
"properties": {
"answer_id": {
"type": "string"
},
"status": {
"type": "string"
"type": "string",
"enum": [
"available",
"deleted"
]
}
}
},
"schema.AdminSetQuestionStatusRequest": {
"schema.AdminUpdateQuestionStatusReq": {
"type": "object",
"required": [
"question_id",
"status"
],
"properties": {
"question_id": {
"type": "string"
},
"status": {
"type": "string"
}
}
},
"schema.AnswerAcceptedReq": {
"type": "object",
"properties": {
"answer_id": {
"type": "string"
},
"question_id": {
"type": "string"
"type": "string",
"enum": [
"available",
"closed",
"deleted"
]
}
}
},
@ -7076,10 +7203,6 @@
"description": "user id",
"type": "string"
},
"ip_info": {
"description": "ip info",
"type": "string"
},
"language": {
"description": "language",
"type": "string"
@ -8076,11 +8199,25 @@
"rank": {
"type": "integer"
},
"status": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"schema.QuestionRecoverReq": {
"type": "object",
"required": [
"question_id"
],
"properties": {
"question_id": {
"type": "string"
}
}
},
"schema.QuestionUpdate": {
"type": "object",
"required": [
@ -8156,6 +8293,28 @@
}
}
},
"schema.RecoverAnswerReq": {
"type": "object",
"required": [
"answer_id"
],
"properties": {
"answer_id": {
"type": "string"
}
}
},
"schema.RecoverTagReq": {
"type": "object",
"required": [
"tag_id"
],
"properties": {
"tag_id": {
"type": "string"
}
}
},
"schema.RemoveAnswerReq": {
"type": "object",
"required": [
@ -8305,7 +8464,7 @@
"description": "user info",
"allOf": [
{
"$ref": "#/definitions/schema.UserBasicInfo"
"$ref": "#/definitions/schema.SearchObjectUser"
}
]
},
@ -8314,6 +8473,26 @@
}
}
},
"schema.SearchObjectUser": {
"type": "object",
"properties": {
"display_name": {
"type": "string"
},
"id": {
"type": "string"
},
"rank": {
"type": "integer"
},
"status": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"schema.SearchResp": {
"type": "object",
"properties": {
@ -8955,7 +9134,7 @@
"type": "string"
},
"captcha_id": {
"description": "captcha_id",
"description": "whether user can delete it",
"type": "string"
},
"comment_id": {
@ -8986,35 +9165,25 @@
"type": "object",
"properties": {
"avatar": {
"description": "avatar",
"allOf": [
{
"$ref": "#/definitions/schema.AvatarInfo"
}
]
"$ref": "#/definitions/schema.AvatarInfo"
},
"bio": {
"description": "bio",
"type": "string",
"maxLength": 4096
},
"display_name": {
"description": "display_name",
"type": "string",
"maxLength": 30
},
"location": {
"description": "location",
"type": "string",
"maxLength": 100
},
"username": {
"description": "username",
"type": "string",
"maxLength": 30
},
"website": {
"description": "website",
"type": "string",
"maxLength": 500
}
@ -9238,6 +9407,9 @@
"user_id"
],
"properties": {
"remove_all_content": {
"type": "boolean"
},
"status": {
"type": "string",
"enum": [
@ -9264,9 +9436,6 @@
"id": {
"type": "string"
},
"ip_info": {
"type": "string"
},
"location": {
"type": "string"
},
@ -9396,10 +9565,6 @@
"description": "user id",
"type": "string"
},
"ip_info": {
"description": "ip info",
"type": "string"
},
"language": {
"description": "language",
"type": "string"

View File

@ -99,6 +99,16 @@ definitions:
type: integer
list: {}
type: object
schema.AcceptAnswerReq:
properties:
answer_id:
type: string
question_id:
maxLength: 30
type: string
required:
- question_id
type: object
schema.ActObjectInfo:
properties:
answer_id:
@ -130,18 +140,14 @@ definitions:
type: string
created_at:
type: integer
id:
type: string
object_id:
type: string
object_type:
type: string
revision_id:
type: string
user_display_name:
type: string
username:
type: string
user_info:
$ref: '#/definitions/schema.UserBasicInfo'
type: object
schema.ActionRecordResp:
properties:
@ -244,26 +250,32 @@ definitions:
description: users info line by line
type: string
type: object
schema.AdminSetAnswerStatusRequest:
schema.AdminUpdateAnswerStatusReq:
properties:
answer_id:
type: string
status:
enum:
- available
- deleted
type: string
required:
- answer_id
- status
type: object
schema.AdminSetQuestionStatusRequest:
schema.AdminUpdateQuestionStatusReq:
properties:
question_id:
type: string
status:
enum:
- available
- closed
- deleted
type: string
type: object
schema.AnswerAcceptedReq:
properties:
answer_id:
type: string
question_id:
type: string
required:
- question_id
- status
type: object
schema.AnswerAddReq:
properties:
@ -596,9 +608,6 @@ definitions:
id:
description: user id
type: string
ip_info:
description: ip info
type: string
language:
description: language
type: string
@ -1304,9 +1313,18 @@ definitions:
type: string
rank:
type: integer
status:
type: string
username:
type: string
type: object
schema.QuestionRecoverReq:
properties:
question_id:
type: string
required:
- question_id
type: object
schema.QuestionUpdate:
properties:
captcha_code:
@ -1361,6 +1379,20 @@ definitions:
required:
- id
type: object
schema.RecoverAnswerReq:
properties:
answer_id:
type: string
required:
- answer_id
type: object
schema.RecoverTagReq:
properties:
tag_id:
type: string
required:
- tag_id
type: object
schema.RemoveAnswerReq:
properties:
captcha_code:
@ -1461,11 +1493,24 @@ definitions:
type: string
user_info:
allOf:
- $ref: '#/definitions/schema.UserBasicInfo'
- $ref: '#/definitions/schema.SearchObjectUser'
description: user info
vote_count:
type: integer
type: object
schema.SearchObjectUser:
properties:
display_name:
type: string
id:
type: string
rank:
type: integer
status:
type: string
username:
type: string
type: object
schema.SearchResp:
properties:
count:
@ -1902,7 +1947,7 @@ definitions:
captcha_code:
type: string
captcha_id:
description: captcha_id
description: whether user can delete it
type: string
comment_id:
description: comment id
@ -1927,27 +1972,20 @@ definitions:
schema.UpdateInfoRequest:
properties:
avatar:
allOf:
- $ref: '#/definitions/schema.AvatarInfo'
description: avatar
$ref: '#/definitions/schema.AvatarInfo'
bio:
description: bio
maxLength: 4096
type: string
display_name:
description: display_name
maxLength: 30
type: string
location:
description: location
maxLength: 100
type: string
username:
description: username
maxLength: 30
type: string
website:
description: website
maxLength: 500
type: string
type: object
@ -2099,6 +2137,8 @@ definitions:
type: object
schema.UpdateUserStatusReq:
properties:
remove_all_content:
type: boolean
status:
enum:
- normal
@ -2120,8 +2160,6 @@ definitions:
type: string
id:
type: string
ip_info:
type: string
location:
type: string
rank:
@ -2214,9 +2252,6 @@ definitions:
id:
description: user id
type: string
ip_info:
description: ip info
type: string
language:
description: language
type: string
@ -2455,14 +2490,14 @@ paths:
put:
consumes:
- application/json
description: Status:[available,deleted]
description: update answer status
parameters:
- description: AdminSetAnswerStatusRequest
- description: AdminUpdateAnswerStatusReq
in: body
name: data
required: true
schema:
$ref: '#/definitions/schema.AdminSetAnswerStatusRequest'
$ref: '#/definitions/schema.AdminUpdateAnswerStatusReq'
produces:
- application/json
responses:
@ -2472,7 +2507,7 @@ paths:
$ref: '#/definitions/handler.RespBody'
security:
- ApiKeyAuth: []
summary: AdminSetAnswerStatus
summary: update answer status
tags:
- admin
/answer/admin/api/dashboard:
@ -2653,14 +2688,14 @@ paths:
put:
consumes:
- application/json
description: Status:[available,closed,deleted]
description: update question status
parameters:
- description: AdminSetQuestionStatusRequest
- description: AdminUpdateQuestionStatusReq
in: body
name: data
required: true
schema:
$ref: '#/definitions/schema.AdminSetQuestionStatusRequest'
$ref: '#/definitions/schema.AdminUpdateQuestionStatusReq'
produces:
- application/json
responses:
@ -2670,7 +2705,7 @@ paths:
$ref: '#/definitions/handler.RespBody'
security:
- ApiKeyAuth: []
summary: AdminSetQuestionStatus
summary: update question status
tags:
- admin
/answer/admin/api/reasons:
@ -3667,12 +3702,12 @@ paths:
- application/json
description: Accepted
parameters:
- description: AnswerAcceptedReq
- description: AcceptAnswerReq
in: body
name: data
required: true
schema:
$ref: '#/definitions/schema.AnswerAcceptedReq'
$ref: '#/definitions/schema.AcceptAnswerReq'
produces:
- application/json
responses:
@ -3745,6 +3780,30 @@ paths:
summary: AnswerList
tags:
- api-answer
/answer/api/v1/answer/recover:
post:
consumes:
- application/json
description: recover deleted answer
parameters:
- description: answer
in: body
name: data
required: true
schema:
$ref: '#/definitions/schema.RecoverAnswerReq'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.RespBody'
security:
- ApiKeyAuth: []
summary: recover answer
tags:
- Answer
/answer/api/v1/collection/switch:
post:
consumes:
@ -4830,6 +4889,30 @@ paths:
summary: get questions by page
tags:
- Question
/answer/api/v1/question/recover:
post:
consumes:
- application/json
description: recover deleted question
parameters:
- description: question
in: body
name: data
required: true
schema:
$ref: '#/definitions/schema.QuestionRecoverReq'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.RespBody'
security:
- ApiKeyAuth: []
summary: recover deleted question
tags:
- Question
/answer/api/v1/question/reopen:
put:
consumes:
@ -4858,7 +4941,7 @@ paths:
get:
consumes:
- application/json
description: add question title like
description: fuzzy query similar questions based on title
parameters:
- default: string
description: title
@ -4875,7 +4958,7 @@ paths:
$ref: '#/definitions/handler.RespBody'
security:
- ApiKeyAuth: []
summary: add question title like
summary: fuzzy query similar questions based on title
tags:
- Question
/answer/api/v1/question/similar/tag:
@ -5312,6 +5395,28 @@ paths:
summary: update tag
tags:
- Tag
/answer/api/v1/tag/recover:
post:
consumes:
- application/json
description: recover delete tag
parameters:
- description: tag
in: body
name: data
required: true
schema:
$ref: '#/definitions/schema.RecoverTagReq'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.RespBody'
summary: recover delete tag
tags:
- Tag
/answer/api/v1/tag/synonym:
put:
consumes:

View File

@ -37,6 +37,8 @@ backend:
other: List
invite_someone_to_answer:
other: Edit
undelete:
other: Undelete
role:
name:
user:

View File

@ -90,6 +90,40 @@ func (ac *AnswerController) RemoveAnswer(ctx *gin.Context) {
handler.HandleResponse(ctx, err, nil)
}
// RecoverAnswer recover answer
// @Summary recover answer
// @Description recover deleted answer
// @Tags Answer
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param data body schema.RecoverAnswerReq true "answer"
// @Success 200 {object} handler.RespBody
// @Router /answer/api/v1/answer/recover [post]
func (ac *AnswerController) RecoverAnswer(ctx *gin.Context) {
req := &schema.RecoverAnswerReq{}
if handler.BindAndCheck(ctx, req) {
return
}
req.AnswerID = uid.DeShortID(req.AnswerID)
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
canList, err := ac.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
permission.AnswerUnDelete,
})
if err != nil {
handler.HandleResponse(ctx, err, nil)
return
}
if !canList[0] {
handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil)
return
}
err = ac.answerService.RecoverAnswer(ctx, req)
handler.HandleResponse(ctx, err, nil)
}
// Get godoc
// @Summary Get Answer
// @Description Get Answer
@ -196,7 +230,8 @@ func (ac *AnswerController) Add(ctx *gin.Context) {
handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil)
return
}
info.MemberActions = permission.GetAnswerPermission(ctx, req.UserID, info.UserID, req.CanEdit, req.CanDelete)
info.MemberActions = permission.GetAnswerPermission(ctx, req.UserID, info.UserID,
0, req.CanEdit, req.CanDelete, false)
handler.HandleResponse(ctx, nil, gin.H{
"info": info,
"question": questionInfo,
@ -293,6 +328,7 @@ func (ac *AnswerController) AnswerList(ctx *gin.Context) {
canList, err := ac.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
permission.AnswerEdit,
permission.AnswerDelete,
permission.AnswerUnDelete,
})
if err != nil {
handler.HandleResponse(ctx, err, nil)
@ -300,6 +336,7 @@ func (ac *AnswerController) AnswerList(ctx *gin.Context) {
}
req.CanEdit = canList[0]
req.CanDelete = canList[1]
req.CanRecover = canList[2]
list, count, err := ac.answerService.SearchList(ctx, req)
if err != nil {
@ -345,25 +382,24 @@ func (ac *AnswerController) Accepted(ctx *gin.Context) {
handler.HandleResponse(ctx, err, nil)
}
// AdminSetAnswerStatus godoc
// @Summary AdminSetAnswerStatus
// @Description Status:[available,deleted]
// AdminUpdateAnswerStatus update answer status
// @Summary update answer status
// @Description update answer status
// @Tags admin
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param data body schema.AdminSetAnswerStatusRequest true "AdminSetAnswerStatusRequest"
// @Router /answer/admin/api/answer/status [put]
// @Param data body schema.AdminUpdateAnswerStatusReq true "AdminUpdateAnswerStatusReq"
// @Success 200 {object} handler.RespBody
func (ac *AnswerController) AdminSetAnswerStatus(ctx *gin.Context) {
req := &schema.AdminSetAnswerStatusRequest{}
// @Router /answer/admin/api/answer/status [put]
func (ac *AnswerController) AdminUpdateAnswerStatus(ctx *gin.Context) {
req := &schema.AdminUpdateAnswerStatusReq{}
if handler.BindAndCheck(ctx, req) {
return
}
req.AnswerID = uid.DeShortID(req.AnswerID)
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
err := ac.answerService.AdminSetAnswerStatus(ctx, req)
handler.HandleResponse(ctx, err, gin.H{})
handler.HandleResponse(ctx, err, nil)
}

View File

@ -221,6 +221,7 @@ func (qc *QuestionController) GetQuestion(ctx *gin.Context) {
permission.QuestionHide,
permission.QuestionShow,
permission.AnswerInviteSomeoneToAnswer,
permission.QuestionUnDelete,
})
if err != nil {
handler.HandleResponse(ctx, err, nil)
@ -237,6 +238,7 @@ func (qc *QuestionController) GetQuestion(ctx *gin.Context) {
req.CanHide = canList[6]
req.CanShow = canList[7]
req.CanInviteOtherToAnswer = canList[8]
req.CanRecover = canList[9]
info, err := qc.questionService.GetQuestionAndAddPV(ctx, id, userID, req)
if err != nil {
@ -627,6 +629,40 @@ func (qc *QuestionController) UpdateQuestion(ctx *gin.Context) {
handler.HandleResponse(ctx, nil, &schema.UpdateQuestionResp{WaitForReview: !req.NoNeedReview})
}
// QuestionRecover recover deleted question
// @Summary recover deleted question
// @Description recover deleted question
// @Tags Question
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param data body schema.QuestionRecoverReq true "question"
// @Success 200 {object} handler.RespBody
// @Router /answer/api/v1/question/recover [post]
func (qc *QuestionController) QuestionRecover(ctx *gin.Context) {
req := &schema.QuestionRecoverReq{}
if handler.BindAndCheck(ctx, req) {
return
}
req.QuestionID = uid.DeShortID(req.QuestionID)
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
canList, err := qc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
permission.QuestionUnDelete,
})
if err != nil {
handler.HandleResponse(ctx, err, nil)
return
}
if !canList[0] {
handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil)
return
}
err = qc.questionService.RecoverQuestion(ctx, req)
handler.HandleResponse(ctx, err, nil)
}
// UpdateQuestionInviteUser update question invite user
// @Summary update question invite user
// @Description update question invite user
@ -842,22 +878,24 @@ func (qc *QuestionController) AdminAnswerPage(ctx *gin.Context) {
handler.HandleResponse(ctx, err, resp)
}
// AdminSetQuestionStatus godoc
// @Summary AdminSetQuestionStatus
// @Description Status:[available,closed,deleted]
// AdminUpdateQuestionStatus update question status
// @Summary update question status
// @Description update question status
// @Tags admin
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param data body schema.AdminSetQuestionStatusRequest true "AdminSetQuestionStatusRequest"
// @Router /answer/admin/api/question/status [put]
// @Param data body schema.AdminUpdateQuestionStatusReq true "AdminUpdateQuestionStatusReq"
// @Success 200 {object} handler.RespBody
func (qc *QuestionController) AdminSetQuestionStatus(ctx *gin.Context) {
req := &schema.AdminSetQuestionStatusRequest{}
// @Router /answer/admin/api/question/status [put]
func (qc *QuestionController) AdminUpdateQuestionStatus(ctx *gin.Context) {
req := &schema.AdminUpdateQuestionStatusReq{}
if handler.BindAndCheck(ctx, req) {
return
}
req.QuestionID = uid.DeShortID(req.QuestionID)
err := qc.questionService.AdminSetQuestionStatus(ctx, req.QuestionID, req.StatusStr)
handler.HandleResponse(ctx, err, gin.H{})
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
err := qc.questionService.AdminSetQuestionStatus(ctx, req)
handler.HandleResponse(ctx, err, nil)
}

View File

@ -167,6 +167,38 @@ func (tc *TagController) UpdateTag(ctx *gin.Context) {
}
}
// RecoverTag recover delete tag
// @Summary recover delete tag
// @Description recover delete tag
// @Tags Tag
// @Accept json
// @Produce json
// @Param data body schema.RecoverTagReq true "tag"
// @Success 200 {object} handler.RespBody
// @Router /answer/api/v1/tag/recover [post]
func (tc *TagController) RecoverTag(ctx *gin.Context) {
req := &schema.RecoverTagReq{}
if handler.BindAndCheck(ctx, req) {
return
}
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
canList, err := tc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
permission.TagUnDelete,
})
if err != nil {
handler.HandleResponse(ctx, err, nil)
return
}
if !canList[0] {
handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil)
return
}
err = tc.tagService.RecoverTag(ctx, req)
handler.HandleResponse(ctx, err, nil)
}
// GetTagInfo get tag one
// @Summary get tag one
// @Description get tag one
@ -187,6 +219,7 @@ func (tc *TagController) GetTagInfo(ctx *gin.Context) {
canList, err := tc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
permission.TagEdit,
permission.TagDelete,
permission.TagUnDelete,
})
if err != nil {
handler.HandleResponse(ctx, err, nil)
@ -194,6 +227,7 @@ func (tc *TagController) GetTagInfo(ctx *gin.Context) {
}
req.CanEdit = canList[0]
req.CanDelete = canList[1]
req.CanRecover = canList[2]
resp, err := tc.tagService.GetTagInfo(ctx, req)
handler.HandleResponse(ctx, err, resp)

View File

@ -7,6 +7,11 @@ const (
TagStatusDeleted = 10
)
var TagStatusDisplayMapping = map[int]string{
TagStatusAvailable: "available",
TagStatusDeleted: "deleted",
}
// Tag tag
type Tag struct {
ID string `xorm:"not null pk comment('tag_id') BIGINT(20) id"`

View File

@ -95,6 +95,9 @@ var (
{ID: 36, Name: "question unpin", PowerType: permission.QuestionUnPin, Description: "untop the question"},
{ID: 37, Name: "question show", PowerType: permission.QuestionShow, Description: "show the question"},
{ID: 38, Name: "invite someone to answer", PowerType: permission.AnswerInviteSomeoneToAnswer, Description: "invite someone to answer"},
{ID: 39, Name: "recover answer", PowerType: permission.AnswerUnDelete, Description: "recover deleted answer"},
{ID: 40, Name: "recover question", PowerType: permission.QuestionUnDelete, Description: "recover deleted question"},
{ID: 41, Name: "recover tag", PowerType: permission.TagUnDelete, Description: "recover deleted tag"},
}
rolePowerRels = []*entity.RolePowerRel{
@ -137,6 +140,9 @@ var (
{RoleID: 2, PowerType: permission.QuestionUnPin},
{RoleID: 2, PowerType: permission.QuestionShow},
{RoleID: 2, PowerType: permission.AnswerInviteSomeoneToAnswer},
{RoleID: 2, PowerType: permission.AnswerUnDelete},
{RoleID: 2, PowerType: permission.QuestionUnDelete},
{RoleID: 2, PowerType: permission.TagUnDelete},
{RoleID: 3, PowerType: permission.QuestionAdd},
{RoleID: 3, PowerType: permission.QuestionEdit},
@ -176,6 +182,9 @@ var (
{RoleID: 3, PowerType: permission.QuestionUnPin},
{RoleID: 3, PowerType: permission.QuestionShow},
{RoleID: 3, PowerType: permission.AnswerInviteSomeoneToAnswer},
{RoleID: 3, PowerType: permission.AnswerUnDelete},
{RoleID: 3, PowerType: permission.QuestionUnDelete},
{RoleID: 3, PowerType: permission.TagUnDelete},
}
adminUserRoleRel = &entity.UserRoleRel{
@ -310,5 +319,8 @@ var (
{ID: 125, Key: "rank.question.show", Value: `-1`},
{ID: 126, Key: "rank.question.hide", Value: `-1`},
{ID: 127, Key: "rank.answer.invite_someone_to_answer", Value: `1000`},
{ID: 128, Key: "rank.answer.undeleted", Value: `-1`},
{ID: 129, Key: "rank.question.undeleted", Value: `-1`},
{ID: 130, Key: "rank.tag.undeleted", Value: `-1`},
}
)

View File

@ -73,6 +73,7 @@ var migrations = []Migration{
NewMigration("v1.1.1", "update the length of revision content", updateTheLengthOfRevisionContent, false),
NewMigration("v1.1.2", "add notification config", addNoticeConfig, true),
NewMigration("v1.1.3", "set default user notification config", setDefaultUserNotificationConfig, false),
NewMigration("v1.2.0", "add recover answer permission", addRecoverPermission, false),
}
func GetMigrations() []Migration {

View File

@ -0,0 +1,79 @@
package migrations
import (
"context"
"fmt"
"github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/service/permission"
"github.com/segmentfault/pacman/log"
"xorm.io/xorm"
)
func addRecoverPermission(ctx context.Context, x *xorm.Engine) error {
powers := []*entity.Power{
{ID: 39, Name: "recover answer", PowerType: permission.AnswerUnDelete, Description: "recover deleted answer"},
{ID: 40, Name: "recover question", PowerType: permission.QuestionUnDelete, Description: "recover deleted question"},
{ID: 41, Name: "recover tag", PowerType: permission.TagUnDelete, Description: "recover deleted tag"},
}
for _, power := range powers {
exist, err := x.Context(ctx).Get(&entity.Power{ID: power.ID})
if err != nil {
return err
}
if exist {
_, err = x.Context(ctx).ID(power.ID).Update(power)
} else {
_, err = x.Context(ctx).Insert(power)
}
if err != nil {
return err
}
}
rolePowerRels := []*entity.RolePowerRel{
{RoleID: 2, PowerType: permission.AnswerUnDelete},
{RoleID: 2, PowerType: permission.QuestionUnDelete},
{RoleID: 2, PowerType: permission.TagUnDelete},
{RoleID: 3, PowerType: permission.AnswerUnDelete},
{RoleID: 3, PowerType: permission.QuestionUnDelete},
{RoleID: 3, PowerType: permission.TagUnDelete},
}
for _, rel := range rolePowerRels {
exist, err := x.Context(ctx).Get(&entity.RolePowerRel{RoleID: rel.RoleID, PowerType: rel.PowerType})
if err != nil {
return err
}
if exist {
continue
}
_, err = x.Context(ctx).Insert(rel)
if err != nil {
return err
}
}
defaultConfigTable := []*entity.Config{
{ID: 128, Key: "rank.answer.undeleted", Value: `-1`},
{ID: 129, Key: "rank.question.undeleted", Value: `-1`},
{ID: 130, Key: "rank.tag.undeleted", Value: `-1`},
}
for _, c := range defaultConfigTable {
exist, err := x.Context(ctx).Get(&entity.Config{ID: c.ID})
if err != nil {
return fmt.Errorf("get config failed: %w", err)
}
if exist {
if _, err = x.Context(ctx).Update(c, &entity.Config{ID: c.ID}); err != nil {
log.Errorf("update %+v config failed: %s", c, err)
return fmt.Errorf("update config failed: %w", err)
}
continue
}
if _, err = x.Context(ctx).Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
log.Errorf("insert %+v config failed: %s", c, err)
return fmt.Errorf("add config failed: %w", err)
}
}
return nil
}

View File

@ -66,17 +66,28 @@ func (ar *answerRepo) AddAnswer(ctx context.Context, answer *entity.Answer) (err
}
// RemoveAnswer delete answer
func (ar *answerRepo) RemoveAnswer(ctx context.Context, id string) (err error) {
id = uid.DeShortID(id)
answer := &entity.Answer{
ID: id,
func (ar *answerRepo) RemoveAnswer(ctx context.Context, answerID string) (err error) {
answerID = uid.DeShortID(answerID)
_, err = ar.data.DB.Context(ctx).ID(answerID).Cols("status").Update(&entity.Answer{
Status: entity.AnswerStatusDeleted,
}
_, err = ar.data.DB.Context(ctx).Where("id = ?", id).Cols("status").Update(answer)
})
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
_ = ar.updateSearch(ctx, answer.ID)
_ = ar.updateSearch(ctx, answerID)
return nil
}
// RecoverAnswer recover answer
func (ar *answerRepo) RecoverAnswer(ctx context.Context, answerID string) (err error) {
answerID = uid.DeShortID(answerID)
_, err = ar.data.DB.Context(ctx).ID(answerID).Cols("status").Update(&entity.Answer{
Status: entity.AnswerStatusAvailable,
})
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
_ = ar.updateSearch(ctx, answerID)
return nil
}
@ -226,10 +237,10 @@ func (ar *answerRepo) UpdateAcceptedStatus(ctx context.Context, acceptedAnswerID
}
// GetByID
func (ar *answerRepo) GetByID(ctx context.Context, id string) (*entity.Answer, bool, error) {
func (ar *answerRepo) GetByID(ctx context.Context, answerID string) (*entity.Answer, bool, error) {
var resp entity.Answer
id = uid.DeShortID(id)
has, err := ar.data.DB.Context(ctx).Where("id =? ", id).Get(&resp)
answerID = uid.DeShortID(answerID)
has, err := ar.data.DB.Context(ctx).ID(answerID).Get(&resp)
if err != nil {
return &resp, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}

View File

@ -138,6 +138,16 @@ func (qr *questionRepo) UpdateQuestionStatusWithOutUpdateTime(ctx context.Contex
return nil
}
func (qr *questionRepo) RecoverQuestion(ctx context.Context, questionID string) (err error) {
questionID = uid.DeShortID(questionID)
_, err = qr.data.DB.Context(ctx).ID(questionID).Cols("status").Update(&entity.Question{Status: entity.QuestionStatusAvailable})
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
_ = qr.updateSearch(ctx, questionID)
return nil
}
func (qr *questionRepo) UpdateQuestionOperation(ctx context.Context, question *entity.Question) (err error) {
question.ID = uid.DeShortID(question.ID)
_, err = qr.data.DB.Context(ctx).Where("id =?", question.ID).Cols("pin", "show").Update(question)

View File

@ -54,6 +54,16 @@ func (tr *tagRelRepo) RemoveTagRelListByObjectID(ctx context.Context, objectID s
return
}
// RecoverTagRelListByObjectID recover tag list
func (tr *tagRelRepo) RecoverTagRelListByObjectID(ctx context.Context, objectID string) (err error) {
objectID = uid.DeShortID(objectID)
_, err = tr.data.DB.Context(ctx).Where("object_id = ?", objectID).Update(&entity.TagRel{Status: entity.TagRelStatusAvailable})
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return
}
func (tr *tagRelRepo) HideTagRelListByObjectID(ctx context.Context, objectID string) (err error) {
objectID = uid.DeShortID(objectID)
_, err = tr.data.DB.Context(ctx).Where("object_id = ?", objectID).Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusHide})

View File

@ -2,7 +2,6 @@ package tag
import (
"context"
"github.com/answerdev/answer/internal/base/data"
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/entity"
@ -49,6 +48,36 @@ func (tr *tagRepo) UpdateTag(ctx context.Context, tag *entity.Tag) (err error) {
return
}
// RecoverTag recover deleted tag
func (tr *tagRepo) RecoverTag(ctx context.Context, tagID string) (err error) {
_, err = tr.data.DB.Context(ctx).ID(tagID).Update(&entity.Tag{Status: entity.TagStatusAvailable})
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return
}
// MustGetTagByID get tag by id
func (tr *tagRepo) MustGetTagByNameOrID(ctx context.Context, tagID, slugName string) (
tag *entity.Tag, exist bool, err error) {
if len(tagID) == 0 && len(slugName) == 0 {
return nil, false, nil
}
tag = &entity.Tag{}
session := tr.data.DB.Context(ctx)
if len(tagID) > 0 {
session.ID(tagID)
}
if len(slugName) > 0 {
session.Where(builder.Eq{"slug_name": slugName})
}
exist, err = session.Get(tag)
if err != nil {
return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return
}
// UpdateTagSynonym update synonym tag
func (tr *tagRepo) UpdateTagSynonym(ctx context.Context, tagSlugNameList []string, mainTagID int64,
mainTagSlugName string,

View File

@ -187,6 +187,7 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
r.GET("/question/tags", a.tagController.SearchTagLike)
r.POST("/tag", a.tagController.AddTag)
r.PUT("/tag", a.tagController.UpdateTag)
r.POST("/tag/recover", a.tagController.RecoverTag)
r.DELETE("/tag", a.tagController.RemoveTag)
r.PUT("/tag/synonym", a.tagController.UpdateTagSynonym)
@ -204,12 +205,14 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
r.PUT("/question/operation", a.questionController.OperationQuestion)
r.PUT("/question/reopen", a.questionController.ReopenQuestion)
r.GET("/question/similar", a.questionController.GetSimilarQuestions)
r.POST("/question/recover", a.questionController.QuestionRecover)
// answer
r.POST("/answer", a.answerController.Add)
r.PUT("/answer", a.answerController.Update)
r.POST("/answer/acceptance", a.answerController.Accepted)
r.DELETE("/answer", a.answerController.RemoveAnswer)
r.POST("/answer/recover", a.answerController.RecoverAnswer)
// user
r.PUT("/user/password", middleware.BanAPIForUserCenter, a.userController.UserModifyPassWord)
@ -247,9 +250,9 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) {
r.GET("/question/page", a.questionController.AdminQuestionPage)
r.PUT("/question/status", a.questionController.AdminSetQuestionStatus)
r.PUT("/question/status", a.questionController.AdminUpdateQuestionStatus)
r.GET("/answer/page", a.questionController.AdminAnswerPage)
r.PUT("/answer/status", a.answerController.AdminSetAnswerStatus)
r.PUT("/answer/status", a.answerController.AdminUpdateAnswerStatus)
// report
r.GET("/reports/page", a.adminReportController.ListReportPage)

View File

@ -14,6 +14,12 @@ type RemoveAnswerReq struct {
CaptchaCode string `json:"captcha_code"`
}
// RecoverAnswerReq recover answer request
type RecoverAnswerReq struct {
AnswerID string `validate:"required" json:"answer_id"`
UserID string `json:"-"`
}
const (
AnswerAcceptedFailed = 1
AnswerAcceptedEnable = 2
@ -26,6 +32,7 @@ type AnswerAddReq struct {
UserID string `json:"-"`
CanEdit bool `json:"-"`
CanDelete bool `json:"-"`
CanRecover bool `json:"-"`
CaptchaID string `json:"captcha_id"`
CaptchaCode string `json:"captcha_code"`
}
@ -68,6 +75,7 @@ type AnswerListReq struct {
IsAdmin bool `json:"-"`
CanEdit bool `json:"-"`
CanDelete bool `json:"-"`
CanRecover bool `json:"-"`
}
type AnswerInfo struct {
@ -121,8 +129,8 @@ func (req *AcceptAnswerReq) Check() (errFields []*validator.FormErrorField, err
return nil, nil
}
type AdminSetAnswerStatusRequest struct {
StatusStr string `json:"status"`
AnswerID string `json:"answer_id"`
UserID string `json:"-"`
type AdminUpdateAnswerStatusReq struct {
AnswerID string `validate:"required" json:"answer_id"`
Status string `validate:"required,oneof=available deleted" json:"status"`
UserID string `json:"-"`
}

View File

@ -131,6 +131,7 @@ type QuestionPermission struct {
// whether user can invite other user to answer this question
CanInviteOtherToAnswer bool `json:"-"`
CanAddTag bool `json:"-"`
CanRecover bool `json:"-"`
}
type CheckCanQuestionUpdate struct {
@ -163,6 +164,11 @@ type QuestionUpdate struct {
CaptchaCode string `json:"captcha_code"`
}
type QuestionRecoverReq struct {
QuestionID string `validate:"required" json:"question_id"`
UserID string `json:"-"`
}
type QuestionUpdateInviteUser struct {
ID string `validate:"required" json:"id"`
InviteUser []string `validate:"omitempty" json:"invite_user"`
@ -430,9 +436,10 @@ func (req *AdminAnswerPageReq) Check() (errField []*validator.FormErrorField, er
return nil, nil
}
type AdminSetQuestionStatusRequest struct {
StatusStr string `json:"status" form:"status"`
QuestionID string `json:"question_id" form:"question_id"`
type AdminUpdateQuestionStatusReq struct {
QuestionID string `validate:"required" json:"question_id"`
Status string `validate:"required,oneof=available closed deleted" json:"status"`
UserID string `json:"-"`
}
type PersonalQuestionPageReq struct {

View File

@ -3,10 +3,8 @@ package schema
import (
"strings"
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/base/validator"
"github.com/answerdev/answer/pkg/converter"
"github.com/segmentfault/pacman/errors"
)
// SearchTagLikeReq get tag list all request
@ -27,13 +25,11 @@ type GetTagInfoReq struct {
// tag id
ID string `validate:"omitempty" form:"id"`
// tag slug name
Name string `validate:"omitempty,gt=0,lte=35" form:"name"`
// user id
UserID string `json:"-"`
// whether user can edit it
CanEdit bool `json:"-"`
// whether user can delete it
CanDelete bool `json:"-"`
Name string `validate:"omitempty,gt=0,lte=35" form:"name"`
UserID string `json:"-"`
CanEdit bool `json:"-"`
CanDelete bool `json:"-"`
CanRecover bool `json:"-"`
}
type GetTamplateTagInfoReq struct {
@ -48,40 +44,25 @@ type GetTamplateTagInfoReq struct {
}
func (r *GetTagInfoReq) Check() (errFields []*validator.FormErrorField, err error) {
if len(r.ID) == 0 && len(r.Name) == 0 {
return nil, errors.BadRequest(reason.RequestFormatError)
}
r.Name = strings.ToLower(r.Name)
return nil, nil
}
// GetTagResp get tag response
type GetTagResp struct {
// tag id
TagID string `json:"tag_id"`
// created time
CreatedAt int64 `json:"created_at"`
// updated time
UpdatedAt int64 `json:"updated_at"`
// slug name
SlugName string `json:"slug_name"`
// display name
DisplayName string `json:"display_name"`
// excerpt
Excerpt string `json:"excerpt"`
// original text
OriginalText string `json:"original_text"`
// parsed text
ParsedText string `json:"parsed_text"`
// description text
Description string `json:"description"`
// follower amount
FollowCount int `json:"follow_count"`
// question amount
QuestionCount int `json:"question_count"`
// is follower
IsFollower bool `json:"is_follower"`
// MemberActions
TagID string `json:"tag_id"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
SlugName string `json:"slug_name"`
DisplayName string `json:"display_name"`
Excerpt string `json:"excerpt"`
OriginalText string `json:"original_text"`
ParsedText string `json:"parsed_text"`
Description string `json:"description"`
FollowCount int `json:"follow_count"`
QuestionCount int `json:"question_count"`
IsFollower bool `json:"is_follower"`
Status string `json:"status"`
MemberActions []*PermissionMemberAction `json:"member_actions"`
// if main tag slug name is not empty, this tag is synonymous with the main tag
MainTagSlugName string `json:"main_tag_slug_name"`
@ -212,6 +193,12 @@ func (r *UpdateTagReq) Check() (errFields []*validator.FormErrorField, err error
return nil, nil
}
// RecoverTagReq update tag request
type RecoverTagReq struct {
TagID string `validate:"required" json:"tag_id"`
UserID string `json:"-"`
}
// UpdateTagResp update tag response
type UpdateTagResp struct {
WaitForReview bool `json:"wait_for_review"`

View File

@ -13,12 +13,13 @@ import (
type AnswerRepo interface {
AddAnswer(ctx context.Context, answer *entity.Answer) (err error)
RemoveAnswer(ctx context.Context, id string) (err error)
RecoverAnswer(ctx context.Context, answerID string) (err error)
UpdateAnswer(ctx context.Context, answer *entity.Answer, cols []string) (err error)
GetAnswer(ctx context.Context, id string) (answer *entity.Answer, exist bool, err error)
GetAnswerList(ctx context.Context, answer *entity.Answer) (answerList []*entity.Answer, err error)
GetAnswerPage(ctx context.Context, page, pageSize int, answer *entity.Answer) (answerList []*entity.Answer, total int64, err error)
UpdateAcceptedStatus(ctx context.Context, acceptedAnswerID string, questionID string) error
GetByID(ctx context.Context, id string) (*entity.Answer, bool, error)
GetByID(ctx context.Context, answerID string) (*entity.Answer, bool, error)
GetCountByQuestionID(ctx context.Context, questionID string) (int64, error)
GetCountByUserID(ctx context.Context, userID string) (int64, error)
GetByUserIDQuestionID(ctx context.Context, userID string, questionID string) (*entity.Answer, bool, error)

View File

@ -3,7 +3,7 @@ package service
import (
"context"
"encoding/json"
"fmt"
"github.com/answerdev/answer/pkg/converter"
"github.com/answerdev/answer/pkg/token"
"time"
@ -153,6 +153,44 @@ func (as *AnswerService) RemoveAnswer(ctx context.Context, req *schema.RemoveAns
return
}
// RecoverAnswer recover deleted answer
func (as *AnswerService) RecoverAnswer(ctx context.Context, req *schema.RecoverAnswerReq) (err error) {
answerInfo, exist, err := as.answerRepo.GetByID(ctx, req.AnswerID)
if err != nil {
return err
}
if !exist {
return errors.BadRequest(reason.AnswerNotFound)
}
if answerInfo.Status != entity.AnswerStatusDeleted {
return nil
}
if err = as.answerRepo.RecoverAnswer(ctx, req.AnswerID); err != nil {
return err
}
if err = as.questionCommon.UpdateAnswerCount(ctx, answerInfo.QuestionID); err != nil {
log.Errorf("update answer count failed: %s", err.Error())
}
userAnswerCount, err := as.answerRepo.GetCountByUserID(ctx, answerInfo.UserID)
if err != nil {
log.Errorf("get user answer count failed: %s", err.Error())
} else {
err = as.userCommon.UpdateAnswerCount(ctx, answerInfo.UserID, int(userAnswerCount))
if err != nil {
log.Errorf("update user answer count failed: %s", err.Error())
}
}
as.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: req.UserID,
TriggerUserID: converter.StringToInt64(req.UserID),
ObjectID: answerInfo.ID,
OriginalObjectID: answerInfo.ID,
ActivityTypeKey: constant.ActAnswerUndeleted,
})
return nil
}
func (as *AnswerService) Insert(ctx context.Context, req *schema.AnswerAddReq) (string, error) {
questionInfo, exist, err := as.questionRepo.GetQuestion(ctx, req.QuestionID)
if err != nil {
@ -438,20 +476,19 @@ func (as *AnswerService) Get(ctx context.Context, answerID, loginUserID string)
return info, questionInfo, has, nil
}
func (as *AnswerService) AdminSetAnswerStatus(ctx context.Context, req *schema.AdminSetAnswerStatusRequest) error {
setStatus, ok := entity.AdminAnswerSearchStatus[req.StatusStr]
func (as *AnswerService) AdminSetAnswerStatus(ctx context.Context, req *schema.AdminUpdateAnswerStatusReq) error {
setStatus, ok := entity.AdminAnswerSearchStatus[req.Status]
if !ok {
return fmt.Errorf("question status does not exist")
return errors.BadRequest(reason.RequestFormatError)
}
answerInfo, exist, err := as.answerRepo.GetAnswer(ctx, req.AnswerID)
if err != nil {
return err
}
if !exist {
return fmt.Errorf("answer does not exist")
return errors.BadRequest(reason.AnswerNotFound)
}
answerInfo.Status = setStatus
err = as.answerRepo.UpdateAnswerStatus(ctx, req.AnswerID, setStatus)
err = as.answerRepo.UpdateAnswerStatus(ctx, answerInfo.ID, setStatus)
if err != nil {
return err
}
@ -469,17 +506,27 @@ func (as *AnswerService) AdminSetAnswerStatus(ctx context.Context, req *schema.A
OriginalObjectID: answerInfo.ID,
ActivityTypeKey: constant.ActAnswerDeleted,
})
msg := &schema.NotificationMsg{}
msg.ObjectID = answerInfo.ID
msg.Type = schema.NotificationTypeInbox
msg.ReceiverUserID = answerInfo.UserID
msg.TriggerUserID = answerInfo.UserID
msg.ObjectType = constant.AnswerObjectType
msg.NotificationAction = constant.NotificationYourAnswerWasDeleted
as.notificationQueueService.Send(ctx, msg)
}
msg := &schema.NotificationMsg{}
msg.ObjectID = answerInfo.ID
msg.Type = schema.NotificationTypeInbox
msg.ReceiverUserID = answerInfo.UserID
msg.TriggerUserID = answerInfo.UserID
msg.ObjectType = constant.AnswerObjectType
msg.NotificationAction = constant.NotificationYourAnswerWasDeleted
as.notificationQueueService.Send(ctx, msg)
// recover
if setStatus == entity.QuestionStatusAvailable && answerInfo.Status == entity.QuestionStatusDeleted {
as.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: req.UserID,
TriggerUserID: converter.StringToInt64(req.UserID),
ObjectID: answerInfo.ID,
OriginalObjectID: answerInfo.ID,
ActivityTypeKey: constant.ActAnswerUndeleted,
})
}
return nil
}
@ -534,7 +581,13 @@ func (as *AnswerService) SearchFormatInfo(ctx context.Context, answers []*entity
for _, item := range list {
item.VoteStatus = as.voteRepo.GetVoteStatus(ctx, item.ID, req.UserID)
item.Collected = collectedMap[item.ID]
item.MemberActions = permission.GetAnswerPermission(ctx, req.UserID, item.UserID, req.CanEdit, req.CanDelete)
item.MemberActions = permission.GetAnswerPermission(ctx,
req.UserID,
item.UserID,
item.Status,
req.CanEdit,
req.CanDelete,
req.CanRecover)
}
return list, nil
}

View File

@ -2,6 +2,7 @@ package permission
import (
"context"
"github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/translator"
@ -9,7 +10,8 @@ import (
)
// GetAnswerPermission get answer permission
func GetAnswerPermission(ctx context.Context, userID string, creatorUserID string, canEdit, canDelete bool) (
func GetAnswerPermission(ctx context.Context, userID, creatorUserID string,
status int, canEdit, canDelete, canRecover bool) (
actions []*schema.PermissionMemberAction) {
lang := handler.GetLangByCtx(ctx)
actions = make([]*schema.PermissionMemberAction, 0)
@ -28,12 +30,20 @@ func GetAnswerPermission(ctx context.Context, userID string, creatorUserID strin
})
}
if canDelete || userID == creatorUserID {
if (canDelete || userID == creatorUserID) && status != entity.AnswerStatusDeleted {
actions = append(actions, &schema.PermissionMemberAction{
Action: "delete",
Name: translator.Tr(lang, deleteActionName),
Type: "confirm",
})
}
if canRecover && status == entity.AnswerStatusDeleted {
actions = append(actions, &schema.PermissionMemberAction{
Action: "undelete",
Name: translator.Tr(lang, undeleteActionName),
Type: "confirm",
})
}
return actions
}

View File

@ -40,12 +40,16 @@ const (
QuestionAudit = "question.audit"
TagAudit = "tag.audit"
TagUseReservedTag = "tag.use_reserved_tag"
AnswerUnDelete = "answer.undeleted"
QuestionUnDelete = "question.undeleted"
TagUnDelete = "tag.undeleted"
)
const (
reportActionName = "action.report"
editActionName = "action.edit"
deleteActionName = "action.delete"
undeleteActionName = "action.undelete"
closeActionName = "action.close"
reopenActionName = "action.reopen"
pinActionName = "action.pin"

View File

@ -2,6 +2,7 @@ package permission
import (
"context"
"github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/translator"
@ -9,8 +10,8 @@ import (
)
// GetQuestionPermission get question permission
func GetQuestionPermission(ctx context.Context, userID string, creatorUserID string,
canEdit, canDelete, canClose, canReopen, canPin, canHide, CanUnPin, canShow bool) (
func GetQuestionPermission(ctx context.Context, userID string, creatorUserID string, status int,
canEdit, canDelete, canClose, canReopen, canPin, canHide, canUnPin, canShow, canRecover bool) (
actions []*schema.PermissionMemberAction) {
lang := handler.GetLangByCtx(ctx)
actions = make([]*schema.PermissionMemberAction, 0)
@ -21,14 +22,14 @@ func GetQuestionPermission(ctx context.Context, userID string, creatorUserID str
Type: "reason",
})
}
if canEdit || userID == creatorUserID {
if (canEdit || userID == creatorUserID) && status != entity.QuestionStatusDeleted {
actions = append(actions, &schema.PermissionMemberAction{
Action: "edit",
Name: translator.Tr(lang, editActionName),
Type: "edit",
})
}
if canClose {
if canClose && status == entity.QuestionStatusAvailable {
actions = append(actions, &schema.PermissionMemberAction{
Action: "close",
Name: translator.Tr(lang, closeActionName),
@ -57,7 +58,7 @@ func GetQuestionPermission(ctx context.Context, userID string, creatorUserID str
})
}
if CanUnPin {
if canUnPin {
actions = append(actions, &schema.PermissionMemberAction{
Action: "unpin",
Name: translator.Tr(lang, unpinActionName),
@ -79,6 +80,14 @@ func GetQuestionPermission(ctx context.Context, userID string, creatorUserID str
Type: "confirm",
})
}
if canRecover && status == entity.QuestionStatusDeleted {
actions = append(actions, &schema.PermissionMemberAction{
Action: "undelete",
Name: translator.Tr(lang, undeleteActionName),
Type: "confirm",
})
}
return actions
}

View File

@ -2,6 +2,7 @@ package permission
import (
"context"
"github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/translator"
@ -9,7 +10,7 @@ import (
)
// GetTagPermission get tag permission
func GetTagPermission(ctx context.Context, canEdit, canDelete bool) (
func GetTagPermission(ctx context.Context, status int, canEdit, canDelete, canRecover bool) (
actions []*schema.PermissionMemberAction) {
lang := handler.GetLangByCtx(ctx)
actions = make([]*schema.PermissionMemberAction, 0)
@ -21,13 +22,21 @@ func GetTagPermission(ctx context.Context, canEdit, canDelete bool) (
})
}
if canDelete {
if canDelete && status != entity.TagStatusDeleted {
actions = append(actions, &schema.PermissionMemberAction{
Action: "delete",
Name: translator.Tr(lang, deleteActionName),
Type: "reason",
})
}
if canRecover && status == entity.QuestionStatusDeleted {
actions = append(actions, &schema.PermissionMemberAction{
Action: "undelete",
Name: translator.Tr(lang, undeleteActionName),
Type: "confirm",
})
}
return actions
}

View File

@ -39,6 +39,7 @@ type QuestionRepo interface {
questionList []*entity.Question, total int64, err error)
UpdateQuestionStatus(ctx context.Context, questionID string, status int) (err error)
UpdateQuestionStatusWithOutUpdateTime(ctx context.Context, question *entity.Question) (err error)
RecoverQuestion(ctx context.Context, questionID string) (err error)
UpdateQuestionOperation(ctx context.Context, question *entity.Question) (err error)
GetQuestionsByTitle(ctx context.Context, title string, pageSize int) (questionList []*entity.Question, err error)
UpdatePvCount(ctx context.Context, questionID string) (err error)

View File

@ -563,13 +563,70 @@ func (qs *QuestionService) UpdateQuestionCheckTags(ctx context.Context, req *sch
return nil, nil
}
func (qs *QuestionService) RecoverQuestion(ctx context.Context, req *schema.QuestionRecoverReq) (err error) {
questionInfo, exist, err := qs.questionRepo.GetQuestion(ctx, req.QuestionID)
if err != nil {
return err
}
if !exist {
return errors.BadRequest(reason.QuestionNotFound)
}
if questionInfo.Status != entity.QuestionStatusDeleted {
return nil
}
err = qs.questionRepo.RecoverQuestion(ctx, req.QuestionID)
if err != nil {
return err
}
// update user's question count
userQuestionCount, err := qs.questioncommon.GetUserQuestionCount(ctx, questionInfo.UserID)
if err != nil {
log.Error("user GetUserQuestionCount error", err.Error())
} else {
err = qs.userCommon.UpdateQuestionCount(ctx, questionInfo.UserID, userQuestionCount)
if err != nil {
log.Error("user IncreaseQuestionCount error", err.Error())
}
}
// update tag's question count
if err = qs.tagCommon.RemoveTagRelListByObjectID(ctx, questionInfo.ID); err != nil {
log.Errorf("remove tag rel list by object id error %v", err)
}
tagIDs := make([]string, 0)
tags, err := qs.tagCommon.GetObjectEntityTag(ctx, questionInfo.ID)
if err != nil {
return err
}
for _, v := range tags {
tagIDs = append(tagIDs, v.ID)
}
if len(tagIDs) > 0 {
if err = qs.tagCommon.RefreshTagQuestionCount(ctx, tagIDs); err != nil {
log.Errorf("update tag's question count failed, %v", err)
}
}
qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: req.UserID,
TriggerUserID: converter.StringToInt64(req.UserID),
ObjectID: questionInfo.ID,
OriginalObjectID: questionInfo.ID,
ActivityTypeKey: constant.ActQuestionUndeleted,
})
return 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)
return errors.BadRequest(reason.QuestionNotFound)
}
//verify invite user
@ -875,8 +932,10 @@ func (qs *QuestionService) GetQuestion(ctx context.Context, questionID, userID s
}
question.Description = htmltext.FetchExcerpt(question.HTML, "...", 240)
question.MemberActions = permission.GetQuestionPermission(ctx, userID, question.UserID,
per.CanEdit, per.CanDelete, per.CanClose, per.CanReopen, per.CanPin, per.CanHide, per.CanUnPin, per.CanShow)
question.MemberActions = permission.GetQuestionPermission(ctx, userID, question.UserID, question.Status,
per.CanEdit, per.CanDelete,
per.CanClose, per.CanReopen, per.CanPin, per.CanHide, per.CanUnPin, per.CanShow,
per.CanRecover)
question.ExtendsActions = permission.GetQuestionExtendsPermission(ctx, per.CanInviteOtherToAnswer)
return question, nil
}
@ -1195,12 +1254,12 @@ func (qs *QuestionService) GetQuestionPage(ctx context.Context, req *schema.Ques
return questions, total, nil
}
func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, questionID string, setStatusStr string) error {
setStatus, ok := entity.AdminQuestionSearchStatus[setStatusStr]
func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, req *schema.AdminUpdateQuestionStatusReq) error {
setStatus, ok := entity.AdminQuestionSearchStatus[req.Status]
if !ok {
return fmt.Errorf("question status does not exist")
return errors.BadRequest(reason.RequestFormatError)
}
questionInfo, exist, err := qs.questionRepo.GetQuestion(ctx, questionID)
questionInfo, exist, err := qs.questionRepo.GetQuestion(ctx, req.QuestionID)
if err != nil {
return err
}
@ -1212,6 +1271,7 @@ func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, questionI
return err
}
msg := &schema.NotificationMsg{}
if setStatus == entity.QuestionStatusDeleted {
// #2372 In order to simplify the process and complexity, as well as to consider if it is in-house,
// facing the problem of recovery.
@ -1225,6 +1285,7 @@ func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, questionI
OriginalObjectID: questionInfo.ID,
ActivityTypeKey: constant.ActQuestionDeleted,
})
msg.NotificationAction = constant.NotificationYourQuestionWasDeleted
}
if setStatus == entity.QuestionStatusAvailable && questionInfo.Status == entity.QuestionStatusClosed {
qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
@ -1241,15 +1302,27 @@ func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, questionI
OriginalObjectID: questionInfo.ID,
ActivityTypeKey: constant.ActQuestionClosed,
})
msg.NotificationAction = constant.NotificationYourQuestionIsClosed
}
// recover
if setStatus == entity.QuestionStatusAvailable && questionInfo.Status == entity.QuestionStatusDeleted {
qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: req.UserID,
TriggerUserID: converter.StringToInt64(req.UserID),
ObjectID: questionInfo.ID,
OriginalObjectID: questionInfo.ID,
ActivityTypeKey: constant.ActQuestionUndeleted,
})
}
if len(msg.NotificationAction) > 0 {
msg.ObjectID = questionInfo.ID
msg.Type = schema.NotificationTypeInbox
msg.ReceiverUserID = questionInfo.UserID
msg.TriggerUserID = req.UserID
msg.ObjectType = constant.QuestionObjectType
qs.notificationQueueService.Send(ctx, msg)
}
msg := &schema.NotificationMsg{}
msg.ObjectID = questionInfo.ID
msg.Type = schema.NotificationTypeInbox
msg.ReceiverUserID = questionInfo.UserID
msg.TriggerUserID = questionInfo.UserID
msg.ObjectType = constant.QuestionObjectType
msg.NotificationAction = constant.NotificationYourQuestionWasDeleted
qs.notificationQueueService.Send(ctx, msg)
return nil
}

View File

@ -91,6 +91,33 @@ func (ts *TagService) UpdateTag(ctx context.Context, req *schema.UpdateTagReq) (
return ts.tagCommonService.UpdateTag(ctx, req)
}
// RecoverTag recover tag
func (ts *TagService) RecoverTag(ctx context.Context, req *schema.RecoverTagReq) (err error) {
tagInfo, exist, err := ts.tagRepo.MustGetTagByNameOrID(ctx, req.TagID, "")
if err != nil {
return err
}
if !exist {
return errors.BadRequest(reason.TagNotFound)
}
if tagInfo.Status != entity.TagStatusDeleted {
return nil
}
err = ts.tagRepo.RecoverTag(ctx, req.TagID)
if err != nil {
return err
}
ts.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: req.UserID,
TriggerUserID: converter.StringToInt64(req.UserID),
ObjectID: req.TagID,
OriginalObjectID: req.TagID,
ActivityTypeKey: constant.ActTagUndeleted,
})
return nil
}
// GetTagInfo get tag one
func (ts *TagService) GetTagInfo(ctx context.Context, req *schema.GetTagInfoReq) (resp *schema.GetTagResp, err error) {
var (
@ -102,6 +129,10 @@ func (ts *TagService) GetTagInfo(ctx context.Context, req *schema.GetTagInfoReq)
} else {
tagInfo, exist, err = ts.tagCommonService.GetTagBySlugName(ctx, req.Name)
}
// If user can recover deleted tag, try to search in all tags including deleted tags
if !exist && req.CanRecover {
tagInfo, exist, err = ts.tagRepo.MustGetTagByNameOrID(ctx, req.ID, req.Name)
}
if err != nil {
return nil, err
}
@ -134,7 +165,8 @@ func (ts *TagService) GetTagInfo(ctx context.Context, req *schema.GetTagInfoReq)
resp.Recommend = tagInfo.Recommend
resp.Reserved = tagInfo.Reserved
resp.IsFollower = ts.checkTagIsFollow(ctx, req.UserID, tagInfo.ID)
resp.MemberActions = permission.GetTagPermission(ctx, req.CanEdit, req.CanDelete)
resp.Status = entity.TagStatusDisplayMapping[tagInfo.Status]
resp.MemberActions = permission.GetTagPermission(ctx, tagInfo.Status, req.CanEdit, req.CanDelete, req.CanRecover)
resp.GetExcerpt()
return resp, nil
}

View File

@ -37,6 +37,8 @@ type TagCommonRepo interface {
type TagRepo interface {
RemoveTag(ctx context.Context, tagID string) (err error)
UpdateTag(ctx context.Context, tag *entity.Tag) (err error)
RecoverTag(ctx context.Context, tagID string) (err error)
MustGetTagByNameOrID(ctx context.Context, tagID, slugName string) (tag *entity.Tag, exist bool, err error)
UpdateTagSynonym(ctx context.Context, tagSlugNameList []string, mainTagID int64, mainTagSlugName string) (err error)
GetTagSynonymCount(ctx context.Context, tagID string) (count int64, err error)
GetTagList(ctx context.Context, tag *entity.Tag) (tagList []*entity.Tag, err error)
@ -45,6 +47,7 @@ type TagRepo interface {
type TagRelRepo interface {
AddTagRelList(ctx context.Context, tagList []*entity.TagRel) (err error)
RemoveTagRelListByObjectID(ctx context.Context, objectID string) (err error)
RecoverTagRelListByObjectID(ctx context.Context, objectID string) (err error)
ShowTagRelListByObjectID(ctx context.Context, objectID string) (err error)
HideTagRelListByObjectID(ctx context.Context, objectID string) (err error)
RemoveTagRelListByIDs(ctx context.Context, ids []int64) (err error)
@ -713,6 +716,11 @@ func (ts *TagCommonService) RemoveTagRelListByObjectID(ctx context.Context, obje
return ts.tagRelRepo.RemoveTagRelListByObjectID(ctx, objectID)
}
// RecoverTagRelListByObjectID recover tag relation by object id
func (ts *TagCommonService) RecoverTagRelListByObjectID(ctx context.Context, objectID string) (err error) {
return ts.tagRelRepo.RecoverTagRelListByObjectID(ctx, objectID)
}
func (ts *TagCommonService) HideTagRelListByObjectID(ctx context.Context, objectID string) (err error) {
return ts.tagRelRepo.HideTagRelListByObjectID(ctx, objectID)
}