Compare commits
159 Commits
v1.4.1-RC2
...
v0.4.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 919ad47e4a | |||
| cfa984d6aa | |||
| bc54bc5f7c | |||
| c6491762d5 | |||
| 8c599136d6 | |||
| e9a0b517e5 | |||
| 741bab581d | |||
| 762628bb1a | |||
| 88df9a4732 | |||
| 8c1c1a664c | |||
| 98114a6a05 | |||
| 9c2e05d280 | |||
| b7e6d3ee35 | |||
| 6ec9dbec87 | |||
| 0fac38860f | |||
| 2676822331 | |||
| f216536e26 | |||
| 761944440c | |||
| 4f1762f01a | |||
| 76ace2c9a3 | |||
| 7004bbe230 | |||
| 661d8a7f8f | |||
| 738febd893 | |||
| 418a4a33dc | |||
| 51dc24e226 | |||
| ce5bf43948 | |||
| 9caddd22b3 | |||
| dc8407447c | |||
| fd27fcca13 | |||
| 3aec45e287 | |||
| c3d0b110b7 | |||
| 17dd8e8930 | |||
| ed9702ab8b | |||
| 3a713fb0c1 | |||
| 04a12ee832 | |||
| ae137d912c | |||
| 8e8cbf6621 | |||
| 8501ef4c64 | |||
| 1d3819673a | |||
| ba02cf4095 | |||
| 89a79a02fa | |||
| 747323b49c | |||
| 697a98951f | |||
| 3a2c8700c9 | |||
| dc1f83f819 | |||
| 0467165264 | |||
| d833cadae2 | |||
| a0b0adde8d | |||
| 784f745cdb | |||
| c9799feb2a | |||
| d7de1f2468 | |||
| 7868adcd7e | |||
| 432f8c3309 | |||
| 2f5f97510f | |||
| 696ff4a94d | |||
| 68a7de29fe | |||
| b5068db443 | |||
| d01e7f5529 | |||
| fd4469f3db | |||
| 4075739fec | |||
| 0c1a77d52c | |||
| f001b63ad3 | |||
| 908d78d3f4 | |||
| 7c725f535d | |||
| 62b0521b9f | |||
| 4d63d72a0b | |||
| 8caad012d5 | |||
| b47b19bb5e | |||
| 7336bae653 | |||
| 6742c5d707 | |||
| 30039173c0 | |||
| 41bb72b110 | |||
| d39759007d | |||
| 5c9e8333d3 | |||
| 8084870096 | |||
| e9dfeeb638 | |||
| 83ad1a185a | |||
| 0519b558b7 | |||
| 045257cd07 | |||
| 177b99f475 | |||
| 73c7ab7094 | |||
| 5f4e20c8fd | |||
| 4a93337a2b | |||
| 7c7a32d91d | |||
| 1b242a182c | |||
| e47c5a7e26 | |||
| 7bdb6d2016 | |||
| e8ab1c4202 | |||
| f6095fd476 | |||
| 20b2d84aa9 | |||
| c9821f80ff | |||
| 82130b70b1 | |||
| 19075b869e | |||
| dc1a672a33 | |||
| aca215a731 | |||
| 952757e2da | |||
| 0863eec2a0 | |||
| 5361315104 | |||
| 5dd8fa4e7f | |||
| c1de1db15f | |||
| e91afef608 | |||
| 59daaa5c5d | |||
| a63f9935c4 | |||
| 70efa79547 | |||
| 8ba55f5955 | |||
| 5e26663906 | |||
| 3a817bb6c8 | |||
| 53f6413d45 | |||
| b08e20ff99 | |||
| 233afca9ca | |||
| 167bac52db | |||
| 59fb67c651 | |||
| 67b7e7d2b1 | |||
| 5f1ebbd950 | |||
| 2abec78482 | |||
| 4b7b40152b | |||
| b473a9e62a | |||
| d78d9e0415 | |||
| beb5603b01 | |||
| 9e5196c0c2 | |||
| ef2b524f05 | |||
| 70842e18d8 | |||
| c299304b66 | |||
| 72866de5c1 | |||
| a66cf236ef | |||
| 96f2ad92d1 | |||
| baa24ec0d1 | |||
| ba64be6845 | |||
| bcaa8b94cb | |||
| f17c28ae1d | |||
| 3662182ff3 | |||
| 3a992965df | |||
| 39f4b7342c | |||
| baf0fce1c1 | |||
| a390227c17 | |||
| 81dc48c00b | |||
| 678d903a09 | |||
| d02f64b7a9 | |||
| d701ae60fb | |||
| 2f6448b2c7 | |||
| f6fa850aa0 | |||
| 9e5c6c7a44 | |||
| 00336de025 | |||
| cca5bcb59b | |||
| 74ba5620bb | |||
| 6b63d17623 | |||
| 23217f92b7 | |||
| 5fed2e2608 | |||
| 94c86fe3f8 | |||
| 6f815fb860 | |||
| 17ee34b49d | |||
| ab6cdc2ce3 | |||
| 701b711a7d | |||
| ded031ede2 | |||
| b1a9ca56a4 | |||
| 0141c1ec6e | |||
| b3d7816331 | |||
| c413bf3b09 | |||
| 902e6636be |
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,7 @@
|
||||
Fixes #
|
||||
|
||||
## Proposed Changes
|
||||
|
||||
-
|
||||
-
|
||||
-
|
||||
@@ -4,9 +4,9 @@ on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
tags:
|
||||
- 2.*
|
||||
- 1.*
|
||||
- 0.*
|
||||
- v2.*
|
||||
- v1.*
|
||||
- v0.*
|
||||
# pull_request:
|
||||
# branches: [ "main" ]
|
||||
|
||||
|
||||
@@ -20,5 +20,6 @@
|
||||
Thumbs*.db
|
||||
tmp
|
||||
vendor/
|
||||
.husky
|
||||
/answer-data/
|
||||
/answer
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
_
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
cd ui && pnpm commitlint --edit $1 --config commitlint.config.js
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
cd ui && npm run pre-commit
|
||||
@@ -1,6 +1,6 @@
|
||||
.PHONY: build clean ui
|
||||
|
||||
VERSION=0.3.0
|
||||
VERSION=0.4.0
|
||||
BIN=answer
|
||||
DIR_SRC=./cmd/answer
|
||||
DOCKER_CMD=docker
|
||||
@@ -26,11 +26,10 @@ generate:
|
||||
go mod tidy
|
||||
|
||||
test:
|
||||
@$(GO) test ./...
|
||||
@$(GO) test ./internal/repo/repo_test
|
||||
|
||||
# clean all build result
|
||||
clean:
|
||||
|
||||
@$(GO) clean ./...
|
||||
@rm -f $(BIN)
|
||||
|
||||
|
||||
@@ -8,10 +8,11 @@ An open-source knowledge-based community software. You can use it to quickly bui
|
||||
|
||||
To learn more about the project, visit [answer.dev](https://answer.dev).
|
||||
|
||||
[](https://github.com/answerdev/answer/blob/main/LICENSE)
|
||||
[](https://golang.org/)
|
||||
[](https://reactjs.org/)
|
||||
[](https://github.com/answerdev/answer/blob/main/LICENSE)
|
||||
[](https://golang.org/)
|
||||
[](https://reactjs.org/)
|
||||
[](https://goreportcard.com/report/github.com/answerdev/answer)
|
||||
[](https://discord.gg/a6PZZbfnFx)
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
||||
+5
-4
@@ -4,14 +4,15 @@
|
||||
|
||||
# Answer - 构建问答社区
|
||||
|
||||
一款极简的、问答形式的知识社区开源软件,用来快速构建产品你的产品问答支持社区、用户问答社区、粉丝社区等。
|
||||
一款问答形式的知识社区开源软件,用来快速构建产品你的产品技术社区、客户支持社区、用户社区等。
|
||||
|
||||
了解更多关于该项目的内容,请访问 [answer.dev](https://answer.dev).
|
||||
|
||||
[](https://github.com/answerdev/answer/blob/main/LICENSE)
|
||||
[](https://golang.org/)
|
||||
[](https://reactjs.org/)
|
||||
[](https://github.com/answerdev/answer/blob/main/LICENSE)
|
||||
[](https://golang.org/)
|
||||
[](https://reactjs.org/)
|
||||
[](https://goreportcard.com/report/github.com/answerdev/answer)
|
||||
[](https://discord.gg/a6PZZbfnFx)
|
||||
|
||||
## 截图
|
||||
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ var (
|
||||
// Name is the name of the project
|
||||
Name = "answer"
|
||||
// Version is the version of the project
|
||||
Version = "development"
|
||||
Version = "0.0.0"
|
||||
// Revision is the git short commit revision number
|
||||
Revision = ""
|
||||
// Time is the build time of the project
|
||||
|
||||
+16
-11
@@ -34,6 +34,7 @@ import (
|
||||
"github.com/answerdev/answer/internal/repo/search_common"
|
||||
"github.com/answerdev/answer/internal/repo/site_info"
|
||||
"github.com/answerdev/answer/internal/repo/tag"
|
||||
"github.com/answerdev/answer/internal/repo/tag_common"
|
||||
"github.com/answerdev/answer/internal/repo/unique"
|
||||
"github.com/answerdev/answer/internal/repo/user"
|
||||
"github.com/answerdev/answer/internal/router"
|
||||
@@ -58,11 +59,12 @@ import (
|
||||
"github.com/answerdev/answer/internal/service/report_backyard"
|
||||
"github.com/answerdev/answer/internal/service/report_handle_backyard"
|
||||
"github.com/answerdev/answer/internal/service/revision_common"
|
||||
"github.com/answerdev/answer/internal/service/search_parser"
|
||||
"github.com/answerdev/answer/internal/service/service_config"
|
||||
"github.com/answerdev/answer/internal/service/siteinfo"
|
||||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||
tag2 "github.com/answerdev/answer/internal/service/tag"
|
||||
"github.com/answerdev/answer/internal/service/tag_common"
|
||||
tag_common2 "github.com/answerdev/answer/internal/service/tag_common"
|
||||
"github.com/answerdev/answer/internal/service/uploader"
|
||||
"github.com/answerdev/answer/internal/service/user_backyard"
|
||||
"github.com/answerdev/answer/internal/service/user_common"
|
||||
@@ -115,8 +117,8 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||
userCommon := usercommon.NewUserCommon(userRepo)
|
||||
answerRepo := answer.NewAnswerRepo(dataData, uniqueIDRepo, userRankRepo, activityRepo)
|
||||
questionRepo := question.NewQuestionRepo(dataData, uniqueIDRepo)
|
||||
tagRepo := tag.NewTagRepo(dataData, uniqueIDRepo)
|
||||
objService := object_info.NewObjService(answerRepo, questionRepo, commentCommonRepo, tagRepo)
|
||||
tagCommonRepo := tag_common.NewTagCommonRepo(dataData, uniqueIDRepo)
|
||||
objService := object_info.NewObjService(answerRepo, questionRepo, commentCommonRepo, tagCommonRepo)
|
||||
voteRepo := activity_common.NewVoteRepo(dataData, activityRepo)
|
||||
commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userCommon, objService, voteRepo)
|
||||
rankService := rank2.NewRankService(userCommon, userRankRepo, objService, configRepo)
|
||||
@@ -127,18 +129,19 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||
serviceVoteRepo := activity.NewVoteRepo(dataData, uniqueIDRepo, configRepo, activityRepo, userRankRepo, voteRepo)
|
||||
voteService := service.NewVoteService(serviceVoteRepo, uniqueIDRepo, configRepo, questionRepo, answerRepo, commentCommonRepo, objService)
|
||||
voteController := controller.NewVoteController(voteService)
|
||||
tagRepo := tag.NewTagRepo(dataData, uniqueIDRepo)
|
||||
tagRelRepo := tag.NewTagRelRepo(dataData)
|
||||
revisionRepo := revision.NewRevisionRepo(dataData, uniqueIDRepo)
|
||||
revisionService := revision_common.NewRevisionService(revisionRepo, userRepo)
|
||||
tagCommonService := tag_common2.NewTagCommonService(tagCommonRepo, tagRelRepo, revisionService, siteInfoCommonService)
|
||||
followRepo := activity_common.NewFollowRepo(dataData, uniqueIDRepo, activityRepo)
|
||||
tagService := tag2.NewTagService(tagRepo, revisionService, followRepo)
|
||||
tagController := controller.NewTagController(tagService, rankService)
|
||||
tagService := tag2.NewTagService(tagRepo, tagCommonService, revisionService, followRepo, siteInfoCommonService)
|
||||
tagController := controller.NewTagController(tagService, tagCommonService, rankService)
|
||||
followFollowRepo := activity.NewFollowRepo(dataData, uniqueIDRepo, activityRepo)
|
||||
followService := follow.NewFollowService(followFollowRepo, followRepo, tagRepo)
|
||||
followService := follow.NewFollowService(followFollowRepo, followRepo, tagCommonRepo)
|
||||
followController := controller.NewFollowController(followService)
|
||||
collectionRepo := collection.NewCollectionRepo(dataData, uniqueIDRepo)
|
||||
collectionGroupRepo := collection.NewCollectionGroupRepo(dataData)
|
||||
tagRelRepo := tag.NewTagRelRepo(dataData)
|
||||
tagCommonService := tagcommon.NewTagCommonService(tagRepo, tagRelRepo, revisionService)
|
||||
collectionCommon := collectioncommon.NewCollectionCommon(collectionRepo)
|
||||
answerCommon := answercommon.NewAnswerCommon(answerRepo)
|
||||
metaRepo := meta.NewMetaRepo(dataData)
|
||||
@@ -154,8 +157,9 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||
answerService := service.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo)
|
||||
dashboardService := dashboard.NewDashboardService(questionRepo, answerRepo, commentCommonRepo, voteRepo, userRepo, reportRepo, configRepo, siteInfoCommonService, serviceConf, dataData)
|
||||
answerController := controller.NewAnswerController(answerService, rankService, dashboardService)
|
||||
searchParser := search_parser.NewSearchParser(tagCommonService, userCommon)
|
||||
searchRepo := search_common.NewSearchRepo(dataData, uniqueIDRepo, userCommon)
|
||||
searchService := service.NewSearchService(searchRepo, tagRepo, userCommon, followRepo)
|
||||
searchService := service.NewSearchService(searchParser, searchRepo)
|
||||
searchController := controller.NewSearchController(searchService)
|
||||
serviceRevisionService := service.NewRevisionService(revisionRepo, userCommon, questionCommon, answerService)
|
||||
revisionController := controller.NewRevisionController(serviceRevisionService)
|
||||
@@ -171,7 +175,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||
reasonService := reason2.NewReasonService(reasonRepo)
|
||||
reasonController := controller.NewReasonController(reasonService)
|
||||
themeController := controller_backyard.NewThemeController()
|
||||
siteInfoService := siteinfo.NewSiteInfoService(siteInfoRepo, emailService)
|
||||
siteInfoService := siteinfo.NewSiteInfoService(siteInfoRepo, emailService, tagCommonService)
|
||||
siteInfoController := controller_backyard.NewSiteInfoController(siteInfoService)
|
||||
siteinfoController := controller.NewSiteinfoController(siteInfoCommonService)
|
||||
notificationRepo := notification.NewNotificationRepo(dataData)
|
||||
@@ -179,7 +183,8 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||
notificationService := notification2.NewNotificationService(dataData, notificationRepo, notificationCommon)
|
||||
notificationController := controller.NewNotificationController(notificationService)
|
||||
dashboardController := controller.NewDashboardController(dashboardService)
|
||||
answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, controller_backyardReportController, userBackyardController, reasonController, themeController, siteInfoController, siteinfoController, notificationController, dashboardController)
|
||||
uploadController := controller.NewUploadController(uploaderService)
|
||||
answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, controller_backyardReportController, userBackyardController, reasonController, themeController, siteInfoController, siteinfoController, notificationController, dashboardController, uploadController)
|
||||
swaggerRouter := router.NewSwaggerRouter(swaggerConf)
|
||||
uiRouter := router.NewUIRouter()
|
||||
authUserMiddleware := middleware.NewAuthUserMiddleware(authService)
|
||||
|
||||
+493
-102
@@ -503,6 +503,77 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/siteinfo/branding": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "get site interface",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "get site interface",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/schema.SiteBrandingResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "update site info branding",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "update site info branding",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "branding info",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.SiteBrandingReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/siteinfo/general": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -645,6 +716,148 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/siteinfo/legal": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Set the legal information for the site",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "Set the legal information for the site",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/schema.SiteLegalResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "update site legal info",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "update site legal info",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "write info",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.SiteLegalReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/siteinfo/write": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "get site interface",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "get site interface",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/schema.SiteWriteResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "update site write info",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "update site write info",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "write info",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.SiteWriteReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/theme/options": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -1323,6 +1536,64 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/file": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "upload file",
|
||||
"consumes": [
|
||||
"multipart/form-data"
|
||||
],
|
||||
"tags": [
|
||||
"Upload"
|
||||
],
|
||||
"summary": "upload file",
|
||||
"parameters": [
|
||||
{
|
||||
"enum": [
|
||||
"post",
|
||||
"avatar",
|
||||
"branding"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "identify the source of the file upload",
|
||||
"name": "source",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"description": "file",
|
||||
"name": "file",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/follow": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -2747,6 +3018,51 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/siteinfo/legal": {
|
||||
"get": {
|
||||
"description": "get site legal info",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"site"
|
||||
],
|
||||
"summary": "get site legal info",
|
||||
"parameters": [
|
||||
{
|
||||
"enum": [
|
||||
"tos",
|
||||
"privacy"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "legal information type",
|
||||
"name": "info_type",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/schema.GetSiteLegalInfoResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/tag": {
|
||||
"get": {
|
||||
"description": "get tag one",
|
||||
@@ -3106,52 +3422,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/user/avatar/upload": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "UserUpdateInfo",
|
||||
"consumes": [
|
||||
"multipart/form-data"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "UserUpdateInfo",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "file",
|
||||
"description": "file",
|
||||
"name": "file",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/user/email": {
|
||||
"put": {
|
||||
"security": [
|
||||
@@ -3671,52 +3941,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/user/post/file": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "upload user post file",
|
||||
"consumes": [
|
||||
"multipart/form-data"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "upload user post file",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "file",
|
||||
"description": "file",
|
||||
"name": "file",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/user/register/email": {
|
||||
"post": {
|
||||
"description": "UserRegisterByEmail",
|
||||
@@ -4634,6 +4858,12 @@ const docTemplate = `{
|
||||
"description": "if main tag slug name is not empty, this tag is synonymous with the main tag",
|
||||
"type": "string"
|
||||
},
|
||||
"recommend": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reserved": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"slug_name": {
|
||||
"description": "slug name",
|
||||
"type": "string"
|
||||
@@ -4877,6 +5107,23 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.GetSiteLegalInfoResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"privacy_policy_original_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"privacy_policy_parsed_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"terms_of_service_original_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"terms_of_service_parsed_text": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.GetTagPageResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -4912,6 +5159,12 @@ const docTemplate = `{
|
||||
"description": "question amount",
|
||||
"type": "integer"
|
||||
},
|
||||
"recommend": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reserved": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"slug_name": {
|
||||
"description": "slug_name",
|
||||
"type": "string"
|
||||
@@ -4972,6 +5225,12 @@ const docTemplate = `{
|
||||
"description": "question amount",
|
||||
"type": "integer"
|
||||
},
|
||||
"recommend": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reserved": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"slug_name": {
|
||||
"description": "slug name",
|
||||
"type": "string"
|
||||
@@ -5535,6 +5794,9 @@ const docTemplate = `{
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"question_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status",
|
||||
"type": "string"
|
||||
@@ -5571,6 +5833,56 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.SiteBrandingReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"logo",
|
||||
"square_icon"
|
||||
],
|
||||
"properties": {
|
||||
"favicon": {
|
||||
"type": "string",
|
||||
"maxLength": 512
|
||||
},
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"maxLength": 512
|
||||
},
|
||||
"mobile_logo": {
|
||||
"type": "string",
|
||||
"maxLength": 512
|
||||
},
|
||||
"square_icon": {
|
||||
"type": "string",
|
||||
"maxLength": 512
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.SiteBrandingResp": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"logo",
|
||||
"square_icon"
|
||||
],
|
||||
"properties": {
|
||||
"favicon": {
|
||||
"type": "string",
|
||||
"maxLength": 512
|
||||
},
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"maxLength": 512
|
||||
},
|
||||
"mobile_logo": {
|
||||
"type": "string",
|
||||
"maxLength": 512
|
||||
},
|
||||
"square_icon": {
|
||||
"type": "string",
|
||||
"maxLength": 512
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.SiteGeneralReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -5647,10 +5959,6 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
},
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"maxLength": 256
|
||||
},
|
||||
"theme": {
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
@@ -5673,10 +5981,6 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
},
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"maxLength": 256
|
||||
},
|
||||
"theme": {
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
@@ -5687,6 +5991,80 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.SiteLegalReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"privacy_policy_original_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"privacy_policy_parsed_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"terms_of_service_original_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"terms_of_service_parsed_text": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.SiteLegalResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"privacy_policy_original_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"privacy_policy_parsed_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"terms_of_service_original_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"terms_of_service_parsed_text": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.SiteWriteReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"recommend_tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required_tag": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reserved_tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.SiteWriteResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"recommend_tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required_tag": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reserved_tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.TagItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5720,6 +6098,12 @@ const docTemplate = `{
|
||||
"description": "if main tag slug name is not empty, this tag is synonymous with the main tag",
|
||||
"type": "string"
|
||||
},
|
||||
"recommend": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reserved": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"slug_name": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -6003,6 +6387,10 @@ const docTemplate = `{
|
||||
},
|
||||
"schema.UserEmailLogin": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"e_mail",
|
||||
"pass"
|
||||
],
|
||||
"properties": {
|
||||
"captcha_code": {
|
||||
"description": "captcha_code",
|
||||
@@ -6014,11 +6402,14 @@ const docTemplate = `{
|
||||
},
|
||||
"e_mail": {
|
||||
"description": "e_mail",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 500
|
||||
},
|
||||
"pass": {
|
||||
"description": "password",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 32,
|
||||
"minLength": 8
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+493
-102
@@ -491,6 +491,77 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/siteinfo/branding": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "get site interface",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "get site interface",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/schema.SiteBrandingResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "update site info branding",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "update site info branding",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "branding info",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.SiteBrandingReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/siteinfo/general": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -633,6 +704,148 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/siteinfo/legal": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Set the legal information for the site",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "Set the legal information for the site",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/schema.SiteLegalResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "update site legal info",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "update site legal info",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "write info",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.SiteLegalReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/siteinfo/write": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "get site interface",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "get site interface",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/schema.SiteWriteResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "update site write info",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "update site write info",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "write info",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.SiteWriteReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/theme/options": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -1311,6 +1524,64 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/file": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "upload file",
|
||||
"consumes": [
|
||||
"multipart/form-data"
|
||||
],
|
||||
"tags": [
|
||||
"Upload"
|
||||
],
|
||||
"summary": "upload file",
|
||||
"parameters": [
|
||||
{
|
||||
"enum": [
|
||||
"post",
|
||||
"avatar",
|
||||
"branding"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "identify the source of the file upload",
|
||||
"name": "source",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"description": "file",
|
||||
"name": "file",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/follow": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -2735,6 +3006,51 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/siteinfo/legal": {
|
||||
"get": {
|
||||
"description": "get site legal info",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"site"
|
||||
],
|
||||
"summary": "get site legal info",
|
||||
"parameters": [
|
||||
{
|
||||
"enum": [
|
||||
"tos",
|
||||
"privacy"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "legal information type",
|
||||
"name": "info_type",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/schema.GetSiteLegalInfoResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/tag": {
|
||||
"get": {
|
||||
"description": "get tag one",
|
||||
@@ -3094,52 +3410,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/user/avatar/upload": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "UserUpdateInfo",
|
||||
"consumes": [
|
||||
"multipart/form-data"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "UserUpdateInfo",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "file",
|
||||
"description": "file",
|
||||
"name": "file",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/user/email": {
|
||||
"put": {
|
||||
"security": [
|
||||
@@ -3659,52 +3929,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/user/post/file": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "upload user post file",
|
||||
"consumes": [
|
||||
"multipart/form-data"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "upload user post file",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "file",
|
||||
"description": "file",
|
||||
"name": "file",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/user/register/email": {
|
||||
"post": {
|
||||
"description": "UserRegisterByEmail",
|
||||
@@ -4622,6 +4846,12 @@
|
||||
"description": "if main tag slug name is not empty, this tag is synonymous with the main tag",
|
||||
"type": "string"
|
||||
},
|
||||
"recommend": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reserved": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"slug_name": {
|
||||
"description": "slug name",
|
||||
"type": "string"
|
||||
@@ -4865,6 +5095,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.GetSiteLegalInfoResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"privacy_policy_original_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"privacy_policy_parsed_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"terms_of_service_original_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"terms_of_service_parsed_text": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.GetTagPageResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -4900,6 +5147,12 @@
|
||||
"description": "question amount",
|
||||
"type": "integer"
|
||||
},
|
||||
"recommend": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reserved": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"slug_name": {
|
||||
"description": "slug_name",
|
||||
"type": "string"
|
||||
@@ -4960,6 +5213,12 @@
|
||||
"description": "question amount",
|
||||
"type": "integer"
|
||||
},
|
||||
"recommend": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reserved": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"slug_name": {
|
||||
"description": "slug name",
|
||||
"type": "string"
|
||||
@@ -5523,6 +5782,9 @@
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"question_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status",
|
||||
"type": "string"
|
||||
@@ -5559,6 +5821,56 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.SiteBrandingReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"logo",
|
||||
"square_icon"
|
||||
],
|
||||
"properties": {
|
||||
"favicon": {
|
||||
"type": "string",
|
||||
"maxLength": 512
|
||||
},
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"maxLength": 512
|
||||
},
|
||||
"mobile_logo": {
|
||||
"type": "string",
|
||||
"maxLength": 512
|
||||
},
|
||||
"square_icon": {
|
||||
"type": "string",
|
||||
"maxLength": 512
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.SiteBrandingResp": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"logo",
|
||||
"square_icon"
|
||||
],
|
||||
"properties": {
|
||||
"favicon": {
|
||||
"type": "string",
|
||||
"maxLength": 512
|
||||
},
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"maxLength": 512
|
||||
},
|
||||
"mobile_logo": {
|
||||
"type": "string",
|
||||
"maxLength": 512
|
||||
},
|
||||
"square_icon": {
|
||||
"type": "string",
|
||||
"maxLength": 512
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.SiteGeneralReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -5635,10 +5947,6 @@
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
},
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"maxLength": 256
|
||||
},
|
||||
"theme": {
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
@@ -5661,10 +5969,6 @@
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
},
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"maxLength": 256
|
||||
},
|
||||
"theme": {
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
@@ -5675,6 +5979,80 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.SiteLegalReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"privacy_policy_original_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"privacy_policy_parsed_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"terms_of_service_original_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"terms_of_service_parsed_text": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.SiteLegalResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"privacy_policy_original_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"privacy_policy_parsed_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"terms_of_service_original_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"terms_of_service_parsed_text": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.SiteWriteReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"recommend_tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required_tag": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reserved_tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.SiteWriteResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"recommend_tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required_tag": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reserved_tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.TagItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5708,6 +6086,12 @@
|
||||
"description": "if main tag slug name is not empty, this tag is synonymous with the main tag",
|
||||
"type": "string"
|
||||
},
|
||||
"recommend": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reserved": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"slug_name": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -5991,6 +6375,10 @@
|
||||
},
|
||||
"schema.UserEmailLogin": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"e_mail",
|
||||
"pass"
|
||||
],
|
||||
"properties": {
|
||||
"captcha_code": {
|
||||
"description": "captcha_code",
|
||||
@@ -6002,11 +6390,14 @@
|
||||
},
|
||||
"e_mail": {
|
||||
"description": "e_mail",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 500
|
||||
},
|
||||
"pass": {
|
||||
"description": "password",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 32,
|
||||
"minLength": 8
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+304
-58
@@ -368,6 +368,10 @@ definitions:
|
||||
description: if main tag slug name is not empty, this tag is synonymous with
|
||||
the main tag
|
||||
type: string
|
||||
recommend:
|
||||
type: boolean
|
||||
reserved:
|
||||
type: boolean
|
||||
slug_name:
|
||||
description: slug name
|
||||
type: string
|
||||
@@ -544,6 +548,17 @@ definitions:
|
||||
smtp_username:
|
||||
type: string
|
||||
type: object
|
||||
schema.GetSiteLegalInfoResp:
|
||||
properties:
|
||||
privacy_policy_original_text:
|
||||
type: string
|
||||
privacy_policy_parsed_text:
|
||||
type: string
|
||||
terms_of_service_original_text:
|
||||
type: string
|
||||
terms_of_service_parsed_text:
|
||||
type: string
|
||||
type: object
|
||||
schema.GetTagPageResp:
|
||||
properties:
|
||||
created_at:
|
||||
@@ -570,6 +585,10 @@ definitions:
|
||||
question_count:
|
||||
description: question amount
|
||||
type: integer
|
||||
recommend:
|
||||
type: boolean
|
||||
reserved:
|
||||
type: boolean
|
||||
slug_name:
|
||||
description: slug_name
|
||||
type: string
|
||||
@@ -615,6 +634,10 @@ definitions:
|
||||
question_count:
|
||||
description: question amount
|
||||
type: integer
|
||||
recommend:
|
||||
type: boolean
|
||||
reserved:
|
||||
type: boolean
|
||||
slug_name:
|
||||
description: slug name
|
||||
type: string
|
||||
@@ -1025,6 +1048,8 @@ definitions:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
question_id:
|
||||
type: string
|
||||
status:
|
||||
description: Status
|
||||
type: string
|
||||
@@ -1050,6 +1075,42 @@ definitions:
|
||||
description: object_type
|
||||
type: string
|
||||
type: object
|
||||
schema.SiteBrandingReq:
|
||||
properties:
|
||||
favicon:
|
||||
maxLength: 512
|
||||
type: string
|
||||
logo:
|
||||
maxLength: 512
|
||||
type: string
|
||||
mobile_logo:
|
||||
maxLength: 512
|
||||
type: string
|
||||
square_icon:
|
||||
maxLength: 512
|
||||
type: string
|
||||
required:
|
||||
- logo
|
||||
- square_icon
|
||||
type: object
|
||||
schema.SiteBrandingResp:
|
||||
properties:
|
||||
favicon:
|
||||
maxLength: 512
|
||||
type: string
|
||||
logo:
|
||||
maxLength: 512
|
||||
type: string
|
||||
mobile_logo:
|
||||
maxLength: 512
|
||||
type: string
|
||||
square_icon:
|
||||
maxLength: 512
|
||||
type: string
|
||||
required:
|
||||
- logo
|
||||
- square_icon
|
||||
type: object
|
||||
schema.SiteGeneralReq:
|
||||
properties:
|
||||
contact_email:
|
||||
@@ -1103,9 +1164,6 @@ definitions:
|
||||
language:
|
||||
maxLength: 128
|
||||
type: string
|
||||
logo:
|
||||
maxLength: 256
|
||||
type: string
|
||||
theme:
|
||||
maxLength: 128
|
||||
type: string
|
||||
@@ -1122,9 +1180,6 @@ definitions:
|
||||
language:
|
||||
maxLength: 128
|
||||
type: string
|
||||
logo:
|
||||
maxLength: 256
|
||||
type: string
|
||||
theme:
|
||||
maxLength: 128
|
||||
type: string
|
||||
@@ -1136,6 +1191,54 @@ definitions:
|
||||
- theme
|
||||
- time_zone
|
||||
type: object
|
||||
schema.SiteLegalReq:
|
||||
properties:
|
||||
privacy_policy_original_text:
|
||||
type: string
|
||||
privacy_policy_parsed_text:
|
||||
type: string
|
||||
terms_of_service_original_text:
|
||||
type: string
|
||||
terms_of_service_parsed_text:
|
||||
type: string
|
||||
type: object
|
||||
schema.SiteLegalResp:
|
||||
properties:
|
||||
privacy_policy_original_text:
|
||||
type: string
|
||||
privacy_policy_parsed_text:
|
||||
type: string
|
||||
terms_of_service_original_text:
|
||||
type: string
|
||||
terms_of_service_parsed_text:
|
||||
type: string
|
||||
type: object
|
||||
schema.SiteWriteReq:
|
||||
properties:
|
||||
recommend_tags:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required_tag:
|
||||
type: boolean
|
||||
reserved_tags:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
schema.SiteWriteResp:
|
||||
properties:
|
||||
recommend_tags:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required_tag:
|
||||
type: boolean
|
||||
reserved_tags:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
schema.TagItem:
|
||||
properties:
|
||||
display_name:
|
||||
@@ -1161,6 +1264,10 @@ definitions:
|
||||
description: if main tag slug name is not empty, this tag is synonymous with
|
||||
the main tag
|
||||
type: string
|
||||
recommend:
|
||||
type: boolean
|
||||
reserved:
|
||||
type: boolean
|
||||
slug_name:
|
||||
type: string
|
||||
type: object
|
||||
@@ -1373,10 +1480,16 @@ definitions:
|
||||
type: string
|
||||
e_mail:
|
||||
description: e_mail
|
||||
maxLength: 500
|
||||
type: string
|
||||
pass:
|
||||
description: password
|
||||
maxLength: 32
|
||||
minLength: 8
|
||||
type: string
|
||||
required:
|
||||
- e_mail
|
||||
- pass
|
||||
type: object
|
||||
schema.UserModifyPassWordRequest:
|
||||
properties:
|
||||
@@ -1784,6 +1897,47 @@ paths:
|
||||
summary: update smtp config
|
||||
tags:
|
||||
- admin
|
||||
/answer/admin/api/siteinfo/branding:
|
||||
get:
|
||||
description: get site interface
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/handler.RespBody'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/schema.SiteBrandingResp'
|
||||
type: object
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: get site interface
|
||||
tags:
|
||||
- admin
|
||||
put:
|
||||
description: update site info branding
|
||||
parameters:
|
||||
- description: branding info
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/schema.SiteBrandingReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.RespBody'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: update site info branding
|
||||
tags:
|
||||
- admin
|
||||
/answer/admin/api/siteinfo/general:
|
||||
get:
|
||||
description: get site general information
|
||||
@@ -1866,6 +2020,88 @@ paths:
|
||||
summary: update site info interface
|
||||
tags:
|
||||
- admin
|
||||
/answer/admin/api/siteinfo/legal:
|
||||
get:
|
||||
description: Set the legal information for the site
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/handler.RespBody'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/schema.SiteLegalResp'
|
||||
type: object
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Set the legal information for the site
|
||||
tags:
|
||||
- admin
|
||||
put:
|
||||
description: update site legal info
|
||||
parameters:
|
||||
- description: write info
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/schema.SiteLegalReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.RespBody'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: update site legal info
|
||||
tags:
|
||||
- admin
|
||||
/answer/admin/api/siteinfo/write:
|
||||
get:
|
||||
description: get site interface
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/handler.RespBody'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/schema.SiteWriteResp'
|
||||
type: object
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: get site interface
|
||||
tags:
|
||||
- admin
|
||||
put:
|
||||
description: update site write info
|
||||
parameters:
|
||||
- description: write info
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/schema.SiteWriteReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.RespBody'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: update site write info
|
||||
tags:
|
||||
- admin
|
||||
/answer/admin/api/theme/options:
|
||||
get:
|
||||
description: Get theme options
|
||||
@@ -2272,6 +2508,41 @@ paths:
|
||||
summary: get comment page
|
||||
tags:
|
||||
- Comment
|
||||
/answer/api/v1/file:
|
||||
post:
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
description: upload file
|
||||
parameters:
|
||||
- description: identify the source of the file upload
|
||||
enum:
|
||||
- post
|
||||
- avatar
|
||||
- branding
|
||||
in: formData
|
||||
name: source
|
||||
required: true
|
||||
type: string
|
||||
- description: file
|
||||
in: formData
|
||||
name: file
|
||||
required: true
|
||||
type: file
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/handler.RespBody'
|
||||
- properties:
|
||||
data:
|
||||
type: string
|
||||
type: object
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: upload file
|
||||
tags:
|
||||
- Upload
|
||||
/answer/api/v1/follow:
|
||||
post:
|
||||
consumes:
|
||||
@@ -3145,6 +3416,33 @@ paths:
|
||||
summary: get site info
|
||||
tags:
|
||||
- site
|
||||
/answer/api/v1/siteinfo/legal:
|
||||
get:
|
||||
description: get site legal info
|
||||
parameters:
|
||||
- description: legal information type
|
||||
enum:
|
||||
- tos
|
||||
- privacy
|
||||
in: query
|
||||
name: info_type
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/handler.RespBody'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/schema.GetSiteLegalInfoResp'
|
||||
type: object
|
||||
summary: get site legal info
|
||||
tags:
|
||||
- site
|
||||
/answer/api/v1/tag:
|
||||
delete:
|
||||
consumes:
|
||||
@@ -3362,32 +3660,6 @@ paths:
|
||||
summary: ActionRecord
|
||||
tags:
|
||||
- User
|
||||
/answer/api/v1/user/avatar/upload:
|
||||
post:
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
description: UserUpdateInfo
|
||||
parameters:
|
||||
- description: file
|
||||
in: formData
|
||||
name: file
|
||||
required: true
|
||||
type: file
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/handler.RespBody'
|
||||
- properties:
|
||||
data:
|
||||
type: string
|
||||
type: object
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: UserUpdateInfo
|
||||
tags:
|
||||
- User
|
||||
/answer/api/v1/user/email:
|
||||
put:
|
||||
consumes:
|
||||
@@ -3708,32 +3980,6 @@ paths:
|
||||
summary: RetrievePassWord
|
||||
tags:
|
||||
- User
|
||||
/answer/api/v1/user/post/file:
|
||||
post:
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
description: upload user post file
|
||||
parameters:
|
||||
- description: file
|
||||
in: formData
|
||||
name: file
|
||||
required: true
|
||||
type: file
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/handler.RespBody'
|
||||
- properties:
|
||||
data:
|
||||
type: string
|
||||
type: object
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: upload user post file
|
||||
tags:
|
||||
- User
|
||||
/answer/api/v1/user/register/email:
|
||||
post:
|
||||
consumes:
|
||||
|
||||
+30
-60
@@ -26,6 +26,10 @@ backend:
|
||||
answer:
|
||||
not_found:
|
||||
other: "Answer do not found."
|
||||
cannot_deleted:
|
||||
other: "No permission to delete."
|
||||
cannot_update:
|
||||
other: "No permission to update."
|
||||
comment:
|
||||
edit_without_permission:
|
||||
other: "Comment are not allowed to edit."
|
||||
@@ -63,6 +67,12 @@ backend:
|
||||
question:
|
||||
not_found:
|
||||
other: "Question not found."
|
||||
cannot_deleted:
|
||||
other: "No permission to delete."
|
||||
cannot_close:
|
||||
other: "No permission to close."
|
||||
cannot_update:
|
||||
other: "No permission to update."
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
other: "Rank fail to meet the condition."
|
||||
@@ -74,6 +84,12 @@ backend:
|
||||
tag:
|
||||
not_found:
|
||||
other: "Tag not found."
|
||||
recommend_tag_not_found:
|
||||
other: "Recommend Tag is not exist."
|
||||
recommend_tag_enter:
|
||||
other: "Please enter at least one required tag."
|
||||
not_contain_synonym_tags:
|
||||
other: "Should not contain synonym tags."
|
||||
theme:
|
||||
not_found:
|
||||
other: "Theme not found."
|
||||
@@ -90,18 +106,17 @@ backend:
|
||||
other: "Username is already in use."
|
||||
set_avatar:
|
||||
other: "Avatar set failed."
|
||||
|
||||
config:
|
||||
read_config_failed:
|
||||
other: "Read config failed"
|
||||
database:
|
||||
connection_failed:
|
||||
other: "Database connection failed"
|
||||
create_table_failed:
|
||||
other: "Create table failed"
|
||||
install:
|
||||
create_config_failed:
|
||||
other: "Can’t create the config.yaml file."
|
||||
config:
|
||||
read_config_failed:
|
||||
other: "Read config failed"
|
||||
database:
|
||||
connection_failed:
|
||||
other: "Database connection failed"
|
||||
create_table_failed:
|
||||
other: "Create table failed"
|
||||
install:
|
||||
create_config_failed:
|
||||
other: "Can’t create the config.yaml file."
|
||||
report:
|
||||
spam:
|
||||
name:
|
||||
@@ -380,7 +395,6 @@ ui:
|
||||
tag_info:
|
||||
created_at: Created
|
||||
edited_at: Edited
|
||||
history: History
|
||||
synonyms:
|
||||
title: Synonyms
|
||||
text: The following tags will be remapped to
|
||||
@@ -498,11 +512,6 @@ ui:
|
||||
label: Answer
|
||||
msg:
|
||||
empty: Answer cannot be empty.
|
||||
edit_summary:
|
||||
label: Edit Summary
|
||||
placeholder: >-
|
||||
Briefly explain your changes (corrected spelling, fixed grammar,
|
||||
improved formatting)
|
||||
btn_post_question: Post your question
|
||||
btn_save_edits: Save edits
|
||||
answer_question: Answer your own question
|
||||
@@ -523,7 +532,6 @@ ui:
|
||||
setting: Settings
|
||||
logout: Log out
|
||||
admin: Admin
|
||||
review: Review
|
||||
search:
|
||||
placeholder: Search
|
||||
footer:
|
||||
@@ -733,9 +741,6 @@ ui:
|
||||
logout: Log out
|
||||
verify: Verify
|
||||
add_question: Add question
|
||||
approve: Approve
|
||||
reject: Reject
|
||||
skip: Skip
|
||||
search:
|
||||
title: Search Results
|
||||
keywords: Keywords
|
||||
@@ -1173,8 +1178,9 @@ ui:
|
||||
label: Recommend Tags
|
||||
text: "Please input tag slug above, one tag per line."
|
||||
required_tag:
|
||||
label: Required Tag
|
||||
text: "Every new question must have at least one recommend tag"
|
||||
title: Required Tag
|
||||
label: Set recommend tag as required
|
||||
text: "Every new question must have at least one recommend tag."
|
||||
reserved_tags:
|
||||
label: Reserved Tags
|
||||
text: "Reserved tags can only be added to a post by moderator."
|
||||
@@ -1183,39 +1189,3 @@ ui:
|
||||
invalid: is invalid
|
||||
btn_submit: Save
|
||||
not_found_props: "Required property {{ key }} not found."
|
||||
page_review:
|
||||
review: Reivew
|
||||
proposed: proposed
|
||||
question_edit: Question edit
|
||||
answer_edit: Answer edit
|
||||
tag_edit: Tag edit
|
||||
edit_summary: Edit summary
|
||||
edit_question: Edit question
|
||||
edit_answer: Edit answer
|
||||
edit_tag: Edit tag
|
||||
empty: No review tasks left.
|
||||
timeline:
|
||||
undeleted: undeleted
|
||||
deleted: deleted
|
||||
downvote: downvote
|
||||
upvote: upvote
|
||||
accept: accept
|
||||
cancelled: cancelled
|
||||
commented: commented
|
||||
rollback: rollback
|
||||
edited: edited
|
||||
answered: answered
|
||||
asked: asked
|
||||
closed: closed
|
||||
reopened: reopened
|
||||
created: created
|
||||
title: "History for"
|
||||
show_votes: "Show votes"
|
||||
n_or_a: N/A
|
||||
title_for: "Timeline for"
|
||||
title_for_tag: "Title for tag"
|
||||
datetime: Datetime
|
||||
type: Type
|
||||
by: By
|
||||
comment: Comment
|
||||
no_data: "We couldn't find anything."
|
||||
|
||||
+1
-1
@@ -571,7 +571,7 @@ ui:
|
||||
character: '用户名只能由 "a-z", "0-9", " - . _" 组成'
|
||||
avatar:
|
||||
label: 头像
|
||||
text: 您可以上传图片作为头像,也可以 <1>重置</1> 为
|
||||
text: 您可以上传图片作为头像。
|
||||
bio:
|
||||
label: 关于我 (可选)
|
||||
website:
|
||||
|
||||
@@ -53,4 +53,8 @@ var (
|
||||
const (
|
||||
SiteTypeGeneral = "general"
|
||||
SiteTypeInterface = "interface"
|
||||
SiteTypeBranding = "branding"
|
||||
SiteTypeWrite = "write"
|
||||
SiteTypeLegal = "legal"
|
||||
)
|
||||
|
||||
|
||||
@@ -124,6 +124,15 @@ func GetLoginUserIDFromContext(ctx *gin.Context) (userID string) {
|
||||
return u.UserID
|
||||
}
|
||||
|
||||
// GetIsAdminFromContext get user is admin from context
|
||||
func GetIsAdminFromContext(ctx *gin.Context) (isAdmin bool) {
|
||||
userInfo := GetUserInfoFromContext(ctx)
|
||||
if userInfo == nil {
|
||||
return false
|
||||
}
|
||||
return userInfo.IsAdmin
|
||||
}
|
||||
|
||||
// GetUserInfoFromContext get user info from context
|
||||
func GetUserInfoFromContext(ctx *gin.Context) (u *entity.UserCacheInfo) {
|
||||
userInfo, exist := ctx.Get(ctxUUIDKey)
|
||||
|
||||
@@ -14,33 +14,43 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
EmailOrPasswordWrong = "error.user.email_or_password_wrong"
|
||||
CommentNotFound = "error.comment.not_found"
|
||||
QuestionNotFound = "error.question.not_found"
|
||||
AnswerNotFound = "error.answer.not_found"
|
||||
CommentEditWithoutPermission = "error.comment.edit_without_permission"
|
||||
DisallowVote = "error.object.disallow_vote"
|
||||
DisallowFollow = "error.object.disallow_follow"
|
||||
DisallowVoteYourSelf = "error.object.disallow_vote_your_self"
|
||||
CaptchaVerificationFailed = "error.object.captcha_verification_failed"
|
||||
UserNotFound = "error.user.not_found"
|
||||
UsernameInvalid = "error.user.username_invalid"
|
||||
UsernameDuplicate = "error.user.username_duplicate"
|
||||
UserSetAvatar = "error.user.set_avatar"
|
||||
EmailDuplicate = "error.email.duplicate"
|
||||
EmailVerifyURLExpired = "error.email.verify_url_expired"
|
||||
EmailNeedToBeVerified = "error.email.need_to_be_verified"
|
||||
UserSuspended = "error.user.suspended"
|
||||
ObjectNotFound = "error.object.not_found"
|
||||
TagNotFound = "error.tag.not_found"
|
||||
RankFailToMeetTheCondition = "error.rank.fail_to_meet_the_condition"
|
||||
ThemeNotFound = "error.theme.not_found"
|
||||
LangNotFound = "error.lang.not_found"
|
||||
ReportHandleFailed = "error.report.handle_failed"
|
||||
ReportNotFound = "error.report.not_found"
|
||||
ReadConfigFailed = "error.config.read_config_failed"
|
||||
DatabaseConnectionFailed = "error.database.connection_failed"
|
||||
InstallCreateTableFailed = "error.database.create_table_failed"
|
||||
InstallConfigFailed = "error.install.create_config_failed"
|
||||
SiteInfoNotFound = "error.site_info.not_found"
|
||||
EmailOrPasswordWrong = "error.object.email_or_password_incorrect"
|
||||
CommentNotFound = "error.comment.not_found"
|
||||
QuestionNotFound = "error.question.not_found"
|
||||
QuestionCannotDeleted = "error.question.cannot_deleted"
|
||||
QuestionCannotClose = "error.question.cannot_close"
|
||||
QuestionCannotUpdate = "error.question.cannot_update"
|
||||
AnswerNotFound = "error.answer.not_found"
|
||||
AnswerCannotDeleted = "error.answer.cannot_deleted"
|
||||
AnswerCannotUpdate = "error.answer.cannot_update"
|
||||
CommentEditWithoutPermission = "error.comment.edit_without_permission"
|
||||
DisallowVote = "error.object.disallow_vote"
|
||||
DisallowFollow = "error.object.disallow_follow"
|
||||
DisallowVoteYourSelf = "error.object.disallow_vote_your_self"
|
||||
CaptchaVerificationFailed = "error.object.captcha_verification_failed"
|
||||
OldPasswordVerificationFailed = "error.object.old_password_verification_failed"
|
||||
NewPasswordSameAsPreviousSetting = "error.object.new_password_same_as_previous_setting"
|
||||
UserNotFound = "error.user.not_found"
|
||||
UsernameInvalid = "error.user.username_invalid"
|
||||
UsernameDuplicate = "error.user.username_duplicate"
|
||||
UserSetAvatar = "error.user.set_avatar"
|
||||
EmailDuplicate = "error.email.duplicate"
|
||||
EmailVerifyURLExpired = "error.email.verify_url_expired"
|
||||
EmailNeedToBeVerified = "error.email.need_to_be_verified"
|
||||
UserSuspended = "error.user.suspended"
|
||||
ObjectNotFound = "error.object.not_found"
|
||||
TagNotFound = "error.tag.not_found"
|
||||
TagNotContainSynonym = "error.tag.not_contain_synonym_tags"
|
||||
RankFailToMeetTheCondition = "error.rank.fail_to_meet_the_condition"
|
||||
ThemeNotFound = "error.theme.not_found"
|
||||
LangNotFound = "error.lang.not_found"
|
||||
ReportHandleFailed = "error.report.handle_failed"
|
||||
ReportNotFound = "error.report.not_found"
|
||||
ReadConfigFailed = "error.config.read_config_failed"
|
||||
DatabaseConnectionFailed = "error.database.connection_failed"
|
||||
InstallCreateTableFailed = "error.database.create_table_failed"
|
||||
InstallConfigFailed = "error.install.create_config_failed"
|
||||
SiteInfoNotFound = "error.site_info.not_found"
|
||||
UploadFileSourceUnsupported = "error.upload.source_unsupported"
|
||||
RecommendTagNotExist = "error.tag.recommend_tag_not_found"
|
||||
)
|
||||
|
||||
@@ -26,10 +26,10 @@ type MyValidator struct {
|
||||
Lang i18n.Language
|
||||
}
|
||||
|
||||
// ErrorField error field
|
||||
type ErrorField struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
// FormErrorField indicates the current form error content. which field is error and error message.
|
||||
type FormErrorField struct {
|
||||
ErrorField string `json:"error_field"`
|
||||
ErrorMsg string `json:"error_msg"`
|
||||
}
|
||||
|
||||
// GlobalValidatorMapping is a mapping from validator to translator used
|
||||
@@ -87,7 +87,7 @@ func GetValidatorByLang(la string) *MyValidator {
|
||||
}
|
||||
|
||||
// Check /
|
||||
func (m *MyValidator) Check(value interface{}) (errField *ErrorField, err error) {
|
||||
func (m *MyValidator) Check(value interface{}) (errFields []*FormErrorField, err error) {
|
||||
err = m.Validate.Struct(value)
|
||||
if err != nil {
|
||||
var valErrors validator.ValidationErrors
|
||||
@@ -97,9 +97,9 @@ func (m *MyValidator) Check(value interface{}) (errField *ErrorField, err error)
|
||||
}
|
||||
|
||||
for _, fieldError := range valErrors {
|
||||
errField = &ErrorField{
|
||||
Key: fieldError.Field(),
|
||||
Value: fieldError.Translate(m.Tran),
|
||||
errField := &FormErrorField{
|
||||
ErrorField: fieldError.Field(),
|
||||
ErrorMsg: fieldError.Translate(m.Tran),
|
||||
}
|
||||
|
||||
// get original tag name from value for set err field key.
|
||||
@@ -108,17 +108,24 @@ func (m *MyValidator) Check(value interface{}) (errField *ErrorField, err error)
|
||||
if found {
|
||||
originalTag := getObjectTagByFieldName(value, fieldName)
|
||||
if len(originalTag) > 0 {
|
||||
errField.Key = originalTag
|
||||
errField.ErrorField = originalTag
|
||||
}
|
||||
}
|
||||
return errField, myErrors.BadRequest(reason.RequestFormatError).WithMsg(fieldError.Translate(m.Tran))
|
||||
errFields = append(errFields, errField)
|
||||
}
|
||||
if len(errFields) > 0 {
|
||||
errMsg := ""
|
||||
if len(errFields) == 1 {
|
||||
errMsg = errFields[0].ErrorMsg
|
||||
}
|
||||
return errFields, myErrors.BadRequest(reason.RequestFormatError).WithMsg(errMsg)
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := value.(Checker); ok {
|
||||
errField, err = v.Check()
|
||||
errFields, err = v.Check()
|
||||
if err != nil {
|
||||
return errField, err
|
||||
return errFields, err
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
@@ -126,7 +133,7 @@ func (m *MyValidator) Check(value interface{}) (errField *ErrorField, err error)
|
||||
|
||||
// Checker .
|
||||
type Checker interface {
|
||||
Check() (errField *ErrorField, err error)
|
||||
Check() (errField []*FormErrorField, err error)
|
||||
}
|
||||
|
||||
func getObjectTagByFieldName(obj interface{}, fieldName string) (tag string) {
|
||||
|
||||
@@ -55,7 +55,8 @@ func (ac *AnswerController) RemoveAnswer(ctx *gin.Context) {
|
||||
handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition))
|
||||
return
|
||||
}
|
||||
|
||||
IsAdmin := middleware.GetIsAdminFromContext(ctx)
|
||||
req.IsAdmin = IsAdmin
|
||||
err := ac.answerService.RemoveAnswer(ctx, req)
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
@@ -147,6 +148,8 @@ func (ac *AnswerController) Update(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
IsAdmin := middleware.GetIsAdminFromContext(ctx)
|
||||
req.IsAdmin = IsAdmin
|
||||
|
||||
if can, err := ac.rankService.CheckRankPermission(ctx, req.UserID, rank.AnswerEditRank); err != nil || !can {
|
||||
handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition))
|
||||
@@ -190,6 +193,8 @@ func (ac *AnswerController) AnswerList(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
req.LoginUserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
IsAdmin := middleware.GetIsAdminFromContext(ctx)
|
||||
req.IsAdmin = IsAdmin
|
||||
list, count, err := ac.answerService.SearchList(ctx, req)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
|
||||
@@ -21,4 +21,5 @@ var ProviderSetController = wire.NewSet(
|
||||
NewNotificationController,
|
||||
NewSiteinfoController,
|
||||
NewDashboardController,
|
||||
NewUploadController,
|
||||
)
|
||||
|
||||
@@ -43,9 +43,10 @@ func (qc *QuestionController) RemoveQuestion(ctx *gin.Context) {
|
||||
}
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
if can, err := qc.rankService.CheckRankPermission(ctx, req.UserID, rank.QuestionDeleteRank); err != nil || !can {
|
||||
handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition))
|
||||
handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), errors.Forbidden(reason.RankFailToMeetTheCondition))
|
||||
return
|
||||
}
|
||||
req.IsAdmin = middleware.GetIsAdminFromContext(ctx)
|
||||
|
||||
err := qc.questionService.RemoveQuestion(ctx, req)
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
@@ -67,6 +68,7 @@ func (qc *QuestionController) CloseQuestion(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
req.IsAdmin = middleware.GetIsAdminFromContext(ctx)
|
||||
err := qc.questionService.CloseQuestion(ctx, req)
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
@@ -85,7 +87,8 @@ func (qc *QuestionController) GetQuestion(c *gin.Context) {
|
||||
id := c.Query("id")
|
||||
ctx := context.Background()
|
||||
userID := middleware.GetLoginUserIDFromContext(c)
|
||||
info, err := qc.questionService.GetQuestion(ctx, id, userID, true)
|
||||
IsAdmin := middleware.GetIsAdminFromContext(c)
|
||||
info, err := qc.questionService.GetQuestion(ctx, id, userID, true, IsAdmin)
|
||||
if err != nil {
|
||||
handler.HandleResponse(c, err, nil)
|
||||
return
|
||||
@@ -213,7 +216,7 @@ func (qc *QuestionController) UpdateQuestion(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
|
||||
req.IsAdmin = middleware.GetIsAdminFromContext(ctx)
|
||||
if can, err := qc.rankService.CheckRankPermission(ctx, req.UserID, rank.QuestionEditRank); err != nil || !can {
|
||||
handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition))
|
||||
return
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
type SiteinfoController struct {
|
||||
@@ -30,9 +31,45 @@ func (sc *SiteinfoController) GetSiteInfo(ctx *gin.Context) {
|
||||
resp := &schema.SiteInfoResp{}
|
||||
resp.General, err = sc.siteInfoService.GetSiteGeneral(ctx)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
log.Error(err)
|
||||
}
|
||||
resp.Interface, err = sc.siteInfoService.GetSiteInterface(ctx)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
resp.Branding, err = sc.siteInfoService.GetSiteBranding(ctx)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
handler.HandleResponse(ctx, nil, resp)
|
||||
}
|
||||
|
||||
// GetSiteLegalInfo get site legal info
|
||||
// @Summary get site legal info
|
||||
// @Description get site legal info
|
||||
// @Tags site
|
||||
// @Param info_type query string true "legal information type" Enums(tos, privacy)
|
||||
// @Produce json
|
||||
// @Success 200 {object} handler.RespBody{data=schema.GetSiteLegalInfoResp}
|
||||
// @Router /answer/api/v1/siteinfo/legal [get]
|
||||
func (sc *SiteinfoController) GetSiteLegalInfo(ctx *gin.Context) {
|
||||
req := &schema.GetSiteLegalInfoReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
resp.Face, err = sc.siteInfoService.GetSiteInterface(ctx)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
siteLegal, err := sc.siteInfoService.GetSiteLegal(ctx)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
resp := &schema.GetSiteLegalInfoResp{}
|
||||
if req.IsTOS() {
|
||||
resp.TermsOfServiceOriginalText = siteLegal.TermsOfServiceOriginalText
|
||||
resp.TermsOfServiceParsedText = siteLegal.TermsOfServiceParsedText
|
||||
} else if req.IsPrivacy() {
|
||||
resp.PrivacyPolicyOriginalText = siteLegal.PrivacyPolicyOriginalText
|
||||
resp.PrivacyPolicyParsedText = siteLegal.PrivacyPolicyParsedText
|
||||
}
|
||||
handler.HandleResponse(ctx, nil, resp)
|
||||
}
|
||||
|
||||
@@ -7,19 +7,25 @@ import (
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/rank"
|
||||
"github.com/answerdev/answer/internal/service/tag"
|
||||
"github.com/answerdev/answer/internal/service/tag_common"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
)
|
||||
|
||||
// TagController tag controller
|
||||
type TagController struct {
|
||||
tagService *tag.TagService
|
||||
rankService *rank.RankService
|
||||
tagService *tag.TagService
|
||||
tagCommonService *tag_common.TagCommonService
|
||||
rankService *rank.RankService
|
||||
}
|
||||
|
||||
// NewTagController new controller
|
||||
func NewTagController(tagService *tag.TagService, rankService *rank.RankService) *TagController {
|
||||
return &TagController{tagService: tagService, rankService: rankService}
|
||||
func NewTagController(
|
||||
tagService *tag.TagService,
|
||||
tagCommonService *tag_common.TagCommonService,
|
||||
rankService *rank.RankService,
|
||||
) *TagController {
|
||||
return &TagController{tagService: tagService, tagCommonService: tagCommonService, rankService: rankService}
|
||||
}
|
||||
|
||||
// SearchTagLike get tag list
|
||||
@@ -36,8 +42,8 @@ func (tc *TagController) SearchTagLike(ctx *gin.Context) {
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := tc.tagService.SearchTagLike(ctx, req)
|
||||
req.IsAdmin = middleware.GetIsAdminFromContext(ctx)
|
||||
resp, err := tc.tagCommonService.SearchTagLike(ctx, req)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/service/uploader"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// file is uploaded by markdown(or something else) editor
|
||||
fileFromPost = "post"
|
||||
// file is used to change the user's avatar
|
||||
fileFromAvatar = "avatar"
|
||||
// file is logo/icon images
|
||||
fileFromBranding = "branding"
|
||||
)
|
||||
|
||||
// UploadController upload controller
|
||||
type UploadController struct {
|
||||
uploaderService *uploader.UploaderService
|
||||
}
|
||||
|
||||
// NewUploadController new controller
|
||||
func NewUploadController(uploaderService *uploader.UploaderService) *UploadController {
|
||||
return &UploadController{
|
||||
uploaderService: uploaderService,
|
||||
}
|
||||
}
|
||||
|
||||
// UploadFile upload file
|
||||
// @Summary upload file
|
||||
// @Description upload file
|
||||
// @Tags Upload
|
||||
// @Accept multipart/form-data
|
||||
// @Security ApiKeyAuth
|
||||
// @Param source formData string true "identify the source of the file upload" Enums(post, avatar, branding)
|
||||
// @Param file formData file true "file"
|
||||
// @Success 200 {object} handler.RespBody{data=string}
|
||||
// @Router /answer/api/v1/file [post]
|
||||
func (uc *UploadController) UploadFile(ctx *gin.Context) {
|
||||
var (
|
||||
url string
|
||||
err error
|
||||
)
|
||||
|
||||
source := ctx.PostForm("source")
|
||||
switch source {
|
||||
case fileFromAvatar:
|
||||
url, err = uc.uploaderService.UploadAvatarFile(ctx)
|
||||
case fileFromPost:
|
||||
url, err = uc.uploaderService.UploadPostFile(ctx)
|
||||
case fileFromBranding:
|
||||
url, err = uc.uploaderService.UploadBrandingFile(ctx)
|
||||
default:
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.UploadFileSourceUnsupported), nil)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
handler.HandleResponse(ctx, err, url)
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
"github.com/answerdev/answer/internal/base/middleware"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/base/translator"
|
||||
"github.com/answerdev/answer/internal/base/validator"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service"
|
||||
"github.com/answerdev/answer/internal/service/action"
|
||||
@@ -17,7 +14,6 @@ import (
|
||||
"github.com/answerdev/answer/internal/service/uploader"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
// UserController user controller
|
||||
@@ -106,24 +102,22 @@ func (uc *UserController) UserEmailLogin(ctx *gin.Context) {
|
||||
|
||||
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecordTypeLogin, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
resp := schema.UserVerifyEmailErrorResponse{
|
||||
Key: "captcha_code",
|
||||
Value: "error.object.verification_failed",
|
||||
}
|
||||
resp.Value = translator.GlobalTrans.Tr(handler.GetLang(ctx), resp.Value)
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), resp)
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := uc.userService.EmailLogin(ctx, req)
|
||||
if err != nil {
|
||||
_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeLogin, ctx.ClientIP())
|
||||
resp := schema.UserVerifyEmailErrorResponse{
|
||||
Key: "e_mail",
|
||||
Value: "error.object.email_or_password_incorrect",
|
||||
}
|
||||
resp.Value = translator.GlobalTrans.Tr(handler.GetLang(ctx), resp.Value)
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), resp)
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "e_mail",
|
||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLang(ctx), reason.EmailOrPasswordWrong),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.EmailOrPasswordWrong), errFields)
|
||||
return
|
||||
}
|
||||
uc.actionService.ActionRecordDel(ctx, schema.ActionRecordTypeLogin, ctx.ClientIP())
|
||||
@@ -146,12 +140,11 @@ func (uc *UserController) RetrievePassWord(ctx *gin.Context) {
|
||||
}
|
||||
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecordTypeFindPass, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
resp := schema.UserVerifyEmailErrorResponse{
|
||||
Key: "captcha_code",
|
||||
Value: "error.object.verification_failed",
|
||||
}
|
||||
resp.Value = translator.GlobalTrans.Tr(handler.GetLang(ctx), resp.Value)
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), resp)
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeFindPass, ctx.ClientIP())
|
||||
@@ -277,12 +270,11 @@ func (uc *UserController) UserVerifyEmailSend(ctx *gin.Context) {
|
||||
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecordTypeEmail, ctx.ClientIP(),
|
||||
req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
resp := schema.UserVerifyEmailErrorResponse{
|
||||
Key: "captcha_code",
|
||||
Value: "error.object.verification_failed",
|
||||
}
|
||||
resp.Value = translator.GlobalTrans.Tr(handler.GetLang(ctx), resp.Value)
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), resp)
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -314,22 +306,19 @@ func (uc *UserController) UserModifyPassWord(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
if !oldPassVerification {
|
||||
resp := schema.UserVerifyEmailErrorResponse{
|
||||
Key: "old_pass",
|
||||
Value: "error.object.old_password_verification_failed",
|
||||
}
|
||||
resp.Value = translator.GlobalTrans.Tr(handler.GetLang(ctx), resp.Value)
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), resp)
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "old_pass",
|
||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLang(ctx), reason.OldPasswordVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.OldPasswordVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
if req.OldPass == req.Pass {
|
||||
|
||||
resp := schema.UserVerifyEmailErrorResponse{
|
||||
Key: "pass",
|
||||
Value: "error.object.new_password_same_as_previous_setting",
|
||||
}
|
||||
resp.Value = translator.GlobalTrans.Tr(handler.GetLang(ctx), resp.Value)
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), resp)
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "pass",
|
||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLang(ctx), reason.NewPasswordSameAsPreviousSetting),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.NewPasswordSameAsPreviousSetting), errFields)
|
||||
return
|
||||
}
|
||||
err = uc.userService.UserModifyPassword(ctx, req)
|
||||
@@ -378,66 +367,6 @@ func (uc *UserController) UserUpdateInterface(ctx *gin.Context) {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
// UploadUserAvatar godoc
|
||||
// @Summary UserUpdateInfo
|
||||
// @Description UserUpdateInfo
|
||||
// @Tags User
|
||||
// @Accept multipart/form-data
|
||||
// @Security ApiKeyAuth
|
||||
// @Param file formData file true "file"
|
||||
// @Success 200 {object} handler.RespBody{data=string}
|
||||
// @Router /answer/api/v1/user/avatar/upload [post]
|
||||
func (uc *UserController) UploadUserAvatar(ctx *gin.Context) {
|
||||
// max size
|
||||
var filesMax int64 = 5 << 20
|
||||
var valuesMax int64 = 5
|
||||
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, filesMax+valuesMax)
|
||||
_, header, err := ctx.Request.FormFile("file")
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
fileExt := strings.ToLower(path.Ext(header.Filename))
|
||||
if fileExt != ".jpg" && fileExt != ".png" && fileExt != ".jpeg" {
|
||||
log.Errorf("upload file format is not supported: %s", fileExt)
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
|
||||
url, err := uc.uploaderService.UploadAvatarFile(ctx, header, fileExt)
|
||||
handler.HandleResponse(ctx, err, url)
|
||||
}
|
||||
|
||||
// UploadUserPostFile godoc
|
||||
// @Summary upload user post file
|
||||
// @Description upload user post file
|
||||
// @Tags User
|
||||
// @Accept multipart/form-data
|
||||
// @Security ApiKeyAuth
|
||||
// @Param file formData file true "file"
|
||||
// @Success 200 {object} handler.RespBody{data=string}
|
||||
// @Router /answer/api/v1/user/post/file [post]
|
||||
func (uc *UserController) UploadUserPostFile(ctx *gin.Context) {
|
||||
// max size
|
||||
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 10*1024*1024)
|
||||
_, header, err := ctx.Request.FormFile("file")
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
fileExt := strings.ToLower(path.Ext(header.Filename))
|
||||
if fileExt != ".jpg" && fileExt != ".png" && fileExt != ".jpeg" {
|
||||
log.Errorf("upload file format is not supported: %s", fileExt)
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
|
||||
url, err := uc.uploaderService.UploadPostFile(ctx, header, fileExt)
|
||||
handler.HandleResponse(ctx, err, url)
|
||||
}
|
||||
|
||||
// ActionRecord godoc
|
||||
// @Summary ActionRecord
|
||||
// @Description ActionRecord
|
||||
@@ -502,19 +431,18 @@ func (uc *UserController) UserChangeEmailSendCode(ctx *gin.Context) {
|
||||
|
||||
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecordTypeEmail, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
resp := schema.UserVerifyEmailErrorResponse{
|
||||
Key: "captcha_code",
|
||||
Value: "error.object.verification_failed",
|
||||
}
|
||||
resp.Value = translator.GlobalTrans.Tr(handler.GetLang(ctx), resp.Value)
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), resp)
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeEmail, ctx.ClientIP())
|
||||
resp, err := uc.userService.UserChangeEmailSendCode(ctx, req)
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
resp.Value = translator.GlobalTrans.Tr(handler.GetLang(ctx), resp.Value)
|
||||
resp.ErrorMsg = translator.GlobalTrans.Tr(handler.GetLang(ctx), resp.ErrorMsg)
|
||||
}
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
return
|
||||
|
||||
@@ -2,16 +2,18 @@ package controller_backyard
|
||||
|
||||
import (
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
"github.com/answerdev/answer/internal/base/middleware"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/siteinfo"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// SiteInfoController site info controller
|
||||
type SiteInfoController struct {
|
||||
siteInfoService *siteinfo.SiteInfoService
|
||||
}
|
||||
|
||||
// NewSiteInfoController new siteinfo controller.
|
||||
// NewSiteInfoController new site info controller
|
||||
func NewSiteInfoController(siteInfoService *siteinfo.SiteInfoService) *SiteInfoController {
|
||||
return &SiteInfoController{
|
||||
siteInfoService: siteInfoService,
|
||||
@@ -44,6 +46,45 @@ func (sc *SiteInfoController) GetInterface(ctx *gin.Context) {
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
// GetSiteBranding get site interface
|
||||
// @Summary get site interface
|
||||
// @Description get site interface
|
||||
// @Security ApiKeyAuth
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Success 200 {object} handler.RespBody{data=schema.SiteBrandingResp}
|
||||
// @Router /answer/admin/api/siteinfo/branding [get]
|
||||
func (sc *SiteInfoController) GetSiteBranding(ctx *gin.Context) {
|
||||
resp, err := sc.siteInfoService.GetSiteBranding(ctx)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
// GetSiteWrite get site interface
|
||||
// @Summary get site interface
|
||||
// @Description get site interface
|
||||
// @Security ApiKeyAuth
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Success 200 {object} handler.RespBody{data=schema.SiteWriteResp}
|
||||
// @Router /answer/admin/api/siteinfo/write [get]
|
||||
func (sc *SiteInfoController) GetSiteWrite(ctx *gin.Context) {
|
||||
resp, err := sc.siteInfoService.GetSiteWrite(ctx)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
// GetSiteLegal Set the legal information for the site
|
||||
// @Summary Set the legal information for the site
|
||||
// @Description Set the legal information for the site
|
||||
// @Security ApiKeyAuth
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Success 200 {object} handler.RespBody{data=schema.SiteLegalResp}
|
||||
// @Router /answer/admin/api/siteinfo/legal [get]
|
||||
func (sc *SiteInfoController) GetSiteLegal(ctx *gin.Context) {
|
||||
resp, err := sc.siteInfoService.GetSiteLegal(ctx)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
// UpdateGeneral update site general information
|
||||
// @Summary update site general information
|
||||
// @Description update site general information
|
||||
@@ -80,6 +121,62 @@ func (sc *SiteInfoController) UpdateInterface(ctx *gin.Context) {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
// UpdateBranding update site branding
|
||||
// @Summary update site info branding
|
||||
// @Description update site info branding
|
||||
// @Security ApiKeyAuth
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Param data body schema.SiteBrandingReq true "branding info"
|
||||
// @Success 200 {object} handler.RespBody{}
|
||||
// @Router /answer/admin/api/siteinfo/branding [put]
|
||||
func (sc *SiteInfoController) UpdateBranding(ctx *gin.Context) {
|
||||
req := &schema.SiteBrandingReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
err := sc.siteInfoService.SaveSiteBranding(ctx, req)
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
// UpdateSiteWrite update site write info
|
||||
// @Summary update site write info
|
||||
// @Description update site write info
|
||||
// @Security ApiKeyAuth
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Param data body schema.SiteWriteReq true "write info"
|
||||
// @Success 200 {object} handler.RespBody{}
|
||||
// @Router /answer/admin/api/siteinfo/write [put]
|
||||
func (sc *SiteInfoController) UpdateSiteWrite(ctx *gin.Context) {
|
||||
req := &schema.SiteWriteReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
|
||||
resp, err := sc.siteInfoService.SaveSiteWrite(ctx, req)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
// UpdateSiteLegal update site legal info
|
||||
// @Summary update site legal info
|
||||
// @Description update site legal info
|
||||
// @Security ApiKeyAuth
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Param data body schema.SiteLegalReq true "write info"
|
||||
// @Success 200 {object} handler.RespBody{}
|
||||
// @Router /answer/admin/api/siteinfo/legal [put]
|
||||
func (sc *SiteInfoController) UpdateSiteLegal(ctx *gin.Context) {
|
||||
req := &schema.SiteLegalReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
err := sc.siteInfoService.SaveSiteLegal(ctx, req)
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
// GetSMTPConfig get smtp config
|
||||
// @Summary GetSMTPConfig get smtp config
|
||||
// @Description GetSMTPConfig get smtp config
|
||||
|
||||
@@ -5,4 +5,5 @@ type UserCacheInfo struct {
|
||||
UserID string `json:"user_id"`
|
||||
UserStatus int `json:"user_status"`
|
||||
EmailStatus int `json:"email_status"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ type Tag struct {
|
||||
FollowCount int `xorm:"not null default 0 INT(11) follow_count"`
|
||||
QuestionCount int `xorm:"not null default 0 INT(11) question_count"`
|
||||
Status int `xorm:"not null default 1 INT(11) status"`
|
||||
Recommend bool `xorm:"not null default false BOOL recommend"`
|
||||
Reserved bool `xorm:"not null default false BOOL reserved"`
|
||||
RevisionID string `xorm:"not null default 0 BIGINT(20) revision_id"`
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ var migrations = []Migration{
|
||||
// 0->1
|
||||
NewMigration("this is first version, no operation", noopMigration),
|
||||
NewMigration("add user language", addUserLanguage),
|
||||
NewMigration("add recommend and reserved tag fields", addTagRecommendedAndReserved),
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current db version
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addTagRecommendedAndReserved(x *xorm.Engine) error {
|
||||
type Tag struct {
|
||||
Recommend bool `xorm:"not null default false BOOL recommend"`
|
||||
Reserved bool `xorm:"not null default false BOOL reserved"`
|
||||
}
|
||||
return x.Sync(new(Tag))
|
||||
}
|
||||
@@ -204,7 +204,7 @@ func (ar *answerRepo) SearchList(ctx context.Context, search *entity.AnswerSearc
|
||||
case entity.AnswerSearchOrderByVote:
|
||||
session = session.OrderBy("vote_count desc")
|
||||
default:
|
||||
session = session.OrderBy("adopted desc,vote_count desc")
|
||||
session = session.OrderBy("adopted desc,vote_count desc,created_at asc")
|
||||
}
|
||||
session = session.And("status = ?", entity.AnswerStatusAvailable)
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/answerdev/answer/internal/repo/search_common"
|
||||
"github.com/answerdev/answer/internal/repo/site_info"
|
||||
"github.com/answerdev/answer/internal/repo/tag"
|
||||
"github.com/answerdev/answer/internal/repo/tag_common"
|
||||
"github.com/answerdev/answer/internal/repo/unique"
|
||||
"github.com/answerdev/answer/internal/repo/user"
|
||||
"github.com/google/wire"
|
||||
@@ -53,6 +54,7 @@ var ProviderSetRepo = wire.NewSet(
|
||||
activity.NewQuestionActivityRepo,
|
||||
activity.NewUserActiveActivityRepo,
|
||||
tag.NewTagRepo,
|
||||
tag_common.NewTagCommonRepo,
|
||||
tag.NewTagRelRepo,
|
||||
collection.NewCollectionRepo,
|
||||
collection.NewCollectionGroupRepo,
|
||||
|
||||
@@ -81,7 +81,7 @@ func Test_notificationRepo_GetNotificationPage(t *testing.T) {
|
||||
err := notificationRepo.AddNotification(context.TODO(), ent)
|
||||
assert.NoError(t, err)
|
||||
|
||||
notificationPage, total, err := notificationRepo.GetNotificationPage(context.TODO(), &schema.NotificationSearch{UserID: userID})
|
||||
notificationPage, total, err := notificationRepo.GetNotificationPage(context.TODO(), &schema.NotificationSearch{UserID: ent.UserID})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, total > 0)
|
||||
assert.Equal(t, notificationPage[0].UserID, ent.UserID)
|
||||
|
||||
@@ -52,7 +52,8 @@ var (
|
||||
func TestMain(t *testing.M) {
|
||||
dbSetting, ok := dbSettingMapping[os.Getenv("TEST_DB_DRIVER")]
|
||||
if !ok {
|
||||
dbSetting = dbSettingMapping[string(schemas.MYSQL)]
|
||||
// Use sqlite3 to test.
|
||||
dbSetting = dbSettingMapping[string(schemas.SQLITE)]
|
||||
}
|
||||
defer func() {
|
||||
if tearDown != nil {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/repo/tag"
|
||||
"github.com/answerdev/answer/internal/repo/tag_common"
|
||||
"github.com/answerdev/answer/internal/repo/unique"
|
||||
"github.com/answerdev/answer/pkg/converter"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -42,8 +43,8 @@ var (
|
||||
|
||||
func addTagList() {
|
||||
uniqueIDRepo := unique.NewUniqueIDRepo(testDataSource)
|
||||
tagRepo := tag.NewTagRepo(testDataSource, uniqueIDRepo)
|
||||
err := tagRepo.AddTagList(context.TODO(), testTagList)
|
||||
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, uniqueIDRepo)
|
||||
err := tagCommonRepo.AddTagList(context.TODO(), testTagList)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -51,9 +52,9 @@ func addTagList() {
|
||||
|
||||
func Test_tagRepo_GetTagByID(t *testing.T) {
|
||||
tagOnce.Do(addTagList)
|
||||
tagRepo := tag.NewTagRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
|
||||
gotTag, exist, err := tagRepo.GetTagByID(context.TODO(), testTagList[0].ID)
|
||||
gotTag, exist, err := tagCommonRepo.GetTagByID(context.TODO(), testTagList[0].ID)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
assert.Equal(t, testTagList[0].SlugName, gotTag.SlugName)
|
||||
@@ -61,9 +62,9 @@ func Test_tagRepo_GetTagByID(t *testing.T) {
|
||||
|
||||
func Test_tagRepo_GetTagBySlugName(t *testing.T) {
|
||||
tagOnce.Do(addTagList)
|
||||
tagRepo := tag.NewTagRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
|
||||
gotTag, exist, err := tagRepo.GetTagBySlugName(context.TODO(), testTagList[0].SlugName)
|
||||
gotTag, exist, err := tagCommonRepo.GetTagBySlugName(context.TODO(), testTagList[0].SlugName)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
assert.Equal(t, testTagList[0].SlugName, gotTag.SlugName)
|
||||
@@ -80,36 +81,36 @@ func Test_tagRepo_GetTagList(t *testing.T) {
|
||||
|
||||
func Test_tagRepo_GetTagListByIDs(t *testing.T) {
|
||||
tagOnce.Do(addTagList)
|
||||
tagRepo := tag.NewTagRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
|
||||
gotTags, err := tagRepo.GetTagListByIDs(context.TODO(), []string{testTagList[0].ID})
|
||||
gotTags, err := tagCommonRepo.GetTagListByIDs(context.TODO(), []string{testTagList[0].ID})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, testTagList[0].SlugName, gotTags[0].SlugName)
|
||||
}
|
||||
|
||||
func Test_tagRepo_GetTagListByName(t *testing.T) {
|
||||
tagOnce.Do(addTagList)
|
||||
tagRepo := tag.NewTagRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
|
||||
gotTags, err := tagRepo.GetTagListByName(context.TODO(), testTagList[0].SlugName, 1)
|
||||
gotTags, err := tagCommonRepo.GetTagListByName(context.TODO(), testTagList[0].SlugName, 1, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, testTagList[0].SlugName, gotTags[0].SlugName)
|
||||
}
|
||||
|
||||
func Test_tagRepo_GetTagListByNames(t *testing.T) {
|
||||
tagOnce.Do(addTagList)
|
||||
tagRepo := tag.NewTagRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
|
||||
gotTags, err := tagRepo.GetTagListByNames(context.TODO(), []string{testTagList[0].SlugName})
|
||||
gotTags, err := tagCommonRepo.GetTagListByNames(context.TODO(), []string{testTagList[0].SlugName})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, testTagList[0].SlugName, gotTags[0].SlugName)
|
||||
}
|
||||
|
||||
func Test_tagRepo_GetTagPage(t *testing.T) {
|
||||
tagOnce.Do(addTagList)
|
||||
tagRepo := tag.NewTagRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
|
||||
gotTags, _, err := tagRepo.GetTagPage(context.TODO(), 1, 1, &entity.Tag{SlugName: testTagList[0].SlugName}, "")
|
||||
gotTags, _, err := tagCommonRepo.GetTagPage(context.TODO(), 1, 1, &entity.Tag{SlugName: testTagList[0].SlugName}, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, testTagList[0].SlugName, gotTags[0].SlugName)
|
||||
}
|
||||
@@ -121,7 +122,9 @@ func Test_tagRepo_RemoveTag(t *testing.T) {
|
||||
err := tagRepo.RemoveTag(context.TODO(), testTagList[1].ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, exist, err := tagRepo.GetTagBySlugName(context.TODO(), testTagList[1].SlugName)
|
||||
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
|
||||
_, exist, err := tagCommonRepo.GetTagBySlugName(context.TODO(), testTagList[1].SlugName)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, exist)
|
||||
}
|
||||
@@ -134,21 +137,22 @@ func Test_tagRepo_UpdateTag(t *testing.T) {
|
||||
err := tagRepo.UpdateTag(context.TODO(), testTagList[0])
|
||||
assert.NoError(t, err)
|
||||
|
||||
gotTag, exist, err := tagRepo.GetTagByID(context.TODO(), testTagList[0].ID)
|
||||
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
|
||||
gotTag, exist, err := tagCommonRepo.GetTagByID(context.TODO(), testTagList[0].ID)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
assert.Equal(t, testTagList[0].DisplayName, gotTag.DisplayName)
|
||||
}
|
||||
|
||||
func Test_tagRepo_UpdateTagQuestionCount(t *testing.T) {
|
||||
uniqueIDRepo := unique.NewUniqueIDRepo(testDataSource)
|
||||
tagRepo := tag.NewTagRepo(testDataSource, uniqueIDRepo)
|
||||
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
|
||||
testTagList[0].DisplayName = "golang"
|
||||
err := tagRepo.UpdateTagQuestionCount(context.TODO(), testTagList[0].ID, 100)
|
||||
err := tagCommonRepo.UpdateTagQuestionCount(context.TODO(), testTagList[0].ID, 100)
|
||||
assert.NoError(t, err)
|
||||
|
||||
gotTag, exist, err := tagRepo.GetTagByID(context.TODO(), testTagList[0].ID)
|
||||
gotTag, exist, err := tagCommonRepo.GetTagByID(context.TODO(), testTagList[0].ID)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
assert.Equal(t, 100, gotTag.QuestionCount)
|
||||
@@ -166,7 +170,9 @@ func Test_tagRepo_UpdateTagSynonym(t *testing.T) {
|
||||
converter.StringToInt64(testTagList[0].ID), testTagList[0].SlugName)
|
||||
assert.NoError(t, err)
|
||||
|
||||
gotTag, exist, err := tagRepo.GetTagByID(context.TODO(), testTagList[2].ID)
|
||||
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
|
||||
gotTag, exist, err := tagCommonRepo.GetTagByID(context.TODO(), testTagList[2].ID)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
assert.Equal(t, testTagList[0].ID, fmt.Sprintf("%d", gotTag.MainTagID))
|
||||
|
||||
@@ -3,10 +3,11 @@ package search_common
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/answerdev/answer/pkg/htmltext"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/answerdev/answer/pkg/htmltext"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/data"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
@@ -67,7 +68,7 @@ func NewSearchRepo(data *data.Data, uniqueIDRepo unique.UniqueIDRepo, userCommon
|
||||
}
|
||||
|
||||
// SearchContents search question and answer data
|
||||
func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagID, userID string, votes int, page, size int, order string) (resp []schema.SearchResp, total int64, err error) {
|
||||
func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagIDs []string, userID string, votes int, page, size int, order string) (resp []schema.SearchResp, total int64, err error) {
|
||||
if words = filterWords(words); len(words) == 0 {
|
||||
return
|
||||
}
|
||||
@@ -116,10 +117,12 @@ func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagID,
|
||||
}
|
||||
|
||||
// check tag
|
||||
if tagID != "" {
|
||||
if len(tagIDs) > 0 {
|
||||
b.Join("INNER", "tag_rel", "question.id = tag_rel.object_id").
|
||||
Where(builder.Eq{"tag_rel.tag_id": tagID})
|
||||
argsQ = append(argsQ, tagID)
|
||||
Where(builder.In("tag_rel.tag_id", tagIDs))
|
||||
for _, tagID := range tagIDs {
|
||||
argsQ = append(argsQ, tagID)
|
||||
}
|
||||
}
|
||||
|
||||
// check user
|
||||
@@ -193,7 +196,7 @@ func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagID,
|
||||
}
|
||||
|
||||
// SearchQuestions search question data
|
||||
func (sr *searchRepo) SearchQuestions(ctx context.Context, words []string, limitNoAccepted bool, answers, page, size int, order string) (resp []schema.SearchResp, total int64, err error) {
|
||||
func (sr *searchRepo) SearchQuestions(ctx context.Context, words []string, notAccepted bool, views, answers int, page, size int, order string) (resp []schema.SearchResp, total int64, err error) {
|
||||
if words = filterWords(words); len(words) == 0 {
|
||||
return
|
||||
}
|
||||
@@ -223,11 +226,26 @@ func (sr *searchRepo) SearchQuestions(ctx context.Context, words []string, limit
|
||||
}
|
||||
|
||||
// check need filter has not accepted
|
||||
if limitNoAccepted {
|
||||
if notAccepted {
|
||||
b.And(builder.Eq{"accepted_answer_id": 0})
|
||||
args = append(args, 0)
|
||||
}
|
||||
|
||||
// check views
|
||||
if views > -1 {
|
||||
b.And(builder.Gte{"view_count": views})
|
||||
args = append(args, views)
|
||||
}
|
||||
|
||||
// check answers
|
||||
if answers == 0 {
|
||||
b.And(builder.Eq{"answer_count": answers})
|
||||
args = append(args, answers)
|
||||
} else if answers > 0 {
|
||||
b.And(builder.Gte{"answer_count": answers})
|
||||
args = append(args, answers)
|
||||
}
|
||||
|
||||
if answers == 0 {
|
||||
b.And(builder.Eq{"answer_count": 0})
|
||||
args = append(args, 0)
|
||||
@@ -274,7 +292,7 @@ func (sr *searchRepo) SearchQuestions(ctx context.Context, words []string, limit
|
||||
}
|
||||
|
||||
// SearchAnswers search answer data
|
||||
func (sr *searchRepo) SearchAnswers(ctx context.Context, words []string, limitAccepted bool, questionID string, page, size int, order string) (resp []schema.SearchResp, total int64, err error) {
|
||||
func (sr *searchRepo) SearchAnswers(ctx context.Context, words []string, tagIDs []string, accepted bool, questionID string, page, size int, order string) (resp []schema.SearchResp, total int64, err error) {
|
||||
if words = filterWords(words); len(words) == 0 {
|
||||
return
|
||||
}
|
||||
@@ -303,11 +321,23 @@ func (sr *searchRepo) SearchAnswers(ctx context.Context, words []string, limitAc
|
||||
}
|
||||
}
|
||||
|
||||
if limitAccepted {
|
||||
// check tags
|
||||
// check tag
|
||||
if len(tagIDs) > 0 {
|
||||
b.Join("INNER", "tag_rel", "question.id = tag_rel.object_id").
|
||||
Where(builder.In("tag_rel.tag_id", tagIDs))
|
||||
for _, tagID := range tagIDs {
|
||||
args = append(args, tagID)
|
||||
}
|
||||
}
|
||||
|
||||
// check limit accepted
|
||||
if accepted {
|
||||
b.Where(builder.Eq{"adopted": schema.AnswerAdoptedEnable})
|
||||
args = append(args, schema.AnswerAdoptedEnable)
|
||||
}
|
||||
|
||||
// check question id
|
||||
if questionID != "" {
|
||||
b.Where(builder.Eq{"question_id": questionID})
|
||||
args = append(args, questionID)
|
||||
@@ -390,11 +420,12 @@ func (sr *searchRepo) parseResult(ctx context.Context, res []map[string][]byte)
|
||||
|
||||
// get tags
|
||||
err = sr.data.DB.
|
||||
Select("`display_name`,`slug_name`,`main_tag_slug_name`").
|
||||
Select("`display_name`,`slug_name`,`main_tag_slug_name`,`recommend`,`reserved`").
|
||||
Table("tag").
|
||||
Join("INNER", "tag_rel", "tag.id = tag_rel.tag_id").
|
||||
Where(builder.Eq{"tag_rel.object_id": r["question_id"]}).
|
||||
And(builder.Eq{"tag_rel.status": entity.TagRelStatusAvailable}).
|
||||
UseBool("recommend", "reserved").
|
||||
Find(&tagsEntity)
|
||||
|
||||
if err != nil {
|
||||
@@ -421,6 +452,7 @@ func (sr *searchRepo) parseResult(ctx context.Context, res []map[string][]byte)
|
||||
|
||||
object = schema.SearchObject{
|
||||
ID: string(r["id"]),
|
||||
QuestionID: string(r["question_id"]),
|
||||
Title: string(r["title"]),
|
||||
Excerpt: htmltext.FetchExcerpt(string(r["parsed_text"]), "...", 240),
|
||||
CreatedAtParsed: tp.Unix(),
|
||||
|
||||
@@ -4,10 +4,9 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/data"
|
||||
"github.com/answerdev/answer/internal/base/pager"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
tagcommon "github.com/answerdev/answer/internal/service/tag_common"
|
||||
"github.com/answerdev/answer/internal/service/tag"
|
||||
"github.com/answerdev/answer/internal/service/unique"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"xorm.io/builder"
|
||||
@@ -23,78 +22,13 @@ type tagRepo struct {
|
||||
func NewTagRepo(
|
||||
data *data.Data,
|
||||
uniqueIDRepo unique.UniqueIDRepo,
|
||||
) tagcommon.TagRepo {
|
||||
) tag.TagRepo {
|
||||
return &tagRepo{
|
||||
data: data,
|
||||
uniqueIDRepo: uniqueIDRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// AddTagList add tag
|
||||
func (tr *tagRepo) AddTagList(ctx context.Context, tagList []*entity.Tag) (err error) {
|
||||
for _, item := range tagList {
|
||||
item.ID, err = tr.uniqueIDRepo.GenUniqueIDStr(ctx, item.TableName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
item.RevisionID = "0"
|
||||
}
|
||||
_, err = tr.data.DB.Insert(tagList)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetTagListByIDs get tag list all
|
||||
func (tr *tagRepo) GetTagListByIDs(ctx context.Context, ids []string) (tagList []*entity.Tag, err error) {
|
||||
tagList = make([]*entity.Tag, 0)
|
||||
session := tr.data.DB.In("id", ids)
|
||||
session.Where(builder.Eq{"status": entity.TagStatusAvailable})
|
||||
err = session.Find(&tagList)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetTagBySlugName get tag by slug name
|
||||
func (tr *tagRepo) GetTagBySlugName(ctx context.Context, slugName string) (tagInfo *entity.Tag, exist bool, err error) {
|
||||
tagInfo = &entity.Tag{}
|
||||
session := tr.data.DB.Where("slug_name = ?", slugName)
|
||||
session.Where(builder.Eq{"status": entity.TagStatusAvailable})
|
||||
exist, err = session.Get(tagInfo)
|
||||
if err != nil {
|
||||
return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetTagListByName get tag list all like name
|
||||
func (tr *tagRepo) GetTagListByName(ctx context.Context, name string, limit int) (tagList []*entity.Tag, err error) {
|
||||
tagList = make([]*entity.Tag, 0)
|
||||
session := tr.data.DB.Where("slug_name LIKE ?", name+"%")
|
||||
session.Where(builder.Eq{"status": entity.TagStatusAvailable})
|
||||
session.Limit(limit).Asc("slug_name")
|
||||
err = session.Find(&tagList)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetTagListByNames get tag list all like name
|
||||
func (tr *tagRepo) GetTagListByNames(ctx context.Context, names []string) (tagList []*entity.Tag, err error) {
|
||||
tagList = make([]*entity.Tag, 0)
|
||||
session := tr.data.DB.In("slug_name", names)
|
||||
session.Where(builder.Eq{"status": entity.TagStatusAvailable})
|
||||
err = session.Find(&tagList)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RemoveTag delete tag
|
||||
func (tr *tagRepo) RemoveTag(ctx context.Context, tagID string) (err error) {
|
||||
session := tr.data.DB.Where(builder.Eq{"id": tagID})
|
||||
@@ -114,16 +48,6 @@ func (tr *tagRepo) UpdateTag(ctx context.Context, tag *entity.Tag) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateTagQuestionCount update tag question count
|
||||
func (tr *tagRepo) UpdateTagQuestionCount(ctx context.Context, tagID string, questionCount int) (err error) {
|
||||
cond := &entity.Tag{QuestionCount: questionCount}
|
||||
_, err = tr.data.DB.Where(builder.Eq{"id": tagID}).MustCols("question_count").Update(cond)
|
||||
if err != nil {
|
||||
err = 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,
|
||||
@@ -137,20 +61,6 @@ func (tr *tagRepo) UpdateTagSynonym(ctx context.Context, tagSlugNameList []strin
|
||||
return
|
||||
}
|
||||
|
||||
// GetTagByID get tag one
|
||||
func (tr *tagRepo) GetTagByID(ctx context.Context, tagID string) (
|
||||
tag *entity.Tag, exist bool, err error,
|
||||
) {
|
||||
tag = &entity.Tag{}
|
||||
session := tr.data.DB.Where(builder.Eq{"id": tagID})
|
||||
session.Where(builder.Eq{"status": entity.TagStatusAvailable})
|
||||
exist, err = session.Get(tag)
|
||||
if err != nil {
|
||||
return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetTagList get tag list all
|
||||
func (tr *tagRepo) GetTagList(ctx context.Context, tag *entity.Tag) (tagList []*entity.Tag, err error) {
|
||||
tagList = make([]*entity.Tag, 0)
|
||||
@@ -161,33 +71,3 @@ func (tr *tagRepo) GetTagList(ctx context.Context, tag *entity.Tag) (tagList []*
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetTagPage get tag page
|
||||
func (tr *tagRepo) GetTagPage(ctx context.Context, page, pageSize int, tag *entity.Tag, queryCond string) (
|
||||
tagList []*entity.Tag, total int64, err error,
|
||||
) {
|
||||
tagList = make([]*entity.Tag, 0)
|
||||
session := tr.data.DB.NewSession()
|
||||
|
||||
if len(tag.SlugName) > 0 {
|
||||
session.Where(builder.Or(builder.Like{"slug_name", tag.SlugName}, builder.Like{"display_name", tag.SlugName}))
|
||||
tag.SlugName = ""
|
||||
}
|
||||
session.Where(builder.Eq{"status": entity.TagStatusAvailable})
|
||||
session.Where("main_tag_id = 0") // if this tag is synonym, exclude it
|
||||
|
||||
switch queryCond {
|
||||
case "popular":
|
||||
session.Desc("question_count")
|
||||
case "name":
|
||||
session.Asc("slug_name")
|
||||
case "newest":
|
||||
session.Desc("created_at")
|
||||
}
|
||||
|
||||
total, err = pager.Help(page, pageSize, &tagList, tag, session)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
package tag_common
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/data"
|
||||
"github.com/answerdev/answer/internal/base/pager"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
tagcommon "github.com/answerdev/answer/internal/service/tag_common"
|
||||
"github.com/answerdev/answer/internal/service/unique"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// tagCommonRepo tag repository
|
||||
type tagCommonRepo struct {
|
||||
data *data.Data
|
||||
uniqueIDRepo unique.UniqueIDRepo
|
||||
}
|
||||
|
||||
// NewTagCommonRepo new repository
|
||||
func NewTagCommonRepo(
|
||||
data *data.Data,
|
||||
uniqueIDRepo unique.UniqueIDRepo,
|
||||
) tagcommon.TagCommonRepo {
|
||||
return &tagCommonRepo{
|
||||
data: data,
|
||||
uniqueIDRepo: uniqueIDRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// GetTagListByIDs get tag list all
|
||||
func (tr *tagCommonRepo) GetTagListByIDs(ctx context.Context, ids []string) (tagList []*entity.Tag, err error) {
|
||||
tagList = make([]*entity.Tag, 0)
|
||||
session := tr.data.DB.In("id", ids)
|
||||
session.Where(builder.Eq{"status": entity.TagStatusAvailable})
|
||||
err = session.OrderBy("recommend desc,reserved desc,id desc").Find(&tagList)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetTagBySlugName get tag by slug name
|
||||
func (tr *tagCommonRepo) GetTagBySlugName(ctx context.Context, slugName string) (tagInfo *entity.Tag, exist bool, err error) {
|
||||
tagInfo = &entity.Tag{}
|
||||
session := tr.data.DB.Where("slug_name = ?", slugName)
|
||||
session.Where(builder.Eq{"status": entity.TagStatusAvailable})
|
||||
exist, err = session.Get(tagInfo)
|
||||
if err != nil {
|
||||
return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetTagListByName get tag list all like name
|
||||
func (tr *tagCommonRepo) GetTagListByName(ctx context.Context, name string, limit int, hasReserved bool) (tagList []*entity.Tag, err error) {
|
||||
tagList = make([]*entity.Tag, 0)
|
||||
cond := &entity.Tag{}
|
||||
session := tr.data.DB.Where("")
|
||||
if name != "" {
|
||||
session.Where("slug_name LIKE ?", name+"%")
|
||||
} else {
|
||||
cond.Recommend = true
|
||||
}
|
||||
session.Where(builder.Eq{"status": entity.TagStatusAvailable})
|
||||
session.Limit(limit).Asc("slug_name")
|
||||
if !hasReserved {
|
||||
cond.Reserved = false
|
||||
session.UseBool("recommend", "reserved")
|
||||
} else {
|
||||
session.UseBool("recommend")
|
||||
}
|
||||
err = session.OrderBy("recommend desc,reserved desc,id desc").Find(&tagList, cond)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (tr *tagCommonRepo) GetRecommendTagList(ctx context.Context) (tagList []*entity.Tag, err error) {
|
||||
tagList = make([]*entity.Tag, 0)
|
||||
cond := &entity.Tag{}
|
||||
session := tr.data.DB.Where("")
|
||||
cond.Recommend = true
|
||||
// session.Where(builder.Eq{"status": entity.TagStatusAvailable})
|
||||
session.Asc("slug_name")
|
||||
session.UseBool("recommend")
|
||||
err = session.Find(&tagList, cond)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (tr *tagCommonRepo) GetReservedTagList(ctx context.Context) (tagList []*entity.Tag, err error) {
|
||||
tagList = make([]*entity.Tag, 0)
|
||||
cond := &entity.Tag{}
|
||||
session := tr.data.DB.Where("")
|
||||
cond.Reserved = true
|
||||
// session.Where(builder.Eq{"status": entity.TagStatusAvailable})
|
||||
session.Asc("slug_name")
|
||||
session.UseBool("reserved")
|
||||
err = session.Find(&tagList, cond)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetTagListByNames get tag list all like name
|
||||
func (tr *tagCommonRepo) GetTagListByNames(ctx context.Context, names []string) (tagList []*entity.Tag, err error) {
|
||||
tagList = make([]*entity.Tag, 0)
|
||||
session := tr.data.DB.In("slug_name", names).UseBool("recommend", "reserved")
|
||||
// session.Where(builder.Eq{"status": entity.TagStatusAvailable})
|
||||
err = session.OrderBy("recommend desc,reserved desc,id desc").Find(&tagList)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetTagByID get tag one
|
||||
func (tr *tagCommonRepo) GetTagByID(ctx context.Context, tagID string) (
|
||||
tag *entity.Tag, exist bool, err error,
|
||||
) {
|
||||
tag = &entity.Tag{}
|
||||
session := tr.data.DB.Where(builder.Eq{"id": tagID})
|
||||
session.Where(builder.Eq{"status": entity.TagStatusAvailable})
|
||||
exist, err = session.Get(tag)
|
||||
if err != nil {
|
||||
return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetTagPage get tag page
|
||||
func (tr *tagCommonRepo) GetTagPage(ctx context.Context, page, pageSize int, tag *entity.Tag, queryCond string) (
|
||||
tagList []*entity.Tag, total int64, err error,
|
||||
) {
|
||||
tagList = make([]*entity.Tag, 0)
|
||||
session := tr.data.DB.NewSession()
|
||||
|
||||
if len(tag.SlugName) > 0 {
|
||||
session.Where(builder.Or(builder.Like{"slug_name", tag.SlugName}, builder.Like{"display_name", tag.SlugName}))
|
||||
tag.SlugName = ""
|
||||
}
|
||||
session.Where(builder.Eq{"status": entity.TagStatusAvailable})
|
||||
session.Where("main_tag_id = 0") // if this tag is synonym, exclude it
|
||||
|
||||
switch queryCond {
|
||||
case "popular":
|
||||
session.Desc("question_count")
|
||||
case "name":
|
||||
session.Asc("slug_name")
|
||||
case "newest":
|
||||
session.Desc("created_at")
|
||||
}
|
||||
|
||||
total, err = pager.Help(page, pageSize, &tagList, tag, session)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AddTagList add tag
|
||||
func (tr *tagCommonRepo) AddTagList(ctx context.Context, tagList []*entity.Tag) (err error) {
|
||||
for _, item := range tagList {
|
||||
item.ID, err = tr.uniqueIDRepo.GenUniqueIDStr(ctx, item.TableName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
item.RevisionID = "0"
|
||||
}
|
||||
_, err = tr.data.DB.Insert(tagList)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateTagQuestionCount update tag question count
|
||||
func (tr *tagCommonRepo) UpdateTagQuestionCount(ctx context.Context, tagID string, questionCount int) (err error) {
|
||||
cond := &entity.Tag{QuestionCount: questionCount}
|
||||
_, err = tr.data.DB.Where(builder.Eq{"id": tagID}).MustCols("question_count").Update(cond)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (tr *tagCommonRepo) UpdateTagsAttribute(ctx context.Context, tags []string, attribute string, value bool) (err error) {
|
||||
bean := &entity.Tag{}
|
||||
switch attribute {
|
||||
case "recommend":
|
||||
bean.Recommend = value
|
||||
case "reserved":
|
||||
bean.Reserved = value
|
||||
default:
|
||||
return
|
||||
}
|
||||
session := tr.data.DB.In("slug_name", tags).Cols(attribute).UseBool(attribute)
|
||||
_, err = session.Update(bean)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"xorm.io/builder"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/data"
|
||||
|
||||
@@ -28,6 +28,7 @@ type AnswerAPIRouter struct {
|
||||
siteinfoController *controller.SiteinfoController
|
||||
notificationController *controller.NotificationController
|
||||
dashboardController *controller.DashboardController
|
||||
uploadController *controller.UploadController
|
||||
}
|
||||
|
||||
func NewAnswerAPIRouter(
|
||||
@@ -52,7 +53,7 @@ func NewAnswerAPIRouter(
|
||||
siteinfoController *controller.SiteinfoController,
|
||||
notificationController *controller.NotificationController,
|
||||
dashboardController *controller.DashboardController,
|
||||
|
||||
uploadController *controller.UploadController,
|
||||
) *AnswerAPIRouter {
|
||||
return &AnswerAPIRouter{
|
||||
langController: langController,
|
||||
@@ -76,6 +77,7 @@ func NewAnswerAPIRouter(
|
||||
notificationController: notificationController,
|
||||
siteinfoController: siteinfoController,
|
||||
dashboardController: dashboardController,
|
||||
uploadController: uploadController,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +136,7 @@ func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) {
|
||||
|
||||
//siteinfo
|
||||
r.GET("/siteinfo", a.siteinfoController.GetSiteInfo)
|
||||
r.GET("/siteinfo/legal", a.siteinfoController.GetSiteLegalInfo)
|
||||
}
|
||||
|
||||
func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
|
||||
@@ -180,8 +183,6 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
|
||||
r.PUT("/user/password", a.userController.UserModifyPassWord)
|
||||
r.PUT("/user/info", a.userController.UserUpdateInfo)
|
||||
r.PUT("/user/interface", a.userController.UserUpdateInterface)
|
||||
r.POST("/user/avatar/upload", a.userController.UploadUserAvatar)
|
||||
r.POST("/user/post/file", a.userController.UploadUserPostFile)
|
||||
r.POST("/user/notice/set", a.userController.UserNoticeSet)
|
||||
|
||||
// vote
|
||||
@@ -196,6 +197,9 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
|
||||
r.GET("/notification/page", a.notificationController.GetList)
|
||||
r.PUT("/notification/read/state/all", a.notificationController.ClearUnRead)
|
||||
r.PUT("/notification/read/state", a.notificationController.ClearIDUnRead)
|
||||
|
||||
// upload file
|
||||
r.POST("/file", a.uploadController.UploadFile)
|
||||
}
|
||||
|
||||
func (a *AnswerAPIRouter) RegisterAnswerCmsAPIRouter(r *gin.RouterGroup) {
|
||||
@@ -224,8 +228,14 @@ func (a *AnswerAPIRouter) RegisterAnswerCmsAPIRouter(r *gin.RouterGroup) {
|
||||
// siteinfo
|
||||
r.GET("/siteinfo/general", a.siteInfoController.GetGeneral)
|
||||
r.GET("/siteinfo/interface", a.siteInfoController.GetInterface)
|
||||
r.GET("/siteinfo/branding", a.siteInfoController.GetSiteBranding)
|
||||
r.GET("/siteinfo/write", a.siteInfoController.GetSiteWrite)
|
||||
r.GET("/siteinfo/legal", a.siteInfoController.GetSiteLegal)
|
||||
r.PUT("/siteinfo/general", a.siteInfoController.UpdateGeneral)
|
||||
r.PUT("/siteinfo/interface", a.siteInfoController.UpdateInterface)
|
||||
r.PUT("/siteinfo/branding", a.siteInfoController.UpdateBranding)
|
||||
r.PUT("/siteinfo/write", a.siteInfoController.UpdateSiteWrite)
|
||||
r.PUT("/siteinfo/legal", a.siteInfoController.UpdateSiteLegal)
|
||||
r.GET("/setting/smtp", a.siteInfoController.GetSMTPConfig)
|
||||
r.PUT("/setting/smtp", a.siteInfoController.UpdateSMTPConfig)
|
||||
|
||||
|
||||
@@ -68,24 +68,23 @@ func (a *UIRouter) Register(r *gin.Engine) {
|
||||
|
||||
// specify the not router for default routes and redirect
|
||||
r.NoRoute(func(c *gin.Context) {
|
||||
name := c.Request.URL.Path
|
||||
urlPath := c.Request.URL.Path
|
||||
filePath := ""
|
||||
var file []byte
|
||||
var err error
|
||||
switch name {
|
||||
switch urlPath {
|
||||
case "/favicon.ico":
|
||||
c.Header("content-type", "image/vnd.microsoft.icon")
|
||||
filePath = UIRootFilePath + name
|
||||
filePath = UIRootFilePath + urlPath
|
||||
case "/manifest.json":
|
||||
filePath = UIRootFilePath + name
|
||||
filePath = UIRootFilePath + urlPath
|
||||
case "/install":
|
||||
// if answer is running by run command user can not access install page.
|
||||
c.Redirect(http.StatusFound, "/")
|
||||
return
|
||||
default:
|
||||
filePath = UIIndexFilePath
|
||||
c.Header("content-type", "text/html;charset=utf-8")
|
||||
}
|
||||
file, err = ui.Build.ReadFile(filePath)
|
||||
file, err := ui.Build.ReadFile(filePath)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
c.Status(http.StatusNotFound)
|
||||
|
||||
@@ -5,7 +5,8 @@ type RemoveAnswerReq struct {
|
||||
// answer id
|
||||
ID string `validate:"required" json:"id"`
|
||||
// user id
|
||||
UserID string `json:"-"`
|
||||
UserID string `json:"-"`
|
||||
IsAdmin bool `json:"-"`
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -28,6 +29,7 @@ type AnswerUpdateReq struct {
|
||||
Content string `json:"content"` // content
|
||||
HTML string `json:"html" ` // html
|
||||
EditSummary string `validate:"omitempty" json:"edit_summary"` // edit_summary
|
||||
IsAdmin bool `json:"-"`
|
||||
}
|
||||
|
||||
type AnswerList struct {
|
||||
@@ -36,6 +38,7 @@ type AnswerList struct {
|
||||
Page int `json:"page" form:"page"` // Query number of pages
|
||||
PageSize int `json:"page_size" form:"page_size"` // Search page size
|
||||
LoginUserID string `json:"-" `
|
||||
IsAdmin bool `json:"-"`
|
||||
}
|
||||
|
||||
type AnswerInfo struct {
|
||||
|
||||
@@ -3,8 +3,9 @@ package schema
|
||||
// RemoveQuestionReq delete question request
|
||||
type RemoveQuestionReq struct {
|
||||
// question id
|
||||
ID string `validate:"required" comment:"question id" json:"id"`
|
||||
UserID string `json:"-" ` // user_id
|
||||
ID string `validate:"required" comment:"question id" json:"id"`
|
||||
UserID string `json:"-" ` // user_id
|
||||
IsAdmin bool `json:"-"`
|
||||
}
|
||||
|
||||
type CloseQuestionReq struct {
|
||||
@@ -12,6 +13,7 @@ type CloseQuestionReq struct {
|
||||
UserID string `json:"-" ` // user_id
|
||||
CloseType int `json:"close_type" ` // close_type
|
||||
CloseMsg string `json:"close_msg" ` // close_type
|
||||
IsAdmin bool `json:"-"`
|
||||
}
|
||||
|
||||
type CloseQuestionMeta struct {
|
||||
@@ -46,7 +48,8 @@ type QuestionUpdate struct {
|
||||
// edit summary
|
||||
EditSummary string `validate:"omitempty" json:"edit_summary"`
|
||||
// user id
|
||||
UserID string `json:"-"`
|
||||
UserID string `json:"-"`
|
||||
IsAdmin bool `json:"-"`
|
||||
}
|
||||
|
||||
type QuestionBaseInfo struct {
|
||||
@@ -169,7 +172,7 @@ type CmsQuestionSearch struct {
|
||||
Page int `json:"page" form:"page"` // Query number of pages
|
||||
PageSize int `json:"page_size" form:"page_size"` // Search page size
|
||||
Status int `json:"-" form:"-"`
|
||||
StatusStr string `json:"status" form:"status"` // Status 1 Available 2 closed 10 UserDeleted
|
||||
StatusStr string `json:"status" form:"status"` // Status 1 Available 2 closed 10 UserDeleted
|
||||
Query string `validate:"omitempty,gt=0,lte=100" json:"query" form:"query" ` //Query string
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ type SearchDTO struct {
|
||||
|
||||
type SearchObject struct {
|
||||
ID string `json:"id"`
|
||||
QuestionID string `json:"question_id"`
|
||||
Title string `json:"title"`
|
||||
Excerpt string `json:"excerpt"`
|
||||
CreatedAtParsed int64 `json:"created_at"`
|
||||
@@ -29,6 +30,8 @@ type TagResp struct {
|
||||
DisplayName string `json:"display_name"`
|
||||
// if main tag slug name is not empty, this tag is synonymous with the main tag
|
||||
MainTagSlugName string `json:"main_tag_slug_name"`
|
||||
Recommend bool `json:"recommend"`
|
||||
Reserved bool `json:"reserved"`
|
||||
}
|
||||
|
||||
type SearchResp struct {
|
||||
|
||||
@@ -24,21 +24,76 @@ func (r *SiteGeneralReq) FormatSiteUrl() {
|
||||
|
||||
// SiteInterfaceReq site interface request
|
||||
type SiteInterfaceReq struct {
|
||||
Logo string `validate:"omitempty,gt=0,lte=256" form:"logo" json:"logo"`
|
||||
Theme string `validate:"required,gt=1,lte=128" form:"theme" json:"theme"`
|
||||
Language string `validate:"required,gt=1,lte=128" form:"language" json:"language"`
|
||||
TimeZone string `validate:"required,gt=1,lte=128" form:"time_zone" json:"time_zone"`
|
||||
}
|
||||
|
||||
// SiteBrandingReq site branding request
|
||||
type SiteBrandingReq struct {
|
||||
Logo string `validate:"required,gt=0,lte=512" form:"logo" json:"logo"`
|
||||
MobileLogo string `validate:"omitempty,gt=0,lte=512" form:"mobile_logo" json:"mobile_logo"`
|
||||
SquareIcon string `validate:"required,gt=0,lte=512" form:"square_icon" json:"square_icon"`
|
||||
Favicon string `validate:"omitempty,gt=0,lte=512" form:"favicon" json:"favicon"`
|
||||
}
|
||||
|
||||
// SiteWriteReq site write request
|
||||
type SiteWriteReq struct {
|
||||
RequiredTag bool `validate:"omitempty" form:"required_tag" json:"required_tag"`
|
||||
RecommendTags []string `validate:"omitempty" form:"recommend_tags" json:"recommend_tags"`
|
||||
ReservedTags []string `validate:"omitempty" form:"reserved_tags" json:"reserved_tags"`
|
||||
UserID string `json:"-"`
|
||||
}
|
||||
|
||||
// SiteLegalReq site branding request
|
||||
type SiteLegalReq struct {
|
||||
TermsOfServiceOriginalText string `json:"terms_of_service_original_text"`
|
||||
TermsOfServiceParsedText string `json:"terms_of_service_parsed_text"`
|
||||
PrivacyPolicyOriginalText string `json:"privacy_policy_original_text"`
|
||||
PrivacyPolicyParsedText string `json:"privacy_policy_parsed_text"`
|
||||
}
|
||||
|
||||
// GetSiteLegalInfoReq site site legal request
|
||||
type GetSiteLegalInfoReq struct {
|
||||
InfoType string `validate:"required,oneof=tos privacy" form:"info_type"`
|
||||
}
|
||||
|
||||
func (r *GetSiteLegalInfoReq) IsTOS() bool {
|
||||
return r.InfoType == "tos"
|
||||
}
|
||||
|
||||
func (r *GetSiteLegalInfoReq) IsPrivacy() bool {
|
||||
return r.InfoType == "privacy"
|
||||
}
|
||||
|
||||
// GetSiteLegalInfoResp get site legal info response
|
||||
type GetSiteLegalInfoResp struct {
|
||||
TermsOfServiceOriginalText string `json:"terms_of_service_original_text,omitempty"`
|
||||
TermsOfServiceParsedText string `json:"terms_of_service_parsed_text,omitempty"`
|
||||
PrivacyPolicyOriginalText string `json:"privacy_policy_original_text,omitempty"`
|
||||
PrivacyPolicyParsedText string `json:"privacy_policy_parsed_text,omitempty"`
|
||||
}
|
||||
|
||||
// SiteGeneralResp site general response
|
||||
type SiteGeneralResp SiteGeneralReq
|
||||
|
||||
// SiteInterfaceResp site interface response
|
||||
type SiteInterfaceResp SiteInterfaceReq
|
||||
|
||||
// SiteBrandingResp site branding response
|
||||
type SiteBrandingResp SiteBrandingReq
|
||||
|
||||
// SiteWriteResp site write response
|
||||
type SiteWriteResp SiteWriteReq
|
||||
|
||||
// SiteLegalResp site write response
|
||||
type SiteLegalResp SiteLegalReq
|
||||
|
||||
// SiteInfoResp get site info response
|
||||
type SiteInfoResp struct {
|
||||
General *SiteGeneralResp `json:"general"`
|
||||
Face *SiteInterfaceResp `json:"interface"`
|
||||
General *SiteGeneralResp `json:"general"`
|
||||
Interface *SiteInterfaceResp `json:"interface"`
|
||||
Branding *SiteBrandingResp `json:"branding"`
|
||||
}
|
||||
|
||||
// UpdateSMTPConfigReq get smtp config request
|
||||
|
||||
@@ -11,7 +11,8 @@ import (
|
||||
// SearchTagLikeReq get tag list all request
|
||||
type SearchTagLikeReq struct {
|
||||
// tag
|
||||
Tag string `validate:"required,gt=0,lte=35" form:"tag"`
|
||||
Tag string `validate:"omitempty" form:"tag"`
|
||||
IsAdmin bool `json:"-"`
|
||||
}
|
||||
|
||||
// GetTagInfoReq get tag info request
|
||||
@@ -24,7 +25,7 @@ type GetTagInfoReq struct {
|
||||
UserID string `json:"-"`
|
||||
}
|
||||
|
||||
func (r *GetTagInfoReq) Check() (errField *validator.ErrorField, err error) {
|
||||
func (r *GetTagInfoReq) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
if len(r.ID) == 0 && len(r.Name) == 0 {
|
||||
return nil, errors.BadRequest(reason.RequestFormatError)
|
||||
}
|
||||
@@ -60,6 +61,8 @@ type GetTagResp struct {
|
||||
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"`
|
||||
Recommend bool `json:"recommend"`
|
||||
Reserved bool `json:"reserved"`
|
||||
}
|
||||
|
||||
func (tr *GetTagResp) GetExcerpt() {
|
||||
@@ -95,6 +98,8 @@ type GetTagPageResp struct {
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
// updated time
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
Recommend bool `json:"recommend"`
|
||||
Reserved bool `json:"reserved"`
|
||||
}
|
||||
|
||||
func (tr *GetTagPageResp) GetExcerpt() {
|
||||
@@ -150,9 +155,9 @@ type UpdateTagReq struct {
|
||||
UserID string `json:"-"`
|
||||
}
|
||||
|
||||
func (r *UpdateTagReq) Check() (errField *validator.ErrorField, err error) {
|
||||
func (r *UpdateTagReq) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
if len(r.EditSummary) == 0 {
|
||||
r.EditSummary = "tag.edit.summary" // to i18n
|
||||
r.EditSummary = "tag.edit.summary" // to do i18n
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -217,4 +222,12 @@ type GetFollowingTagsResp struct {
|
||||
DisplayName string `json:"display_name"`
|
||||
// if main tag slug name is not empty, this tag is synonymous with the main tag
|
||||
MainTagSlugName string `json:"main_tag_slug_name"`
|
||||
Recommend bool `json:"recommend"`
|
||||
Reserved bool `json:"reserved"`
|
||||
}
|
||||
|
||||
type SearchTagLikeResp struct {
|
||||
SlugName string `json:"slug_name"`
|
||||
Recommend bool `json:"recommend"`
|
||||
Reserved bool `json:"reserved"`
|
||||
}
|
||||
|
||||
@@ -219,10 +219,10 @@ var UserStatusShowMsg = map[int]string{
|
||||
|
||||
// EmailLogin
|
||||
type UserEmailLogin struct {
|
||||
Email string `json:"e_mail" ` // e_mail
|
||||
Pass string `json:"pass" ` // password
|
||||
CaptchaID string `json:"captcha_id" ` // captcha_id
|
||||
CaptchaCode string `json:"captcha_code" ` // captcha_code
|
||||
Email string `validate:"required,email,gt=0,lte=500" json:"e_mail"` // e_mail
|
||||
Pass string `validate:"required,gte=8,lte=32" json:"pass"` // password
|
||||
CaptchaID string `json:"captcha_id"` // captcha_id
|
||||
CaptchaCode string `json:"captcha_code"` // captcha_code
|
||||
}
|
||||
|
||||
// UserRegisterReq user register request
|
||||
@@ -236,14 +236,16 @@ type UserRegisterReq struct {
|
||||
IP string `json:"-" `
|
||||
}
|
||||
|
||||
func (u *UserRegisterReq) Check() (errField *validator.ErrorField, err error) {
|
||||
func (u *UserRegisterReq) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
// TODO i18n
|
||||
err = checker.CheckPassword(8, 32, 0, u.Pass)
|
||||
if err != nil {
|
||||
return &validator.ErrorField{
|
||||
Key: "pass",
|
||||
Value: err.Error(),
|
||||
}, err
|
||||
errField := &validator.FormErrorField{
|
||||
ErrorField: "pass",
|
||||
ErrorMsg: err.Error(),
|
||||
}
|
||||
errFields = append(errFields, errField)
|
||||
return errFields, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -255,14 +257,16 @@ type UserModifyPassWordRequest struct {
|
||||
Pass string `json:"pass" ` // password
|
||||
}
|
||||
|
||||
func (u *UserModifyPassWordRequest) Check() (errField *validator.ErrorField, err error) {
|
||||
func (u *UserModifyPassWordRequest) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
// TODO i18n
|
||||
err = checker.CheckPassword(8, 32, 0, u.Pass)
|
||||
if err != nil {
|
||||
return &validator.ErrorField{
|
||||
Key: "pass",
|
||||
Value: err.Error(),
|
||||
}, err
|
||||
errField := &validator.FormErrorField{
|
||||
ErrorField: "pass",
|
||||
ErrorMsg: err.Error(),
|
||||
}
|
||||
errFields = append(errFields, errField)
|
||||
return errFields, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -292,16 +296,17 @@ type AvatarInfo struct {
|
||||
Custom string `validate:"omitempty,gt=0,lte=200" json:"custom"`
|
||||
}
|
||||
|
||||
func (u *UpdateInfoRequest) Check() (errField *validator.ErrorField, err error) {
|
||||
func (u *UpdateInfoRequest) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
if len(u.Username) > 0 {
|
||||
re := regexp.MustCompile(`^[a-z0-9._-]{4,30}$`)
|
||||
match := re.MatchString(u.Username)
|
||||
if !match {
|
||||
err = errors.BadRequest(reason.UsernameInvalid)
|
||||
return &validator.ErrorField{
|
||||
Key: "username",
|
||||
Value: err.Error(),
|
||||
}, err
|
||||
errField := &validator.FormErrorField{
|
||||
ErrorField: "username",
|
||||
ErrorMsg: err.Error(),
|
||||
}
|
||||
errFields = append(errFields, errField)
|
||||
return errFields, errors.BadRequest(reason.UsernameInvalid)
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
@@ -327,14 +332,16 @@ type UserRePassWordRequest struct {
|
||||
Content string `json:"-"`
|
||||
}
|
||||
|
||||
func (u *UserRePassWordRequest) Check() (errField *validator.ErrorField, err error) {
|
||||
func (u *UserRePassWordRequest) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
// TODO i18n
|
||||
err = checker.CheckPassword(8, 32, 0, u.Pass)
|
||||
if err != nil {
|
||||
return &validator.ErrorField{
|
||||
Key: "pass",
|
||||
Value: err.Error(),
|
||||
}, err
|
||||
errField := &validator.FormErrorField{
|
||||
ErrorField: "pass",
|
||||
ErrorMsg: err.Error(),
|
||||
}
|
||||
errFields = append(errFields, errField)
|
||||
return errFields, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -410,8 +417,3 @@ type UserVerifyEmailSendReq struct {
|
||||
CaptchaID string `validate:"omitempty,gt=0,lte=500" json:"captcha_id"`
|
||||
CaptchaCode string `validate:"omitempty,gt=0,lte=500" json:"captcha_code"`
|
||||
}
|
||||
|
||||
type UserVerifyEmailErrorResponse struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
@@ -73,11 +73,29 @@ func (as *AnswerService) RemoveAnswer(ctx context.Context, req *schema.RemoveAns
|
||||
if !exist {
|
||||
return nil
|
||||
}
|
||||
if answerInfo.UserID != req.UserID {
|
||||
return errors.BadRequest(reason.UnauthorizedError)
|
||||
}
|
||||
if answerInfo.Adopted == schema.AnswerAdoptedEnable {
|
||||
return errors.BadRequest(reason.UnauthorizedError)
|
||||
if !req.IsAdmin {
|
||||
if answerInfo.UserID != req.UserID {
|
||||
return errors.BadRequest(reason.AnswerCannotDeleted)
|
||||
}
|
||||
if answerInfo.VoteCount > 0 {
|
||||
return errors.BadRequest(reason.AnswerCannotDeleted)
|
||||
}
|
||||
if answerInfo.Adopted == schema.AnswerAdoptedEnable {
|
||||
return errors.BadRequest(reason.AnswerCannotDeleted)
|
||||
}
|
||||
questionInfo, exist, err := as.questionRepo.GetQuestion(ctx, answerInfo.QuestionID)
|
||||
if err != nil {
|
||||
return errors.BadRequest(reason.AnswerCannotDeleted)
|
||||
}
|
||||
if !exist {
|
||||
return errors.BadRequest(reason.AnswerCannotDeleted)
|
||||
}
|
||||
if questionInfo.AnswerCount > 1 {
|
||||
return errors.BadRequest(reason.AnswerCannotDeleted)
|
||||
}
|
||||
if questionInfo.AcceptedAnswerID != "" {
|
||||
return errors.BadRequest(reason.AnswerCannotDeleted)
|
||||
}
|
||||
}
|
||||
|
||||
// user add question count
|
||||
@@ -164,6 +182,19 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq
|
||||
if !exist {
|
||||
return "", errors.BadRequest(reason.QuestionNotFound)
|
||||
}
|
||||
if !req.IsAdmin {
|
||||
answerInfo, exist, err := as.answerRepo.GetByID(ctx, req.ID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !exist {
|
||||
return "", nil
|
||||
}
|
||||
if answerInfo.UserID != req.UserID {
|
||||
return "", errors.BadRequest(reason.AnswerCannotUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
insertData := new(entity.Answer)
|
||||
insertData.ID = req.ID
|
||||
@@ -361,14 +392,14 @@ func (as *AnswerService) SearchList(ctx context.Context, search *schema.AnswerLi
|
||||
if err != nil {
|
||||
return list, count, err
|
||||
}
|
||||
AnswerList, err := as.SearchFormatInfo(ctx, dblist, search.LoginUserID)
|
||||
AnswerList, err := as.SearchFormatInfo(ctx, dblist, search.LoginUserID, search.IsAdmin)
|
||||
if err != nil {
|
||||
return AnswerList, count, err
|
||||
}
|
||||
return AnswerList, count, nil
|
||||
}
|
||||
|
||||
func (as *AnswerService) SearchFormatInfo(ctx context.Context, dblist []*entity.Answer, loginUserID string) ([]*schema.AnswerInfo, error) {
|
||||
func (as *AnswerService) SearchFormatInfo(ctx context.Context, dblist []*entity.Answer, loginUserID string, isAdmin bool) ([]*schema.AnswerInfo, error) {
|
||||
list := make([]*schema.AnswerInfo, 0)
|
||||
objectIds := make([]string, 0)
|
||||
userIds := make([]string, 0)
|
||||
@@ -411,7 +442,7 @@ func (as *AnswerService) SearchFormatInfo(ctx context.Context, dblist []*entity.
|
||||
}
|
||||
|
||||
for _, item := range list {
|
||||
item.MemberActions = permission.GetAnswerPermission(loginUserID, item.UserID)
|
||||
item.MemberActions = permission.GetAnswerPermission(ctx, loginUserID, item.UserID, isAdmin)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
|
||||
@@ -43,6 +43,7 @@ func (as *AuthService) GetUserCacheInfo(ctx context.Context, accessToken string)
|
||||
log.Infof("user status updated: %+v", cacheInfo)
|
||||
userCacheInfo.UserStatus = cacheInfo.UserStatus
|
||||
userCacheInfo.EmailStatus = cacheInfo.EmailStatus
|
||||
userCacheInfo.IsAdmin = cacheInfo.IsAdmin
|
||||
// update current user cache info
|
||||
err := as.authRepo.SetUserCacheInfo(ctx, accessToken, userCacheInfo)
|
||||
if err != nil {
|
||||
@@ -58,6 +59,10 @@ func (as *AuthService) SetUserCacheInfo(ctx context.Context, userInfo *entity.Us
|
||||
return accessToken, err
|
||||
}
|
||||
|
||||
func (as *AuthService) SetUserStatus(ctx context.Context, userInfo *entity.UserCacheInfo) (err error) {
|
||||
return as.authRepo.SetUserStatus(ctx, userInfo.UserID, userInfo)
|
||||
}
|
||||
|
||||
func (as *AuthService) UpdateUserCacheInfo(ctx context.Context, token string, userInfo *entity.UserCacheInfo) (err error) {
|
||||
err = as.authRepo.SetUserCacheInfo(ctx, token, userInfo)
|
||||
if err != nil {
|
||||
|
||||
@@ -121,7 +121,7 @@ func (cs *CommentService) AddComment(ctx context.Context, req *schema.AddComment
|
||||
|
||||
resp = &schema.GetCommentResp{}
|
||||
resp.SetFromComment(comment)
|
||||
resp.MemberActions = permission.GetCommentPermission(req.UserID, resp.UserID)
|
||||
resp.MemberActions = permission.GetCommentPermission(ctx, req.UserID, resp.UserID)
|
||||
|
||||
// get reply user info
|
||||
if len(resp.ReplyUserID) > 0 {
|
||||
@@ -222,7 +222,7 @@ func (cs *CommentService) GetComment(ctx context.Context, req *schema.GetComment
|
||||
// check if current user vote this comment
|
||||
resp.IsVote = cs.checkIsVote(ctx, req.UserID, resp.CommentID)
|
||||
|
||||
resp.MemberActions = permission.GetCommentPermission(req.UserID, resp.UserID)
|
||||
resp.MemberActions = permission.GetCommentPermission(ctx, req.UserID, resp.UserID)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ func (cs *CommentService) GetCommentWithPage(ctx context.Context, req *schema.Ge
|
||||
// check if current user vote this comment
|
||||
commentResp.IsVote = cs.checkIsVote(ctx, req.UserID, commentResp.CommentID)
|
||||
|
||||
commentResp.MemberActions = permission.GetCommentPermission(req.UserID, commentResp.UserID)
|
||||
commentResp.MemberActions = permission.GetCommentPermission(ctx, req.UserID, commentResp.UserID)
|
||||
resp = append(resp, commentResp)
|
||||
}
|
||||
return pager.NewPageModel(total, resp), nil
|
||||
|
||||
@@ -76,17 +76,16 @@ func (ds *DashboardService) StatisticalByCache(ctx context.Context) (*schema.Das
|
||||
if err != nil {
|
||||
info, statisticalErr := ds.Statistical(ctx)
|
||||
if statisticalErr != nil {
|
||||
return dashboardInfo, err
|
||||
return nil, statisticalErr
|
||||
}
|
||||
setCacheErr := ds.SetCache(ctx, info)
|
||||
if setCacheErr != nil {
|
||||
log.Error("ds.SetCache", setCacheErr)
|
||||
if setCacheErr := ds.SetCache(ctx, info); setCacheErr != nil {
|
||||
log.Errorf("set dashboard statistical failed: %s", setCacheErr)
|
||||
}
|
||||
return info, err
|
||||
return info, nil
|
||||
}
|
||||
err = json.Unmarshal([]byte(infoStr), dashboardInfo)
|
||||
if err != nil {
|
||||
return dashboardInfo, err
|
||||
if err = json.Unmarshal([]byte(infoStr), dashboardInfo); err != nil {
|
||||
log.Errorf("parsing dashboard information failed: %s", err)
|
||||
return nil, errors.InternalServer(reason.UnknownError)
|
||||
}
|
||||
startTime := time.Now().Unix() - schema.AppStartTime.Unix()
|
||||
dashboardInfo.AppStartTime = fmt.Sprintf("%d", startTime)
|
||||
|
||||
@@ -15,7 +15,7 @@ type FollowRepo interface {
|
||||
}
|
||||
|
||||
type FollowService struct {
|
||||
tagRepo tagcommon.TagRepo
|
||||
tagRepo tagcommon.TagCommonRepo
|
||||
followRepo FollowRepo
|
||||
followCommonRepo activity_common.FollowRepo
|
||||
}
|
||||
@@ -23,7 +23,7 @@ type FollowService struct {
|
||||
func NewFollowService(
|
||||
followRepo FollowRepo,
|
||||
followCommonRepo activity_common.FollowRepo,
|
||||
tagRepo tagcommon.TagRepo,
|
||||
tagRepo tagcommon.TagCommonRepo,
|
||||
) *FollowService {
|
||||
return &FollowService{
|
||||
followRepo: followRepo,
|
||||
|
||||
@@ -19,7 +19,7 @@ type ObjService struct {
|
||||
answerRepo answercommon.AnswerRepo
|
||||
questionRepo questioncommon.QuestionRepo
|
||||
commentRepo comment_common.CommentCommonRepo
|
||||
tagRepo tagcommon.TagRepo
|
||||
tagRepo tagcommon.TagCommonRepo
|
||||
}
|
||||
|
||||
// NewObjService new object service
|
||||
@@ -27,7 +27,7 @@ func NewObjService(
|
||||
answerRepo answercommon.AnswerRepo,
|
||||
questionRepo questioncommon.QuestionRepo,
|
||||
commentRepo comment_common.CommentCommonRepo,
|
||||
tagRepo tagcommon.TagRepo) *ObjService {
|
||||
tagRepo tagcommon.TagCommonRepo) *ObjService {
|
||||
return &ObjService{
|
||||
answerRepo: answerRepo,
|
||||
questionRepo: questionRepo,
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package permission
|
||||
|
||||
import "github.com/answerdev/answer/internal/schema"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
)
|
||||
|
||||
// TODO: There is currently no permission management
|
||||
func GetCommentPermission(userID string, commentCreatorUserID string) (
|
||||
func GetCommentPermission(ctx context.Context, userID string, commentCreatorUserID string) (
|
||||
actions []*schema.PermissionMemberAction) {
|
||||
actions = make([]*schema.PermissionMemberAction, 0)
|
||||
if len(userID) > 0 {
|
||||
@@ -31,7 +35,7 @@ func GetCommentPermission(userID string, commentCreatorUserID string) (
|
||||
return actions
|
||||
}
|
||||
|
||||
func GetTagPermission(userID string, tagCreatorUserID string) (
|
||||
func GetTagPermission(ctx context.Context, userID string, tagCreatorUserID string) (
|
||||
actions []*schema.PermissionMemberAction) {
|
||||
if userID != tagCreatorUserID {
|
||||
return []*schema.PermissionMemberAction{}
|
||||
@@ -50,18 +54,20 @@ func GetTagPermission(userID string, tagCreatorUserID string) (
|
||||
}
|
||||
}
|
||||
|
||||
func GetAnswerPermission(userID string, answerAuthID string) (
|
||||
func GetAnswerPermission(ctx context.Context, userID string, answerAuthID string, isAdmin bool) (
|
||||
actions []*schema.PermissionMemberAction) {
|
||||
actions = make([]*schema.PermissionMemberAction, 0)
|
||||
if len(userID) > 0 {
|
||||
actions = append(actions, &schema.PermissionMemberAction{
|
||||
Action: "report",
|
||||
Name: "Flag",
|
||||
Type: "reason",
|
||||
})
|
||||
}
|
||||
if userID != answerAuthID {
|
||||
return actions
|
||||
if !isAdmin {
|
||||
if len(userID) > 0 {
|
||||
actions = append(actions, &schema.PermissionMemberAction{
|
||||
Action: "report",
|
||||
Name: "Flag",
|
||||
Type: "reason",
|
||||
})
|
||||
}
|
||||
if userID != answerAuthID {
|
||||
return actions
|
||||
}
|
||||
}
|
||||
actions = append(actions, []*schema.PermissionMemberAction{
|
||||
{
|
||||
@@ -78,18 +84,19 @@ func GetAnswerPermission(userID string, answerAuthID string) (
|
||||
return actions
|
||||
}
|
||||
|
||||
func GetQuestionPermission(userID string, questionAuthID string) (
|
||||
actions []*schema.PermissionMemberAction) {
|
||||
func GetQuestionPermission(ctx context.Context, userID string, questionAuthID string, isAdmin bool) (actions []*schema.PermissionMemberAction) {
|
||||
actions = make([]*schema.PermissionMemberAction, 0)
|
||||
if len(userID) > 0 {
|
||||
actions = append(actions, &schema.PermissionMemberAction{
|
||||
Action: "report",
|
||||
Name: "Flag",
|
||||
Type: "reason",
|
||||
})
|
||||
}
|
||||
if userID != questionAuthID {
|
||||
return actions
|
||||
if !isAdmin {
|
||||
if len(userID) > 0 {
|
||||
actions = append(actions, &schema.PermissionMemberAction{
|
||||
Action: "report",
|
||||
Name: "Flag",
|
||||
Type: "reason",
|
||||
})
|
||||
}
|
||||
if userID != questionAuthID {
|
||||
return actions
|
||||
}
|
||||
}
|
||||
actions = append(actions, []*schema.PermissionMemberAction{
|
||||
{
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/answerdev/answer/internal/service/revision_common"
|
||||
"github.com/answerdev/answer/internal/service/siteinfo"
|
||||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||
"github.com/answerdev/answer/internal/service/search_parser"
|
||||
"github.com/answerdev/answer/internal/service/tag"
|
||||
tagcommon "github.com/answerdev/answer/internal/service/tag_common"
|
||||
"github.com/answerdev/answer/internal/service/uploader"
|
||||
@@ -57,6 +58,7 @@ var ProviderSetService = wire.NewSet(
|
||||
revision_common.NewRevisionService,
|
||||
NewRevisionService,
|
||||
rank.NewRankService,
|
||||
search_parser.NewSearchParser,
|
||||
NewSearchService,
|
||||
meta.NewMetaService,
|
||||
object_info.NewObjService,
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/constant"
|
||||
@@ -70,6 +71,12 @@ func (qs *QuestionService) CloseQuestion(ctx context.Context, req *schema.CloseQ
|
||||
if !has {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !req.IsAdmin {
|
||||
if questionInfo.UserID != req.UserID {
|
||||
return errors.BadRequest(reason.QuestionCannotClose)
|
||||
}
|
||||
}
|
||||
questionInfo.Status = entity.QuestionStatusclosed
|
||||
err = qs.questionRepo.UpdateQuestionStatus(ctx, questionInfo)
|
||||
if err != nil {
|
||||
@@ -105,6 +112,16 @@ func (qs *QuestionService) CloseMsgList(ctx context.Context, lang i18n.Language)
|
||||
|
||||
// AddQuestion add question
|
||||
func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.QuestionAdd) (questionInfo *schema.QuestionInfo, err error) {
|
||||
recommendExist, err := qs.tagCommon.ExistRecommend(ctx, req.Tags)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !recommendExist {
|
||||
err = fmt.Errorf("recommend is not exist")
|
||||
err = errors.BadRequest(reason.RecommendTagNotExist).WithError(err).WithStack()
|
||||
return
|
||||
}
|
||||
|
||||
questionInfo = &schema.QuestionInfo{}
|
||||
question := &entity.Question{}
|
||||
now := time.Now()
|
||||
@@ -150,7 +167,7 @@ func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.Question
|
||||
log.Error("user IncreaseQuestionCount error", err.Error())
|
||||
}
|
||||
|
||||
questionInfo, err = qs.GetQuestion(ctx, question.ID, question.UserID, false)
|
||||
questionInfo, err = qs.GetQuestion(ctx, question.ID, question.UserID, false, false)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -163,15 +180,31 @@ func (qs *QuestionService) RemoveQuestion(ctx context.Context, req *schema.Remov
|
||||
if !has {
|
||||
return nil
|
||||
}
|
||||
if questionInfo.UserID != req.UserID {
|
||||
return errors.BadRequest(reason.UnauthorizedError)
|
||||
}
|
||||
if !req.IsAdmin {
|
||||
if questionInfo.UserID != req.UserID {
|
||||
return errors.BadRequest(reason.QuestionCannotDeleted)
|
||||
}
|
||||
|
||||
if questionInfo.AcceptedAnswerID != "" {
|
||||
return errors.BadRequest(reason.UnauthorizedError)
|
||||
}
|
||||
if questionInfo.AnswerCount > 0 {
|
||||
return errors.BadRequest(reason.UnauthorizedError)
|
||||
if questionInfo.AcceptedAnswerID != "0" {
|
||||
return errors.BadRequest(reason.QuestionCannotDeleted)
|
||||
}
|
||||
if questionInfo.AnswerCount > 1 {
|
||||
return errors.BadRequest(reason.QuestionCannotDeleted)
|
||||
}
|
||||
|
||||
if questionInfo.AnswerCount == 1 {
|
||||
answersearch := &entity.AnswerSearch{}
|
||||
answersearch.QuestionID = req.ID
|
||||
answerList, _, err := qs.questioncommon.AnswerCommon.Search(ctx, answersearch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, answer := range answerList {
|
||||
if answer.VoteCount > 0 {
|
||||
return errors.BadRequest(reason.QuestionCannotDeleted)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
questionInfo.Status = entity.QuestionStatusDeleted
|
||||
@@ -212,9 +245,34 @@ func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.Quest
|
||||
if !has {
|
||||
return
|
||||
}
|
||||
if dbinfo.UserID != req.UserID {
|
||||
if !req.IsAdmin {
|
||||
if dbinfo.UserID != req.UserID {
|
||||
return questionInfo, errors.BadRequest(reason.QuestionCannotUpdate)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//CheckChangeTag
|
||||
oldTags, err := qs.tagCommon.GetObjectEntityTag(ctx, question.ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tagNameList := make([]string, 0)
|
||||
for _, tag := range req.Tags {
|
||||
tagNameList = append(tagNameList, tag.SlugName)
|
||||
}
|
||||
Tags, err := qs.tagCommon.GetTagListByNames(ctx, tagNameList)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
CheckTag, CheckTaglist := qs.CheckChangeTag(ctx, oldTags, Tags)
|
||||
if !CheckTag {
|
||||
err = errors.BadRequest(reason.UnauthorizedError).WithMsg(fmt.Sprintf("tag [%s] cannot be modified",
|
||||
strings.Join(CheckTaglist, ",")))
|
||||
return
|
||||
}
|
||||
|
||||
//update question to db
|
||||
err = qs.questionRepo.UpdateQuestion(ctx, question, []string{"title", "original_text", "parsed_text", "updated_at"})
|
||||
if err != nil {
|
||||
return
|
||||
@@ -241,12 +299,12 @@ func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.Quest
|
||||
return
|
||||
}
|
||||
|
||||
questionInfo, err = qs.GetQuestion(ctx, question.ID, question.UserID, false)
|
||||
questionInfo, err = qs.GetQuestion(ctx, question.ID, question.UserID, false, false)
|
||||
return
|
||||
}
|
||||
|
||||
// GetQuestion get question one
|
||||
func (qs *QuestionService) GetQuestion(ctx context.Context, id, loginUserID string, addpv bool) (resp *schema.QuestionInfo, err error) {
|
||||
func (qs *QuestionService) GetQuestion(ctx context.Context, id, loginUserID string, addpv bool, isAdmin bool) (resp *schema.QuestionInfo, err error) {
|
||||
question, err := qs.questioncommon.Info(ctx, id, loginUserID)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -258,7 +316,7 @@ func (qs *QuestionService) GetQuestion(ctx context.Context, id, loginUserID stri
|
||||
}
|
||||
}
|
||||
|
||||
question.MemberActions = permission.GetQuestionPermission(loginUserID, question.UserID)
|
||||
question.MemberActions = permission.GetQuestionPermission(ctx, loginUserID, question.UserID, isAdmin)
|
||||
return question, nil
|
||||
}
|
||||
|
||||
@@ -266,6 +324,10 @@ func (qs *QuestionService) ChangeTag(ctx context.Context, objectTagData *schema.
|
||||
return qs.tagCommon.ObjectChangeTag(ctx, objectTagData)
|
||||
}
|
||||
|
||||
func (qs *QuestionService) CheckChangeTag(ctx context.Context, oldobjectTagData, objectTagData []*entity.Tag) (bool, []string) {
|
||||
return qs.tagCommon.ObjectCheckChangeTag(ctx, oldobjectTagData, objectTagData)
|
||||
}
|
||||
|
||||
func (qs *QuestionService) SearchUserList(ctx context.Context, userName, order string, page, pageSize int, loginUserID string) ([]*schema.UserQuestionInfo, int64, error) {
|
||||
userlist := make([]*schema.UserQuestionInfo, 0)
|
||||
|
||||
@@ -481,7 +543,7 @@ func (qs *QuestionService) SearchByTitleLike(ctx context.Context, title string,
|
||||
// SimilarQuestion
|
||||
func (qs *QuestionService) SimilarQuestion(ctx context.Context, questionID string, loginUserID string) ([]*schema.QuestionInfo, int64, error) {
|
||||
list := make([]*schema.QuestionInfo, 0)
|
||||
questionInfo, err := qs.GetQuestion(ctx, questionID, loginUserID, false)
|
||||
questionInfo, err := qs.GetQuestion(ctx, questionID, loginUserID, false, false)
|
||||
if err != nil {
|
||||
return list, 0, err
|
||||
}
|
||||
@@ -502,14 +564,13 @@ func (qs *QuestionService) SimilarQuestion(ctx context.Context, questionID strin
|
||||
// SearchList
|
||||
func (qs *QuestionService) SearchList(ctx context.Context, req *schema.QuestionSearch, loginUserID string) ([]*schema.QuestionInfo, int64, error) {
|
||||
if len(req.Tag) > 0 {
|
||||
taginfo, has, err := qs.tagCommon.GetTagListByName(ctx, req.Tag)
|
||||
tagInfo, has, err := qs.tagCommon.GetTagBySlugName(ctx, strings.ToLower(req.Tag))
|
||||
if err != nil {
|
||||
log.Error("tagCommon.GetTagListByNames error", err)
|
||||
}
|
||||
if has {
|
||||
req.TagIDs = append(req.TagIDs, taginfo.ID)
|
||||
req.TagIDs = append(req.TagIDs, tagInfo.ID)
|
||||
}
|
||||
|
||||
}
|
||||
list := make([]*schema.QuestionInfo, 0)
|
||||
if req.UserName != "" {
|
||||
|
||||
@@ -113,6 +113,8 @@ func (rs *RevisionService) parseItem(ctx context.Context, item *schema.GetRevisi
|
||||
ParsedText: tag.ParsedText,
|
||||
FollowCount: tag.FollowCount,
|
||||
QuestionCount: tag.QuestionCount,
|
||||
Recommend: tag.Recommend,
|
||||
Reserved: tag.Reserved,
|
||||
}
|
||||
tagInfo.GetExcerpt()
|
||||
item.ContentParsed = tagInfo
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/search_common"
|
||||
)
|
||||
|
||||
type AcceptedAnswerSearch struct {
|
||||
repo search_common.SearchRepo
|
||||
w string
|
||||
page int
|
||||
size int
|
||||
order string
|
||||
}
|
||||
|
||||
func NewAcceptedAnswerSearch(repo search_common.SearchRepo) *AcceptedAnswerSearch {
|
||||
return &AcceptedAnswerSearch{
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AcceptedAnswerSearch) Parse(dto *schema.SearchDTO) (ok bool) {
|
||||
var (
|
||||
q,
|
||||
w,
|
||||
p string
|
||||
)
|
||||
|
||||
q = dto.Query
|
||||
w = dto.Query
|
||||
p = `isaccepted:yes`
|
||||
|
||||
if strings.Index(q, p) == 0 {
|
||||
ok = true
|
||||
w = strings.TrimPrefix(q, p)
|
||||
}
|
||||
|
||||
s.w = strings.TrimSpace(w)
|
||||
s.page = dto.Page
|
||||
s.size = dto.Size
|
||||
s.order = dto.Order
|
||||
return
|
||||
}
|
||||
func (s *AcceptedAnswerSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) {
|
||||
|
||||
words := strings.Split(s.w, " ")
|
||||
if len(words) > 3 {
|
||||
words = words[:4]
|
||||
}
|
||||
|
||||
return s.repo.SearchAnswers(ctx, words, true, "", s.page, s.size, s.order)
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/search_common"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type AnswerSearch struct {
|
||||
repo search_common.SearchRepo
|
||||
w string
|
||||
page int
|
||||
size int
|
||||
order string
|
||||
}
|
||||
|
||||
func NewAnswerSearch(repo search_common.SearchRepo) *AnswerSearch {
|
||||
return &AnswerSearch{
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AnswerSearch) Parse(dto *schema.SearchDTO) (ok bool) {
|
||||
var (
|
||||
q,
|
||||
w,
|
||||
p string
|
||||
)
|
||||
|
||||
q = dto.Query
|
||||
w = dto.Query
|
||||
p = `is:answer`
|
||||
|
||||
if strings.Index(q, p) == 0 {
|
||||
ok = true
|
||||
w = strings.TrimPrefix(q, p)
|
||||
}
|
||||
|
||||
s.w = strings.TrimSpace(w)
|
||||
s.page = dto.Page
|
||||
s.size = dto.Size
|
||||
s.order = dto.Order
|
||||
return
|
||||
}
|
||||
func (s *AnswerSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) {
|
||||
|
||||
words := strings.Split(s.w, " ")
|
||||
if len(words) > 3 {
|
||||
words = words[:4]
|
||||
}
|
||||
|
||||
return s.repo.SearchAnswers(ctx, words, false, "", s.page, s.size, s.order)
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/search_common"
|
||||
"github.com/answerdev/answer/pkg/converter"
|
||||
)
|
||||
|
||||
type AnswersSearch struct {
|
||||
repo search_common.SearchRepo
|
||||
exp int
|
||||
w string
|
||||
page int
|
||||
size int
|
||||
order string
|
||||
}
|
||||
|
||||
func NewAnswersSearch(repo search_common.SearchRepo) *AnswersSearch {
|
||||
return &AnswersSearch{
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AnswersSearch) Parse(dto *schema.SearchDTO) (ok bool) {
|
||||
var (
|
||||
q,
|
||||
w,
|
||||
p,
|
||||
exp string
|
||||
)
|
||||
|
||||
q = dto.Query
|
||||
w = dto.Query
|
||||
p = `(?m)^answers:([0-9]+)`
|
||||
|
||||
re := regexp.MustCompile(p)
|
||||
res := re.FindStringSubmatch(q)
|
||||
if len(res) == 2 {
|
||||
exp = res[1]
|
||||
trimLen := len(res[0])
|
||||
w = q[trimLen:]
|
||||
ok = true
|
||||
}
|
||||
|
||||
s.exp = converter.StringToInt(exp)
|
||||
s.w = strings.TrimSpace(w)
|
||||
s.page = dto.Page
|
||||
s.size = dto.Size
|
||||
s.order = dto.Order
|
||||
return
|
||||
}
|
||||
|
||||
func (s *AnswersSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) {
|
||||
|
||||
words := strings.Split(s.w, " ")
|
||||
if len(words) > 3 {
|
||||
words = words[:4]
|
||||
}
|
||||
|
||||
return s.repo.SearchQuestions(ctx, words, false, s.exp, s.page, s.size, s.order)
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/search_common"
|
||||
usercommon "github.com/answerdev/answer/internal/service/user_common"
|
||||
)
|
||||
|
||||
type AuthorSearch struct {
|
||||
repo search_common.SearchRepo
|
||||
userCommon *usercommon.UserCommon
|
||||
exp string
|
||||
w string
|
||||
page int
|
||||
size int
|
||||
order string
|
||||
}
|
||||
|
||||
func NewAuthorSearch(repo search_common.SearchRepo, userCommon *usercommon.UserCommon) *AuthorSearch {
|
||||
return &AuthorSearch{
|
||||
repo: repo,
|
||||
userCommon: userCommon,
|
||||
}
|
||||
}
|
||||
|
||||
// Parse
|
||||
// example: "user:12345" -> {exp="" w="12345"}
|
||||
func (s *AuthorSearch) Parse(dto *schema.SearchDTO) (ok bool) {
|
||||
var (
|
||||
exp,
|
||||
q,
|
||||
w,
|
||||
p,
|
||||
me,
|
||||
name string
|
||||
)
|
||||
exp = ""
|
||||
q = dto.Query
|
||||
w = q
|
||||
p = `(?m)^user:([a-z0-9._-]+)`
|
||||
me = "user:me"
|
||||
|
||||
re := regexp.MustCompile(p)
|
||||
res := re.FindStringSubmatch(q)
|
||||
if len(res) == 2 {
|
||||
name = res[1]
|
||||
user, has, err := s.userCommon.GetUserBasicInfoByUserName(nil, name)
|
||||
if err == nil && has {
|
||||
exp = user.ID
|
||||
trimLen := len(res[0])
|
||||
w = q[trimLen:]
|
||||
ok = true
|
||||
}
|
||||
} else if strings.Index(q, me) == 0 {
|
||||
exp = dto.UserID
|
||||
w = strings.TrimPrefix(q, me)
|
||||
ok = true
|
||||
}
|
||||
|
||||
w = strings.TrimSpace(w)
|
||||
s.exp = exp
|
||||
s.w = w
|
||||
s.page = dto.Page
|
||||
s.size = dto.Size
|
||||
s.order = dto.Order
|
||||
return
|
||||
}
|
||||
|
||||
func (s *AuthorSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) {
|
||||
var (
|
||||
words []string
|
||||
)
|
||||
|
||||
if len(s.exp) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
words = strings.Split(s.w, " ")
|
||||
if len(words) > 3 {
|
||||
words = words[:4]
|
||||
}
|
||||
|
||||
resp, total, err = s.repo.SearchContents(ctx, words, "", s.exp, -1, s.page, s.size, s.order)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/search_common"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type InQuestionSearch struct {
|
||||
repo search_common.SearchRepo
|
||||
w string
|
||||
exp string
|
||||
page int
|
||||
size int
|
||||
order string
|
||||
}
|
||||
|
||||
func NewInQuestionSearch(repo search_common.SearchRepo) *InQuestionSearch {
|
||||
return &InQuestionSearch{
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *InQuestionSearch) Parse(dto *schema.SearchDTO) (ok bool) {
|
||||
var (
|
||||
w,
|
||||
q,
|
||||
p,
|
||||
exp string
|
||||
)
|
||||
|
||||
q = dto.Query
|
||||
w = dto.Query
|
||||
p = `(?m)^inquestion:([0-9]+)`
|
||||
|
||||
re := regexp.MustCompile(p)
|
||||
res := re.FindStringSubmatch(q)
|
||||
if len(res) == 2 {
|
||||
exp = res[1]
|
||||
trimLen := len(res[0])
|
||||
w = q[trimLen:]
|
||||
ok = true
|
||||
}
|
||||
|
||||
s.exp = exp
|
||||
s.w = strings.TrimSpace(w)
|
||||
s.page = dto.Page
|
||||
s.size = dto.Size
|
||||
s.order = dto.Order
|
||||
return
|
||||
}
|
||||
func (s *InQuestionSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) {
|
||||
var (
|
||||
words []string
|
||||
)
|
||||
|
||||
words = strings.Split(s.w, " ")
|
||||
if len(words) > 3 {
|
||||
words = words[:4]
|
||||
}
|
||||
|
||||
return s.repo.SearchAnswers(ctx, words, false, s.exp, s.page, s.size, s.order)
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/search_common"
|
||||
)
|
||||
|
||||
type NotAcceptedQuestion struct {
|
||||
repo search_common.SearchRepo
|
||||
w string
|
||||
page int
|
||||
size int
|
||||
order string
|
||||
}
|
||||
|
||||
func NewNotAcceptedQuestion(repo search_common.SearchRepo) *NotAcceptedQuestion {
|
||||
return &NotAcceptedQuestion{
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *NotAcceptedQuestion) Parse(dto *schema.SearchDTO) (ok bool) {
|
||||
var (
|
||||
q,
|
||||
w,
|
||||
p string
|
||||
)
|
||||
|
||||
q = dto.Query
|
||||
w = dto.Query
|
||||
p = `hasaccepted:no`
|
||||
|
||||
if strings.Index(q, p) == 0 {
|
||||
ok = true
|
||||
w = strings.TrimPrefix(q, p)
|
||||
}
|
||||
|
||||
s.w = strings.TrimSpace(w)
|
||||
s.page = dto.Page
|
||||
s.size = dto.Size
|
||||
s.order = dto.Order
|
||||
return
|
||||
}
|
||||
func (s *NotAcceptedQuestion) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) {
|
||||
var (
|
||||
words []string
|
||||
)
|
||||
|
||||
words = strings.Split(s.w, " ")
|
||||
if len(words) > 3 {
|
||||
words = words[:4]
|
||||
}
|
||||
|
||||
return s.repo.SearchQuestions(ctx, words, true, -1, s.page, s.size, s.order)
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/search_common"
|
||||
)
|
||||
|
||||
type ObjectSearch struct {
|
||||
repo search_common.SearchRepo
|
||||
w string
|
||||
page int
|
||||
size int
|
||||
order string
|
||||
}
|
||||
|
||||
func NewObjectSearch(repo search_common.SearchRepo) *ObjectSearch {
|
||||
return &ObjectSearch{
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ObjectSearch) Parse(dto *schema.SearchDTO) (ok bool) {
|
||||
var (
|
||||
w string
|
||||
)
|
||||
w = strings.TrimSpace(dto.Query)
|
||||
if len(w) > 0 {
|
||||
ok = true
|
||||
}
|
||||
|
||||
s.w = w
|
||||
s.page = dto.Page
|
||||
s.size = dto.Size
|
||||
s.order = dto.Order
|
||||
return
|
||||
}
|
||||
func (s *ObjectSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) {
|
||||
|
||||
words := strings.Split(s.w, " ")
|
||||
if len(words) > 3 {
|
||||
words = words[:4]
|
||||
}
|
||||
return s.repo.SearchContents(ctx, words, "", "", -1, s.page, s.size, s.order)
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/search_common"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type QuestionSearch struct {
|
||||
repo search_common.SearchRepo
|
||||
w string
|
||||
page int
|
||||
size int
|
||||
order string
|
||||
}
|
||||
|
||||
func NewQuestionSearch(repo search_common.SearchRepo) *QuestionSearch {
|
||||
return &QuestionSearch{
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *QuestionSearch) Parse(dto *schema.SearchDTO) (ok bool) {
|
||||
var (
|
||||
q,
|
||||
w,
|
||||
p string
|
||||
)
|
||||
|
||||
q = dto.Query
|
||||
w = dto.Query
|
||||
p = `is:question`
|
||||
|
||||
if strings.Index(q, p) == 0 {
|
||||
ok = true
|
||||
w = strings.TrimPrefix(q, p)
|
||||
}
|
||||
|
||||
s.w = strings.TrimSpace(w)
|
||||
s.page = dto.Page
|
||||
s.size = dto.Size
|
||||
s.order = dto.Order
|
||||
return
|
||||
}
|
||||
|
||||
func (s *QuestionSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) {
|
||||
|
||||
words := strings.Split(s.w, " ")
|
||||
if len(words) > 3 {
|
||||
words = words[:4]
|
||||
}
|
||||
|
||||
return s.repo.SearchQuestions(ctx, words, false, -1, s.page, s.size, s.order)
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/search_common"
|
||||
"github.com/answerdev/answer/pkg/converter"
|
||||
)
|
||||
|
||||
type ScoreSearch struct {
|
||||
repo search_common.SearchRepo
|
||||
exp int
|
||||
w string
|
||||
page int
|
||||
size int
|
||||
order string
|
||||
}
|
||||
|
||||
func NewScoreSearch(repo search_common.SearchRepo) *ScoreSearch {
|
||||
return &ScoreSearch{
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ScoreSearch) Parse(dto *schema.SearchDTO) (ok bool) {
|
||||
exp := ""
|
||||
q := dto.Query
|
||||
w := q
|
||||
p := `(?m)^score:([0-9]+)`
|
||||
|
||||
re := regexp.MustCompile(p)
|
||||
res := re.FindStringSubmatch(w)
|
||||
if len(res) == 2 {
|
||||
exp = res[1]
|
||||
trimLen := len(res[0])
|
||||
w = q[trimLen:]
|
||||
ok = true
|
||||
}
|
||||
|
||||
w = strings.TrimSpace(w)
|
||||
s.exp = converter.StringToInt(exp)
|
||||
s.w = w
|
||||
s.page = dto.Page
|
||||
s.size = dto.Size
|
||||
s.order = dto.Order
|
||||
return
|
||||
}
|
||||
func (s *ScoreSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) {
|
||||
var (
|
||||
words []string
|
||||
)
|
||||
|
||||
words = strings.Split(s.w, " ")
|
||||
if len(words) > 3 {
|
||||
words = words[:4]
|
||||
}
|
||||
|
||||
resp, total, err = s.repo.SearchContents(ctx, words, "", "", s.exp, s.page, s.size, s.order)
|
||||
return
|
||||
}
|
||||
@@ -9,27 +9,28 @@ import (
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/activity_common"
|
||||
"github.com/answerdev/answer/internal/service/search_common"
|
||||
tagcommon "github.com/answerdev/answer/internal/service/tag_common"
|
||||
"github.com/answerdev/answer/internal/service/tag_common"
|
||||
)
|
||||
|
||||
type TagSearch struct {
|
||||
repo search_common.SearchRepo
|
||||
tagRepo tagcommon.TagRepo
|
||||
followCommon activity_common.FollowRepo
|
||||
page int
|
||||
size int
|
||||
exp string
|
||||
w string
|
||||
userID string
|
||||
Extra schema.GetTagPageResp
|
||||
order string
|
||||
repo search_common.SearchRepo
|
||||
tagCommonService *tag_common.TagCommonService
|
||||
followCommon activity_common.FollowRepo
|
||||
page int
|
||||
size int
|
||||
exp string
|
||||
w string
|
||||
userID string
|
||||
Extra schema.GetTagPageResp
|
||||
order string
|
||||
}
|
||||
|
||||
func NewTagSearch(repo search_common.SearchRepo, tagRepo tagcommon.TagRepo, followCommon activity_common.FollowRepo) *TagSearch {
|
||||
func NewTagSearch(repo search_common.SearchRepo,
|
||||
tagCommonService *tag_common.TagCommonService, followCommon activity_common.FollowRepo) *TagSearch {
|
||||
return &TagSearch{
|
||||
repo: repo,
|
||||
tagRepo: tagRepo,
|
||||
followCommon: followCommon,
|
||||
repo: repo,
|
||||
tagCommonService: tagCommonService,
|
||||
followCommon: followCommon,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +66,7 @@ func (ts *TagSearch) Search(ctx context.Context) (resp []schema.SearchResp, tota
|
||||
tag *entity.Tag
|
||||
exists, followed bool
|
||||
)
|
||||
tag, exists, err = ts.tagRepo.GetTagBySlugName(ctx, ts.exp)
|
||||
tag, exists, err = ts.tagCommonService.GetTagBySlugName(ctx, ts.exp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -82,6 +83,8 @@ func (ts *TagSearch) Search(ctx context.Context) (resp []schema.SearchResp, tota
|
||||
ParsedText: tag.ParsedText,
|
||||
QuestionCount: tag.QuestionCount,
|
||||
IsFollower: followed,
|
||||
Recommend: tag.Recommend,
|
||||
Reserved: tag.Reserved,
|
||||
}
|
||||
ts.Extra.GetExcerpt()
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/search_common"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ViewsSearch struct {
|
||||
repo search_common.SearchRepo
|
||||
exp string
|
||||
q string
|
||||
order string
|
||||
}
|
||||
|
||||
func NewViewsSearch(repo search_common.SearchRepo) *ViewsSearch {
|
||||
return &ViewsSearch{
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ViewsSearch) Parse(dto *schema.SearchDTO) (ok bool) {
|
||||
exp := ""
|
||||
w := dto.Query
|
||||
q := w
|
||||
p := `(?m)^views:([0-9]+)`
|
||||
|
||||
re := regexp.MustCompile(p)
|
||||
res := re.FindStringSubmatch(q)
|
||||
if len(res) == 2 {
|
||||
exp = res[1]
|
||||
trimLen := len(res[0])
|
||||
q = w[trimLen:]
|
||||
ok = true
|
||||
}
|
||||
|
||||
q = strings.TrimSpace(q)
|
||||
s.exp = exp
|
||||
s.q = q
|
||||
s.order = dto.Order
|
||||
return
|
||||
}
|
||||
func (s *ViewsSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) {
|
||||
return
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/search_common"
|
||||
)
|
||||
|
||||
type WithinSearch struct {
|
||||
repo search_common.SearchRepo
|
||||
w string
|
||||
page int
|
||||
size int
|
||||
order string
|
||||
}
|
||||
|
||||
func NewWithinSearch(repo search_common.SearchRepo) *WithinSearch {
|
||||
return &WithinSearch{
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WithinSearch) Parse(dto *schema.SearchDTO) (ok bool) {
|
||||
var (
|
||||
q string
|
||||
w []rune
|
||||
hasEnd bool
|
||||
)
|
||||
|
||||
q = dto.Query
|
||||
|
||||
if q[0:1] == `"` {
|
||||
for _, v := range []rune(q) {
|
||||
if len(w) == 0 && string(v) == `"` {
|
||||
continue
|
||||
} else if string(v) == `"` {
|
||||
hasEnd = true
|
||||
break
|
||||
} else {
|
||||
w = append(w, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hasEnd {
|
||||
ok = true
|
||||
}
|
||||
|
||||
s.w = string(w)
|
||||
s.page = dto.Page
|
||||
s.size = dto.Size
|
||||
s.order = dto.Order
|
||||
return
|
||||
}
|
||||
|
||||
func (s *WithinSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) {
|
||||
return s.repo.SearchContents(ctx, []string{s.w}, "", "", -1, s.page, s.size, s.order)
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
type SearchRepo interface {
|
||||
SearchContents(ctx context.Context, words []string, tagID, userID string, votes, page, size int, order string) (resp []schema.SearchResp, total int64, err error)
|
||||
SearchQuestions(ctx context.Context, words []string, limitNoAccepted bool, answers, page, size int, order string) (resp []schema.SearchResp, total int64, err error)
|
||||
SearchAnswers(ctx context.Context, words []string, limitAccepted bool, questionID string, page, size int, order string) (resp []schema.SearchResp, total int64, err error)
|
||||
SearchContents(ctx context.Context, words []string, tagIDs []string, userID string, votes, page, size int, order string) (resp []schema.SearchResp, total int64, err error)
|
||||
SearchQuestions(ctx context.Context, words []string, notAccepted bool, views, answers int, page, size int, order string) (resp []schema.SearchResp, total int64, err error)
|
||||
SearchAnswers(ctx context.Context, words []string, tagIDs []string, accepted bool, questionID string, page, size int, order string) (resp []schema.SearchResp, total int64, err error)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
package search_parser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/tag_common"
|
||||
usercommon "github.com/answerdev/answer/internal/service/user_common"
|
||||
"github.com/answerdev/answer/pkg/converter"
|
||||
)
|
||||
|
||||
type SearchParser struct {
|
||||
tagCommonService *tag_common.TagCommonService
|
||||
userCommon *usercommon.UserCommon
|
||||
}
|
||||
|
||||
func NewSearchParser(tagCommonService *tag_common.TagCommonService, userCommon *usercommon.UserCommon) *SearchParser {
|
||||
return &SearchParser{
|
||||
tagCommonService: tagCommonService,
|
||||
userCommon: userCommon,
|
||||
}
|
||||
}
|
||||
|
||||
// ParseStructure parse search structure, maybe match one of type all/questions/answers,
|
||||
// but if match two type, it will return false
|
||||
func (sp *SearchParser) ParseStructure(dto *schema.SearchDTO) (
|
||||
searchType string,
|
||||
// search all
|
||||
userID string,
|
||||
votes int,
|
||||
// search questions
|
||||
notAccepted bool,
|
||||
isQuestion bool,
|
||||
views,
|
||||
answers int,
|
||||
// search answers
|
||||
accepted bool,
|
||||
questionID string,
|
||||
isAnswer bool,
|
||||
// common fields
|
||||
tags,
|
||||
words []string,
|
||||
) {
|
||||
var (
|
||||
query = dto.Query
|
||||
currentUserID = dto.UserID
|
||||
all = 0
|
||||
q = 0
|
||||
a = 0
|
||||
withWords = []string{}
|
||||
limitWords = 5
|
||||
)
|
||||
|
||||
// match tags
|
||||
tags = sp.parseTags(&query)
|
||||
|
||||
// match all
|
||||
userID = sp.parseUserID(&query, currentUserID)
|
||||
if userID != "" {
|
||||
searchType = "all"
|
||||
all = 1
|
||||
}
|
||||
votes = sp.parseVotes(&query)
|
||||
if votes != -1 {
|
||||
searchType = "all"
|
||||
all = 1
|
||||
}
|
||||
withWords = sp.parseWithin(&query)
|
||||
if len(withWords) > 0 {
|
||||
searchType = "all"
|
||||
all = 1
|
||||
}
|
||||
|
||||
// match questions
|
||||
notAccepted = sp.parseNotAccepted(&query)
|
||||
if notAccepted {
|
||||
searchType = "question"
|
||||
q = 1
|
||||
}
|
||||
isQuestion = sp.parseIsQuestion(&query)
|
||||
if isQuestion {
|
||||
searchType = "question"
|
||||
q = 1
|
||||
}
|
||||
views = sp.parseViews(&query)
|
||||
if views != -1 {
|
||||
searchType = "question"
|
||||
q = 1
|
||||
}
|
||||
answers = sp.parseAnswers(&query)
|
||||
if answers != -1 {
|
||||
searchType = "question"
|
||||
q = 1
|
||||
}
|
||||
|
||||
// match answers
|
||||
accepted = sp.parseAccepted(&query)
|
||||
if accepted {
|
||||
searchType = "answer"
|
||||
a = 1
|
||||
}
|
||||
questionID = sp.parseQuestionID(&query)
|
||||
if questionID != "" {
|
||||
searchType = "answer"
|
||||
a = 1
|
||||
}
|
||||
isAnswer = sp.parseIsAnswer(&query)
|
||||
if isAnswer {
|
||||
searchType = "answer"
|
||||
a = 1
|
||||
}
|
||||
|
||||
words = strings.Split(query, " ")
|
||||
if len(withWords) > 0 {
|
||||
words = append(withWords, words...)
|
||||
}
|
||||
|
||||
// check limit words
|
||||
if len(words) > limitWords {
|
||||
words = words[:limitWords]
|
||||
}
|
||||
|
||||
// check tags' search is all or question
|
||||
if len(tags) > 0 {
|
||||
if len(words) > 0 {
|
||||
searchType = "all"
|
||||
all = 1
|
||||
} else {
|
||||
searchType = "question"
|
||||
q = 1
|
||||
}
|
||||
}
|
||||
|
||||
// check match types greater than 1
|
||||
if all+q+a > 1 {
|
||||
searchType = ""
|
||||
}
|
||||
|
||||
// check not match
|
||||
if all+q+a == 0 && len(words) > 0 {
|
||||
searchType = "all"
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// parseTags parse search tags, return tag ids array
|
||||
func (sp *SearchParser) parseTags(query *string) (tags []string) {
|
||||
var (
|
||||
// expire tag pattern
|
||||
exprTag = `(?m)\[([a-zA-Z0-9-\+\.#]+)\]{1}?`
|
||||
q = *query
|
||||
limit = 5
|
||||
)
|
||||
|
||||
re := regexp.MustCompile(exprTag)
|
||||
res := re.FindAllStringSubmatch(q, -1)
|
||||
if len(res) == 0 {
|
||||
return
|
||||
}
|
||||
tags = make([]string, len(res))
|
||||
for i, item := range res {
|
||||
tag, exists, err := sp.tagCommonService.GetTagBySlugName(context.TODO(), item[1])
|
||||
if err != nil || !exists {
|
||||
continue
|
||||
}
|
||||
tags[i] = tag.ID
|
||||
}
|
||||
|
||||
// limit maximum 5 tags
|
||||
if len(tags) > limit {
|
||||
tags = tags[:limit]
|
||||
}
|
||||
|
||||
q = strings.TrimSpace(re.ReplaceAllString(q, ""))
|
||||
*query = q
|
||||
return
|
||||
}
|
||||
|
||||
// parseUserID return user id or current login user id
|
||||
func (sp *SearchParser) parseUserID(query *string, currentUserID string) (userID string) {
|
||||
var (
|
||||
exprUserID = `(?m)^user:([a-z0-9._-]+)`
|
||||
exprMe = "user:me"
|
||||
q = *query
|
||||
)
|
||||
|
||||
re := regexp.MustCompile(exprUserID)
|
||||
res := re.FindStringSubmatch(q)
|
||||
if len(res) == 2 {
|
||||
name := res[1]
|
||||
user, has, err := sp.userCommon.GetUserBasicInfoByUserName(nil, name)
|
||||
if err == nil && has {
|
||||
userID = user.ID
|
||||
q = re.ReplaceAllString(q, "")
|
||||
}
|
||||
} else if strings.Index(q, exprMe) != -1 {
|
||||
userID = currentUserID
|
||||
q = strings.ReplaceAll(q, exprMe, "")
|
||||
}
|
||||
*query = strings.TrimSpace(q)
|
||||
return
|
||||
}
|
||||
|
||||
// parseVotes return the votes of search query
|
||||
func (sp *SearchParser) parseVotes(query *string) (votes int) {
|
||||
var (
|
||||
expr = `(?m)^score:([0-9]+)`
|
||||
q = *query
|
||||
)
|
||||
votes = -1
|
||||
|
||||
re := regexp.MustCompile(expr)
|
||||
res := re.FindStringSubmatch(q)
|
||||
if len(res) == 2 {
|
||||
votes = converter.StringToInt(res[1])
|
||||
q = re.ReplaceAllString(q, "")
|
||||
}
|
||||
|
||||
*query = strings.TrimSpace(q)
|
||||
return
|
||||
}
|
||||
|
||||
// parseWithin parse quotes within words like: "hello world"
|
||||
func (sp *SearchParser) parseWithin(query *string) (words []string) {
|
||||
var (
|
||||
q = *query
|
||||
expr = `(?U)(".+")`
|
||||
)
|
||||
re := regexp.MustCompile(expr)
|
||||
matches := re.FindAllStringSubmatch(q, -1)
|
||||
words = []string{}
|
||||
for _, match := range matches {
|
||||
words = append(words, match[1])
|
||||
}
|
||||
q = re.ReplaceAllString(q, "")
|
||||
*query = strings.TrimSpace(q)
|
||||
return
|
||||
}
|
||||
|
||||
// parseNotAccepted return the question has not accepted the answer
|
||||
func (sp *SearchParser) parseNotAccepted(query *string) (notAccepted bool) {
|
||||
var (
|
||||
q = *query
|
||||
expr = `hasaccepted:no`
|
||||
)
|
||||
|
||||
if strings.Index(q, expr) != -1 {
|
||||
q = strings.ReplaceAll(q, expr, "")
|
||||
notAccepted = true
|
||||
}
|
||||
|
||||
*query = strings.TrimSpace(q)
|
||||
return
|
||||
}
|
||||
|
||||
// parseIsQuestion check the result if only limit question or not
|
||||
func (sp *SearchParser) parseIsQuestion(query *string) (isQuestion bool) {
|
||||
var (
|
||||
q = *query
|
||||
expr = `is:question`
|
||||
)
|
||||
|
||||
if strings.Index(q, expr) == 0 {
|
||||
q = strings.ReplaceAll(q, expr, "")
|
||||
isQuestion = true
|
||||
}
|
||||
|
||||
*query = strings.TrimSpace(q)
|
||||
return
|
||||
}
|
||||
|
||||
// parseViews check search has views or not
|
||||
func (sp *SearchParser) parseViews(query *string) (views int) {
|
||||
var (
|
||||
q = *query
|
||||
expr = `(?m)^views:([0-9]+)`
|
||||
)
|
||||
views = -1
|
||||
|
||||
re := regexp.MustCompile(expr)
|
||||
res := re.FindStringSubmatch(q)
|
||||
if len(res) == 2 {
|
||||
views = converter.StringToInt(res[1])
|
||||
q = re.ReplaceAllString(q, "")
|
||||
}
|
||||
*query = strings.TrimSpace(q)
|
||||
return
|
||||
}
|
||||
|
||||
// parseAnswers check whether specified answer count for question
|
||||
func (sp *SearchParser) parseAnswers(query *string) (answers int) {
|
||||
var (
|
||||
q = *query
|
||||
expr = `(?m)^answers:([0-9]+)`
|
||||
)
|
||||
answers = -1
|
||||
|
||||
re := regexp.MustCompile(expr)
|
||||
res := re.FindStringSubmatch(q)
|
||||
if len(res) == 2 {
|
||||
answers = converter.StringToInt(res[1])
|
||||
q = re.ReplaceAllString(q, "")
|
||||
}
|
||||
|
||||
*query = strings.TrimSpace(q)
|
||||
return
|
||||
}
|
||||
|
||||
// parseAccepted check the search is limit accepted answer or not
|
||||
func (sp *SearchParser) parseAccepted(query *string) (accepted bool) {
|
||||
var (
|
||||
q = *query
|
||||
expr = `isaccepted:yes`
|
||||
)
|
||||
|
||||
if strings.Index(q, expr) != -1 {
|
||||
accepted = true
|
||||
strings.ReplaceAll(q, expr, "")
|
||||
}
|
||||
|
||||
*query = strings.TrimSpace(q)
|
||||
return
|
||||
}
|
||||
|
||||
// parseQuestionID check whether specified question's id
|
||||
func (sp *SearchParser) parseQuestionID(query *string) (questionID string) {
|
||||
var (
|
||||
q = *query
|
||||
expr = `(?m)^inquestion:([0-9]+)`
|
||||
)
|
||||
|
||||
re := regexp.MustCompile(expr)
|
||||
res := re.FindStringSubmatch(q)
|
||||
if len(res) == 2 {
|
||||
questionID = res[1]
|
||||
q = re.ReplaceAllString(q, "")
|
||||
}
|
||||
|
||||
*query = strings.TrimSpace(q)
|
||||
return
|
||||
}
|
||||
|
||||
// parseIsAnswer check the result if only limit answer or not
|
||||
func (sp *SearchParser) parseIsAnswer(query *string) (isAnswer bool) {
|
||||
var (
|
||||
q = *query
|
||||
expr = `is:answer`
|
||||
)
|
||||
|
||||
if strings.Index(q, expr) != -1 {
|
||||
isAnswer = true
|
||||
q = strings.ReplaceAll(q, expr, "")
|
||||
}
|
||||
|
||||
*query = strings.TrimSpace(q)
|
||||
return
|
||||
}
|
||||
@@ -2,92 +2,61 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/activity_common"
|
||||
"github.com/answerdev/answer/internal/service/search"
|
||||
"github.com/answerdev/answer/internal/service/search_common"
|
||||
tagcommon "github.com/answerdev/answer/internal/service/tag_common"
|
||||
usercommon "github.com/answerdev/answer/internal/service/user_common"
|
||||
"github.com/answerdev/answer/internal/service/search_parser"
|
||||
)
|
||||
|
||||
type Search interface {
|
||||
Parse(dto *schema.SearchDTO) (ok bool)
|
||||
Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error)
|
||||
}
|
||||
|
||||
type SearchService struct {
|
||||
searchRepo search_common.SearchRepo
|
||||
tagSearch *search.TagSearch
|
||||
withinSearch *search.WithinSearch
|
||||
authorSearch *search.AuthorSearch
|
||||
scoreSearch *search.ScoreSearch
|
||||
answersSearch *search.AnswersSearch
|
||||
notAcceptedQuestion *search.NotAcceptedQuestion
|
||||
acceptedAnswerSearch *search.AcceptedAnswerSearch
|
||||
inQuestionSearch *search.InQuestionSearch
|
||||
questionSearch *search.QuestionSearch
|
||||
answerSearch *search.AnswerSearch
|
||||
viewsSearch *search.ViewsSearch
|
||||
objectSearch *search.ObjectSearch
|
||||
searchParser *search_parser.SearchParser
|
||||
searchRepo search_common.SearchRepo
|
||||
}
|
||||
|
||||
func NewSearchService(
|
||||
searchParser *search_parser.SearchParser,
|
||||
searchRepo search_common.SearchRepo,
|
||||
tagRepo tagcommon.TagRepo,
|
||||
userCommon *usercommon.UserCommon,
|
||||
followCommon activity_common.FollowRepo,
|
||||
) *SearchService {
|
||||
return &SearchService{
|
||||
searchRepo: searchRepo,
|
||||
tagSearch: search.NewTagSearch(searchRepo, tagRepo, followCommon),
|
||||
withinSearch: search.NewWithinSearch(searchRepo),
|
||||
authorSearch: search.NewAuthorSearch(searchRepo, userCommon),
|
||||
scoreSearch: search.NewScoreSearch(searchRepo),
|
||||
answersSearch: search.NewAnswersSearch(searchRepo),
|
||||
acceptedAnswerSearch: search.NewAcceptedAnswerSearch(searchRepo),
|
||||
notAcceptedQuestion: search.NewNotAcceptedQuestion(searchRepo),
|
||||
inQuestionSearch: search.NewInQuestionSearch(searchRepo),
|
||||
questionSearch: search.NewQuestionSearch(searchRepo),
|
||||
answerSearch: search.NewAnswerSearch(searchRepo),
|
||||
viewsSearch: search.NewViewsSearch(searchRepo),
|
||||
objectSearch: search.NewObjectSearch(searchRepo),
|
||||
searchParser: searchParser,
|
||||
searchRepo: searchRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// Search search contents
|
||||
func (ss *SearchService) Search(ctx context.Context, dto *schema.SearchDTO) (resp []schema.SearchResp, total int64, extra interface{}, err error) {
|
||||
extra = nil
|
||||
if dto.Page < 1 {
|
||||
dto.Page = 1
|
||||
}
|
||||
|
||||
switch {
|
||||
case ss.tagSearch.Parse(dto):
|
||||
resp, total, err = ss.tagSearch.Search(ctx)
|
||||
extra = ss.tagSearch.Extra
|
||||
case ss.withinSearch.Parse(dto):
|
||||
resp, total, err = ss.withinSearch.Search(ctx)
|
||||
case ss.authorSearch.Parse(dto):
|
||||
resp, total, err = ss.authorSearch.Search(ctx)
|
||||
case ss.scoreSearch.Parse(dto):
|
||||
resp, total, err = ss.scoreSearch.Search(ctx)
|
||||
case ss.answersSearch.Parse(dto):
|
||||
resp, total, err = ss.answersSearch.Search(ctx)
|
||||
case ss.acceptedAnswerSearch.Parse(dto):
|
||||
resp, total, err = ss.acceptedAnswerSearch.Search(ctx)
|
||||
case ss.notAcceptedQuestion.Parse(dto):
|
||||
resp, total, err = ss.notAcceptedQuestion.Search(ctx)
|
||||
case ss.inQuestionSearch.Parse(dto):
|
||||
resp, total, err = ss.inQuestionSearch.Search(ctx)
|
||||
case ss.questionSearch.Parse(dto):
|
||||
resp, total, err = ss.questionSearch.Search(ctx)
|
||||
case ss.answerSearch.Parse(dto):
|
||||
resp, total, err = ss.answerSearch.Search(ctx)
|
||||
case ss.viewsSearch.Parse(dto):
|
||||
resp, total, err = ss.viewsSearch.Search(ctx)
|
||||
default:
|
||||
ss.objectSearch.Parse(dto)
|
||||
resp, total, err = ss.objectSearch.Search(ctx)
|
||||
// search type
|
||||
searchType,
|
||||
// search all
|
||||
userID,
|
||||
votes,
|
||||
// search questions
|
||||
notAccepted,
|
||||
_,
|
||||
views,
|
||||
answers,
|
||||
// search answers
|
||||
accepted,
|
||||
questionID,
|
||||
_,
|
||||
// common fields
|
||||
tags,
|
||||
words := ss.searchParser.ParseStructure(dto)
|
||||
|
||||
switch searchType {
|
||||
case "all":
|
||||
resp, total, err = ss.searchRepo.SearchContents(ctx, words, tags, userID, votes, dto.Page, dto.Size, dto.Order)
|
||||
if err != nil {
|
||||
return nil, 0, nil, err
|
||||
}
|
||||
case "question":
|
||||
resp, total, err = ss.searchRepo.SearchQuestions(ctx, words, notAccepted, views, answers, dto.Page, dto.Size, dto.Order)
|
||||
case "answer":
|
||||
resp, total, err = ss.searchRepo.SearchAnswers(ctx, words, tags, accepted, questionID, dto.Page, dto.Size, dto.Order)
|
||||
}
|
||||
return resp, total, extra, err
|
||||
return
|
||||
}
|
||||
|
||||
@@ -11,47 +11,107 @@ import (
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/export"
|
||||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||
tagcommon "github.com/answerdev/answer/internal/service/tag_common"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
type SiteInfoService struct {
|
||||
siteInfoRepo siteinfo_common.SiteInfoRepo
|
||||
emailService *export.EmailService
|
||||
siteInfoRepo siteinfo_common.SiteInfoRepo
|
||||
emailService *export.EmailService
|
||||
tagCommonService *tagcommon.TagCommonService
|
||||
}
|
||||
|
||||
func NewSiteInfoService(siteInfoRepo siteinfo_common.SiteInfoRepo, emailService *export.EmailService) *SiteInfoService {
|
||||
func NewSiteInfoService(
|
||||
siteInfoRepo siteinfo_common.SiteInfoRepo,
|
||||
emailService *export.EmailService,
|
||||
tagCommonService *tagcommon.TagCommonService) *SiteInfoService {
|
||||
return &SiteInfoService{
|
||||
siteInfoRepo: siteInfoRepo,
|
||||
emailService: emailService,
|
||||
siteInfoRepo: siteInfoRepo,
|
||||
emailService: emailService,
|
||||
tagCommonService: tagCommonService,
|
||||
}
|
||||
}
|
||||
|
||||
// GetSiteGeneral get site info general
|
||||
func (s *SiteInfoService) GetSiteGeneral(ctx context.Context) (resp *schema.SiteGeneralResp, err error) {
|
||||
resp = &schema.SiteGeneralResp{}
|
||||
siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, constant.SiteTypeGeneral)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
log.Error(err)
|
||||
return resp, nil
|
||||
}
|
||||
if !exist {
|
||||
return nil, errors.BadRequest(reason.SiteInfoNotFound)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
resp = &schema.SiteGeneralResp{}
|
||||
_ = json.Unmarshal([]byte(siteInfo.Content), resp)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetSiteInterface get site info interface
|
||||
func (s *SiteInfoService) GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error) {
|
||||
resp = &schema.SiteInterfaceResp{}
|
||||
siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, constant.SiteTypeInterface)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return resp, nil
|
||||
}
|
||||
if !exist {
|
||||
return resp, nil
|
||||
}
|
||||
_ = json.Unmarshal([]byte(siteInfo.Content), resp)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetSiteBranding get site info branding
|
||||
func (s *SiteInfoService) GetSiteBranding(ctx context.Context) (resp *schema.SiteBrandingReq, err error) {
|
||||
resp = &schema.SiteBrandingReq{}
|
||||
siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, constant.SiteTypeBranding)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return resp, nil
|
||||
}
|
||||
if !exist {
|
||||
return resp, nil
|
||||
}
|
||||
_ = json.Unmarshal([]byte(siteInfo.Content), resp)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetSiteWrite get site info write
|
||||
func (s *SiteInfoService) GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error) {
|
||||
resp = &schema.SiteWriteResp{}
|
||||
siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, constant.SiteTypeWrite)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return resp, nil
|
||||
}
|
||||
if exist {
|
||||
_ = json.Unmarshal([]byte(siteInfo.Content), resp)
|
||||
}
|
||||
|
||||
resp.RecommendTags, err = s.tagCommonService.GetSiteWriteRecommendTag(ctx)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
resp.ReservedTags, err = s.tagCommonService.GetSiteWriteReservedTag(ctx)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetSiteLegal get site legal info
|
||||
func (s *SiteInfoService) GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) {
|
||||
resp = &schema.SiteLegalResp{}
|
||||
siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, constant.SiteTypeLegal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exist {
|
||||
return nil, errors.BadRequest(reason.SiteInfoNotFound)
|
||||
return resp, nil
|
||||
}
|
||||
resp = &schema.SiteInterfaceResp{}
|
||||
_ = json.Unmarshal([]byte(siteInfo.Content), resp)
|
||||
return resp, nil
|
||||
}
|
||||
@@ -109,6 +169,44 @@ func (s *SiteInfoService) SaveSiteInterface(ctx context.Context, req schema.Site
|
||||
return
|
||||
}
|
||||
|
||||
// SaveSiteBranding save site branding information
|
||||
func (s *SiteInfoService) SaveSiteBranding(ctx context.Context, req *schema.SiteBrandingReq) (err error) {
|
||||
content, _ := json.Marshal(req)
|
||||
data := &entity.SiteInfo{
|
||||
Type: constant.SiteTypeBranding,
|
||||
Content: string(content),
|
||||
Status: 1,
|
||||
}
|
||||
return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeBranding, data)
|
||||
}
|
||||
|
||||
// SaveSiteWrite save site configuration about write
|
||||
func (s *SiteInfoService) SaveSiteWrite(ctx context.Context, req *schema.SiteWriteReq) (resp interface{}, err error) {
|
||||
errData, err := s.tagCommonService.SetSiteWriteTag(ctx, req.RecommendTags, req.ReservedTags, req.UserID)
|
||||
if err != nil {
|
||||
return errData, err
|
||||
}
|
||||
|
||||
content, _ := json.Marshal(req)
|
||||
data := &entity.SiteInfo{
|
||||
Type: constant.SiteTypeWrite,
|
||||
Content: string(content),
|
||||
Status: 1,
|
||||
}
|
||||
return nil, s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeWrite, data)
|
||||
}
|
||||
|
||||
// SaveSiteLegal save site legal configuration
|
||||
func (s *SiteInfoService) SaveSiteLegal(ctx context.Context, req *schema.SiteLegalReq) (err error) {
|
||||
content, _ := json.Marshal(req)
|
||||
data := &entity.SiteInfo{
|
||||
Type: constant.SiteTypeLegal,
|
||||
Content: string(content),
|
||||
Status: 1,
|
||||
}
|
||||
return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeLegal, data)
|
||||
}
|
||||
|
||||
// GetSMTPConfig get smtp config
|
||||
func (s *SiteInfoService) GetSMTPConfig(ctx context.Context) (
|
||||
resp *schema.GetSMTPConfigResp, err error,
|
||||
|
||||
@@ -5,9 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/constant"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
)
|
||||
|
||||
type SiteInfoCommonService struct {
|
||||
@@ -22,29 +20,70 @@ func NewSiteInfoCommonService(siteInfoRepo SiteInfoRepo) *SiteInfoCommonService
|
||||
|
||||
// GetSiteGeneral get site info general
|
||||
func (s *SiteInfoCommonService) GetSiteGeneral(ctx context.Context) (resp *schema.SiteGeneralResp, err error) {
|
||||
resp = &schema.SiteGeneralResp{}
|
||||
siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, constant.SiteTypeGeneral)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return resp, err
|
||||
}
|
||||
if !exist {
|
||||
return nil, errors.BadRequest(reason.SiteInfoNotFound)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
resp = &schema.SiteGeneralResp{}
|
||||
_ = json.Unmarshal([]byte(siteInfo.Content), resp)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetSiteInterface get site info interface
|
||||
func (s *SiteInfoCommonService) GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error) {
|
||||
resp = &schema.SiteInterfaceResp{}
|
||||
siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, constant.SiteTypeInterface)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
if !exist {
|
||||
return resp, nil
|
||||
}
|
||||
_ = json.Unmarshal([]byte(siteInfo.Content), resp)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetSiteBranding get site info branding
|
||||
func (s *SiteInfoCommonService) GetSiteBranding(ctx context.Context) (resp *schema.SiteBrandingResp, err error) {
|
||||
resp = &schema.SiteBrandingResp{}
|
||||
siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, constant.SiteTypeBranding)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
if !exist {
|
||||
return resp, nil
|
||||
}
|
||||
_ = json.Unmarshal([]byte(siteInfo.Content), resp)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetSiteWrite get site info write
|
||||
func (s *SiteInfoCommonService) GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error) {
|
||||
resp = &schema.SiteWriteResp{}
|
||||
siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, constant.SiteTypeWrite)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
if !exist {
|
||||
return resp, nil
|
||||
}
|
||||
_ = json.Unmarshal([]byte(siteInfo.Content), resp)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetSiteLegal get site info write
|
||||
func (s *SiteInfoCommonService) GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) {
|
||||
resp = &schema.SiteLegalResp{}
|
||||
siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, constant.SiteTypeLegal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exist {
|
||||
return nil, errors.BadRequest(reason.SiteInfoNotFound)
|
||||
return resp, nil
|
||||
}
|
||||
resp = &schema.SiteInterfaceResp{}
|
||||
_ = json.Unmarshal([]byte(siteInfo.Content), resp)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ package tag
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/answerdev/answer/internal/service/revision_common"
|
||||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||
"github.com/answerdev/answer/internal/service/tag_common"
|
||||
"github.com/answerdev/answer/pkg/htmltext"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/pager"
|
||||
@@ -12,44 +15,44 @@ import (
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/activity_common"
|
||||
"github.com/answerdev/answer/internal/service/permission"
|
||||
tagcommon "github.com/answerdev/answer/internal/service/tag_common"
|
||||
"github.com/answerdev/answer/pkg/converter"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
type TagRepo interface {
|
||||
RemoveTag(ctx context.Context, tagID string) (err error)
|
||||
UpdateTag(ctx context.Context, tag *entity.Tag) (err error)
|
||||
UpdateTagSynonym(ctx context.Context, tagSlugNameList []string, mainTagID int64, mainTagSlugName string) (err error)
|
||||
GetTagList(ctx context.Context, tag *entity.Tag) (tagList []*entity.Tag, err error)
|
||||
}
|
||||
|
||||
// TagService user service
|
||||
type TagService struct {
|
||||
tagRepo tagcommon.TagRepo
|
||||
revisionService *revision_common.RevisionService
|
||||
followCommon activity_common.FollowRepo
|
||||
tagRepo TagRepo
|
||||
tagCommonService *tag_common.TagCommonService
|
||||
revisionService *revision_common.RevisionService
|
||||
followCommon activity_common.FollowRepo
|
||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
||||
}
|
||||
|
||||
// NewTagService new tag service
|
||||
func NewTagService(
|
||||
tagRepo tagcommon.TagRepo,
|
||||
tagRepo TagRepo,
|
||||
tagCommonService *tag_common.TagCommonService,
|
||||
revisionService *revision_common.RevisionService,
|
||||
followCommon activity_common.FollowRepo) *TagService {
|
||||
followCommon activity_common.FollowRepo,
|
||||
siteInfoService *siteinfo_common.SiteInfoCommonService) *TagService {
|
||||
return &TagService{
|
||||
tagRepo: tagRepo,
|
||||
revisionService: revisionService,
|
||||
followCommon: followCommon,
|
||||
tagRepo: tagRepo,
|
||||
tagCommonService: tagCommonService,
|
||||
revisionService: revisionService,
|
||||
followCommon: followCommon,
|
||||
siteInfoService: siteInfoService,
|
||||
}
|
||||
}
|
||||
|
||||
// SearchTagLike get tag list all
|
||||
func (ts *TagService) SearchTagLike(ctx context.Context, req *schema.SearchTagLikeReq) (resp []string, err error) {
|
||||
tags, err := ts.tagRepo.GetTagListByName(ctx, req.Tag, 5)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, tag := range tags {
|
||||
resp = append(resp, tag.SlugName)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// RemoveTag delete tag
|
||||
func (ts *TagService) RemoveTag(ctx context.Context, tagID string) (err error) {
|
||||
// TODO permission
|
||||
@@ -71,7 +74,7 @@ func (ts *TagService) UpdateTag(ctx context.Context, req *schema.UpdateTagReq) (
|
||||
return err
|
||||
}
|
||||
|
||||
tagInfo, exist, err := ts.tagRepo.GetTagByID(ctx, req.TagID)
|
||||
tagInfo, exist, err := ts.tagCommonService.GetTagByID(ctx, req.TagID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -116,9 +119,9 @@ func (ts *TagService) GetTagInfo(ctx context.Context, req *schema.GetTagInfoReq)
|
||||
exist bool
|
||||
)
|
||||
if len(req.ID) > 0 {
|
||||
tagInfo, exist, err = ts.tagRepo.GetTagByID(ctx, req.ID)
|
||||
tagInfo, exist, err = ts.tagCommonService.GetTagByID(ctx, req.ID)
|
||||
} else {
|
||||
tagInfo, exist, err = ts.tagRepo.GetTagBySlugName(ctx, req.Name)
|
||||
tagInfo, exist, err = ts.tagCommonService.GetTagBySlugName(ctx, req.Name)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -130,7 +133,7 @@ func (ts *TagService) GetTagInfo(ctx context.Context, req *schema.GetTagInfoReq)
|
||||
resp = &schema.GetTagResp{}
|
||||
// if tag is synonyms get original tag info
|
||||
if tagInfo.MainTagID > 0 {
|
||||
tagInfo, exist, err = ts.tagRepo.GetTagByID(ctx, converter.IntToString(tagInfo.MainTagID))
|
||||
tagInfo, exist, err = ts.tagCommonService.GetTagByID(ctx, converter.IntToString(tagInfo.MainTagID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -148,8 +151,10 @@ func (ts *TagService) GetTagInfo(ctx context.Context, req *schema.GetTagInfoReq)
|
||||
resp.ParsedText = tagInfo.ParsedText
|
||||
resp.FollowCount = tagInfo.FollowCount
|
||||
resp.QuestionCount = tagInfo.QuestionCount
|
||||
resp.Recommend = tagInfo.Recommend
|
||||
resp.Reserved = tagInfo.Reserved
|
||||
resp.IsFollower = ts.checkTagIsFollow(ctx, req.UserID, tagInfo.ID)
|
||||
resp.MemberActions = permission.GetTagPermission(req.UserID, req.UserID)
|
||||
resp.MemberActions = permission.GetTagPermission(ctx, req.UserID, req.UserID)
|
||||
resp.GetExcerpt()
|
||||
return resp, nil
|
||||
}
|
||||
@@ -165,7 +170,7 @@ func (ts *TagService) GetFollowingTags(ctx context.Context, userID string) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tagList, err := ts.tagRepo.GetTagListByIDs(ctx, objIDs)
|
||||
tagList, err := ts.tagCommonService.GetTagListByIDs(ctx, objIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -174,9 +179,11 @@ func (ts *TagService) GetFollowingTags(ctx context.Context, userID string) (
|
||||
TagID: t.ID,
|
||||
SlugName: t.SlugName,
|
||||
DisplayName: t.DisplayName,
|
||||
Recommend: t.Recommend,
|
||||
Reserved: t.Reserved,
|
||||
}
|
||||
if t.MainTagID > 0 {
|
||||
mainTag, exist, err := ts.tagRepo.GetTagByID(ctx, converter.IntToString(t.MainTagID))
|
||||
mainTag, exist, err := ts.tagCommonService.GetTagByID(ctx, converter.IntToString(t.MainTagID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -192,7 +199,7 @@ func (ts *TagService) GetFollowingTags(ctx context.Context, userID string) (
|
||||
// GetTagSynonyms get tag synonyms
|
||||
func (ts *TagService) GetTagSynonyms(ctx context.Context, req *schema.GetTagSynonymsReq) (
|
||||
resp []*schema.GetTagSynonymsResp, err error) {
|
||||
tag, exist, err := ts.tagRepo.GetTagByID(ctx, req.TagID)
|
||||
tag, exist, err := ts.tagCommonService.GetTagByID(ctx, req.TagID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -241,7 +248,7 @@ func (ts *TagService) UpdateTagSynonym(ctx context.Context, req *schema.UpdateTa
|
||||
req.Format()
|
||||
addSynonymTagList := make([]string, 0)
|
||||
removeSynonymTagList := make([]string, 0)
|
||||
mainTagInfo, exist, err := ts.tagRepo.GetTagByID(ctx, req.TagID)
|
||||
mainTagInfo, exist, err := ts.tagCommonService.GetTagByID(ctx, req.TagID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -253,7 +260,7 @@ func (ts *TagService) UpdateTagSynonym(ctx context.Context, req *schema.UpdateTa
|
||||
for _, item := range req.SynonymTagList {
|
||||
addSynonymTagList = append(addSynonymTagList, item.SlugName)
|
||||
}
|
||||
tagListInDB, err := ts.tagRepo.GetTagListByNames(ctx, addSynonymTagList)
|
||||
tagListInDB, err := ts.tagCommonService.GetTagListByNames(ctx, addSynonymTagList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -278,7 +285,7 @@ func (ts *TagService) UpdateTagSynonym(ctx context.Context, req *schema.UpdateTa
|
||||
}
|
||||
|
||||
if len(needAddTagList) > 0 {
|
||||
err = ts.tagRepo.AddTagList(ctx, needAddTagList)
|
||||
err = ts.tagCommonService.AddTagList(ctx, needAddTagList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -336,7 +343,7 @@ func (ts *TagService) GetTagWithPage(ctx context.Context, req *schema.GetTagWith
|
||||
page := req.Page
|
||||
pageSize := req.PageSize
|
||||
|
||||
tags, total, err := ts.tagRepo.GetTagPage(ctx, page, pageSize, tag, req.QueryCond)
|
||||
tags, total, err := ts.tagCommonService.GetTagPage(ctx, page, pageSize, tag, req.QueryCond)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -355,6 +362,8 @@ func (ts *TagService) GetTagWithPage(ctx context.Context, req *schema.GetTagWith
|
||||
IsFollower: ts.checkTagIsFollow(ctx, req.UserID, tag.ID),
|
||||
CreatedAt: tag.CreatedAt.Unix(),
|
||||
UpdatedAt: tag.UpdatedAt.Unix(),
|
||||
Recommend: tag.Recommend,
|
||||
Reserved: tag.Reserved,
|
||||
})
|
||||
}
|
||||
return pager.NewPageModel(total, resp), nil
|
||||
|
||||
@@ -1,30 +1,34 @@
|
||||
package tagcommon
|
||||
package tag_common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/answerdev/answer/internal/service/revision_common"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/base/validator"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/revision_common"
|
||||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
type TagRepo interface {
|
||||
type TagCommonRepo interface {
|
||||
AddTagList(ctx context.Context, tagList []*entity.Tag) (err error)
|
||||
GetTagListByIDs(ctx context.Context, ids []string) (tagList []*entity.Tag, err error)
|
||||
GetTagBySlugName(ctx context.Context, slugName string) (tagInfo *entity.Tag, exist bool, err error)
|
||||
GetTagListByName(ctx context.Context, name string, limit int) (tagList []*entity.Tag, err error)
|
||||
GetTagListByName(ctx context.Context, name string, limit int, hasReserved bool) (tagList []*entity.Tag, err error)
|
||||
GetTagListByNames(ctx context.Context, names []string) (tagList []*entity.Tag, err error)
|
||||
RemoveTag(ctx context.Context, tagID string) (err error)
|
||||
UpdateTag(ctx context.Context, tag *entity.Tag) (err error)
|
||||
UpdateTagQuestionCount(ctx context.Context, tagID string, questionCount int) (err error)
|
||||
UpdateTagSynonym(ctx context.Context, tagSlugNameList []string, mainTagID int64, mainTagSlugName string) (err error)
|
||||
GetTagByID(ctx context.Context, tagID string) (tag *entity.Tag, exist bool, err error)
|
||||
GetTagList(ctx context.Context, tag *entity.Tag) (tagList []*entity.Tag, err error)
|
||||
GetTagPage(ctx context.Context, page, pageSize int, tag *entity.Tag, queryCond string) (tagList []*entity.Tag, total int64, err error)
|
||||
GetRecommendTagList(ctx context.Context) (tagList []*entity.Tag, err error)
|
||||
GetReservedTagList(ctx context.Context) (tagList []*entity.Tag, err error)
|
||||
UpdateTagsAttribute(ctx context.Context, tags []string, attribute string, value bool) (err error)
|
||||
UpdateTagQuestionCount(ctx context.Context, tagID string, questionCount int) (err error)
|
||||
}
|
||||
|
||||
type TagRelRepo interface {
|
||||
@@ -40,39 +44,209 @@ type TagRelRepo interface {
|
||||
// TagCommonService user service
|
||||
type TagCommonService struct {
|
||||
revisionService *revision_common.RevisionService
|
||||
tagRepo TagRepo
|
||||
tagCommonRepo TagCommonRepo
|
||||
tagRelRepo TagRelRepo
|
||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
||||
}
|
||||
|
||||
// NewTagCommonService new tag service
|
||||
func NewTagCommonService(tagRepo TagRepo, tagRelRepo TagRelRepo,
|
||||
func NewTagCommonService(tagCommonRepo TagCommonRepo, tagRelRepo TagRelRepo,
|
||||
revisionService *revision_common.RevisionService,
|
||||
siteInfoService *siteinfo_common.SiteInfoCommonService,
|
||||
) *TagCommonService {
|
||||
return &TagCommonService{
|
||||
tagRepo: tagRepo,
|
||||
tagCommonRepo: tagCommonRepo,
|
||||
tagRelRepo: tagRelRepo,
|
||||
revisionService: revisionService,
|
||||
siteInfoService: siteInfoService,
|
||||
}
|
||||
}
|
||||
|
||||
// GetTagListByName
|
||||
func (ts *TagCommonService) GetTagListByName(ctx context.Context, tagName string) (tagInfo *entity.Tag, exist bool, err error) {
|
||||
tagName = strings.ToLower(tagName)
|
||||
return ts.tagRepo.GetTagBySlugName(ctx, tagName)
|
||||
// SearchTagLike get tag list all
|
||||
func (ts *TagCommonService) SearchTagLike(ctx context.Context, req *schema.SearchTagLikeReq) (resp []schema.SearchTagLikeResp, err error) {
|
||||
tags, err := ts.tagCommonRepo.GetTagListByName(ctx, req.Tag, 5, req.IsAdmin)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ts.tagsFormatRecommendAndReserved(ctx, tags)
|
||||
for _, tag := range tags {
|
||||
item := schema.SearchTagLikeResp{}
|
||||
item.SlugName = tag.SlugName
|
||||
item.Recommend = tag.Recommend
|
||||
item.Reserved = tag.Reserved
|
||||
resp = append(resp, item)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (ts *TagCommonService) GetSiteWriteRecommendTag(ctx context.Context) (tags []string, err error) {
|
||||
tags = make([]string, 0)
|
||||
list, err := ts.tagCommonRepo.GetRecommendTagList(ctx)
|
||||
if err != nil {
|
||||
return tags, err
|
||||
}
|
||||
for _, item := range list {
|
||||
tags = append(tags, item.SlugName)
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
func (ts *TagCommonService) SetSiteWriteTag(ctx context.Context, recommendTags, reservedTags []string, userID string) (
|
||||
errFields []*validator.FormErrorField, err error) {
|
||||
recommendErr := ts.CheckTag(ctx, recommendTags, userID)
|
||||
reservedErr := ts.CheckTag(ctx, reservedTags, userID)
|
||||
if recommendErr != nil {
|
||||
errFields = append(errFields, &validator.FormErrorField{
|
||||
ErrorField: "recommend_tags",
|
||||
ErrorMsg: recommendErr.Error(),
|
||||
})
|
||||
err = recommendErr
|
||||
}
|
||||
if reservedErr != nil {
|
||||
errFields = append(errFields, &validator.FormErrorField{
|
||||
ErrorField: "reserved_tags",
|
||||
ErrorMsg: reservedErr.Error(),
|
||||
})
|
||||
err = reservedErr
|
||||
}
|
||||
if len(errFields) > 0 {
|
||||
return errFields, err
|
||||
}
|
||||
|
||||
err = ts.SetTagsAttribute(ctx, recommendTags, "recommend")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = ts.SetTagsAttribute(ctx, reservedTags, "reserved")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (ts *TagCommonService) GetSiteWriteReservedTag(ctx context.Context) (tags []string, err error) {
|
||||
tags = make([]string, 0)
|
||||
list, err := ts.tagCommonRepo.GetReservedTagList(ctx)
|
||||
if err != nil {
|
||||
return tags, err
|
||||
}
|
||||
for _, item := range list {
|
||||
tags = append(tags, item.SlugName)
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// SetTagsAttribute
|
||||
func (ts *TagCommonService) SetTagsAttribute(ctx context.Context, tags []string, attribute string) (err error) {
|
||||
var tagslist []string
|
||||
switch attribute {
|
||||
case "recommend":
|
||||
tagslist, err = ts.GetSiteWriteRecommendTag(ctx)
|
||||
case "reserved":
|
||||
tagslist, err = ts.GetSiteWriteReservedTag(ctx)
|
||||
default:
|
||||
return
|
||||
}
|
||||
err = ts.tagCommonRepo.UpdateTagsAttribute(ctx, tagslist, attribute, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ts.tagCommonRepo.UpdateTagsAttribute(ctx, tags, attribute, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ts *TagCommonService) GetTagListByNames(ctx context.Context, tagNames []string) ([]*entity.Tag, error) {
|
||||
for k, tagname := range tagNames {
|
||||
tagNames[k] = strings.ToLower(tagname)
|
||||
}
|
||||
return ts.tagRepo.GetTagListByNames(ctx, tagNames)
|
||||
tagList, err := ts.tagCommonRepo.GetTagListByNames(ctx, tagNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ts.tagsFormatRecommendAndReserved(ctx, tagList)
|
||||
return tagList, nil
|
||||
}
|
||||
|
||||
//
|
||||
func (ts *TagCommonService) ExistRecommend(ctx context.Context, tags []*schema.TagItem) (bool, error) {
|
||||
taginfo, err := ts.siteInfoService.GetSiteWrite(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !taginfo.RequiredTag {
|
||||
return true, nil
|
||||
}
|
||||
tagNames := make([]string, 0)
|
||||
for _, item := range tags {
|
||||
tagNames = append(tagNames, item.SlugName)
|
||||
}
|
||||
list, err := ts.GetTagListByNames(ctx, tagNames)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, item := range list {
|
||||
if item.Recommend {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// GetObjectTag get object tag
|
||||
func (ts *TagCommonService) GetObjectTag(ctx context.Context, objectId string) (objTags []*schema.TagResp, err error) {
|
||||
objTags = make([]*schema.TagResp, 0)
|
||||
tagsInfoList, err := ts.GetObjectEntityTag(ctx, objectId)
|
||||
return ts.TagFormat(ctx, tagsInfoList)
|
||||
}
|
||||
|
||||
// AddTagList get object tag
|
||||
func (ts *TagCommonService) AddTagList(ctx context.Context, tagList []*entity.Tag) (err error) {
|
||||
return ts.tagCommonRepo.AddTagList(ctx, tagList)
|
||||
}
|
||||
|
||||
// GetTagByID get object tag
|
||||
func (ts *TagCommonService) GetTagByID(ctx context.Context, tagID string) (tag *entity.Tag, exist bool, err error) {
|
||||
tag, exist, err = ts.tagCommonRepo.GetTagByID(ctx, tagID)
|
||||
if !exist {
|
||||
return
|
||||
}
|
||||
ts.tagFormatRecommendAndReserved(ctx, tag)
|
||||
return
|
||||
}
|
||||
|
||||
// GetTagBySlugName get object tag
|
||||
func (ts *TagCommonService) GetTagBySlugName(ctx context.Context, slugName string) (tag *entity.Tag, exist bool, err error) {
|
||||
tag, exist, err = ts.tagCommonRepo.GetTagBySlugName(ctx, slugName)
|
||||
if !exist {
|
||||
return
|
||||
}
|
||||
ts.tagFormatRecommendAndReserved(ctx, tag)
|
||||
return
|
||||
}
|
||||
|
||||
// GetTagListByIDs get object tag
|
||||
func (ts *TagCommonService) GetTagListByIDs(ctx context.Context, ids []string) (tagList []*entity.Tag, err error) {
|
||||
tagList, err = ts.tagCommonRepo.GetTagListByIDs(ctx, ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ts.tagsFormatRecommendAndReserved(ctx, tagList)
|
||||
return
|
||||
}
|
||||
|
||||
// GetTagPage get object tag
|
||||
func (ts *TagCommonService) GetTagPage(ctx context.Context, page, pageSize int, tag *entity.Tag, queryCond string) (
|
||||
tagList []*entity.Tag, total int64, err error) {
|
||||
tagList, total, err = ts.tagCommonRepo.GetTagPage(ctx, page, pageSize, tag, queryCond)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
ts.tagsFormatRecommendAndReserved(ctx, tagList)
|
||||
return
|
||||
}
|
||||
|
||||
func (ts *TagCommonService) GetObjectEntityTag(ctx context.Context, objectId string) (objTags []*entity.Tag, err error) {
|
||||
tagIDList := make([]string, 0)
|
||||
tagList, err := ts.tagRelRepo.GetObjectTagRelList(ctx, objectId)
|
||||
if err != nil {
|
||||
@@ -81,20 +255,57 @@ func (ts *TagCommonService) GetObjectTag(ctx context.Context, objectId string) (
|
||||
for _, tag := range tagList {
|
||||
tagIDList = append(tagIDList, tag.TagID)
|
||||
}
|
||||
tagsInfoList, err := ts.tagRepo.GetTagListByIDs(ctx, tagIDList)
|
||||
objTags, err = ts.GetTagListByIDs(ctx, tagIDList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, tagInfo := range tagsInfoList {
|
||||
return objTags, nil
|
||||
}
|
||||
|
||||
func (ts *TagCommonService) TagFormat(ctx context.Context, tags []*entity.Tag) (objTags []*schema.TagResp, err error) {
|
||||
objTags = make([]*schema.TagResp, 0)
|
||||
for _, tagInfo := range tags {
|
||||
objTags = append(objTags, &schema.TagResp{
|
||||
SlugName: tagInfo.SlugName,
|
||||
DisplayName: tagInfo.DisplayName,
|
||||
MainTagSlugName: tagInfo.MainTagSlugName,
|
||||
Recommend: tagInfo.Recommend,
|
||||
Reserved: tagInfo.Reserved,
|
||||
})
|
||||
}
|
||||
return objTags, nil
|
||||
}
|
||||
|
||||
func (ts *TagCommonService) tagsFormatRecommendAndReserved(ctx context.Context, tagList []*entity.Tag) {
|
||||
if len(tagList) == 0 {
|
||||
return
|
||||
}
|
||||
tagConfig, err := ts.siteInfoService.GetSiteWrite(ctx)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
if !tagConfig.RequiredTag {
|
||||
for _, tag := range tagList {
|
||||
tag.Recommend = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *TagCommonService) tagFormatRecommendAndReserved(ctx context.Context, tag *entity.Tag) {
|
||||
if tag == nil {
|
||||
return
|
||||
}
|
||||
tagConfig, err := ts.siteInfoService.GetSiteWrite(ctx)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
if !tagConfig.RequiredTag {
|
||||
tag.Recommend = false
|
||||
}
|
||||
}
|
||||
|
||||
// BatchGetObjectTag batch get object tag
|
||||
func (ts *TagCommonService) BatchGetObjectTag(ctx context.Context, objectIds []string) (map[string][]*schema.TagResp, error) {
|
||||
objectIDTagMap := make(map[string][]*schema.TagResp)
|
||||
@@ -108,7 +319,7 @@ func (ts *TagCommonService) BatchGetObjectTag(ctx context.Context, objectIds []s
|
||||
for _, tag := range tagList {
|
||||
tagIDList = append(tagIDList, tag.TagID)
|
||||
}
|
||||
tagsInfoList, err := ts.tagRepo.GetTagListByIDs(ctx, tagIDList)
|
||||
tagsInfoList, err := ts.GetTagListByIDs(ctx, tagIDList)
|
||||
if err != nil {
|
||||
return objectIDTagMap, err
|
||||
}
|
||||
@@ -123,13 +334,98 @@ func (ts *TagCommonService) BatchGetObjectTag(ctx context.Context, objectIds []s
|
||||
SlugName: tagInfo.SlugName,
|
||||
DisplayName: tagInfo.DisplayName,
|
||||
MainTagSlugName: tagInfo.MainTagSlugName,
|
||||
Recommend: tagInfo.Recommend,
|
||||
Reserved: tagInfo.Reserved,
|
||||
}
|
||||
objectIDTagMap[item.ObjectID] = append(objectIDTagMap[item.ObjectID], t)
|
||||
}
|
||||
}
|
||||
for _, taglist := range objectIDTagMap {
|
||||
sort.SliceStable(taglist, func(i, j int) bool {
|
||||
return taglist[i].Reserved
|
||||
})
|
||||
sort.SliceStable(taglist, func(i, j int) bool {
|
||||
return taglist[i].Recommend
|
||||
})
|
||||
}
|
||||
return objectIDTagMap, nil
|
||||
}
|
||||
|
||||
func (ts *TagCommonService) CheckTag(ctx context.Context, tags []string, userID string) (err error) {
|
||||
if len(tags) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// find tags name
|
||||
tagListInDb, err := ts.GetTagListByNames(ctx, tags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tagInDbMapping := make(map[string]*entity.Tag)
|
||||
checktags := make([]string, 0)
|
||||
|
||||
for _, tag := range tagListInDb {
|
||||
if tag.MainTagID != 0 {
|
||||
checktags = append(checktags, fmt.Sprintf("\"%s\"", tag.SlugName))
|
||||
}
|
||||
tagInDbMapping[tag.SlugName] = tag
|
||||
}
|
||||
if len(checktags) > 0 {
|
||||
err = errors.BadRequest(reason.TagNotContainSynonym).WithMsg(fmt.Sprintf("Should not contain synonym tags %s", strings.Join(checktags, ",")))
|
||||
return err
|
||||
}
|
||||
|
||||
addTagList := make([]*entity.Tag, 0)
|
||||
addTagMsgList := make([]string, 0)
|
||||
for _, tag := range tags {
|
||||
_, ok := tagInDbMapping[tag]
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
item := &entity.Tag{}
|
||||
item.SlugName = tag
|
||||
item.DisplayName = tag
|
||||
item.OriginalText = ""
|
||||
item.ParsedText = ""
|
||||
item.Status = entity.TagStatusAvailable
|
||||
addTagList = append(addTagList, item)
|
||||
addTagMsgList = append(addTagMsgList, tag)
|
||||
}
|
||||
|
||||
if len(addTagList) > 0 {
|
||||
err = errors.BadRequest(reason.TagNotFound).WithMsg(fmt.Sprintf("tag [%s] does not exist",
|
||||
strings.Join(addTagMsgList, ",")))
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ts *TagCommonService) ObjectCheckChangeTag(ctx context.Context, oldobjectTagData, objectTagData []*entity.Tag) (bool, []string) {
|
||||
reservedTagsMap := make(map[string]bool)
|
||||
needTagsMap := make([]string, 0)
|
||||
for _, tag := range objectTagData {
|
||||
if tag.Reserved {
|
||||
reservedTagsMap[tag.SlugName] = true
|
||||
}
|
||||
}
|
||||
for _, tag := range oldobjectTagData {
|
||||
if tag.Reserved {
|
||||
_, ok := reservedTagsMap[tag.SlugName]
|
||||
if !ok {
|
||||
needTagsMap = append(needTagsMap, tag.SlugName)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(needTagsMap) > 0 {
|
||||
return false, needTagsMap
|
||||
}
|
||||
|
||||
return true, []string{}
|
||||
}
|
||||
|
||||
// ObjectChangeTag change object tag list
|
||||
func (ts *TagCommonService) ObjectChangeTag(ctx context.Context, objectTagData *schema.TagChange) (err error) {
|
||||
if len(objectTagData.Tags) == 0 {
|
||||
@@ -144,7 +440,7 @@ func (ts *TagCommonService) ObjectChangeTag(ctx context.Context, objectTagData *
|
||||
}
|
||||
|
||||
// find tags name
|
||||
tagListInDb, err := ts.tagRepo.GetTagListByNames(ctx, thisObjTagNameList)
|
||||
tagListInDb, err := ts.tagCommonRepo.GetTagListByNames(ctx, thisObjTagNameList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -171,7 +467,7 @@ func (ts *TagCommonService) ObjectChangeTag(ctx context.Context, objectTagData *
|
||||
}
|
||||
|
||||
if len(addTagList) > 0 {
|
||||
err = ts.tagRepo.AddTagList(ctx, addTagList)
|
||||
err = ts.tagCommonRepo.AddTagList(ctx, addTagList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -205,7 +501,7 @@ func (ts *TagCommonService) RefreshTagQuestionCount(ctx context.Context, tagIDs
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ts.tagRepo.UpdateTagQuestionCount(ctx, tagID, int(count))
|
||||
err = ts.tagCommonRepo.UpdateTagQuestionCount(ctx, tagID, int(count))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,12 +4,14 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/service/service_config"
|
||||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||
@@ -24,6 +26,25 @@ const (
|
||||
avatarSubPath = "avatar"
|
||||
avatarThumbSubPath = "avatar_thumb"
|
||||
postSubPath = "post"
|
||||
brandingSubPath = "branding"
|
||||
)
|
||||
|
||||
var (
|
||||
subPathList = []string{
|
||||
avatarSubPath,
|
||||
avatarThumbSubPath,
|
||||
postSubPath,
|
||||
brandingSubPath,
|
||||
}
|
||||
FormatExts = map[string]imaging.Format{
|
||||
".jpg": imaging.JPEG,
|
||||
".jpeg": imaging.JPEG,
|
||||
".png": imaging.PNG,
|
||||
".gif": imaging.GIF,
|
||||
".tif": imaging.TIFF,
|
||||
".tiff": imaging.TIFF,
|
||||
".bmp": imaging.BMP,
|
||||
}
|
||||
)
|
||||
|
||||
// UploaderService user service
|
||||
@@ -35,13 +56,11 @@ type UploaderService struct {
|
||||
// NewUploaderService new upload service
|
||||
func NewUploaderService(serviceConfig *service_config.ServiceConfig,
|
||||
siteInfoService *siteinfo_common.SiteInfoCommonService) *UploaderService {
|
||||
err := dir.CreateDirIfNotExist(filepath.Join(serviceConfig.UploadPath, avatarSubPath))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = dir.CreateDirIfNotExist(filepath.Join(serviceConfig.UploadPath, postSubPath))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
for _, subPath := range subPathList {
|
||||
err := dir.CreateDirIfNotExist(filepath.Join(serviceConfig.UploadPath, subPath))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return &UploaderService{
|
||||
serviceConfig: serviceConfig,
|
||||
@@ -49,23 +68,26 @@ func NewUploaderService(serviceConfig *service_config.ServiceConfig,
|
||||
}
|
||||
}
|
||||
|
||||
func (us *UploaderService) UploadAvatarFile(ctx *gin.Context, file *multipart.FileHeader, fileExt string) (
|
||||
url string, err error) {
|
||||
// UploadAvatarFile upload avatar file
|
||||
func (us *UploaderService) UploadAvatarFile(ctx *gin.Context) (url string, err error) {
|
||||
// max size
|
||||
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 5*1024*1024)
|
||||
_, file, err := ctx.Request.FormFile("file")
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
fileExt := strings.ToLower(path.Ext(file.Filename))
|
||||
if _, ok := FormatExts[fileExt]; !ok {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
|
||||
newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt)
|
||||
avatarFilePath := path.Join(avatarSubPath, newFilename)
|
||||
return us.uploadFile(ctx, file, avatarFilePath)
|
||||
}
|
||||
|
||||
var FormatExts = map[string]imaging.Format{
|
||||
".jpg": imaging.JPEG,
|
||||
".jpeg": imaging.JPEG,
|
||||
".png": imaging.PNG,
|
||||
".gif": imaging.GIF,
|
||||
".tif": imaging.TIFF,
|
||||
".tiff": imaging.TIFF,
|
||||
".bmp": imaging.BMP,
|
||||
}
|
||||
|
||||
func (us *UploaderService) AvatarThumbFile(ctx *gin.Context, uploadPath, fileName string, size int) (
|
||||
avatarfile []byte, err error) {
|
||||
if size > 1024 {
|
||||
@@ -73,12 +95,12 @@ func (us *UploaderService) AvatarThumbFile(ctx *gin.Context, uploadPath, fileNam
|
||||
}
|
||||
thumbFileName := fmt.Sprintf("%d_%d@%s", size, size, fileName)
|
||||
thumbfilePath := fmt.Sprintf("%s/%s/%s", uploadPath, avatarThumbSubPath, thumbFileName)
|
||||
avatarfile, err = ioutil.ReadFile(thumbfilePath)
|
||||
avatarfile, err = os.ReadFile(thumbfilePath)
|
||||
if err == nil {
|
||||
return avatarfile, nil
|
||||
}
|
||||
filePath := fmt.Sprintf("%s/avatar/%s", uploadPath, fileName)
|
||||
avatarfile, err = ioutil.ReadFile(filePath)
|
||||
avatarfile, err = os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return avatarfile, errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
||||
}
|
||||
@@ -117,13 +139,47 @@ func (us *UploaderService) AvatarThumbFile(ctx *gin.Context, uploadPath, fileNam
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (us *UploaderService) UploadPostFile(ctx *gin.Context, file *multipart.FileHeader, fileExt string) (
|
||||
func (us *UploaderService) UploadPostFile(ctx *gin.Context) (
|
||||
url string, err error) {
|
||||
// max size
|
||||
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 10*1024*1024)
|
||||
_, file, err := ctx.Request.FormFile("file")
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
fileExt := strings.ToLower(path.Ext(file.Filename))
|
||||
if _, ok := FormatExts[fileExt]; !ok {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
|
||||
newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt)
|
||||
avatarFilePath := path.Join(postSubPath, newFilename)
|
||||
return us.uploadFile(ctx, file, avatarFilePath)
|
||||
}
|
||||
|
||||
func (us *UploaderService) UploadBrandingFile(ctx *gin.Context) (
|
||||
url string, err error) {
|
||||
// max size
|
||||
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 10*1024*1024)
|
||||
_, file, err := ctx.Request.FormFile("file")
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
fileExt := strings.ToLower(path.Ext(file.Filename))
|
||||
_, ok := FormatExts[fileExt]
|
||||
if !ok && fileExt != ".ico" {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
|
||||
newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt)
|
||||
avatarFilePath := path.Join(brandingSubPath, newFilename)
|
||||
return us.uploadFile(ctx, file, avatarFilePath)
|
||||
}
|
||||
|
||||
func (us *UploaderService) uploadFile(ctx *gin.Context, file *multipart.FileHeader, fileSubPath string) (
|
||||
url string, err error) {
|
||||
siteGeneral, err := us.siteInfoService.GetSiteGeneral(ctx)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/Chain-Zhang/pinyin"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/base/translator"
|
||||
"github.com/answerdev/answer/internal/base/validator"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/activity"
|
||||
@@ -112,6 +113,7 @@ func (us *UserService) EmailLogin(ctx context.Context, req *schema.UserEmailLogi
|
||||
UserID: userInfo.ID,
|
||||
EmailStatus: userInfo.MailStatus,
|
||||
UserStatus: userInfo.Status,
|
||||
IsAdmin: userInfo.IsAdmin,
|
||||
}
|
||||
resp.AccessToken, err = us.authService.SetUserCacheInfo(ctx, userCacheInfo)
|
||||
if err != nil {
|
||||
@@ -322,6 +324,7 @@ func (us *UserService) UserRegisterByEmail(ctx context.Context, registerUserInfo
|
||||
UserID: userInfo.ID,
|
||||
EmailStatus: userInfo.MailStatus,
|
||||
UserStatus: userInfo.Status,
|
||||
IsAdmin: userInfo.IsAdmin,
|
||||
}
|
||||
resp.AccessToken, err = us.authService.SetUserCacheInfo(ctx, userCacheInfo)
|
||||
if err != nil {
|
||||
@@ -408,11 +411,16 @@ func (us *UserService) UserVerifyEmail(ctx context.Context, req *schema.UserVeri
|
||||
UserID: userInfo.ID,
|
||||
EmailStatus: userInfo.MailStatus,
|
||||
UserStatus: userInfo.Status,
|
||||
IsAdmin: userInfo.IsAdmin,
|
||||
}
|
||||
resp.AccessToken, err = us.authService.SetUserCacheInfo(ctx, userCacheInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// User verified email will update user email status. So user status cache should be updated.
|
||||
if err = us.authService.SetUserStatus(ctx, userCacheInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.IsAdmin = userInfo.IsAdmin
|
||||
if resp.IsAdmin {
|
||||
err = us.authService.SetCmsUserCacheInfo(ctx, resp.AccessToken, &entity.UserCacheInfo{UserID: userInfo.ID})
|
||||
@@ -478,7 +486,7 @@ func (us *UserService) encryptPassword(ctx context.Context, Pass string) (string
|
||||
|
||||
// UserChangeEmailSendCode user change email verification
|
||||
func (us *UserService) UserChangeEmailSendCode(ctx context.Context, req *schema.UserChangeEmailSendCodeReq) (
|
||||
resp *schema.UserVerifyEmailErrorResponse, err error) {
|
||||
resp *validator.FormErrorField, err error) {
|
||||
userInfo, exist, err := us.userRepo.GetByUserID(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -492,9 +500,9 @@ func (us *UserService) UserChangeEmailSendCode(ctx context.Context, req *schema.
|
||||
return nil, err
|
||||
}
|
||||
if exist {
|
||||
resp = &schema.UserVerifyEmailErrorResponse{
|
||||
Key: "e_mail",
|
||||
Value: reason.EmailDuplicate,
|
||||
resp = &validator.FormErrorField{
|
||||
ErrorField: "e_mail",
|
||||
ErrorMsg: reason.EmailDuplicate,
|
||||
}
|
||||
return resp, errors.BadRequest(reason.EmailDuplicate)
|
||||
}
|
||||
|
||||
+1
-3
@@ -23,9 +23,8 @@ module.exports = {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
},
|
||||
plugins: ['react', '@typescript-eslint', 'prettier'],
|
||||
plugins: ['react', '@typescript-eslint'],
|
||||
rules: {
|
||||
'prettier/prettier': 'error',
|
||||
'no-unused-vars': 'off',
|
||||
'no-console': 'off',
|
||||
'import/prefer-default-export': 'off',
|
||||
@@ -51,7 +50,6 @@ module.exports = {
|
||||
'react-hooks/exhaustive-deps': 'off',
|
||||
'react/jsx-props-no-spreading': 'off',
|
||||
'@typescript-eslint/default-param-last': 'off',
|
||||
'no-nested-ternary': 'off',
|
||||
'import/order': [
|
||||
'error',
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"*.{js,jsx,ts,tsx}": ["eslint --cache --fix"],
|
||||
"*.{js,jsx,scss,md,json}": ["prettier --write"],
|
||||
"*.{js,jsx,less,md,json}": ["prettier --write"],
|
||||
"*.ts?(x)": ["prettier --parser=typescript --write"]
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
extends: ['@commitlint/routes-conventional'],
|
||||
};
|
||||
|
||||
+2
-4
@@ -9,8 +9,7 @@
|
||||
"lint": "eslint . --cache --fix --ext .ts,.tsx",
|
||||
"prettier": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,scss,md}\"",
|
||||
"prepare": "cd .. && husky install",
|
||||
"preinstall": "node ./scripts/preinstall.js",
|
||||
"pre-commit": "lint-staged"
|
||||
"preinstall": "node ./scripts/preinstall.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.27.2",
|
||||
@@ -20,7 +19,6 @@
|
||||
"codemirror": "5.65.0",
|
||||
"copy-to-clipboard": "^3.3.2",
|
||||
"dayjs": "^1.11.5",
|
||||
"diff": "^5.1.0",
|
||||
"i18next": "^21.9.0",
|
||||
"katex": "^0.16.2",
|
||||
"lodash": "^4.17.21",
|
||||
@@ -35,12 +33,12 @@
|
||||
"react-helmet-async": "^1.3.0",
|
||||
"react-i18next": "^11.18.3",
|
||||
"react-router-dom": "^6.4.0",
|
||||
"semver": "^7.3.8",
|
||||
"swr": "^1.3.0",
|
||||
"zustand": "^4.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.0.3",
|
||||
"@commitlint/config-conventional": "^17.2.0",
|
||||
"@fullhuman/postcss-purgecss": "^4.1.3",
|
||||
"@testing-library/dom": "^8.17.1",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
|
||||
Generated
+53
-37
@@ -2,7 +2,6 @@ lockfileVersion: 5.4
|
||||
|
||||
specifiers:
|
||||
'@commitlint/cli': ^17.0.3
|
||||
'@commitlint/config-conventional': ^17.2.0
|
||||
'@fullhuman/postcss-purgecss': ^4.1.3
|
||||
'@testing-library/dom': ^8.17.1
|
||||
'@testing-library/jest-dom': ^4.2.4
|
||||
@@ -26,7 +25,6 @@ specifiers:
|
||||
copy-to-clipboard: ^3.3.2
|
||||
customize-cra: ^1.0.0
|
||||
dayjs: ^1.11.5
|
||||
diff: ^5.1.0
|
||||
eslint: ^8.0.1
|
||||
eslint-config-airbnb: ^19.0.4
|
||||
eslint-config-airbnb-typescript: ^17.0.0
|
||||
@@ -61,6 +59,7 @@ specifiers:
|
||||
react-router-dom: ^6.4.0
|
||||
react-scripts: 5.0.1
|
||||
sass: ^1.54.4
|
||||
semver: ^7.3.8
|
||||
swr: ^1.3.0
|
||||
typescript: ^4.8.3
|
||||
yaml-loader: ^0.8.0
|
||||
@@ -74,7 +73,6 @@ dependencies:
|
||||
codemirror: 5.65.0
|
||||
copy-to-clipboard: 3.3.2
|
||||
dayjs: 1.11.5
|
||||
diff: 5.1.0
|
||||
i18next: 21.9.2
|
||||
katex: 0.16.2
|
||||
lodash: 4.17.21
|
||||
@@ -89,12 +87,12 @@ dependencies:
|
||||
react-helmet-async: 1.3.0_biqbaboplfbrettd7655fr4n2y
|
||||
react-i18next: 11.18.6_ulhmqqxshznzmtuvahdi5nasbq
|
||||
react-router-dom: 6.4.0_biqbaboplfbrettd7655fr4n2y
|
||||
semver: 7.3.8
|
||||
swr: 1.3.0_react@18.2.0
|
||||
zustand: 4.1.1_react@18.2.0
|
||||
|
||||
devDependencies:
|
||||
'@commitlint/cli': 17.1.2
|
||||
'@commitlint/config-conventional': 17.2.0
|
||||
'@fullhuman/postcss-purgecss': 4.1.3_postcss@8.4.16
|
||||
'@testing-library/dom': 8.18.1
|
||||
'@testing-library/jest-dom': 4.2.4
|
||||
@@ -1423,13 +1421,6 @@ packages:
|
||||
- '@swc/wasm'
|
||||
dev: true
|
||||
|
||||
/@commitlint/config-conventional/17.2.0:
|
||||
resolution: {integrity: sha512-g5hQqRa80f++SYS233dbDSg16YdyounMTAhVcmqtInNeY/GF3aA4st9SVtJxpeGrGmueMrU4L+BBb+6Vs5wrcg==}
|
||||
engines: {node: '>=v14'}
|
||||
dependencies:
|
||||
conventional-changelog-conventionalcommits: 5.0.0
|
||||
dev: true
|
||||
|
||||
/@commitlint/config-validator/17.1.0:
|
||||
resolution: {integrity: sha512-Q1rRRSU09ngrTgeTXHq6ePJs2KrI+axPTgkNYDWSJIuS1Op4w3J30vUfSXjwn5YEJHklK3fSqWNHmBhmTR7Vdg==}
|
||||
engines: {node: '>=v14'}
|
||||
@@ -1491,7 +1482,7 @@ packages:
|
||||
cosmiconfig-typescript-loader: 4.1.0_3owiowz3ujipd4k6pbqn3n7oui
|
||||
lodash: 4.17.21
|
||||
resolve-from: 5.0.0
|
||||
ts-node: 10.9.1_ao52im6kiihokc7tdj7weudhra
|
||||
ts-node: 10.9.1_ck2axrxkiif44rdbzjywaqjysa
|
||||
typescript: 4.8.3
|
||||
transitivePeerDependencies:
|
||||
- '@swc/core'
|
||||
@@ -2678,7 +2669,7 @@ packages:
|
||||
eslint: 8.23.1
|
||||
ignore: 5.2.0
|
||||
regexpp: 3.2.0
|
||||
semver: 7.3.7
|
||||
semver: 7.3.8
|
||||
tsutils: 3.21.0_typescript@4.8.3
|
||||
typescript: 4.8.3
|
||||
transitivePeerDependencies:
|
||||
@@ -2759,7 +2750,7 @@ packages:
|
||||
debug: 4.3.4
|
||||
globby: 11.1.0
|
||||
is-glob: 4.0.3
|
||||
semver: 7.3.7
|
||||
semver: 7.3.8
|
||||
tsutils: 3.21.0_typescript@4.8.3
|
||||
typescript: 4.8.3
|
||||
transitivePeerDependencies:
|
||||
@@ -3483,7 +3474,7 @@ packages:
|
||||
/builtins/5.0.1:
|
||||
resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==}
|
||||
dependencies:
|
||||
semver: 7.3.7
|
||||
semver: 7.3.8
|
||||
dev: true
|
||||
|
||||
/bytes/3.0.0:
|
||||
@@ -3774,15 +3765,6 @@ packages:
|
||||
q: 1.5.1
|
||||
dev: true
|
||||
|
||||
/conventional-changelog-conventionalcommits/5.0.0:
|
||||
resolution: {integrity: sha512-lCDbA+ZqVFQGUj7h9QBKoIpLhl8iihkO0nCTyRNzuXtcd7ubODpYB04IFy31JloiJgG0Uovu8ot8oxRzn7Nwtw==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
compare-func: 2.0.0
|
||||
lodash: 4.17.21
|
||||
q: 1.5.1
|
||||
dev: true
|
||||
|
||||
/conventional-commits-parser/3.2.4:
|
||||
resolution: {integrity: sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -3841,7 +3823,7 @@ packages:
|
||||
dependencies:
|
||||
'@types/node': 14.18.29
|
||||
cosmiconfig: 7.0.1
|
||||
ts-node: 10.9.1_ao52im6kiihokc7tdj7weudhra
|
||||
ts-node: 10.9.1_ck2axrxkiif44rdbzjywaqjysa
|
||||
typescript: 4.8.3
|
||||
dev: true
|
||||
|
||||
@@ -3925,7 +3907,7 @@ packages:
|
||||
postcss-modules-scope: 3.0.0_postcss@8.4.16
|
||||
postcss-modules-values: 4.0.0_postcss@8.4.16
|
||||
postcss-value-parser: 4.2.0
|
||||
semver: 7.3.7
|
||||
semver: 7.3.8
|
||||
webpack: 5.74.0
|
||||
|
||||
/css-minimizer-webpack-plugin/3.4.1_webpack@5.74.0:
|
||||
@@ -4762,11 +4744,6 @@ packages:
|
||||
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
|
||||
engines: {node: '>=0.3.1'}
|
||||
|
||||
/diff/5.1.0:
|
||||
resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==}
|
||||
engines: {node: '>=0.3.1'}
|
||||
dev: false
|
||||
|
||||
/dir-glob/3.0.1:
|
||||
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -5297,7 +5274,7 @@ packages:
|
||||
is-core-module: 2.10.0
|
||||
minimatch: 3.1.2
|
||||
resolve: 1.22.1
|
||||
semver: 7.3.7
|
||||
semver: 7.3.8
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-prettier/4.2.1_cabrci5exjdaojcvd6xoxgeowu:
|
||||
@@ -5761,7 +5738,7 @@ packages:
|
||||
memfs: 3.4.7
|
||||
minimatch: 3.1.2
|
||||
schema-utils: 2.7.0
|
||||
semver: 7.3.7
|
||||
semver: 7.3.8
|
||||
tapable: 1.1.3
|
||||
typescript: 4.8.3
|
||||
webpack: 5.74.0
|
||||
@@ -6933,7 +6910,7 @@ packages:
|
||||
jest-util: 27.5.1
|
||||
natural-compare: 1.4.0
|
||||
pretty-format: 27.5.1
|
||||
semver: 7.3.7
|
||||
semver: 7.3.8
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -7640,7 +7617,7 @@ packages:
|
||||
dependencies:
|
||||
hosted-git-info: 4.1.0
|
||||
is-core-module: 2.10.0
|
||||
semver: 7.3.7
|
||||
semver: 7.3.8
|
||||
validate-npm-package-license: 3.0.4
|
||||
dev: true
|
||||
|
||||
@@ -8244,7 +8221,7 @@ packages:
|
||||
cosmiconfig: 7.0.1
|
||||
klona: 2.0.5
|
||||
postcss: 8.4.16
|
||||
semver: 7.3.7
|
||||
semver: 7.3.8
|
||||
webpack: 5.74.0
|
||||
|
||||
/postcss-logical/5.0.4_postcss@8.4.16:
|
||||
@@ -9069,7 +9046,7 @@ packages:
|
||||
resolve: 1.22.1
|
||||
resolve-url-loader: 4.0.0
|
||||
sass-loader: 12.6.0_sass@1.54.9+webpack@5.74.0
|
||||
semver: 7.3.7
|
||||
semver: 7.3.8
|
||||
source-map-loader: 3.0.1_webpack@5.74.0
|
||||
style-loader: 3.3.1_webpack@5.74.0
|
||||
tailwindcss: 3.1.8_57znarxsqwmnneadci5z5fd5gu
|
||||
@@ -9516,6 +9493,14 @@ packages:
|
||||
hasBin: true
|
||||
dependencies:
|
||||
lru-cache: 6.0.0
|
||||
dev: true
|
||||
|
||||
/semver/7.3.8:
|
||||
resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
lru-cache: 6.0.0
|
||||
|
||||
/send/0.18.0:
|
||||
resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
|
||||
@@ -10211,6 +10196,37 @@ packages:
|
||||
v8-compile-cache-lib: 3.0.1
|
||||
yn: 3.1.1
|
||||
|
||||
/ts-node/10.9.1_ck2axrxkiif44rdbzjywaqjysa:
|
||||
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@swc/core': '>=1.2.50'
|
||||
'@swc/wasm': '>=1.2.50'
|
||||
'@types/node': '*'
|
||||
typescript: '>=2.7'
|
||||
peerDependenciesMeta:
|
||||
'@swc/core':
|
||||
optional: true
|
||||
'@swc/wasm':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@cspotcode/source-map-support': 0.8.1
|
||||
'@tsconfig/node10': 1.0.9
|
||||
'@tsconfig/node12': 1.0.11
|
||||
'@tsconfig/node14': 1.0.3
|
||||
'@tsconfig/node16': 1.0.3
|
||||
'@types/node': 14.18.29
|
||||
acorn: 8.8.0
|
||||
acorn-walk: 8.2.0
|
||||
arg: 4.1.3
|
||||
create-require: 1.1.1
|
||||
diff: 4.0.2
|
||||
make-error: 1.3.6
|
||||
typescript: 4.8.3
|
||||
v8-compile-cache-lib: 3.0.1
|
||||
yn: 3.1.1
|
||||
dev: true
|
||||
|
||||
/tsconfig-paths/3.14.1:
|
||||
resolution: {integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==}
|
||||
dependencies:
|
||||
|
||||
@@ -557,12 +557,3 @@ export const TIMEZONES = [
|
||||
},
|
||||
];
|
||||
export const DEFAULT_TIMEZONE = 'UTC+0';
|
||||
|
||||
export const TIMELINE_NORMAL_ACTIVITY_TYPE = [
|
||||
'undeleted',
|
||||
'deleted',
|
||||
'downvote',
|
||||
'upvote',
|
||||
'reopened',
|
||||
'closed',
|
||||
];
|
||||
|
||||
@@ -124,7 +124,6 @@ export interface UserInfoRes extends UserInfoBase {
|
||||
*/
|
||||
mail_status: number;
|
||||
language: string;
|
||||
is_admin: boolean;
|
||||
e_mail?: string;
|
||||
[prop: string]: any;
|
||||
}
|
||||
@@ -157,13 +156,6 @@ export interface SetNoticeReq {
|
||||
notice_switch: boolean;
|
||||
}
|
||||
|
||||
export interface NotificationStatus {
|
||||
inbox: number;
|
||||
achievement: number;
|
||||
revision: number;
|
||||
can_revision: boolean;
|
||||
}
|
||||
|
||||
export interface QuestionDetailRes {
|
||||
id: string;
|
||||
title: string;
|
||||
@@ -199,6 +191,7 @@ export interface AnswerItem {
|
||||
create_time: string;
|
||||
update_time: string;
|
||||
user_info: UserInfoBase;
|
||||
|
||||
[prop: string]: any;
|
||||
}
|
||||
|
||||
@@ -380,61 +373,3 @@ export interface AdminDashboard {
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface TimelineReq {
|
||||
show_vote: boolean;
|
||||
object_id: string;
|
||||
}
|
||||
|
||||
export interface TimelineItem {
|
||||
activity_id: number;
|
||||
revision_id: number;
|
||||
created_at: number;
|
||||
activity_type: string;
|
||||
username: string;
|
||||
user_display_name: string;
|
||||
comment: string;
|
||||
object_id: string;
|
||||
object_type: string;
|
||||
cancelled: boolean;
|
||||
cancelled_at: any;
|
||||
}
|
||||
|
||||
export interface TimelineObject {
|
||||
title: string;
|
||||
object_type: string;
|
||||
question_id: string;
|
||||
answer_id: string;
|
||||
main_tag_slug_name?: string;
|
||||
}
|
||||
|
||||
export interface TimelineRes {
|
||||
object_info: TimelineObject;
|
||||
timeline: TimelineItem[];
|
||||
}
|
||||
|
||||
export interface ReviewItem {
|
||||
type: 'question' | 'answer' | 'tag';
|
||||
info: {
|
||||
object_id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
html: string;
|
||||
tags: Tag[];
|
||||
};
|
||||
unreviewed_info: {
|
||||
id: string;
|
||||
use_id: string;
|
||||
object_id: string;
|
||||
title: string;
|
||||
status: 0 | 1;
|
||||
create_at: number;
|
||||
user_info: UserInfoBase;
|
||||
reason: string;
|
||||
content: Tag | QuestionDetailRes | AnswerItem;
|
||||
};
|
||||
}
|
||||
export interface ReviewResp {
|
||||
count: number;
|
||||
list: ReviewItem[];
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ interface Props {
|
||||
data: any;
|
||||
showAvatar?: boolean;
|
||||
avatarSize?: string;
|
||||
showReputation?: boolean;
|
||||
avatarSearchStr?: string;
|
||||
className?: string;
|
||||
}
|
||||
@@ -19,7 +18,6 @@ const Index: FC<Props> = ({
|
||||
avatarSize = '20px',
|
||||
className = 'fs-14',
|
||||
avatarSearchStr = 's=48',
|
||||
showReputation = true,
|
||||
}) => {
|
||||
return (
|
||||
<div className={`text-secondary ${className}`}>
|
||||
@@ -49,11 +47,9 @@ const Index: FC<Props> = ({
|
||||
</>
|
||||
)}
|
||||
|
||||
{showReputation && (
|
||||
<span className="fw-bold" title="Reputation">
|
||||
{formatCount(data?.rank)}
|
||||
</span>
|
||||
)}
|
||||
<span className="fw-bold" title="Reputation">
|
||||
{formatCount(data?.rank)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
import { FC, memo } from 'react';
|
||||
|
||||
import { Tag } from '@/components';
|
||||
import { diffText } from '@/utils';
|
||||
|
||||
interface Props {
|
||||
objectType: string | 'question' | 'answer' | 'tag';
|
||||
newData: Record<string, any>;
|
||||
oldData?: Record<string, any>;
|
||||
className?: string;
|
||||
opts?: Partial<{
|
||||
showTitle: boolean;
|
||||
showTagUrlSlug: boolean;
|
||||
}>;
|
||||
}
|
||||
|
||||
const Index: FC<Props> = ({
|
||||
objectType,
|
||||
newData,
|
||||
oldData,
|
||||
className = '',
|
||||
opts = {
|
||||
showTitle: true,
|
||||
showTagUrlSlug: true,
|
||||
},
|
||||
}) => {
|
||||
if (!newData?.original_text) return null;
|
||||
|
||||
let tag = newData.tags;
|
||||
if (objectType === 'question' && oldData?.tags) {
|
||||
const addTags = newData.tags.filter(
|
||||
(c) => !oldData?.tags?.find((p) => p.slug_name === c.slug_name),
|
||||
);
|
||||
|
||||
let deleteTags = oldData?.tags
|
||||
.filter((c) => !newData?.tags.find((p) => p.slug_name === c.slug_name))
|
||||
.map((v) => ({ ...v, state: 'delete' }));
|
||||
|
||||
deleteTags = deleteTags?.map((v) => {
|
||||
const index = oldData?.tags?.findIndex(
|
||||
(c) => c.slug_name === v.slug_name,
|
||||
);
|
||||
return {
|
||||
...v,
|
||||
pre_index: index,
|
||||
};
|
||||
});
|
||||
|
||||
tag = newData.tags.map((item) => {
|
||||
const find = addTags.find((c) => c.slug_name === item.slug_name);
|
||||
if (find) {
|
||||
return {
|
||||
...find,
|
||||
state: 'add',
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
deleteTags.forEach((v) => {
|
||||
tag.splice(v.pre_index, 0, v);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{objectType !== 'answer' && opts?.showTitle && (
|
||||
<h5
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: diffText(newData.title, oldData?.title),
|
||||
}}
|
||||
className="mb-3"
|
||||
/>
|
||||
)}
|
||||
{objectType === 'question' && (
|
||||
<div className="mb-4">
|
||||
{tag.map((item) => {
|
||||
return (
|
||||
<Tag
|
||||
key={item.slug_name}
|
||||
className="me-1"
|
||||
data={item}
|
||||
textClassName={`d-inline-block review-text-${item.state}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{objectType === 'tag' && opts?.showTagUrlSlug && (
|
||||
<div className="mb-4">
|
||||
{`/tags/${
|
||||
newData?.main_tag_slug_name
|
||||
? diffText(
|
||||
newData.main_tag_slug_name,
|
||||
oldData?.main_tag_slug_name,
|
||||
)
|
||||
: diffText(newData.slug_name, oldData?.slug_name)
|
||||
}`}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: diffText(newData.original_text, oldData?.original_text),
|
||||
}}
|
||||
className="pre-line text-break font-monospace"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Index);
|
||||
@@ -1,15 +1,13 @@
|
||||
import { FC, memo, ReactNode } from 'react';
|
||||
import { FC, memo } from 'react';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
const Index: FC<{ children?: ReactNode }> = ({ children }) => {
|
||||
const Index: FC = () => {
|
||||
return (
|
||||
<div className="text-center py-5">
|
||||
{children || (
|
||||
<Trans i18nKey="personal.list_empty">
|
||||
We couldn't find anything. <br /> Try different or less specific
|
||||
keywords.
|
||||
</Trans>
|
||||
)}
|
||||
<Trans i18nKey="personal.list_empty">
|
||||
We couldn't find anything. <br /> Try different or less specific
|
||||
keywords.
|
||||
</Trans>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,10 +9,7 @@ const Index = () => {
|
||||
<p className="text-center mb-0 fs-14 text-secondary">
|
||||
<Trans i18nKey="footer.build_on">
|
||||
Built on
|
||||
{/* eslint-disable-next-line react/jsx-no-target-blank */}
|
||||
<a href="https://answer.dev/" target="_blank">
|
||||
Answer
|
||||
</a>
|
||||
<a href="/"> Answer </a>
|
||||
- the open-source software that powers Q&A communities.
|
||||
<br />
|
||||
Made with love. © 2022 Answer .
|
||||
|
||||
@@ -3,11 +3,10 @@ import { Nav, Dropdown } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link, NavLink } from 'react-router-dom';
|
||||
|
||||
import type * as Type from '@/common/interface';
|
||||
import { Avatar, Icon } from '@/components';
|
||||
|
||||
interface Props {
|
||||
redDot: Type.NotificationStatus | undefined;
|
||||
redDot;
|
||||
userInfo;
|
||||
logOut: () => void;
|
||||
}
|
||||
@@ -57,16 +56,6 @@ const Index: FC<Props> = ({ redDot, userInfo, logOut }) => {
|
||||
{userInfo?.is_admin ? (
|
||||
<Dropdown.Item href="/admin">{t('header.nav.admin')}</Dropdown.Item>
|
||||
) : null}
|
||||
{redDot?.can_revision ? (
|
||||
<Dropdown.Item href="/review" className="position-relative">
|
||||
{t('header.nav.review')}
|
||||
{redDot?.revision > 0 && (
|
||||
<span className="position-absolute top-50 translate-middle-y end-0 me-3 p-2 bg-danger border border-light rounded-circle">
|
||||
<span className="visually-hidden">New Review</span>
|
||||
</span>
|
||||
)}
|
||||
</Dropdown.Item>
|
||||
) : null}
|
||||
<Dropdown.Divider />
|
||||
<Dropdown.Item onClick={logOut}>
|
||||
{t('header.nav.logout')}
|
||||
|
||||
@@ -122,6 +122,9 @@ const Header: FC = () => {
|
||||
<NavLink className="nav-link" to="/tags">
|
||||
{t('header.nav.tag')}
|
||||
</NavLink>
|
||||
<NavLink className="nav-link d-none" to="/users">
|
||||
{t('header.nav.user')}
|
||||
</NavLink>
|
||||
</Nav>
|
||||
</Col>
|
||||
<hr className="hr lg-none mt-2" />
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Modal } from '@/components';
|
||||
import { useReportModal, useToast } from '@/hooks';
|
||||
import Share from '../Share';
|
||||
import { deleteQuestion, deleteAnswer, editCheck } from '@/services';
|
||||
import { deleteQuestion, deleteAnswer } from '@/services';
|
||||
import { tryNormalLogged } from '@/utils/guard';
|
||||
|
||||
interface IProps {
|
||||
@@ -96,15 +96,6 @@ const Index: FC<IProps> = ({
|
||||
});
|
||||
}
|
||||
};
|
||||
const handleEdit = async (evt) => {
|
||||
let checkObjectId = qid;
|
||||
if (type === 'answer') {
|
||||
checkObjectId = aid;
|
||||
}
|
||||
editCheck(checkObjectId).catch(() => {
|
||||
evt.preventDefault();
|
||||
});
|
||||
};
|
||||
|
||||
const handleAction = (action) => {
|
||||
if (!tryNormalLogged(true)) {
|
||||
@@ -133,7 +124,6 @@ const Index: FC<IProps> = ({
|
||||
key={item.action}
|
||||
to={editUrl}
|
||||
className="link-secondary p-0 fs-14 me-3"
|
||||
onClick={handleEdit}
|
||||
style={{ lineHeight: '23px' }}>
|
||||
{item.name}
|
||||
</Link>
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface JSONSchema {
|
||||
[key: string]: {
|
||||
type: 'string' | 'boolean';
|
||||
title: string;
|
||||
label?: string;
|
||||
description?: string;
|
||||
enum?: Array<string | boolean>;
|
||||
enumNames?: string[];
|
||||
@@ -108,6 +109,17 @@ const SchemaForm: FC<IProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const { name, value } = e.target;
|
||||
const data = {
|
||||
...formData,
|
||||
[name]: { ...formData[name], value, isInvalid: false },
|
||||
};
|
||||
if (onChange instanceof Function) {
|
||||
onChange(data);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSwitchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, checked } = e.target;
|
||||
const data = {
|
||||
@@ -228,7 +240,7 @@ const SchemaForm: FC<IProps> = ({
|
||||
return (
|
||||
<Form noValidate onSubmit={handleSubmit}>
|
||||
{keys.map((key) => {
|
||||
const { title, description } = properties[key];
|
||||
const { title, description, label } = properties[key];
|
||||
const { 'ui:widget': widget = 'input', 'ui:options': options = {} } =
|
||||
uiSchema[key] || {};
|
||||
if (widget === 'select') {
|
||||
@@ -237,6 +249,9 @@ const SchemaForm: FC<IProps> = ({
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<Form.Select
|
||||
aria-label={description}
|
||||
name={key}
|
||||
value={formData[key]?.value}
|
||||
onChange={handleSelectChange}
|
||||
isInvalid={formData[key].isInvalid}>
|
||||
{properties[key].enum?.map((item, index) => {
|
||||
return (
|
||||
@@ -249,7 +264,7 @@ const SchemaForm: FC<IProps> = ({
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData[key]?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
<Form.Text>{description}</Form.Text>
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
@@ -279,13 +294,12 @@ const SchemaForm: FC<IProps> = ({
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData[key]?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
<Form.Text>{description}</Form.Text>
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
if (widget === 'switch') {
|
||||
console.log(formData[key]?.value, 'switch=====');
|
||||
return (
|
||||
<Form.Group key={title} className="mb-3" controlId={key}>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
@@ -294,7 +308,7 @@ const SchemaForm: FC<IProps> = ({
|
||||
id={title}
|
||||
name={key}
|
||||
type="switch"
|
||||
label={title}
|
||||
label={label}
|
||||
checked={formData[key]?.value}
|
||||
feedback={formData[key]?.errorMsg}
|
||||
feedbackType="invalid"
|
||||
@@ -304,7 +318,7 @@ const SchemaForm: FC<IProps> = ({
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData[key]?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
<Form.Text>{description}</Form.Text>
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
@@ -314,7 +328,8 @@ const SchemaForm: FC<IProps> = ({
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<TimeZonePicker
|
||||
value={formData[key]?.value}
|
||||
onChange={handleInputChange}
|
||||
name={key}
|
||||
onChange={handleSelectChange}
|
||||
/>
|
||||
<Form.Control
|
||||
name={key}
|
||||
@@ -324,7 +339,7 @@ const SchemaForm: FC<IProps> = ({
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData[key]?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
<Form.Text>{description}</Form.Text>
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
@@ -347,7 +362,7 @@ const SchemaForm: FC<IProps> = ({
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData[key]?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
<Form.Text>{description}</Form.Text>
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
@@ -370,7 +385,7 @@ const SchemaForm: FC<IProps> = ({
|
||||
{formData[key]?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
<Form.Text>{description}</Form.Text>
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
@@ -390,7 +405,7 @@ const SchemaForm: FC<IProps> = ({
|
||||
{formData[key]?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
<Form.Text>{description}</Form.Text>
|
||||
</Form.Group>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -3,22 +3,17 @@ import React, { memo, FC } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Tag } from '@/common/interface';
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
|
||||
interface IProps {
|
||||
data: Tag;
|
||||
href?: string;
|
||||
className?: string;
|
||||
textClassName?: string;
|
||||
}
|
||||
|
||||
const Index: FC<IProps> = ({
|
||||
data,
|
||||
href,
|
||||
className = '',
|
||||
textClassName = '',
|
||||
}) => {
|
||||
href ||= pathFactory.tagLanding(data);
|
||||
const Index: FC<IProps> = ({ className = '', href, data }) => {
|
||||
href ||= `/tags/${encodeURIComponent(
|
||||
data.main_tag_slug_name || data.slug_name,
|
||||
)}`.toLowerCase();
|
||||
|
||||
return (
|
||||
<a
|
||||
@@ -29,7 +24,7 @@ const Index: FC<IProps> = ({
|
||||
data.recommend && 'badge-tag-required',
|
||||
className,
|
||||
)}>
|
||||
<span className={textClassName}>{data.slug_name}</span>
|
||||
{data.slug_name}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -229,12 +229,7 @@ const TagSelector: FC<IProps> = ({
|
||||
{showRequiredTagText &&
|
||||
tags &&
|
||||
tags.filter((v) => v.recommend)?.length > 0 && (
|
||||
<Dropdown.Item
|
||||
disabled
|
||||
style={{ fontWeight: 500 }}
|
||||
className="text-secondary">
|
||||
{t('tag_required_text')}
|
||||
</Dropdown.Item>
|
||||
<h6 className="dropdown-header">{t('tag_required_text')}</h6>
|
||||
)}
|
||||
|
||||
{tags?.map((item, index) => {
|
||||
|
||||
@@ -10,19 +10,10 @@ interface Props {
|
||||
data: any;
|
||||
time: number;
|
||||
preFix: string;
|
||||
isLogged: boolean;
|
||||
timelinePath: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Index: FC<Props> = ({
|
||||
data,
|
||||
time,
|
||||
preFix,
|
||||
isLogged,
|
||||
timelinePath,
|
||||
className = '',
|
||||
}) => {
|
||||
const Index: FC<Props> = ({ data, time, preFix, className = '' }) => {
|
||||
return (
|
||||
<div className={classnames('d-flex', className)}>
|
||||
{data?.status !== 'deleted' ? (
|
||||
@@ -71,18 +62,7 @@ const Index: FC<Props> = ({
|
||||
{formatCount(data?.rank)}
|
||||
</span>
|
||||
</div>
|
||||
{time &&
|
||||
(isLogged ? (
|
||||
<Link to={timelinePath}>
|
||||
<FormatTime
|
||||
time={time}
|
||||
preFix={preFix}
|
||||
className="link-secondary"
|
||||
/>
|
||||
</Link>
|
||||
) : (
|
||||
<FormatTime time={time} preFix={preFix} />
|
||||
))}
|
||||
{time && <FormatTime time={time} preFix={preFix} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user