mirror of https://gitee.com/answerdev/answer.git
Merge branch 'feat/ui-0.5.0' into feat/ui-0.6.0
This commit is contained in:
commit
9ceb9e0a07
|
@ -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" ]
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@ stages:
|
|||
"compile the golang project":
|
||||
image: golang:1.18
|
||||
stage: compile-golang
|
||||
before_script:
|
||||
- export GOPROXY=https://goproxy.cn,direct
|
||||
script:
|
||||
- make generate
|
||||
- make build
|
||||
|
|
5
Makefile
5
Makefile
|
@ -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/Jm7Y4cbUej)
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
|
|
@ -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/Jm7Y4cbUej)
|
||||
|
||||
## 截图
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
595
docs/docs.go
595
docs/docs.go
|
@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -74,6 +74,10 @@ backend:
|
|||
tag:
|
||||
not_found:
|
||||
other: "Tag not found."
|
||||
recommend_tag_not_found:
|
||||
other: "Recommend Tag is not exist."
|
||||
not_contain_synonym_tags:
|
||||
other: "Should not contain synonym tags."
|
||||
theme:
|
||||
not_found:
|
||||
other: "Theme not found."
|
||||
|
@ -1187,8 +1191,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."
|
||||
|
@ -1198,7 +1203,7 @@ ui:
|
|||
btn_submit: Save
|
||||
not_found_props: "Required property {{ key }} not found."
|
||||
page_review:
|
||||
review: Reivew
|
||||
review: Review
|
||||
proposed: proposed
|
||||
question_edit: Question edit
|
||||
answer_edit: Answer edit
|
||||
|
@ -1224,11 +1229,12 @@ ui:
|
|||
reopened: reopened
|
||||
created: created
|
||||
title: "History for"
|
||||
tag_title: "Timeline for"
|
||||
show_votes: "Show votes"
|
||||
n_or_a: N/A
|
||||
title_for_question: "Timeline for"
|
||||
title_for_answer: "Timeline for answers to {{ title }} by {{ author }}"
|
||||
title_for_tag: "Title for tag"
|
||||
title_for_answer: "Timeline for answer to {{ title }} by {{ author }}"
|
||||
title_for_tag: "Timeline for tag"
|
||||
datetime: Datetime
|
||||
type: Type
|
||||
by: By
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
EmailOrPasswordWrong = "error.user.email_or_password_wrong"
|
||||
EmailOrPasswordWrong = "error.object.email_or_password_incorrect"
|
||||
CommentNotFound = "error.comment.not_found"
|
||||
QuestionNotFound = "error.question.not_found"
|
||||
AnswerNotFound = "error.answer.not_found"
|
||||
|
@ -23,6 +23,8 @@ const (
|
|||
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"
|
||||
|
@ -33,6 +35,7 @@ const (
|
|||
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"
|
||||
|
@ -43,4 +46,6 @@ const (
|
|||
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) {
|
||||
|
|
|
@ -21,4 +21,5 @@ var ProviderSetController = wire.NewSet(
|
|||
NewNotificationController,
|
||||
NewSiteinfoController,
|
||||
NewDashboardController,
|
||||
NewUploadController,
|
||||
)
|
||||
|
|
|
@ -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,9 @@ func (tc *TagController) SearchTagLike(ctx *gin.Context) {
|
|||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := tc.tagService.SearchTagLike(ctx, req)
|
||||
userinfo := middleware.GetUserInfoFromContext(ctx)
|
||||
req.IsAdmin = userinfo.IsAdmin
|
||||
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)
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -76,9 +76,25 @@ func (as *AnswerService) RemoveAnswer(ctx context.Context, req *schema.RemoveAns
|
|||
if answerInfo.UserID != req.UserID {
|
||||
return errors.BadRequest(reason.UnauthorizedError)
|
||||
}
|
||||
if answerInfo.VoteCount > 0 {
|
||||
return errors.BadRequest(reason.UnauthorizedError)
|
||||
}
|
||||
if answerInfo.Adopted == schema.AnswerAdoptedEnable {
|
||||
return errors.BadRequest(reason.UnauthorizedError)
|
||||
}
|
||||
questionInfo, exist, err := as.questionRepo.GetQuestion(ctx, answerInfo.QuestionID)
|
||||
if err != nil {
|
||||
return errors.BadRequest(reason.UnauthorizedError)
|
||||
}
|
||||
if !exist {
|
||||
return errors.BadRequest(reason.UnauthorizedError)
|
||||
}
|
||||
if questionInfo.AnswerCount > 1 {
|
||||
return errors.BadRequest(reason.UnauthorizedError)
|
||||
}
|
||||
if questionInfo.AcceptedAnswerID != "" {
|
||||
return errors.BadRequest(reason.UnauthorizedError)
|
||||
}
|
||||
|
||||
// user add question count
|
||||
err = as.questionCommon.UpdateAnswerCount(ctx, answerInfo.QuestionID, -1)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
@ -105,6 +106,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()
|
||||
|
@ -215,6 +226,28 @@ func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.Quest
|
|||
if dbinfo.UserID != req.UserID {
|
||||
return
|
||||
}
|
||||
|
||||
//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
|
||||
|
@ -266,6 +299,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)
|
||||
|
||||
|
@ -502,14 +539,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,6 +151,8 @@ 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.GetExcerpt()
|
||||
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"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"
|
||||
},
|
||||
|
|
|
@ -60,6 +60,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
|
||||
|
@ -88,6 +89,7 @@ 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
|
||||
|
||||
|
@ -1489,7 +1491,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'
|
||||
|
@ -2670,7 +2672,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:
|
||||
|
@ -2751,7 +2753,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:
|
||||
|
@ -3475,7 +3477,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:
|
||||
|
@ -3833,7 +3835,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
|
||||
|
||||
|
@ -3917,7 +3919,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:
|
||||
|
@ -5289,7 +5291,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:
|
||||
|
@ -5753,7 +5755,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
|
||||
|
@ -6925,7 +6927,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
|
||||
|
||||
|
@ -7632,7 +7634,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
|
||||
|
||||
|
@ -8236,7 +8238,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:
|
||||
|
@ -9061,7 +9063,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
|
||||
|
@ -9508,6 +9510,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==}
|
||||
|
@ -10203,6 +10213,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:
|
||||
|
|
|
@ -69,6 +69,7 @@ export interface AnswerParams {
|
|||
html: string;
|
||||
question_id: string;
|
||||
id: string;
|
||||
edit_summary?: string;
|
||||
}
|
||||
|
||||
export interface LoginReqParams {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -66,7 +66,7 @@ a {
|
|||
display: inline-block;
|
||||
font-size: 14px;
|
||||
background: rgba($blue-100, 0.5);
|
||||
padding: 1px 7px;
|
||||
padding: 0px 7px 1px;
|
||||
color: $blue-700;
|
||||
border: 1px solid transparent;
|
||||
&:hover {
|
||||
|
|
|
@ -5,6 +5,8 @@ import { Link } from 'react-router-dom';
|
|||
|
||||
import type * as Type from '@/common/interface';
|
||||
|
||||
const { gt, gte } = require('semver');
|
||||
|
||||
interface IProps {
|
||||
data: Type.AdminDashboard['info'];
|
||||
}
|
||||
|
@ -12,7 +14,12 @@ interface IProps {
|
|||
const HealthStatus: FC<IProps> = ({ data }) => {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'admin.dashboard' });
|
||||
const { version, remote_version } = data.version_info || {};
|
||||
const isLatest = version === remote_version;
|
||||
let isLatest = false;
|
||||
let hasNewerVersion = false;
|
||||
if (version && remote_version) {
|
||||
isLatest = gte(version, remote_version);
|
||||
hasNewerVersion = gt(remote_version, version);
|
||||
}
|
||||
return (
|
||||
<Card className="mb-4">
|
||||
<Card.Body>
|
||||
|
@ -30,7 +37,7 @@ const HealthStatus: FC<IProps> = ({ data }) => {
|
|||
{t('latest')}
|
||||
</a>
|
||||
)}
|
||||
{!isLatest && remote_version && (
|
||||
{!isLatest && hasNewerVersion && (
|
||||
<a
|
||||
className="ms-1 badge rounded-pill text-bg-warning"
|
||||
target="_blank"
|
||||
|
|
|
@ -75,19 +75,6 @@ const Interface: FC = () => {
|
|||
},
|
||||
});
|
||||
|
||||
// const onChange = (fieldName, fieldValue) => {
|
||||
// if (!formData[fieldName]) {
|
||||
// return;
|
||||
// }
|
||||
// const fieldData: FormDataType = {
|
||||
// [fieldName]: {
|
||||
// value: fieldValue,
|
||||
// isInvalid: false,
|
||||
// errorMsg: '',
|
||||
// },
|
||||
// };
|
||||
// setFormData({ ...formData, ...fieldData });
|
||||
// };
|
||||
const uiSchema: UISchema = {
|
||||
theme: {
|
||||
'ui:widget': 'select',
|
||||
|
@ -211,119 +198,6 @@ const Interface: FC = () => {
|
|||
onSubmit={onSubmit}
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
{/* <Form noValidate onSubmit={onSubmit}>
|
||||
<Form.Group controlId="logo" className="mb-3">
|
||||
<Form.Label>{t('logo.label')}</Form.Label>
|
||||
<Stack gap={2}>
|
||||
<div
|
||||
className="bg-light overflow-hidden"
|
||||
style={{ width: '288px', height: '96px' }}>
|
||||
{formData.logo.value ? (
|
||||
<Image
|
||||
width="288"
|
||||
height="96"
|
||||
className="object-fit-contain"
|
||||
src={formData.logo.value}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="d-inline-flex">
|
||||
<UploadImg type="logo" upload={imgUpload} className="mb-2" />
|
||||
</div>
|
||||
</Stack>
|
||||
<Form.Text as="div" className="text-muted">
|
||||
<Trans i18nKey="admin.interface.logo.text">
|
||||
You can upload your image or
|
||||
<Button
|
||||
variant="link"
|
||||
size="sm"
|
||||
className="p-0 mx-1"
|
||||
onClick={(evt) => {
|
||||
evt.preventDefault();
|
||||
onChange('logo', '');
|
||||
}}>
|
||||
reset it
|
||||
</Button>
|
||||
to the site title text.
|
||||
</Trans>
|
||||
</Form.Text>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData.logo.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Form.Group controlId="theme" className="mb-3">
|
||||
<Form.Label>{t('theme.label')}</Form.Label>
|
||||
<Form.Select
|
||||
value={formData.theme.value}
|
||||
isInvalid={formData.theme.isInvalid}
|
||||
onChange={(evt) => {
|
||||
onChange('theme', evt.target.value);
|
||||
}}>
|
||||
{themes?.map((item) => {
|
||||
return (
|
||||
<option value={item.value} key={item.value}>
|
||||
{item.label}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</Form.Select>
|
||||
<Form.Text as="div">{t('theme.text')}</Form.Text>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData.theme.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Form.Group controlId="language" className="mb-3">
|
||||
<Form.Label>{t('language.label')}</Form.Label>
|
||||
<Form.Select
|
||||
value={formData.language.value}
|
||||
isInvalid={formData.language.isInvalid}
|
||||
onChange={(evt) => {
|
||||
onChange('language', evt.target.value);
|
||||
}}>
|
||||
{langs?.map((item) => {
|
||||
return (
|
||||
<option value={item.value} key={item.value}>
|
||||
{item.label}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</Form.Select>
|
||||
<Form.Text as="div">{t('language.text')}</Form.Text>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData.language.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Form.Group controlId="time-zone" className="mb-3">
|
||||
<Form.Label>{t('time_zone.label')}</Form.Label>
|
||||
<Form.Select
|
||||
value={formData.time_zone.value}
|
||||
isInvalid={formData.time_zone.isInvalid}
|
||||
onChange={(evt) => {
|
||||
onChange('time_zone', evt.target.value);
|
||||
}}>
|
||||
{TIMEZONES?.map((item) => {
|
||||
return (
|
||||
<optgroup label={item.label} key={item.label}>
|
||||
{item.options.map((option) => {
|
||||
return (
|
||||
<option value={option.value} key={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</optgroup>
|
||||
);
|
||||
})}
|
||||
</Form.Select>
|
||||
<Form.Text as="div">{t('time_zone.text')}</Form.Text>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData.time_zone.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Button variant="primary" type="submit">
|
||||
{t('save', { keyPrefix: 'btns' })}
|
||||
</Button>
|
||||
</Form> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -78,11 +78,13 @@ const Legal: FC = () => {
|
|||
|
||||
useEffect(() => {
|
||||
getLegalSetting().then((setting) => {
|
||||
const formMeta = { ...formData };
|
||||
formMeta.terms_of_service.value = setting.terms_of_service_original_text;
|
||||
formMeta.privacy_policy.value = setting.privacy_policy_original_text;
|
||||
|
||||
setFormData(formMeta);
|
||||
if (setting) {
|
||||
const formMeta = { ...formData };
|
||||
formMeta.terms_of_service.value =
|
||||
setting.terms_of_service_original_text;
|
||||
formMeta.privacy_policy.value = setting.privacy_policy_original_text;
|
||||
setFormData(formMeta);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
|
|
@ -29,7 +29,8 @@ const Legal: FC = () => {
|
|||
},
|
||||
required_tag: {
|
||||
type: 'boolean',
|
||||
title: t('required_tag.label'),
|
||||
title: t('required_tag.title'),
|
||||
label: t('required_tag.label'),
|
||||
description: t('required_tag.text'),
|
||||
},
|
||||
reserved_tags: {
|
||||
|
@ -43,7 +44,7 @@ const Legal: FC = () => {
|
|||
recommend_tags: {
|
||||
'ui:widget': 'textarea',
|
||||
'ui:options': {
|
||||
rows: 5,
|
||||
rows: 10,
|
||||
},
|
||||
},
|
||||
required_tag: {
|
||||
|
@ -52,7 +53,7 @@ const Legal: FC = () => {
|
|||
reserved_tags: {
|
||||
'ui:widget': 'textarea',
|
||||
'ui:options': {
|
||||
rows: 5,
|
||||
rows: 10,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -61,14 +62,19 @@ const Legal: FC = () => {
|
|||
const onSubmit = (evt) => {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
let recommend_tags = [];
|
||||
if (formData.recommend_tags.value?.trim()) {
|
||||
recommend_tags = formData.recommend_tags.value.trim().split('\n');
|
||||
}
|
||||
let reserved_tags = [];
|
||||
if (formData.reserved_tags.value?.trim()) {
|
||||
reserved_tags = formData.reserved_tags.value.trim().split('\n');
|
||||
}
|
||||
const reqParams: Type.AdminSettingsWrite = {
|
||||
recommend_tags: formData.recommend_tags.value.trim().split('\n'),
|
||||
recommend_tags,
|
||||
reserved_tags,
|
||||
required_tag: formData.required_tag.value,
|
||||
reserved_tags: formData.reserved_tags.value.trim().split('\n'),
|
||||
};
|
||||
|
||||
console.log(reqParams);
|
||||
postRequireAndReservedTag(reqParams)
|
||||
.then(() => {
|
||||
Toast.onShow({
|
||||
|
@ -86,9 +92,13 @@ const Legal: FC = () => {
|
|||
|
||||
const initData = () => {
|
||||
getRequireAndReservedTag().then((res) => {
|
||||
formData.recommend_tags.value = res.recommend_tags.join('\n');
|
||||
if (Array.isArray(res.recommend_tags)) {
|
||||
formData.recommend_tags.value = res.recommend_tags.join('\n');
|
||||
}
|
||||
formData.required_tag.value = res.required_tag;
|
||||
formData.reserved_tags.value = res.reserved_tags.join('\n');
|
||||
if (Array.isArray(res.reserved_tags)) {
|
||||
formData.reserved_tags.value = res.reserved_tags.join('\n');
|
||||
}
|
||||
setFormData({ ...formData });
|
||||
});
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@ const Index: FC = () => {
|
|||
return (
|
||||
<>
|
||||
<PageTitle title={t('privacy')} />
|
||||
<h3>{t('privacy')}</h3>
|
||||
<h3 className="mb-4">{t('privacy')}</h3>
|
||||
<div
|
||||
className="fmt"
|
||||
dangerouslySetInnerHTML={{
|
||||
|
|
|
@ -22,7 +22,7 @@ const Index: FC = () => {
|
|||
return (
|
||||
<>
|
||||
<PageTitle title={t('tos')} />
|
||||
<h3>{t('tos')}</h3>
|
||||
<h3 className="mb-4">{t('tos')}</h3>
|
||||
<div
|
||||
className="fmt"
|
||||
dangerouslySetInnerHTML={{
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
.sub-container {
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 2rem;
|
||||
}
|
|
@ -1,20 +1,27 @@
|
|||
import { FC } from 'react';
|
||||
import { Container, Row, Col } from 'react-bootstrap';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
import { AccordionNav } from '@/components';
|
||||
import { ADMIN_LEGAL_MENUS } from '@/common/constants';
|
||||
|
||||
import './index.scss';
|
||||
import { Container, Row, Col, Nav } from 'react-bootstrap';
|
||||
import { Outlet, NavLink } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const Index: FC = () => {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'nav_menus' });
|
||||
return (
|
||||
<Container className="sub-container">
|
||||
<Row>
|
||||
<Col lg={2}>
|
||||
<AccordionNav menus={ADMIN_LEGAL_MENUS} />
|
||||
<Container className="pt-4 mt-2 mb-5">
|
||||
<Row className="justify-content-center">
|
||||
<Col xxl={10}>
|
||||
<Nav
|
||||
className="mb-4 flex-nowrap"
|
||||
variant="pills"
|
||||
style={{ overflow: 'auto' }}>
|
||||
<NavLink to="/tos" key="tos" className="nav-link">
|
||||
{t('tos')}
|
||||
</NavLink>
|
||||
<NavLink to="/privacy" key="privacy" className="nav-link">
|
||||
{t('privacy')}
|
||||
</NavLink>
|
||||
</Nav>
|
||||
</Col>
|
||||
<Col lg={6}>
|
||||
<Col xxl={10}>
|
||||
<Outlet />
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -60,6 +60,11 @@ const Ask = () => {
|
|||
const [formData, setFormData] = useState<FormDataItem>(initFormData);
|
||||
const [checked, setCheckState] = useState(false);
|
||||
const [focusType, setForceType] = useState('');
|
||||
const resetForm = () => {
|
||||
setFormData(initFormData);
|
||||
setCheckState(false);
|
||||
setForceType('');
|
||||
};
|
||||
|
||||
const editorRef = useRef<EditorRef>({
|
||||
getHtml: () => '',
|
||||
|
@ -75,7 +80,11 @@ const Ask = () => {
|
|||
const { data: similarQuestions = { list: [] } } = useQueryQuestionByTitle(
|
||||
isEdit ? '' : formData.title.value,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEdit) {
|
||||
resetForm();
|
||||
}
|
||||
}, [isEdit]);
|
||||
const { data: revisions = [] } = useQueryRevisions(qid);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.edit-answer-wrap {
|
||||
.content-wrap {
|
||||
.question-content-wrap {
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
|
|
|
@ -62,6 +62,13 @@ const Ask = () => {
|
|||
...formData,
|
||||
answer: { ...formData.answer, value },
|
||||
});
|
||||
const handleSummaryChange = (evt) => {
|
||||
const v = evt.currentTarget.value;
|
||||
setFormData({
|
||||
...formData,
|
||||
description: { ...formData.description, value: v },
|
||||
});
|
||||
};
|
||||
|
||||
const checkValidated = (): boolean => {
|
||||
let bol = true;
|
||||
|
@ -100,6 +107,7 @@ const Ask = () => {
|
|||
html: editorRef.current.getHtml(),
|
||||
question_id: qid,
|
||||
id: aid,
|
||||
edit_summary: formData.description.value,
|
||||
};
|
||||
modifyAnswer(params).then(() => {
|
||||
navigate(`/questions/${qid}/${aid}`);
|
||||
|
@ -132,7 +140,7 @@ const Ask = () => {
|
|||
<h5 className="mb-3">{data?.question.title}</h5>
|
||||
</a>
|
||||
|
||||
<div className="content-wrap">
|
||||
<div className="question-content-wrap">
|
||||
<div
|
||||
ref={questionContentRef}
|
||||
className="content position-absolute top-0 w-100"
|
||||
|
@ -166,7 +174,7 @@ const Ask = () => {
|
|||
</Form.Select>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group controlId="answer" className="mt-4">
|
||||
<Form.Group controlId="answer" className="mt-3">
|
||||
<Form.Label>{t('form.fields.answer.label')}</Form.Label>
|
||||
<Editor
|
||||
value={formData.answer.value}
|
||||
|
@ -198,6 +206,7 @@ const Ask = () => {
|
|||
<Form.Label>{t('form.fields.edit_summary.label')}</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
onChange={handleSummaryChange}
|
||||
defaultValue={formData.description.value}
|
||||
isInvalid={formData.description.isInvalid}
|
||||
placeholder={t('form.fields.edit_summary.placeholder')}
|
||||
|
|
|
@ -3,8 +3,14 @@ import { Container, Row, Col, Alert, Stack, Button } from 'react-bootstrap';
|
|||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { BaseUserCard, FormatTime, Empty, DiffContent } from '@/components';
|
||||
import { useReviewList, revisionAudit } from '@/services';
|
||||
import {
|
||||
BaseUserCard,
|
||||
FormatTime,
|
||||
Empty,
|
||||
DiffContent,
|
||||
PageTitle,
|
||||
} from '@/components';
|
||||
import { getReviewList, revisionAudit } from '@/services';
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
import type * as Type from '@/common/interface';
|
||||
|
||||
|
@ -13,24 +19,46 @@ const Index: FC = () => {
|
|||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [noTasks, setNoTasks] = useState(false);
|
||||
const [page, setPage] = useState(1);
|
||||
const { data: reviewResp, mutate: mutateList } = useReviewList(page);
|
||||
const [reviewResp, setReviewResp] = useState<Type.ReviewResp>();
|
||||
const ro = reviewResp?.list[0];
|
||||
const { info, type, unreviewed_info } = ro || {
|
||||
info: null,
|
||||
type: '',
|
||||
unreviewed_info: null,
|
||||
};
|
||||
const reviewInfo = unreviewed_info?.content;
|
||||
const mutateNextPage = () => {
|
||||
const count = reviewResp?.count;
|
||||
if (count && page < count) {
|
||||
setPage(page + 1);
|
||||
} else {
|
||||
const resolveNextOne = (resp, pageNumber) => {
|
||||
const { count, list = [] } = resp;
|
||||
// auto rollback
|
||||
if (!list.length && count && page !== 1) {
|
||||
pageNumber = 1;
|
||||
setPage(pageNumber);
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
queryNextOne(pageNumber);
|
||||
return;
|
||||
}
|
||||
if (pageNumber !== page) {
|
||||
setPage(pageNumber);
|
||||
}
|
||||
setReviewResp(resp);
|
||||
if (!list.length) {
|
||||
setNoTasks(true);
|
||||
}
|
||||
setTimeout(() => {
|
||||
window.scrollTo({ top: 0 });
|
||||
}, 150);
|
||||
};
|
||||
const queryNextOne = (pageNumber) => {
|
||||
getReviewList(pageNumber)
|
||||
.then((resp) => {
|
||||
resolveNextOne(resp, pageNumber);
|
||||
})
|
||||
.catch((ex) => {
|
||||
console.log('ex: ', ex);
|
||||
});
|
||||
};
|
||||
const reviewInfo = unreviewed_info?.content;
|
||||
const handlingSkip = () => {
|
||||
mutateNextPage();
|
||||
queryNextOne(page + 1);
|
||||
};
|
||||
const handlingApprove = () => {
|
||||
if (!unreviewed_info) {
|
||||
|
@ -39,7 +67,7 @@ const Index: FC = () => {
|
|||
setIsLoading(true);
|
||||
revisionAudit(unreviewed_info.id, 'approve')
|
||||
.then(() => {
|
||||
mutateList();
|
||||
queryNextOne(page);
|
||||
})
|
||||
.catch((ex) => {
|
||||
console.log('ex: ', ex);
|
||||
|
@ -55,7 +83,7 @@ const Index: FC = () => {
|
|||
setIsLoading(true);
|
||||
revisionAudit(unreviewed_info.id, 'reject')
|
||||
.then(() => {
|
||||
mutateList();
|
||||
queryNextOne(page);
|
||||
})
|
||||
.catch((ex) => {
|
||||
console.log('ex: ', ex);
|
||||
|
@ -93,16 +121,11 @@ const Index: FC = () => {
|
|||
editSummary ||= t('edit_tag');
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!reviewResp) {
|
||||
return;
|
||||
}
|
||||
window.scrollTo({ top: 0 });
|
||||
if (!reviewResp.list || !reviewResp.list.length) {
|
||||
setNoTasks(true);
|
||||
}
|
||||
}, [reviewResp]);
|
||||
queryNextOne(page);
|
||||
}, []);
|
||||
return (
|
||||
<Container className="pt-2 mt-4 mb-5">
|
||||
<PageTitle title={t('review')} />
|
||||
<Row>
|
||||
<Col lg={{ span: 7, offset: 1 }}>
|
||||
<h3 className="mb-4">{t('review')}</h3>
|
||||
|
|
|
@ -73,7 +73,9 @@ const Index: FC = () => {
|
|||
<Row className="py-3 justify-content-center">
|
||||
<Col xxl={10}>
|
||||
<h5 className="mb-4">
|
||||
{t('title')}{' '}
|
||||
{timelineData?.object_info.object_type === 'tag'
|
||||
? t('tag_title')
|
||||
: t('title')}{' '}
|
||||
<Link to={linkUrl}>{timelineData?.object_info?.title}</Link>
|
||||
</h5>
|
||||
{timelineData?.object_info.object_type !== 'tag' && (
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import useSWR from 'swr';
|
||||
|
||||
import request from '@/utils/request';
|
||||
import * as Type from '@/common/interface';
|
||||
|
||||
|
@ -16,16 +14,7 @@ export const revisionAudit = (id: string, operation: 'approve' | 'reject') => {
|
|||
});
|
||||
};
|
||||
|
||||
export const useReviewList = (page: number) => {
|
||||
export const getReviewList = (page: number) => {
|
||||
const apiUrl = `/answer/api/v1/revisions/unreviewed?page=${page}`;
|
||||
const { data, error, mutate } = useSWR<Type.ReviewResp, Error>(
|
||||
apiUrl,
|
||||
request.instance.get,
|
||||
);
|
||||
return {
|
||||
data,
|
||||
isLoading: !data && !error,
|
||||
error,
|
||||
mutate,
|
||||
};
|
||||
return request.get<Type.ReviewResp>(apiUrl);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue