mirror of https://gitee.com/answerdev/answer.git
Merge branch 'dev' into fix/search
This commit is contained in:
commit
9444b71bd9
|
@ -39,7 +39,21 @@ builds:
|
|||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- arm64
|
||||
- arm64
|
||||
- id: build-arm7
|
||||
main: ./cmd/answer/.
|
||||
binary: answer
|
||||
ldflags: -s -w -X main.Version={{.Version}} -X main.Revision={{.ShortCommit}} -X main.Time={{.Date}} -X main.BuildUser=goreleaser
|
||||
env:
|
||||
- CC=arm-linux-gnueabihf-gcc
|
||||
- CXX=arm-linux-gnueabihf-g++
|
||||
- AR=arm-linux-gnueabihf-ar
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- arm
|
||||
goarm:
|
||||
- 7
|
||||
- id: build-darwin-arm64
|
||||
main: ./cmd/answer/.
|
||||
binary: answer
|
||||
|
|
2
Makefile
2
Makefile
|
@ -1,6 +1,6 @@
|
|||
.PHONY: build clean ui
|
||||
|
||||
VERSION=1.0.0
|
||||
VERSION=1.0.1
|
||||
BIN=answer
|
||||
DIR_SRC=./cmd/answer
|
||||
DOCKER_CMD=docker
|
||||
|
|
|
@ -96,7 +96,7 @@ To run answer, use:
|
|||
fmt.Println("read config failed: ", err.Error())
|
||||
return
|
||||
}
|
||||
if err = migrations.Migrate(c.Data.Database); err != nil {
|
||||
if err = migrations.Migrate(c.Data.Database, c.Data.Cache); err != nil {
|
||||
fmt.Println("migrate failed: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/answerdev/answer/internal/base/translator"
|
||||
"github.com/answerdev/answer/internal/controller"
|
||||
"github.com/answerdev/answer/internal/controller/template_render"
|
||||
"github.com/answerdev/answer/internal/controller_backyard"
|
||||
"github.com/answerdev/answer/internal/controller_admin"
|
||||
"github.com/answerdev/answer/internal/repo"
|
||||
"github.com/answerdev/answer/internal/router"
|
||||
"github.com/answerdev/answer/internal/service"
|
||||
|
@ -38,7 +38,7 @@ func initApplication(
|
|||
server.ProviderSetServer,
|
||||
router.ProviderSetRouter,
|
||||
controller.ProviderSetController,
|
||||
controller_backyard.ProviderSetController,
|
||||
controller_admin.ProviderSetController,
|
||||
templaterender.ProviderSetTemplateRenderController,
|
||||
service.ProviderSetService,
|
||||
cron.ProviderSetService,
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/answerdev/answer/internal/base/translator"
|
||||
"github.com/answerdev/answer/internal/controller"
|
||||
"github.com/answerdev/answer/internal/controller/template_render"
|
||||
"github.com/answerdev/answer/internal/controller_backyard"
|
||||
"github.com/answerdev/answer/internal/controller_admin"
|
||||
"github.com/answerdev/answer/internal/repo/activity"
|
||||
"github.com/answerdev/answer/internal/repo/activity_common"
|
||||
"github.com/answerdev/answer/internal/repo/answer"
|
||||
|
@ -61,8 +61,8 @@ import (
|
|||
rank2 "github.com/answerdev/answer/internal/service/rank"
|
||||
reason2 "github.com/answerdev/answer/internal/service/reason"
|
||||
report2 "github.com/answerdev/answer/internal/service/report"
|
||||
"github.com/answerdev/answer/internal/service/report_backyard"
|
||||
"github.com/answerdev/answer/internal/service/report_handle_backyard"
|
||||
"github.com/answerdev/answer/internal/service/report_admin"
|
||||
"github.com/answerdev/answer/internal/service/report_handle_admin"
|
||||
"github.com/answerdev/answer/internal/service/revision_common"
|
||||
role2 "github.com/answerdev/answer/internal/service/role"
|
||||
"github.com/answerdev/answer/internal/service/search_parser"
|
||||
|
@ -72,7 +72,7 @@ import (
|
|||
tag2 "github.com/answerdev/answer/internal/service/tag"
|
||||
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_admin"
|
||||
"github.com/answerdev/answer/internal/service/user_common"
|
||||
"github.com/segmentfault/pacman"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
|
@ -177,18 +177,18 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
|||
revisionController := controller.NewRevisionController(serviceRevisionService, rankService)
|
||||
rankController := controller.NewRankController(rankService)
|
||||
commonRepo := common.NewCommonRepo(dataData, uniqueIDRepo)
|
||||
reportHandle := report_handle_backyard.NewReportHandle(questionCommon, commentRepo, configRepo)
|
||||
reportBackyardService := report_backyard.NewReportBackyardService(reportRepo, userCommon, commonRepo, answerRepo, questionRepo, commentCommonRepo, reportHandle, configRepo)
|
||||
controller_backyardReportController := controller_backyard.NewReportController(reportBackyardService)
|
||||
userBackyardRepo := user.NewUserBackyardRepo(dataData, authRepo)
|
||||
userBackyardService := user_backyard.NewUserBackyardService(userBackyardRepo, userRoleRelService, authService, userCommon)
|
||||
userBackyardController := controller_backyard.NewUserBackyardController(userBackyardService)
|
||||
reportHandle := report_handle_admin.NewReportHandle(questionCommon, commentRepo, configRepo)
|
||||
reportAdminService := report_admin.NewReportAdminService(reportRepo, userCommon, commonRepo, answerRepo, questionRepo, commentCommonRepo, reportHandle, configRepo)
|
||||
controller_adminReportController := controller_admin.NewReportController(reportAdminService)
|
||||
userAdminRepo := user.NewUserAdminRepo(dataData, authRepo)
|
||||
userAdminService := user_admin.NewUserAdminService(userAdminRepo, userRoleRelService, authService, userCommon)
|
||||
userAdminController := controller_admin.NewUserAdminController(userAdminService)
|
||||
reasonRepo := reason.NewReasonRepo(configRepo)
|
||||
reasonService := reason2.NewReasonService(reasonRepo)
|
||||
reasonController := controller.NewReasonController(reasonService)
|
||||
themeController := controller_backyard.NewThemeController()
|
||||
themeController := controller_admin.NewThemeController()
|
||||
siteInfoService := siteinfo.NewSiteInfoService(siteInfoRepo, siteInfoCommonService, emailService, tagCommonService)
|
||||
siteInfoController := controller_backyard.NewSiteInfoController(siteInfoService)
|
||||
siteInfoController := controller_admin.NewSiteInfoController(siteInfoService)
|
||||
siteinfoController := controller.NewSiteinfoController(siteInfoCommonService)
|
||||
notificationRepo := notification.NewNotificationRepo(dataData)
|
||||
notificationCommon := notificationcommon.NewNotificationCommon(dataData, notificationRepo, userCommon, activityRepo, followRepo, objService)
|
||||
|
@ -201,8 +201,8 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
|||
commentCommonService := comment_common.NewCommentCommonService(commentCommonRepo)
|
||||
activityService := activity2.NewActivityService(activityActivityRepo, userCommon, activityCommon, tagCommonService, objService, commentCommonService, revisionService, metaService)
|
||||
activityController := controller.NewActivityController(activityCommon, activityService)
|
||||
roleController := controller_backyard.NewRoleController(roleService)
|
||||
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, activityController, roleController)
|
||||
roleController := controller_admin.NewRoleController(roleService)
|
||||
answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, controller_adminReportController, userAdminController, reasonController, themeController, siteInfoController, siteinfoController, notificationController, dashboardController, uploadController, activityController, roleController)
|
||||
swaggerRouter := router.NewSwaggerRouter(swaggerConf)
|
||||
uiRouter := router.NewUIRouter(siteinfoController, siteInfoCommonService)
|
||||
authUserMiddleware := middleware.NewAuthUserMiddleware(authService, siteInfoCommonService)
|
||||
|
|
44
docs/docs.go
44
docs/docs.go
|
@ -33,7 +33,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "CmsSearchList",
|
||||
"summary": "AdminSearchAnswerList",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
|
@ -184,7 +184,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "CmsSearchList",
|
||||
"summary": "AdminSearchList",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
|
@ -1671,7 +1671,7 @@ const docTemplate = `{
|
|||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Adopted",
|
||||
"description": "Accepted",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
|
@ -1681,15 +1681,15 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"api-answer"
|
||||
],
|
||||
"summary": "Adopted",
|
||||
"summary": "Accepted",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "AnswerAdoptedReq",
|
||||
"description": "AnswerAcceptedReq",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.AnswerAdoptedReq"
|
||||
"$ref": "#/definitions/schema.AnswerAcceptedReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -5490,6 +5490,17 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"schema.AnswerAcceptedReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"answer_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"question_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.AnswerAddReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -5507,17 +5518,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"schema.AnswerAdoptedReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"answer_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"question_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.AnswerUpdateReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -7011,7 +7011,6 @@ const docTemplate = `{
|
|||
"type": "object",
|
||||
"required": [
|
||||
"language",
|
||||
"theme",
|
||||
"time_zone"
|
||||
],
|
||||
"properties": {
|
||||
|
@ -7019,10 +7018,6 @@ const docTemplate = `{
|
|||
"type": "string",
|
||||
"maxLength": 128
|
||||
},
|
||||
"theme": {
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
},
|
||||
"time_zone": {
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
|
@ -7033,7 +7028,6 @@ const docTemplate = `{
|
|||
"type": "object",
|
||||
"required": [
|
||||
"language",
|
||||
"theme",
|
||||
"time_zone"
|
||||
],
|
||||
"properties": {
|
||||
|
@ -7041,10 +7035,6 @@ const docTemplate = `{
|
|||
"type": "string",
|
||||
"maxLength": 128
|
||||
},
|
||||
"theme": {
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
},
|
||||
"time_zone": {
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "CmsSearchList",
|
||||
"summary": "AdminSearchAnswerList",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
|
@ -172,7 +172,7 @@
|
|||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "CmsSearchList",
|
||||
"summary": "AdminSearchList",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
|
@ -1659,7 +1659,7 @@
|
|||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Adopted",
|
||||
"description": "Accepted",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
|
@ -1669,15 +1669,15 @@
|
|||
"tags": [
|
||||
"api-answer"
|
||||
],
|
||||
"summary": "Adopted",
|
||||
"summary": "Accepted",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "AnswerAdoptedReq",
|
||||
"description": "AnswerAcceptedReq",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.AnswerAdoptedReq"
|
||||
"$ref": "#/definitions/schema.AnswerAcceptedReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -5478,6 +5478,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"schema.AnswerAcceptedReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"answer_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"question_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.AnswerAddReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -5495,17 +5506,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"schema.AnswerAdoptedReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"answer_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"question_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.AnswerUpdateReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -6999,7 +6999,6 @@
|
|||
"type": "object",
|
||||
"required": [
|
||||
"language",
|
||||
"theme",
|
||||
"time_zone"
|
||||
],
|
||||
"properties": {
|
||||
|
@ -7007,10 +7006,6 @@
|
|||
"type": "string",
|
||||
"maxLength": 128
|
||||
},
|
||||
"theme": {
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
},
|
||||
"time_zone": {
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
|
@ -7021,7 +7016,6 @@
|
|||
"type": "object",
|
||||
"required": [
|
||||
"language",
|
||||
"theme",
|
||||
"time_zone"
|
||||
],
|
||||
"properties": {
|
||||
|
@ -7029,10 +7023,6 @@
|
|||
"type": "string",
|
||||
"maxLength": 128
|
||||
},
|
||||
"theme": {
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
},
|
||||
"time_zone": {
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
|
|
|
@ -207,6 +207,13 @@ definitions:
|
|||
status:
|
||||
type: string
|
||||
type: object
|
||||
schema.AnswerAcceptedReq:
|
||||
properties:
|
||||
answer_id:
|
||||
type: string
|
||||
question_id:
|
||||
type: string
|
||||
type: object
|
||||
schema.AnswerAddReq:
|
||||
properties:
|
||||
content:
|
||||
|
@ -219,13 +226,6 @@ definitions:
|
|||
description: question_id
|
||||
type: string
|
||||
type: object
|
||||
schema.AnswerAdoptedReq:
|
||||
properties:
|
||||
answer_id:
|
||||
type: string
|
||||
question_id:
|
||||
type: string
|
||||
type: object
|
||||
schema.AnswerUpdateReq:
|
||||
properties:
|
||||
content:
|
||||
|
@ -1302,15 +1302,11 @@ definitions:
|
|||
language:
|
||||
maxLength: 128
|
||||
type: string
|
||||
theme:
|
||||
maxLength: 128
|
||||
type: string
|
||||
time_zone:
|
||||
maxLength: 128
|
||||
type: string
|
||||
required:
|
||||
- language
|
||||
- theme
|
||||
- time_zone
|
||||
type: object
|
||||
schema.SiteInterfaceResp:
|
||||
|
@ -1318,15 +1314,11 @@ definitions:
|
|||
language:
|
||||
maxLength: 128
|
||||
type: string
|
||||
theme:
|
||||
maxLength: 128
|
||||
type: string
|
||||
time_zone:
|
||||
maxLength: 128
|
||||
type: string
|
||||
required:
|
||||
- language
|
||||
- theme
|
||||
- time_zone
|
||||
type: object
|
||||
schema.SiteLegalReq:
|
||||
|
@ -1932,7 +1924,7 @@ paths:
|
|||
$ref: '#/definitions/handler.RespBody'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: CmsSearchList
|
||||
summary: AdminSearchAnswerList
|
||||
tags:
|
||||
- admin
|
||||
/answer/admin/api/answer/status:
|
||||
|
@ -2024,7 +2016,7 @@ paths:
|
|||
$ref: '#/definitions/handler.RespBody'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: CmsSearchList
|
||||
summary: AdminSearchList
|
||||
tags:
|
||||
- admin
|
||||
/answer/admin/api/question/status:
|
||||
|
@ -2889,14 +2881,14 @@ paths:
|
|||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Adopted
|
||||
description: Accepted
|
||||
parameters:
|
||||
- description: AnswerAdoptedReq
|
||||
- description: AnswerAcceptedReq
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/schema.AnswerAdoptedReq'
|
||||
$ref: '#/definitions/schema.AnswerAcceptedReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
|
@ -2906,7 +2898,7 @@ paths:
|
|||
type: string
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Adopted
|
||||
summary: Accepted
|
||||
tags:
|
||||
- api-answer
|
||||
/answer/api/v1/answer/info:
|
||||
|
|
7
go.mod
7
go.mod
|
@ -27,10 +27,10 @@ require (
|
|||
github.com/mojocn/base64Captcha v1.3.5
|
||||
github.com/ory/dockertest/v3 v3.9.1
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/segmentfault/pacman v1.0.1
|
||||
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20221018072427-a15dd1434e05
|
||||
github.com/segmentfault/pacman v1.0.2
|
||||
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20221219081300-f734f4a16aa0
|
||||
github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20221018072427-a15dd1434e05
|
||||
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20221207032920-3662d1e32068
|
||||
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20221219081300-f734f4a16aa0
|
||||
github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20221018072427-a15dd1434e05
|
||||
github.com/segmentfault/pacman/contrib/server/http v0.0.0-20221018072427-a15dd1434e05
|
||||
github.com/spf13/cobra v1.6.1
|
||||
|
@ -38,6 +38,7 @@ require (
|
|||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a
|
||||
github.com/swaggo/gin-swagger v1.5.3
|
||||
github.com/swaggo/swag v1.8.7
|
||||
github.com/yuin/goldmark v1.4.13
|
||||
golang.org/x/crypto v0.1.0
|
||||
golang.org/x/net v0.1.0
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
|
|
13
go.sum
13
go.sum
|
@ -599,14 +599,14 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0
|
|||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
||||
github.com/segmentfault/pacman v1.0.1 h1:GFdvPtNxvVVjnDM4ty02D/+4unHwG9PmjcOZSc2wRXE=
|
||||
github.com/segmentfault/pacman v1.0.1/go.mod h1:5lNp5REd8QMThmBUvR3Fi9Y3AsOB4GRq7soCB4QLqOs=
|
||||
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20221018072427-a15dd1434e05 h1:rXsXgC/HR7m4V425l9pDBW/qxJv6zCh6pEvvO1ZCNsI=
|
||||
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20221018072427-a15dd1434e05/go.mod h1:rmf1TCwz67dyM+AmTwSd1BxTo2AOYHj262lP93bOZbs=
|
||||
github.com/segmentfault/pacman v1.0.2 h1:tXWkEzePiSVQXYwFH3tOuxC1/DJ5ISi35F93lKNGs3o=
|
||||
github.com/segmentfault/pacman v1.0.2/go.mod h1:5lNp5REd8QMThmBUvR3Fi9Y3AsOB4GRq7soCB4QLqOs=
|
||||
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20221219081300-f734f4a16aa0 h1:4x0qG7H2M3qH7Yo2BhGrVlji1iTmRAWgINY/JyENeHs=
|
||||
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20221219081300-f734f4a16aa0/go.mod h1:rmf1TCwz67dyM+AmTwSd1BxTo2AOYHj262lP93bOZbs=
|
||||
github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20221018072427-a15dd1434e05 h1:BlqTgc3/MYKG6vMI2MI+6o+7P4Gy5PXlawu185wPXAk=
|
||||
github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20221018072427-a15dd1434e05/go.mod h1:prPjFam7MyZ5b3S9dcDOt2tMPz6kf7C9c243s9zSwPY=
|
||||
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20221207032920-3662d1e32068 h1:ln/qgrC62e7/XHGPiikWFV4dyYgCaWeZYkmSGqrHZp4=
|
||||
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20221207032920-3662d1e32068/go.mod h1:7QcRmnV7OYq4hNOOCWXT5HXnN/u756JUsqIW0Bw8n9E=
|
||||
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20221219081300-f734f4a16aa0 h1:zaAwBSpwUVrV2BBs1f1hfkv0rY/KdZLyKK8U9NKiurI=
|
||||
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20221219081300-f734f4a16aa0/go.mod h1:7QcRmnV7OYq4hNOOCWXT5HXnN/u756JUsqIW0Bw8n9E=
|
||||
github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20221018072427-a15dd1434e05 h1:jcGZU2juv0L3eFEkuZYV14ESLUlWfGMWnP0mjOfrSZc=
|
||||
github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20221018072427-a15dd1434e05/go.mod h1:L4GqtXLoR73obTYqUQIzfkm8NG8pvZafxFb6KZFSSHk=
|
||||
github.com/segmentfault/pacman/contrib/server/http v0.0.0-20221018072427-a15dd1434e05 h1:91is1nKNbfTOl8CvMYiFgg4c5Vmol+5mVmMV/jDXD+A=
|
||||
|
@ -693,6 +693,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -210,7 +210,7 @@ backend:
|
|||
other: "answered question"
|
||||
update_answer:
|
||||
other: "updated answer"
|
||||
adopt_answer:
|
||||
accept_answer:
|
||||
other: "accepted answer"
|
||||
comment_question:
|
||||
other: "commented question"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -2,5 +2,25 @@
|
|||
language_options:
|
||||
- label: "简体中文(CN)"
|
||||
value: "zh_CN"
|
||||
- label: "繁體中文(CN)"
|
||||
value: "zh_TW"
|
||||
- label: "English(US)"
|
||||
value: "en_US"
|
||||
- label: "Deutsch(DE)"
|
||||
value: "de_DE"
|
||||
- label: "Español(ES)"
|
||||
value: "es_ES"
|
||||
- label: "Français(FR)"
|
||||
value: "fr_FR"
|
||||
- label: "Italiano(IT)"
|
||||
value: "it_IT"
|
||||
- label: "日本語(JA)"
|
||||
value: "ja_JP"
|
||||
- label: "한국어(KO)"
|
||||
value: "ko_KR"
|
||||
- label: "Português(PT)"
|
||||
value: "pt_PT"
|
||||
- label: "Русский(RU)"
|
||||
value: "ru_RU"
|
||||
- label: "Tiếng Việt(VI)"
|
||||
value: "vi_VN"
|
||||
|
|
1228
i18n/it_IT.yaml
1228
i18n/it_IT.yaml
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
544
i18n/zh_CN.yaml
544
i18n/zh_CN.yaml
|
@ -1,3 +1,4 @@
|
|||
#The following fields are used for back-end
|
||||
backend:
|
||||
base:
|
||||
success:
|
||||
|
@ -10,7 +11,6 @@ backend:
|
|||
other: "未登录"
|
||||
database_error:
|
||||
other: "数据服务异常"
|
||||
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
|
@ -26,21 +26,23 @@ backend:
|
|||
other: "拥有进入网站的全部权限。"
|
||||
moderator:
|
||||
other: "有权访问所有的帖子,无法进入管理员设置页面。"
|
||||
|
||||
email:
|
||||
other: "邮箱"
|
||||
password:
|
||||
other: "密码"
|
||||
|
||||
email_or_password_wrong_error: &email_or_password_wrong
|
||||
email_or_password_wrong_error:
|
||||
other: "邮箱或密码错误"
|
||||
|
||||
error:
|
||||
admin:
|
||||
email_or_password_wrong: *email_or_password_wrong
|
||||
email_or_password_wrong:
|
||||
other: 邮箱或密码错误
|
||||
answer:
|
||||
not_found:
|
||||
other: "答案未找到"
|
||||
cannot_deleted:
|
||||
other: "无删除权限"
|
||||
cannot_update:
|
||||
other: "无修改权限"
|
||||
comment:
|
||||
edit_without_permission:
|
||||
other: "不允许编辑评论"
|
||||
|
@ -78,6 +80,12 @@ backend:
|
|||
question:
|
||||
not_found:
|
||||
other: "问题未找到"
|
||||
cannot_deleted:
|
||||
other: "无删除权限"
|
||||
cannot_close:
|
||||
other: "无关闭权限"
|
||||
cannot_update:
|
||||
other: "无更新权限"
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
other: "级别不符合条件"
|
||||
|
@ -102,9 +110,15 @@ backend:
|
|||
theme:
|
||||
not_found:
|
||||
other: "主题未找到"
|
||||
revision:
|
||||
review_underway:
|
||||
other: "目前无法编辑,有一个版本在审阅队列中。"
|
||||
no_permission:
|
||||
other: "无权限修改"
|
||||
user:
|
||||
email_or_password_wrong:
|
||||
other: *email_or_password_wrong
|
||||
other:
|
||||
other: 邮箱或密码错误
|
||||
not_found:
|
||||
other: "用户未找到"
|
||||
suspended:
|
||||
|
@ -115,71 +129,75 @@ backend:
|
|||
other: "用户名已被使用"
|
||||
set_avatar:
|
||||
other: "头像设置错误"
|
||||
config:
|
||||
read_config_failed:
|
||||
other: "读取配置失败"
|
||||
database:
|
||||
connection_failed:
|
||||
other: "数据连接异常!"
|
||||
create_table_failed:
|
||||
other: "创建表失败"
|
||||
install:
|
||||
create_config_failed:
|
||||
other: "无法创建配置文件"
|
||||
cannot_update_your_role:
|
||||
other: "你无法修改自己的角色"
|
||||
not_allowed_registration:
|
||||
other: "目前该网站尚未开放注册"
|
||||
revision:
|
||||
review_underway:
|
||||
other: "目前无法编辑,有一个版本在审阅队列中。"
|
||||
no_permission:
|
||||
other: "无权限修改"
|
||||
|
||||
report:
|
||||
spam:
|
||||
name:
|
||||
other: "垃圾信息"
|
||||
description:
|
||||
other: "此帖子是一个广告贴,或是破坏性行为。它对当前的主题无用,也不相关。"
|
||||
desc:
|
||||
other: "This post is an advertisement, or vandalism. It is not useful or relevant to the current topic."
|
||||
rude:
|
||||
name:
|
||||
other: "粗鲁或辱骂的"
|
||||
description:
|
||||
other: "有理智的人都会发现此内容不适合进行尊重的讨论。"
|
||||
desc:
|
||||
other: "A reasonable person would find this content inappropriate for respectful discourse."
|
||||
duplicate:
|
||||
name:
|
||||
other: "重复信息"
|
||||
description:
|
||||
other: "此问题以前就有人问过,而且已经有了答案。"
|
||||
desc:
|
||||
other: "This question has been asked before and already has an answer."
|
||||
not_answer:
|
||||
name:
|
||||
other: "不是答案"
|
||||
description:
|
||||
other: "此帖子是作为一个答案发布的,但它并没有试图回答这个问题。总之,它可能应该是个编辑,评论,另一个问题或者被删除。"
|
||||
desc:
|
||||
other: "This was posted as an answer, but it does not attempt to answer the question. It should possibly be an edit, a comment, another question, or deleted altogether."
|
||||
not_need:
|
||||
name:
|
||||
other: "不再需要"
|
||||
description:
|
||||
other: "此条评论是过时的,对话性的或与本帖无关。"
|
||||
desc:
|
||||
other: "This comment is outdated, conversational or not relevant to this post."
|
||||
other:
|
||||
name:
|
||||
other: "其他原因"
|
||||
description:
|
||||
other: "此帖子需要工作人员关注,因为是上述所列以外的其他理由。"
|
||||
|
||||
desc:
|
||||
other: "This post requires staff attention for another reason not listed above."
|
||||
question:
|
||||
close:
|
||||
duplicate:
|
||||
name:
|
||||
other: "垃圾信息"
|
||||
description:
|
||||
other: "此问题以前就有人问过,而且已经有了答案。"
|
||||
desc:
|
||||
other: "This question has been asked before and already has an answer."
|
||||
guideline:
|
||||
name:
|
||||
other: "社区特定原因"
|
||||
description:
|
||||
other: "此问题不符合社区准则。"
|
||||
desc:
|
||||
other: "This question doesn't meet a community guideline."
|
||||
multiple:
|
||||
name:
|
||||
other: "需要细节或澄清"
|
||||
description:
|
||||
other: "此问题目前涵盖多个问题。它应该只集中在一个问题上。"
|
||||
desc:
|
||||
other: "This question currently includes multiple questions in one. It should focus on one problem only."
|
||||
other:
|
||||
name:
|
||||
other: "其他原因"
|
||||
description:
|
||||
other: "此帖子需要上述所列以外的其他理由。"
|
||||
|
||||
desc:
|
||||
other: "This post requires another reason not listed above."
|
||||
notification:
|
||||
action:
|
||||
update_question:
|
||||
|
@ -188,7 +206,7 @@ backend:
|
|||
other: "回答了问题"
|
||||
update_answer:
|
||||
other: "更新了答案"
|
||||
adopt_answer:
|
||||
accept_answer:
|
||||
other: "接受了答案"
|
||||
comment_question:
|
||||
other: "评论了问题"
|
||||
|
@ -206,7 +224,7 @@ backend:
|
|||
other: "你的答案已被删除"
|
||||
your_comment_was_deleted:
|
||||
other: "你的评论已被删除"
|
||||
# The following fields are used for interface presentation(Front-end)
|
||||
#The following fields are used for interface presentation(Front-end)
|
||||
ui:
|
||||
how_to_format:
|
||||
title: 如何设定文本格式
|
||||
|
@ -244,6 +262,11 @@ ui:
|
|||
confirm_email: 确认电子邮件
|
||||
account_suspended: 账号已封禁
|
||||
admin: 后台管理
|
||||
change_email: 修改邮箱
|
||||
install: Answer 安装
|
||||
upgrade: Answer 升级
|
||||
maintenance: 网站维护
|
||||
users: Users
|
||||
notifications:
|
||||
title: 通知
|
||||
inbox: 收件箱
|
||||
|
@ -252,7 +275,7 @@ ui:
|
|||
show_more: 显示更多
|
||||
suspended:
|
||||
title: 账号已封禁
|
||||
until_time: '你的账号被封禁至{{ time }}。'
|
||||
until_time: "你的账号被封禁至{{ time }}。"
|
||||
forever: 你的账号已被永久封禁。
|
||||
end: 违反了我们的社区准则。
|
||||
editor:
|
||||
|
@ -399,6 +422,7 @@ ui:
|
|||
tag_info:
|
||||
created_at: 创建于
|
||||
edited_at: 编辑于
|
||||
history: 历史
|
||||
synonyms:
|
||||
title: 同义词
|
||||
text: 以下标签等同于
|
||||
|
@ -409,7 +433,8 @@ ui:
|
|||
synonyms_text: 以下标签等同于
|
||||
delete:
|
||||
title: 删除标签
|
||||
content: <p>不允许删除有关联问题的标签。</p><p>请先从关联的问题中删除此标签的引用。</p>
|
||||
content: >-
|
||||
<p>不允许删除有关联问题的标签。</p><p>请先从关联的问题中删除此标签的引用。</p>
|
||||
content2: 确定要删除吗?
|
||||
close: 关闭
|
||||
edit_tag:
|
||||
|
@ -428,17 +453,20 @@ ui:
|
|||
label: 描述
|
||||
edit_summary:
|
||||
label: 编辑概要
|
||||
placeholder: 简单描述更改原因 (错别字、文字表达、格式等等)
|
||||
placeholder: >-
|
||||
简单描述更改原因 (错别字、文字表达、格式等等)
|
||||
btn_save_edits: 保存更改
|
||||
btn_cancel: 取消
|
||||
dates:
|
||||
long_date: MM月DD日
|
||||
long_date_with_year: YYYY年MM月DD日
|
||||
long_date_with_time: 'YYYY年MM月DD日 HH:mm'
|
||||
long_date_with_year: "YYYY年MM月DD日"
|
||||
long_date_with_time: "YYYY年MM月DD日 HH:mm"
|
||||
now: 刚刚
|
||||
x_seconds_ago: '{{count}} 秒前'
|
||||
x_minutes_ago: '{{count}} 分钟前'
|
||||
x_hours_ago: '{{count}} 小时前'
|
||||
x_seconds_ago: "{{count}} 秒前"
|
||||
x_minutes_ago: "{{count}} 分钟前"
|
||||
x_hours_ago: "{{count}} 小时前"
|
||||
hour: 小时
|
||||
day: 天
|
||||
comment:
|
||||
btn_add_comment: 添加评论
|
||||
reply_to: 回复
|
||||
|
@ -449,8 +477,10 @@ ui:
|
|||
btn_save_edits: 保存
|
||||
btn_cancel: 取消
|
||||
show_more: 显示更多评论
|
||||
tip_question: 使用评论提问更多信息或者提出改进意见。尽量避免使用评论功能回答问题。
|
||||
tip_answer: 使用评论对回答者进行回复,或者通知回答者你已更新了问题的内容。如果要补充或者完善问题的内容,请在原问题中更改。
|
||||
tip_question: >-
|
||||
使用评论提问更多信息或者提出改进意见。尽量避免使用评论功能回答问题。
|
||||
tip_answer: >-
|
||||
使用评论对回答者进行回复,或者通知回答者你已更新了问题的内容。如果要补充或者完善问题的内容,请在原问题中更改。
|
||||
edit_answer:
|
||||
title: 编辑回答
|
||||
default_reason: 编辑回答
|
||||
|
@ -462,7 +492,8 @@ ui:
|
|||
label: 回答内容
|
||||
edit_summary:
|
||||
label: 编辑概要
|
||||
placeholder: 简单描述更改原因 (错别字、文字表达、格式等等)
|
||||
placeholder: >-
|
||||
简单描述更改原因 (错别字、文字表达、格式等等)
|
||||
btn_save_edits: 保存更改
|
||||
btn_cancel: 取消
|
||||
tags:
|
||||
|
@ -504,6 +535,10 @@ ui:
|
|||
label: 回答内容
|
||||
msg:
|
||||
empty: 回答内容不能为空
|
||||
edit_summary:
|
||||
label: 编辑理由
|
||||
placeholder: >-
|
||||
简单描述更改原因 (错别字、文字表达、格式等等)
|
||||
btn_post_question: 提交问题
|
||||
btn_save_edits: 保存更改
|
||||
answer_question: 直接发表回答
|
||||
|
@ -512,8 +547,9 @@ ui:
|
|||
add_btn: 添加标签
|
||||
create_btn: 创建新标签
|
||||
search_tag: 搜索标签
|
||||
hint: 选择至少一个与问题相关的标签。
|
||||
hint: "选择至少一个与问题相关的标签。"
|
||||
no_result: 没有匹配的标签
|
||||
tag_required_text: 必填标签 (至少一个)
|
||||
header:
|
||||
nav:
|
||||
question: 问题
|
||||
|
@ -523,6 +559,7 @@ ui:
|
|||
setting: 账号设置
|
||||
logout: 退出登录
|
||||
admin: 后台管理
|
||||
review: 审查
|
||||
search:
|
||||
placeholder: 搜索
|
||||
footer:
|
||||
|
@ -538,16 +575,20 @@ ui:
|
|||
msg:
|
||||
empty: 不能为空
|
||||
inactive:
|
||||
first: '马上就好了!我们发送了一封激活邮件到 <bold>{{mail}}</bold>。请按照邮件中的说明激活您的帐户。'
|
||||
info: 如果没有收到,请检查您的垃圾邮件文件夹。
|
||||
another: '我们向您发送了另一封激活电子邮件,地址为 <bold>{{mail}}</bold>。它可能需要几分钟才能到达;请务必检查您的垃圾邮件文件夹。'
|
||||
first: >-
|
||||
就差一步!我们发送了一封激活邮件到 <bold>{{mail}}</bold>。请按照邮件中的说明激活您的账户。
|
||||
info: "如果没有收到,请检查您的垃圾邮件文件夹。"
|
||||
another: >-
|
||||
我们向您发送了另一封激活电子邮件,地址为 <bold>{{mail}}</bold>。它可能需要几分钟才能到达;请务必检查您的垃圾邮件文件夹。
|
||||
btn_name: 重新发送激活邮件
|
||||
change_btn_name: 更改邮箱
|
||||
msg:
|
||||
empty: 不能为空
|
||||
login:
|
||||
page_title: 欢迎来到 Answer
|
||||
info_sign: 没有帐户?<1>注册</1>
|
||||
info_login: 已经有一个帐户?<1>登录</1>
|
||||
info_sign: 没有账户?<1>注册</1>
|
||||
info_login: 已经有一个账户?<1>登录</1>
|
||||
agreements: 登录即表示您同意<1>隐私政策</1>和<3>服务条款</3>。
|
||||
forgot_pass: 忘记密码?
|
||||
name:
|
||||
label: 昵称
|
||||
|
@ -566,16 +607,29 @@ ui:
|
|||
account_forgot:
|
||||
page_title: 忘记密码
|
||||
btn_name: 发送恢复邮件
|
||||
send_success: '如无意外,你的邮箱 <strong>{{mail}}</strong> 将会收到一封重置密码的邮件,请根据指引重置你的密码。'
|
||||
send_success: >-
|
||||
如无意外,你的邮箱 <strong>{{mail}}</strong> 将会收到一封重置密码的邮件,请根据指引重置你的密码。
|
||||
email:
|
||||
label: 邮箱
|
||||
msg:
|
||||
empty: 邮箱不能为空
|
||||
change_email:
|
||||
page_title: 欢迎来到 Answer
|
||||
btn_cancel: 取消
|
||||
btn_update: 更新电子邮件地址
|
||||
send_success: >-
|
||||
If an account matches <strong>{{mail}}</strong>, you should receive an email with instructions on how to reset your password shortly.
|
||||
email:
|
||||
label: 新邮箱
|
||||
msg:
|
||||
empty: 邮箱不能为空
|
||||
password_reset:
|
||||
page_title: 密码重置
|
||||
btn_name: 重置我的密码
|
||||
reset_success: 你已经成功更改密码,将返回登录页面
|
||||
link_invalid: 抱歉,此密码重置链接已失效。也许是你已经重置过密码了?
|
||||
reset_success: >-
|
||||
你已经成功更改密码,将返回登录页面
|
||||
link_invalid: >-
|
||||
抱歉,此密码重置链接已失效。也许是你已经重置过密码了?
|
||||
to_login: 前往登录页面
|
||||
password:
|
||||
label: 密码
|
||||
|
@ -593,7 +647,8 @@ ui:
|
|||
account: 账号
|
||||
interface: 界面
|
||||
profile:
|
||||
btn_name: 保存更改
|
||||
heading: Profile
|
||||
btn_name: Save
|
||||
display_name:
|
||||
label: 昵称
|
||||
msg: 昵称不能为空
|
||||
|
@ -606,24 +661,33 @@ ui:
|
|||
character: '用户名只能由 "a-z", "0-9", " - . _" 组成'
|
||||
avatar:
|
||||
label: 头像
|
||||
text: 您可以上传图片作为头像。
|
||||
gravatar: Gravatar
|
||||
gravatar_text: 您可以在 <1>gravatar.com</1> 更改图像
|
||||
custom: 自定义
|
||||
btn_refresh: 刷新
|
||||
custom_text: 您可以上传您的图片。
|
||||
default: System
|
||||
msg: 请上传头像
|
||||
bio:
|
||||
label: 关于我 (可选)
|
||||
website:
|
||||
label: 网站 (可选)
|
||||
placeholder: 'https://example.com'
|
||||
placeholder: "https://example.com"
|
||||
msg: 格式不正确
|
||||
location:
|
||||
label: 位置 (可选)
|
||||
placeholder: '城市, 国家'
|
||||
placeholder: "城市, 国家"
|
||||
notification:
|
||||
heading: Notifications
|
||||
email:
|
||||
label: 邮件通知
|
||||
radio: 你的提问有新的回答,评论,和其他
|
||||
radio: "你的提问有新的回答,评论,和其他"
|
||||
account:
|
||||
heading: Account
|
||||
change_email_btn: 更改邮箱
|
||||
change_pass_btn: 更改密码
|
||||
change_email_info: 邮件已发送。请根据指引完成验证。
|
||||
change_email_info: >-
|
||||
邮件已发送。请根据指引完成验证。
|
||||
email:
|
||||
label: 邮箱
|
||||
msg: 邮箱不能为空
|
||||
|
@ -639,6 +703,7 @@ ui:
|
|||
pass_confirm:
|
||||
label: 确认新密码
|
||||
interface:
|
||||
heading: Interface
|
||||
lang:
|
||||
label: 界面语言
|
||||
text: 设置用户界面语言,在刷新页面后生效。
|
||||
|
@ -646,6 +711,8 @@ ui:
|
|||
update: 更新成功
|
||||
update_password: 更改密码成功。
|
||||
flag_success: 感谢您的标记,我们会尽快处理。
|
||||
forbidden_operate_self: Forbidden to operate on yourself
|
||||
review: 您的修订将在审核通过后显示。
|
||||
related_question:
|
||||
title: 相关问题
|
||||
btn: 我要提问
|
||||
|
@ -670,10 +737,16 @@ ui:
|
|||
write_answer:
|
||||
title: 你的回答
|
||||
btn_name: 提交你的回答
|
||||
add_another_answer: Add another answer
|
||||
confirm_title: 继续回答
|
||||
continue: 继续
|
||||
confirm_info: <p>您确定要提交一个新的回答吗?</p><p>您可以直接编辑和改善您之前的回答的。</p>
|
||||
confirm_info: >-
|
||||
<p>您确定要提交一个新的回答吗?</p><p>您可以直接编辑和改善您之前的回答的。</p>
|
||||
empty: 回答内容不能为空。
|
||||
reopen:
|
||||
title: Reopen this post
|
||||
content: Are you sure you want to reopen?
|
||||
success: This post has been reopened
|
||||
delete:
|
||||
title: 删除
|
||||
question: >-
|
||||
|
@ -693,27 +766,31 @@ ui:
|
|||
logout: 退出登录
|
||||
verify: 验证
|
||||
add_question: 我要提问
|
||||
approve: 批准
|
||||
reject: 拒绝
|
||||
skip: 略过
|
||||
search:
|
||||
title: 搜索结果
|
||||
keywords: 关键词
|
||||
options: 选项
|
||||
follow: 关注
|
||||
following: 已关注
|
||||
counts: '{{count}} 个结果'
|
||||
counts: "{{count}} 个结果"
|
||||
more: 更多
|
||||
sort_btns:
|
||||
relevance: 相关性
|
||||
newest: 最新的
|
||||
active: 活跃的
|
||||
score: 评分
|
||||
more: 更多
|
||||
tips:
|
||||
title: 高级搜索提示
|
||||
tag: '<1>[tag]</1> 在指定标签中搜索'
|
||||
user: '<1>user:username</1> 根据作者搜索'
|
||||
answer: '<1>answers:0</1> 搜索未回答的问题'
|
||||
score: '<1>score:3</1> 评分 3 分或以上'
|
||||
question: '<1>is:question</1> 只搜索问题'
|
||||
is_answer: '<1>is:answer</1> 只搜索回答'
|
||||
tag: "<1>[tag]</1> 在指定标签中搜索"
|
||||
user: "<1>user:username</1> 根据作者搜索"
|
||||
answer: "<1>answers:0</1> 搜索未回答的问题"
|
||||
score: "<1>score:3</1> 评分 3 分或以上"
|
||||
question: "<1>is:question</1> 只搜索问题"
|
||||
is_answer: "<1>is:answer</1> 只搜索回答"
|
||||
empty: 找不到任何相关的内容。<br /> 请尝试其他关键字,或者减少查找内容的长度。
|
||||
share:
|
||||
name: 分享
|
||||
|
@ -729,9 +806,11 @@ ui:
|
|||
page_title: 欢迎来到 Answer
|
||||
success: 你的账号已通过验证,即将返回首页。
|
||||
link: 返回首页
|
||||
invalid: 抱歉,此验证链接已失效。也许是你的账号已经通过验证了?
|
||||
invalid: >-
|
||||
抱歉,此验证链接已失效。也许是你的账号已经通过验证了?
|
||||
confirm_new_email: 你的电子邮箱已更新
|
||||
confirm_new_email_invalid: 抱歉,此验证链接已失效。也许是你的邮箱已经成功更改了?
|
||||
confirm_new_email_invalid: >-
|
||||
抱歉,此验证链接已失效。也许是你的邮箱已经成功更改了?
|
||||
question:
|
||||
following_tags: 已关注的标签
|
||||
edit: 编辑
|
||||
|
@ -739,8 +818,8 @@ ui:
|
|||
follow_tag_tip: 按照标签整理您的问题列表。
|
||||
hot_questions: 热点问题
|
||||
all_questions: 全部问题
|
||||
x_questions: '{{ count }} 个问题'
|
||||
x_answers: '{{ count }} 个回答'
|
||||
x_questions: "{{ count }} 个问题"
|
||||
x_answers: "{{ count }} 个回答"
|
||||
questions: 个问题
|
||||
answers: 回答
|
||||
newest: 最新
|
||||
|
@ -767,12 +846,12 @@ ui:
|
|||
newest: 最新
|
||||
score: 评分
|
||||
edit_profile: 编辑我的资料
|
||||
visited_x_days: 'Visited {{ count }} days'
|
||||
viewed: Viewed
|
||||
visited_x_days: "已访问 {{ count }} 天"
|
||||
viewed: 阅读次数
|
||||
joined: 加入于
|
||||
last_login: 上次登录
|
||||
about_me: 关于我
|
||||
about_me_empty: '// Hello, World !'
|
||||
about_me_empty: "Hello, World!"
|
||||
top_answers: 热门回答
|
||||
top_questions: 热门问题
|
||||
stats: 状态
|
||||
|
@ -788,12 +867,94 @@ ui:
|
|||
x_votes: 得票
|
||||
x_answers: 个回答
|
||||
x_questions: 个问题
|
||||
install:
|
||||
title: Answer
|
||||
next: 下一步
|
||||
done: 完成
|
||||
config_yaml_error: 无法创建配置文件
|
||||
lang:
|
||||
label: Please Choose a Language
|
||||
db_type:
|
||||
label: Database Engine
|
||||
db_username:
|
||||
label: 用户名
|
||||
placeholder: root
|
||||
msg: 用户名不能为空
|
||||
db_password:
|
||||
label: 密码
|
||||
placeholder: root
|
||||
msg: 密码不能为空
|
||||
db_host:
|
||||
label: 数据库服务器地址
|
||||
placeholder: "db: 3306"
|
||||
msg: 数据库地址不能为空
|
||||
db_name:
|
||||
label: 数据库名
|
||||
placeholder: answer
|
||||
msg: 数据库名称不能为空。
|
||||
db_file:
|
||||
label: Database File
|
||||
placeholder: /data/answer.db
|
||||
msg: 数据库文件不能为空。
|
||||
config_yaml:
|
||||
title: 创建 config.yaml
|
||||
label: 已创建 config.yaml 文件。
|
||||
desc: >-
|
||||
You can create the <1>config.yaml</1> file manually in the <1>/var/wwww/xxx/</1> directory and paste the following text into it.
|
||||
info: "完成后,点击“下一步”按钮。"
|
||||
site_information: 站点信息
|
||||
admin_account: 管理员账户
|
||||
site_name:
|
||||
label: 站点名称
|
||||
msg: 站点名称不能为空。
|
||||
site_url:
|
||||
label: 站点地址(URL)
|
||||
text: The address of your site.
|
||||
msg:
|
||||
empty: 站点URL不能为空。
|
||||
incorrect: 站点URL格式不正确。
|
||||
contact_email:
|
||||
label: 联系邮箱
|
||||
text: Email address of key contact responsible for this site.
|
||||
msg:
|
||||
empty: Contact Email cannot be empty.
|
||||
incorrect: Contact Email incorrect format.
|
||||
admin_name:
|
||||
label: Name
|
||||
msg: Name cannot be empty.
|
||||
admin_password:
|
||||
label: Password
|
||||
text: >-
|
||||
You will need this password to log in. Please store it in a secure location.
|
||||
msg: Password cannot be empty.
|
||||
admin_email:
|
||||
label: Email
|
||||
text: You will need this email to log in.
|
||||
msg:
|
||||
empty: Email cannot be empty.
|
||||
incorrect: Email incorrect format.
|
||||
ready_title: Your Answer is Ready!
|
||||
ready_desc: >-
|
||||
If you ever feel like changing more settings, visit <1>admin section</1>; find it in the site menu.
|
||||
good_luck: "Have fun, and good luck!"
|
||||
warn_title: Warning
|
||||
warn_desc: >-
|
||||
The file <1>config.yaml</1> already exists. If you need to reset any of the configuration items in this file, please delete it first.
|
||||
install_now: You may try <1>installing now</1>.
|
||||
installed: 已安裝
|
||||
installed_desc: >-
|
||||
You appear to have already installed. To reinstall please clear your old database tables first.
|
||||
db_failed: 数据连接异常!
|
||||
db_failed_desc: >-
|
||||
This either means that the database information in your <1>config.yaml</1> file is incorrect or that contact with the database server could not be established. This could mean your host’s database server is down.
|
||||
page_404:
|
||||
desc: 页面不存在
|
||||
back_home: 回到主页
|
||||
page_50X:
|
||||
desc: 服务器遇到了一个错误,无法完成你的请求。
|
||||
back_home: 回到主页
|
||||
page_maintenance:
|
||||
desc: "We are under maintenance, we’ll be back soon."
|
||||
nav_menus:
|
||||
dashboard: 后台管理
|
||||
contents: 内容管理
|
||||
|
@ -805,13 +966,52 @@ ui:
|
|||
general: 一般
|
||||
interface: 界面
|
||||
smtp: SMTP
|
||||
branding: 品牌
|
||||
legal: 法律条款
|
||||
write: 撰写
|
||||
tos: 服务条款
|
||||
privacy: 隐私政策
|
||||
seo: SEO
|
||||
customize: Customize
|
||||
themes: Themes
|
||||
css-html: CSS/HTML
|
||||
login: Login
|
||||
admin:
|
||||
admin_header:
|
||||
title: 后台管理
|
||||
dashboard:
|
||||
title: 后台管理
|
||||
welcome: 欢迎来到 Answer 后台管理!
|
||||
version: 版本
|
||||
site_statistics: 站点统计
|
||||
questions: "问题:"
|
||||
answers: "回答:"
|
||||
comments: "评论:"
|
||||
votes: "投票:"
|
||||
active_users: "活跃用户:"
|
||||
flags: "举报:"
|
||||
site_health_status: '健康状态:'
|
||||
version: "版本"
|
||||
https: "HTTPS:"
|
||||
uploading_files: "上传文件:"
|
||||
smtp: "SMTP:"
|
||||
timezone: "时区:"
|
||||
system_info: 系统信息
|
||||
storage_used: "已用存储空间:"
|
||||
uptime: "运行时间:"
|
||||
answer_links: 回答链接
|
||||
documents: 文档
|
||||
feedback: 用户反馈
|
||||
review: 审查
|
||||
config: 配置
|
||||
update_to: 更新到
|
||||
latest: 最新版本
|
||||
check_failed: 校验失败
|
||||
"yes": "是"
|
||||
"no": "否"
|
||||
not_allowed: 拒绝
|
||||
allowed: 允许
|
||||
enabled: 已启用
|
||||
disabled: 停用
|
||||
flags:
|
||||
title: 举报
|
||||
pending: 等待处理
|
||||
|
@ -838,7 +1038,7 @@ ui:
|
|||
msg:
|
||||
empty: 请选择一个原因
|
||||
status_modal:
|
||||
title: '更改 {{ type }} 状态为...'
|
||||
title: "更改 {{ type }} 状态为..."
|
||||
normal_name: 正常
|
||||
normal_desc: 所有用户都可以访问
|
||||
closed_name: 关闭
|
||||
|
@ -848,6 +1048,10 @@ ui:
|
|||
btn_cancel: 取消
|
||||
btn_submit: 提交
|
||||
btn_next: 下一步
|
||||
user_role_modal:
|
||||
title: 更改用户状态为...
|
||||
btn_cancel: 取消
|
||||
btn_submit: 提交
|
||||
users:
|
||||
title: 用户
|
||||
name: 名称
|
||||
|
@ -857,13 +1061,50 @@ ui:
|
|||
delete_at: 删除时间
|
||||
suspend_at: 封禁时间
|
||||
status: 状态
|
||||
role: 角色
|
||||
action: 操作
|
||||
change: 更改
|
||||
all: 全部
|
||||
staff: 工作人员
|
||||
inactive: 不活跃
|
||||
suspended: 已封禁
|
||||
deleted: 已删除
|
||||
normal: 正常
|
||||
Moderator: 版主
|
||||
Admin: 管理员
|
||||
User: 用户
|
||||
filter:
|
||||
placeholder: "按名称筛选,用户:id"
|
||||
set_new_password: 设置新密码
|
||||
change_status: 更改状态
|
||||
change_role: 更改角色
|
||||
show_logs: 显示日志
|
||||
add_user: Add user
|
||||
new_password_modal:
|
||||
title: Set new password
|
||||
form:
|
||||
fields:
|
||||
password:
|
||||
label: Password
|
||||
text: The user will be logged out and need to login again.
|
||||
msg: Password must be at 8 - 32 characters in length.
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
user_modal:
|
||||
title: Add new user
|
||||
form:
|
||||
fields:
|
||||
display_name:
|
||||
label: Display Name
|
||||
msg: display_name must be at 4 - 30 characters in length.
|
||||
email:
|
||||
label: Email
|
||||
msg: Email is not valid.
|
||||
password:
|
||||
label: Password
|
||||
msg: Password must be at 8 - 32 characters in length.
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
questions:
|
||||
page_title: 问题
|
||||
normal: 正常
|
||||
|
@ -876,6 +1117,8 @@ ui:
|
|||
status: 状态
|
||||
action: 操作
|
||||
change: 更改
|
||||
filter:
|
||||
placeholder: "Filter by title, question:id"
|
||||
answers:
|
||||
page_title: 回答
|
||||
normal: 正常
|
||||
|
@ -886,6 +1129,8 @@ ui:
|
|||
status: 状态
|
||||
action: 操作
|
||||
change: 更改
|
||||
filter:
|
||||
placeholder: "Filter by title, answer:id"
|
||||
general:
|
||||
page_title: 一般
|
||||
name:
|
||||
|
@ -914,6 +1159,10 @@ ui:
|
|||
label: 界面语言
|
||||
msg: 不能为空
|
||||
text: 设置用户界面语言,在刷新页面后生效。
|
||||
time_zone:
|
||||
label: Timezone
|
||||
msg: Timezone cannot be empty.
|
||||
text: Choose a city in the same timezone as you.
|
||||
smtp:
|
||||
page_title: SMTP
|
||||
from_email:
|
||||
|
@ -949,8 +1198,139 @@ ui:
|
|||
text: 提供用于接收测试邮件的邮箱地址。
|
||||
msg: 地址无效
|
||||
smtp_authentication:
|
||||
label: SMTP 认证
|
||||
label: Enable authentication
|
||||
title: SMTP Authentication
|
||||
msg: 不能为空
|
||||
'yes': 是
|
||||
'no': 否
|
||||
"yes": "是"
|
||||
"no": "否"
|
||||
branding:
|
||||
page_title: Branding
|
||||
logo:
|
||||
label: Logo
|
||||
msg: Logo cannot be empty.
|
||||
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
|
||||
mobile_logo:
|
||||
label: Mobile Logo (optional)
|
||||
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the “logo” setting will be used.
|
||||
square_icon:
|
||||
label: Square Icon
|
||||
msg: Square icon cannot be empty.
|
||||
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
|
||||
favicon:
|
||||
label: Favicon (optional)
|
||||
text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, “square icon” will be used.
|
||||
legal:
|
||||
page_title: Legal
|
||||
terms_of_service:
|
||||
label: Terms of Service
|
||||
text: "You can add terms of service content here. If you already have a document hosted elsewhere, provide the full URL here."
|
||||
privacy_policy:
|
||||
label: Privacy Policy
|
||||
text: "You can add privacy policy content here. If you already have a document hosted elsewhere, provide the full URL here."
|
||||
write:
|
||||
page_title: Write
|
||||
recommend_tags:
|
||||
label: Recommend Tags
|
||||
text: "Please input tag slug above, one tag per line."
|
||||
required_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."
|
||||
seo:
|
||||
page_title: SEO
|
||||
permalink:
|
||||
label: Permalink
|
||||
text: Custom URL structures can improve the usability, and forward-compatibility of your links.
|
||||
robots:
|
||||
label: robots.txt
|
||||
text: This will permanently override any related site settings.
|
||||
themes:
|
||||
page_title: Themes
|
||||
themes:
|
||||
label: Themes
|
||||
text: Select an existing theme.
|
||||
navbar_style:
|
||||
label: Navbar Style
|
||||
text: Select an existing theme.
|
||||
primary_color:
|
||||
label: Primary Color
|
||||
text: Modify the colors used by your themes
|
||||
css_and_html:
|
||||
page_title: CSS and HTML
|
||||
custom_css:
|
||||
label: Custom CSS
|
||||
text: This will insert as <link>
|
||||
head:
|
||||
label: Head
|
||||
text: This will insert before </head>
|
||||
header:
|
||||
label: Header
|
||||
text: This will insert after <body>
|
||||
footer:
|
||||
label: Footer
|
||||
text: This will insert before </html>.
|
||||
login:
|
||||
page_title: Login
|
||||
membership:
|
||||
title: Membership
|
||||
label: Allow new registrations
|
||||
text: Turn off to prevent anyone from creating a new account.
|
||||
private:
|
||||
title: Private
|
||||
label: Login required
|
||||
text: Only logged in users can access this community.
|
||||
form:
|
||||
empty: cannot be empty
|
||||
invalid: is invalid
|
||||
btn_submit: Save
|
||||
not_found_props: "Required property {{ key }} not found."
|
||||
page_review:
|
||||
review: Review
|
||||
proposed: 提案
|
||||
question_edit: 问题编辑
|
||||
answer_edit: 回答编辑
|
||||
tag_edit: '标签管理: 编辑标签'
|
||||
edit_summary: 编辑汇总
|
||||
edit_question: 编辑问题
|
||||
edit_answer: 编辑回答
|
||||
edit_tag: 编辑标签
|
||||
empty: 没有剩余的审核任务。
|
||||
timeline:
|
||||
undeleted: 取消删除
|
||||
deleted: 删除
|
||||
downvote: 反对
|
||||
upvote: 赞同
|
||||
accept: 采纳
|
||||
cancelled: 已取消
|
||||
commented: '评论:'
|
||||
rollback: 回滚
|
||||
edited: 最后编辑于
|
||||
answered: 回答于
|
||||
asked: 提问于
|
||||
closed: 关闭
|
||||
reopened: 重新开启
|
||||
created: 创建于
|
||||
title: "历史记录"
|
||||
tag_title: "时间线"
|
||||
show_votes: "显示投票"
|
||||
n_or_a: N/A
|
||||
title_for_question: "时间线"
|
||||
title_for_answer: "{{ title }} 的 {{ author }} 回答时间线"
|
||||
title_for_tag: "时间线"
|
||||
datetime: 日期时间
|
||||
type: 类型
|
||||
by: 由
|
||||
comment: 评论
|
||||
no_data: "空空如也"
|
||||
users:
|
||||
title: Users
|
||||
users_with_the_most_reputation: Users with the highest reputation scores
|
||||
users_with_the_most_vote: Users who voted the most
|
||||
staffs: Our community staff
|
||||
reputation: reputation
|
||||
votes: votes
|
||||
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -7,8 +7,8 @@ const (
|
|||
AnswerTheQuestion = "notification.action.answer_the_question"
|
||||
// UpdateAnswer update answer
|
||||
UpdateAnswer = "notification.action.update_answer"
|
||||
// AdoptAnswer adopt answer
|
||||
AdoptAnswer = "notification.action.adopt_answer"
|
||||
// AcceptAnswer accept answer
|
||||
AcceptAnswer = "notification.action.accept_answer"
|
||||
// CommentQuestion comment question
|
||||
CommentQuestion = "notification.action.comment_question"
|
||||
// CommentAnswer comment answer
|
||||
|
|
|
@ -52,7 +52,7 @@ func BindAndCheck(ctx *gin.Context, data interface{}) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
errField, err := validator.GetValidatorByLang(lang.Abbr()).Check(data)
|
||||
errField, err := validator.GetValidatorByLang(lang).Check(data)
|
||||
if err != nil {
|
||||
HandleResponse(ctx, err, errField)
|
||||
return true
|
||||
|
@ -70,6 +70,6 @@ func BindAndCheckReturnErr(ctx *gin.Context, data interface{}) (errFields []*val
|
|||
return nil
|
||||
}
|
||||
|
||||
errFields, _ = validator.GetValidatorByLang(lang.Abbr()).Check(data)
|
||||
errFields, _ = validator.GetValidatorByLang(lang).Check(data)
|
||||
return errFields
|
||||
}
|
||||
|
|
|
@ -11,14 +11,10 @@ import (
|
|||
// GetLang get language from header
|
||||
func GetLang(ctx *gin.Context) i18n.Language {
|
||||
acceptLanguage := ctx.GetHeader(constant.AcceptLanguageFlag)
|
||||
switch i18n.Language(acceptLanguage) {
|
||||
case i18n.LanguageChinese:
|
||||
return i18n.LanguageChinese
|
||||
case i18n.LanguageEnglish:
|
||||
return i18n.LanguageEnglish
|
||||
default:
|
||||
return i18n.DefaultLang
|
||||
if len(acceptLanguage) == 0 {
|
||||
return i18n.DefaultLanguage
|
||||
}
|
||||
return i18n.Language(acceptLanguage)
|
||||
}
|
||||
|
||||
// GetLangByCtx get language from header
|
||||
|
@ -27,5 +23,5 @@ func GetLangByCtx(ctx context.Context) i18n.Language {
|
|||
if ok {
|
||||
return acceptLanguage
|
||||
}
|
||||
return i18n.DefaultLang
|
||||
return i18n.DefaultLanguage
|
||||
}
|
||||
|
|
|
@ -113,7 +113,7 @@ func (am *AuthUserMiddleware) MustAuth() gin.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func (am *AuthUserMiddleware) CmsAuth() gin.HandlerFunc {
|
||||
func (am *AuthUserMiddleware) AdminAuth() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
token := ExtractToken(ctx)
|
||||
if len(token) == 0 {
|
||||
|
@ -121,7 +121,7 @@ func (am *AuthUserMiddleware) CmsAuth() gin.HandlerFunc {
|
|||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
userInfo, err := am.authService.GetCmsUserCacheInfo(ctx, token)
|
||||
userInfo, err := am.authService.GetAdminUserCacheInfo(ctx, token)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
|
||||
ctx.Abort()
|
||||
|
|
|
@ -57,9 +57,9 @@ func NewHTTPServer(debug bool,
|
|||
authV1.Use(authUserMiddleware.MustAuth())
|
||||
answerRouter.RegisterAnswerAPIRouter(authV1)
|
||||
|
||||
cmsauthV1 := r.Group("/answer/admin/api")
|
||||
cmsauthV1.Use(authUserMiddleware.CmsAuth())
|
||||
answerRouter.RegisterAnswerCmsAPIRouter(cmsauthV1)
|
||||
adminauthV1 := r.Group("/answer/admin/api")
|
||||
adminauthV1.Use(authUserMiddleware.AdminAuth())
|
||||
answerRouter.RegisterAnswerAdminAPIRouter(adminauthV1)
|
||||
|
||||
templateRouter.RegisterTemplateRouter(rootGroup)
|
||||
return r
|
||||
|
|
|
@ -49,6 +49,7 @@ var funcMap = template.FuncMap{
|
|||
k := converter.InterfaceToString(params[i])
|
||||
v := converter.InterfaceToString(params[i+1])
|
||||
trans = strings.ReplaceAll(trans, "{{ "+k+" }}", v)
|
||||
trans = strings.ReplaceAll(trans, "{{"+k+"}}", v)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,23 +2,65 @@ package validator
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/base/translator"
|
||||
"github.com/go-playground/locales"
|
||||
german "github.com/go-playground/locales/de"
|
||||
english "github.com/go-playground/locales/en"
|
||||
zhongwen "github.com/go-playground/locales/zh"
|
||||
spanish "github.com/go-playground/locales/es"
|
||||
french "github.com/go-playground/locales/fr"
|
||||
italian "github.com/go-playground/locales/it"
|
||||
japanese "github.com/go-playground/locales/ja"
|
||||
korean "github.com/go-playground/locales/ko"
|
||||
portuguese "github.com/go-playground/locales/pt"
|
||||
russian "github.com/go-playground/locales/ru"
|
||||
vietnamese "github.com/go-playground/locales/vi"
|
||||
chinese "github.com/go-playground/locales/zh"
|
||||
chineseTraditional "github.com/go-playground/locales/zh_Hant_TW"
|
||||
ut "github.com/go-playground/universal-translator"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/go-playground/validator/v10/translations/en"
|
||||
"github.com/go-playground/validator/v10/translations/es"
|
||||
"github.com/go-playground/validator/v10/translations/fr"
|
||||
"github.com/go-playground/validator/v10/translations/it"
|
||||
"github.com/go-playground/validator/v10/translations/ja"
|
||||
"github.com/go-playground/validator/v10/translations/pt"
|
||||
"github.com/go-playground/validator/v10/translations/ru"
|
||||
"github.com/go-playground/validator/v10/translations/vi"
|
||||
"github.com/go-playground/validator/v10/translations/zh"
|
||||
"github.com/go-playground/validator/v10/translations/zh_tw"
|
||||
myErrors "github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/i18n"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
type TranslatorLocal struct {
|
||||
La i18n.Language
|
||||
Lo locales.Translator
|
||||
RegisterFunc func(v *validator.Validate, trans ut.Translator) (err error)
|
||||
}
|
||||
|
||||
var (
|
||||
allLanguageTranslators = []*TranslatorLocal{
|
||||
{La: i18n.LanguageChinese, Lo: chinese.New(), RegisterFunc: zh.RegisterDefaultTranslations},
|
||||
{La: i18n.LanguageChineseTraditional, Lo: chineseTraditional.New(), RegisterFunc: zh_tw.RegisterDefaultTranslations},
|
||||
{La: i18n.LanguageEnglish, Lo: english.New(), RegisterFunc: en.RegisterDefaultTranslations},
|
||||
{La: i18n.LanguageGerman, Lo: german.New(), RegisterFunc: nil},
|
||||
{La: i18n.LanguageSpanish, Lo: spanish.New(), RegisterFunc: es.RegisterDefaultTranslations},
|
||||
{La: i18n.LanguageFrench, Lo: french.New(), RegisterFunc: fr.RegisterDefaultTranslations},
|
||||
{La: i18n.LanguageItalian, Lo: italian.New(), RegisterFunc: it.RegisterDefaultTranslations},
|
||||
{La: i18n.LanguageJapanese, Lo: japanese.New(), RegisterFunc: ja.RegisterDefaultTranslations},
|
||||
{La: i18n.LanguageKorean, Lo: korean.New(), RegisterFunc: nil},
|
||||
{La: i18n.LanguagePortuguese, Lo: portuguese.New(), RegisterFunc: pt.RegisterDefaultTranslations},
|
||||
{La: i18n.LanguageRussian, Lo: russian.New(), RegisterFunc: ru.RegisterDefaultTranslations},
|
||||
{La: i18n.LanguageVietnamese, Lo: vietnamese.New(), RegisterFunc: vi.RegisterDefaultTranslations},
|
||||
}
|
||||
)
|
||||
|
||||
// MyValidator my validator
|
||||
type MyValidator struct {
|
||||
Validate *validator.Validate
|
||||
|
@ -33,26 +75,24 @@ type FormErrorField struct {
|
|||
}
|
||||
|
||||
// GlobalValidatorMapping is a mapping from validator to translator used
|
||||
var GlobalValidatorMapping = make(map[string]*MyValidator, 0)
|
||||
var GlobalValidatorMapping = make(map[i18n.Language]*MyValidator, 0)
|
||||
|
||||
func init() {
|
||||
zhTran, zhVal := getTran(zhongwen.New(), i18n.LanguageChinese.Abbr()), createDefaultValidator(i18n.LanguageChinese)
|
||||
if err := zh.RegisterDefaultTranslations(zhVal, zhTran); err != nil {
|
||||
panic(err)
|
||||
for _, t := range allLanguageTranslators {
|
||||
tran, val := getTran(t.Lo), createDefaultValidator(t.La)
|
||||
if t.RegisterFunc != nil {
|
||||
if err := t.RegisterFunc(val, tran); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
GlobalValidatorMapping[t.La] = &MyValidator{Validate: val, Tran: tran, Lang: t.La}
|
||||
}
|
||||
GlobalValidatorMapping[i18n.LanguageChinese.Abbr()] = &MyValidator{Validate: zhVal, Tran: zhTran, Lang: i18n.LanguageChinese}
|
||||
|
||||
enTran, enVal := getTran(english.New(), i18n.LanguageEnglish.Abbr()), createDefaultValidator(i18n.LanguageEnglish)
|
||||
if err := en.RegisterDefaultTranslations(enVal, enTran); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
GlobalValidatorMapping[i18n.LanguageEnglish.Abbr()] = &MyValidator{Validate: enVal, Tran: enTran, Lang: i18n.LanguageEnglish}
|
||||
}
|
||||
|
||||
func getTran(lo locales.Translator, la string) ut.Translator {
|
||||
tran, ok := ut.New(lo, lo).GetTranslator(la)
|
||||
func getTran(lo locales.Translator) ut.Translator {
|
||||
tran, ok := ut.New(lo, lo).GetTranslator(lo.Locale())
|
||||
if !ok {
|
||||
panic(ok)
|
||||
panic(fmt.Sprintf("not found translator %s", lo.Locale()))
|
||||
}
|
||||
return tran
|
||||
}
|
||||
|
@ -79,11 +119,11 @@ func createDefaultValidator(la i18n.Language) *validator.Validate {
|
|||
return validate
|
||||
}
|
||||
|
||||
func GetValidatorByLang(la string) *MyValidator {
|
||||
if GlobalValidatorMapping[la] != nil {
|
||||
return GlobalValidatorMapping[la]
|
||||
func GetValidatorByLang(lang i18n.Language) *MyValidator {
|
||||
if GlobalValidatorMapping[lang] != nil {
|
||||
return GlobalValidatorMapping[lang]
|
||||
}
|
||||
return GlobalValidatorMapping[i18n.DefaultLang.Abbr()]
|
||||
return GlobalValidatorMapping[i18n.DefaultLanguage]
|
||||
}
|
||||
|
||||
// Check /
|
||||
|
|
|
@ -232,18 +232,18 @@ func (ac *AnswerController) AnswerList(ctx *gin.Context) {
|
|||
})
|
||||
}
|
||||
|
||||
// Adopted godoc
|
||||
// @Summary Adopted
|
||||
// @Description Adopted
|
||||
// Accepted godoc
|
||||
// @Summary Accepted
|
||||
// @Description Accepted
|
||||
// @Tags api-answer
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param data body schema.AnswerAdoptedReq true "AnswerAdoptedReq"
|
||||
// @Param data body schema.AnswerAcceptedReq true "AnswerAcceptedReq"
|
||||
// @Success 200 {string} string ""
|
||||
// @Router /answer/api/v1/answer/acceptance [post]
|
||||
func (ac *AnswerController) Adopted(ctx *gin.Context) {
|
||||
req := &schema.AnswerAdoptedReq{}
|
||||
func (ac *AnswerController) Accepted(ctx *gin.Context) {
|
||||
req := &schema.AnswerAcceptedReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
|
@ -259,7 +259,7 @@ func (ac *AnswerController) Adopted(ctx *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
err = ac.answerService.UpdateAdopted(ctx, req)
|
||||
err = ac.answerService.UpdateAccepted(ctx, req)
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
|
|
|
@ -277,6 +277,19 @@ func (qc *QuestionController) AddQuestion(ctx *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
errList, err := qc.questionService.CheckAddQuestion(ctx, req)
|
||||
if err != nil {
|
||||
errlist, ok := errList.([]*validator.FormErrorField)
|
||||
if ok {
|
||||
errFields = append(errFields, errlist...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(errFields) > 0 {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), errFields)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := qc.questionService.AddQuestion(ctx, req)
|
||||
if err != nil {
|
||||
errlist, ok := resp.([]*validator.FormErrorField)
|
||||
|
@ -284,6 +297,7 @@ func (qc *QuestionController) AddQuestion(ctx *gin.Context) {
|
|||
errFields = append(errFields, errlist...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(errFields) > 0 {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), errFields)
|
||||
return
|
||||
|
@ -480,8 +494,8 @@ func (qc *QuestionController) UserCollectionList(ctx *gin.Context) {
|
|||
})
|
||||
}
|
||||
|
||||
// CmsSearchList godoc
|
||||
// @Summary CmsSearchList
|
||||
// AdminSearchList godoc
|
||||
// @Summary AdminSearchList
|
||||
// @Description Status:[available,closed,deleted]
|
||||
// @Tags admin
|
||||
// @Accept json
|
||||
|
@ -493,21 +507,21 @@ func (qc *QuestionController) UserCollectionList(ctx *gin.Context) {
|
|||
// @Param query query string false "question id or title"
|
||||
// @Success 200 {object} handler.RespBody
|
||||
// @Router /answer/admin/api/question/page [get]
|
||||
func (qc *QuestionController) CmsSearchList(ctx *gin.Context) {
|
||||
req := &schema.CmsQuestionSearch{}
|
||||
func (qc *QuestionController) AdminSearchList(ctx *gin.Context) {
|
||||
req := &schema.AdminQuestionSearch{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
userID := middleware.GetLoginUserIDFromContext(ctx)
|
||||
questionList, count, err := qc.questionService.CmsSearchList(ctx, req, userID)
|
||||
questionList, count, err := qc.questionService.AdminSearchList(ctx, req, userID)
|
||||
handler.HandleResponse(ctx, err, gin.H{
|
||||
"list": questionList,
|
||||
"count": count,
|
||||
})
|
||||
}
|
||||
|
||||
// CmsSearchAnswerList godoc
|
||||
// @Summary CmsSearchList
|
||||
// AdminSearchAnswerList godoc
|
||||
// @Summary AdminSearchAnswerList
|
||||
// @Description Status:[available,deleted]
|
||||
// @Tags admin
|
||||
// @Accept json
|
||||
|
@ -520,13 +534,13 @@ func (qc *QuestionController) CmsSearchList(ctx *gin.Context) {
|
|||
// @Param question_id query string false "question id"
|
||||
// @Success 200 {object} handler.RespBody
|
||||
// @Router /answer/admin/api/answer/page [get]
|
||||
func (qc *QuestionController) CmsSearchAnswerList(ctx *gin.Context) {
|
||||
req := &entity.CmsAnswerSearch{}
|
||||
func (qc *QuestionController) AdminSearchAnswerList(ctx *gin.Context) {
|
||||
req := &entity.AdminAnswerSearch{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
userID := middleware.GetLoginUserIDFromContext(ctx)
|
||||
questionList, count, err := qc.questionService.CmsSearchAnswerList(ctx, req, userID)
|
||||
questionList, count, err := qc.questionService.AdminSearchAnswerList(ctx, req, userID)
|
||||
handler.HandleResponse(ctx, err, gin.H{
|
||||
"list": questionList,
|
||||
"count": count,
|
||||
|
|
|
@ -264,7 +264,7 @@ func (tc *TemplateController) QuestionInfo(ctx *gin.Context) {
|
|||
jsonLD.MainEntity.Author.Name = detail.UserInfo.DisplayName
|
||||
answerList := make([]*schema.SuggestedAnswerItem, 0)
|
||||
for _, answer := range answers {
|
||||
if answer.Adopted == schema.AnswerAdoptedEnable {
|
||||
if answer.Accepted == schema.AnswerAcceptedEnable {
|
||||
acceptedAnswerItem := &schema.AcceptedAnswerItem{}
|
||||
acceptedAnswerItem.Type = "Answer"
|
||||
acceptedAnswerItem.Text = answer.HTML
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package controller_backyard
|
||||
package controller_admin
|
||||
|
||||
import "github.com/google/wire"
|
||||
|
||||
// ProviderSetController is controller providers.
|
||||
var ProviderSetController = wire.NewSet(
|
||||
NewReportController,
|
||||
NewUserBackyardController,
|
||||
NewUserAdminController,
|
||||
NewThemeController,
|
||||
NewSiteInfoController,
|
||||
NewRoleController,
|
|
@ -1,20 +1,20 @@
|
|||
package controller_backyard
|
||||
package controller_admin
|
||||
|
||||
import (
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/report_backyard"
|
||||
"github.com/answerdev/answer/internal/service/report_admin"
|
||||
"github.com/answerdev/answer/pkg/converter"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ReportController report controller
|
||||
type ReportController struct {
|
||||
reportService *report_backyard.ReportBackyardService
|
||||
reportService *report_admin.ReportAdminService
|
||||
}
|
||||
|
||||
// NewReportController new controller
|
||||
func NewReportController(reportService *report_backyard.ReportBackyardService) *ReportController {
|
||||
func NewReportController(reportService *report_admin.ReportAdminService) *ReportController {
|
||||
return &ReportController{reportService: reportService}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package controller_backyard
|
||||
package controller_admin
|
||||
|
||||
import (
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
|
@ -1,4 +1,4 @@
|
|||
package controller_backyard
|
||||
package controller_admin
|
||||
|
||||
import (
|
||||
"net/http"
|
|
@ -1,4 +1,4 @@
|
|||
package controller_backyard
|
||||
package controller_admin
|
||||
|
||||
import (
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
|
@ -1,21 +1,21 @@
|
|||
package controller_backyard
|
||||
package controller_admin
|
||||
|
||||
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/user_backyard"
|
||||
"github.com/answerdev/answer/internal/service/user_admin"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// UserBackyardController user controller
|
||||
type UserBackyardController struct {
|
||||
userService *user_backyard.UserBackyardService
|
||||
// UserAdminController user controller
|
||||
type UserAdminController struct {
|
||||
userService *user_admin.UserAdminService
|
||||
}
|
||||
|
||||
// NewUserBackyardController new controller
|
||||
func NewUserBackyardController(userService *user_backyard.UserBackyardService) *UserBackyardController {
|
||||
return &UserBackyardController{userService: userService}
|
||||
// NewUserAdminController new controller
|
||||
func NewUserAdminController(userService *user_admin.UserAdminService) *UserAdminController {
|
||||
return &UserAdminController{userService: userService}
|
||||
}
|
||||
|
||||
// UpdateUserStatus update user
|
||||
|
@ -28,7 +28,7 @@ func NewUserBackyardController(userService *user_backyard.UserBackyardService) *
|
|||
// @Param data body schema.UpdateUserStatusReq true "user"
|
||||
// @Success 200 {object} handler.RespBody
|
||||
// @Router /answer/admin/api/user/status [put]
|
||||
func (uc *UserBackyardController) UpdateUserStatus(ctx *gin.Context) {
|
||||
func (uc *UserAdminController) UpdateUserStatus(ctx *gin.Context) {
|
||||
req := &schema.UpdateUserStatusReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
|
@ -48,7 +48,7 @@ func (uc *UserBackyardController) UpdateUserStatus(ctx *gin.Context) {
|
|||
// @Param data body schema.UpdateUserRoleReq true "user"
|
||||
// @Success 200 {object} handler.RespBody
|
||||
// @Router /answer/admin/api/user/role [put]
|
||||
func (uc *UserBackyardController) UpdateUserRole(ctx *gin.Context) {
|
||||
func (uc *UserAdminController) UpdateUserRole(ctx *gin.Context) {
|
||||
req := &schema.UpdateUserRoleReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
|
@ -70,7 +70,7 @@ func (uc *UserBackyardController) UpdateUserRole(ctx *gin.Context) {
|
|||
// @Param data body schema.AddUserReq true "user"
|
||||
// @Success 200 {object} handler.RespBody
|
||||
// @Router /answer/admin/api/user [post]
|
||||
func (uc *UserBackyardController) AddUser(ctx *gin.Context) {
|
||||
func (uc *UserAdminController) AddUser(ctx *gin.Context) {
|
||||
req := &schema.AddUserReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
|
@ -92,7 +92,7 @@ func (uc *UserBackyardController) AddUser(ctx *gin.Context) {
|
|||
// @Param data body schema.UpdateUserPasswordReq true "user"
|
||||
// @Success 200 {object} handler.RespBody
|
||||
// @Router /answer/admin/api/user/password [put]
|
||||
func (uc *UserBackyardController) UpdateUserPassword(ctx *gin.Context) {
|
||||
func (uc *UserAdminController) UpdateUserPassword(ctx *gin.Context) {
|
||||
req := &schema.UpdateUserPasswordReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
|
@ -117,7 +117,7 @@ func (uc *UserBackyardController) UpdateUserPassword(ctx *gin.Context) {
|
|||
// @Param status query string false "user status" Enums(suspended, deleted, inactive)
|
||||
// @Success 200 {object} handler.RespBody{data=pager.PageModel{records=[]schema.GetUserPageResp}}
|
||||
// @Router /answer/admin/api/users/page [get]
|
||||
func (uc *UserBackyardController) GetUserPage(ctx *gin.Context) {
|
||||
func (uc *UserAdminController) GetUserPage(ctx *gin.Context) {
|
||||
req := &schema.GetUserPageReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
|
@ -11,7 +11,7 @@ const (
|
|||
AnswerStatusDeleted = 10
|
||||
)
|
||||
|
||||
var CmsAnswerSearchStatus = map[string]int{
|
||||
var AdminAnswerSearchStatus = map[string]int{
|
||||
"available": AnswerStatusAvailable,
|
||||
"deleted": AnswerStatusDeleted,
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ type Answer struct {
|
|||
OriginalText string `xorm:"not null MEDIUMTEXT original_text"`
|
||||
ParsedText string `xorm:"not null MEDIUMTEXT parsed_text"`
|
||||
Status int `xorm:"not null default 1 INT(11) status"`
|
||||
Adopted int `xorm:"not null default 1 INT(11) adopted"`
|
||||
Accepted int `xorm:"not null default 1 INT(11) adopted"`
|
||||
CommentCount int `xorm:"not null default 0 INT(11) comment_count"`
|
||||
VoteCount int `xorm:"not null default 0 INT(11) vote_count"`
|
||||
RevisionID string `xorm:"not null default 0 BIGINT(20) revision_id"`
|
||||
|
@ -40,7 +40,7 @@ type AnswerSearch struct {
|
|||
PageSize int `json:"page_size" form:"page_size"` // Search page size
|
||||
}
|
||||
|
||||
type CmsAnswerSearch struct {
|
||||
type AdminAnswerSearch struct {
|
||||
Page int `json:"page" form:"page"` // Query number of pages
|
||||
PageSize int `json:"page_size" form:"page_size"` // Search page size
|
||||
Status int `json:"-" form:"-"`
|
||||
|
|
|
@ -10,13 +10,13 @@ const (
|
|||
QuestionStatusDeleted = 10
|
||||
)
|
||||
|
||||
var CmsQuestionSearchStatus = map[string]int{
|
||||
var AdminQuestionSearchStatus = map[string]int{
|
||||
"available": QuestionStatusAvailable,
|
||||
"closed": QuestionStatusClosed,
|
||||
"deleted": QuestionStatusDeleted,
|
||||
}
|
||||
|
||||
var CmsQuestionSearchStatusIntToString = map[int]string{
|
||||
var AdminQuestionSearchStatusIntToString = map[int]string{
|
||||
QuestionStatusAvailable: "available",
|
||||
QuestionStatusClosed: "closed",
|
||||
QuestionStatusDeleted: "deleted",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/data"
|
||||
|
@ -14,11 +15,13 @@ const minDBVersion = 0 // answer 1.0.0
|
|||
type Migration interface {
|
||||
Description() string
|
||||
Migrate(*xorm.Engine) error
|
||||
ShouldCleanCache() bool
|
||||
}
|
||||
|
||||
type migration struct {
|
||||
description string
|
||||
migrate func(*xorm.Engine) error
|
||||
description string
|
||||
migrate func(*xorm.Engine) error
|
||||
shouldCleanCache bool
|
||||
}
|
||||
|
||||
// Description returns the migration's description
|
||||
|
@ -31,9 +34,14 @@ func (m *migration) Migrate(x *xorm.Engine) error {
|
|||
return m.migrate(x)
|
||||
}
|
||||
|
||||
// ShouldCleanCache should clean the cache
|
||||
func (m *migration) ShouldCleanCache() bool {
|
||||
return m.shouldCleanCache
|
||||
}
|
||||
|
||||
// NewMigration creates a new migration
|
||||
func NewMigration(desc string, fn func(*xorm.Engine) error) Migration {
|
||||
return &migration{description: desc, migrate: fn}
|
||||
func NewMigration(desc string, fn func(*xorm.Engine) error, shouldCleanCache bool) Migration {
|
||||
return &migration{description: desc, migrate: fn, shouldCleanCache: shouldCleanCache}
|
||||
}
|
||||
|
||||
// Use noopMigration when there is a migration that has been no-oped
|
||||
|
@ -41,12 +49,12 @@ var noopMigration = func(_ *xorm.Engine) error { return nil }
|
|||
|
||||
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),
|
||||
NewMigration("add activity timeline", addActivityTimeline),
|
||||
NewMigration("add user role", addRoleFeatures),
|
||||
NewMigration("add theme and private mode", addThemeAndPrivateMode),
|
||||
NewMigration("this is first version, no operation", noopMigration, false),
|
||||
NewMigration("add user language", addUserLanguage, false),
|
||||
NewMigration("add recommend and reserved tag fields", addTagRecommendedAndReserved, false),
|
||||
NewMigration("add activity timeline", addActivityTimeline, false),
|
||||
NewMigration("add user role", addRoleFeatures, false),
|
||||
NewMigration("add theme and private mode", addThemeAndPrivateMode, true),
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current db version
|
||||
|
@ -76,8 +84,12 @@ func ExpectedVersion() int64 {
|
|||
}
|
||||
|
||||
// Migrate database to current version
|
||||
func Migrate(dataConf *data.Database) error {
|
||||
engine, err := data.NewDB(false, dataConf)
|
||||
func Migrate(dbConf *data.Database, cacheConf *data.CacheConf) error {
|
||||
cache, cacheCleanup, err := data.NewCache(cacheConf)
|
||||
if err != nil {
|
||||
fmt.Println("new check failed:", err.Error())
|
||||
}
|
||||
engine, err := data.NewDB(false, dbConf)
|
||||
if err != nil {
|
||||
fmt.Println("new database failed: ", err.Error())
|
||||
return err
|
||||
|
@ -98,6 +110,11 @@ func Migrate(dataConf *data.Database) error {
|
|||
fmt.Printf("[migrate] migrate to db version %d failed: %s\n", currentDBVersion+1, err.Error())
|
||||
return err
|
||||
}
|
||||
if migrationFunc.ShouldCleanCache() {
|
||||
if err := cache.Flush(context.Background()); err != nil {
|
||||
fmt.Printf("[migrate] flush cache failed: %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
fmt.Printf("[migrate] migrate to db version %d success\n", currentDBVersion+1)
|
||||
if _, err := engine.Update(&entity.Version{ID: 1, VersionNumber: currentDBVersion + 1}); err != nil {
|
||||
fmt.Printf("[migrate] migrate to db version %d, update failed: %s", currentDBVersion+1, err.Error())
|
||||
|
@ -105,5 +122,8 @@ func Migrate(dataConf *data.Database) error {
|
|||
}
|
||||
currentDBVersion++
|
||||
}
|
||||
if cache != nil {
|
||||
cacheCleanup()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -214,7 +214,7 @@ func (ar *AnswerActivityRepo) AcceptAnswer(ctx context.Context,
|
|||
if act.UserID != questionUserID {
|
||||
msg.TriggerUserID = questionUserID
|
||||
msg.ObjectType = constant.AnswerObjectType
|
||||
msg.NotificationAction = constant.AdoptAnswer
|
||||
msg.NotificationAction = constant.AcceptAnswer
|
||||
notice_queue.AddNotification(msg)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -220,7 +220,7 @@ func (vr *VoteRepo) voteCancel(ctx context.Context, objectID string, userID, obj
|
|||
}
|
||||
|
||||
// trigger user rank and send notification
|
||||
if hasRank != 0 && existsActivity.Rank > 0 {
|
||||
if hasRank != 0 && existsActivity.Rank != 0 {
|
||||
_, err = vr.userRankRepo.TriggerUserRank(ctx, session, activityUserID, -deltaRank, activityType)
|
||||
if err != nil {
|
||||
return
|
||||
|
|
|
@ -133,22 +133,22 @@ func (ar *answerRepo) GetAnswerPage(ctx context.Context, page, pageSize int, ans
|
|||
return
|
||||
}
|
||||
|
||||
// UpdateAdopted
|
||||
// UpdateAccepted
|
||||
// If no answer is selected, the answer id can be 0
|
||||
func (ar *answerRepo) UpdateAdopted(ctx context.Context, id string, questionID string) error {
|
||||
func (ar *answerRepo) UpdateAccepted(ctx context.Context, id string, questionID string) error {
|
||||
if questionID == "" {
|
||||
return nil
|
||||
}
|
||||
var data entity.Answer
|
||||
data.ID = id
|
||||
|
||||
data.Adopted = schema.AnswerAdoptedFailed
|
||||
data.Accepted = schema.AnswerAcceptedFailed
|
||||
_, err := ar.data.DB.Where("question_id =?", questionID).Cols("adopted").Update(&data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if id != "0" {
|
||||
data.Adopted = schema.AnswerAdoptedEnable
|
||||
data.Accepted = schema.AnswerAcceptedEnable
|
||||
_, err = ar.data.DB.Where("id = ?", id).Cols("adopted").Update(&data)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
|
@ -216,7 +216,7 @@ func (ar *answerRepo) SearchList(ctx context.Context, search *entity.AnswerSearc
|
|||
return rows, count, nil
|
||||
}
|
||||
|
||||
func (ar *answerRepo) CmsSearchList(ctx context.Context, search *entity.CmsAnswerSearch) ([]*entity.Answer, int64, error) {
|
||||
func (ar *answerRepo) AdminSearchList(ctx context.Context, search *entity.AdminAnswerSearch) ([]*entity.Answer, int64, error) {
|
||||
var (
|
||||
count int64
|
||||
err error
|
||||
|
|
|
@ -95,8 +95,8 @@ func (ar *authRepo) RemoveUserStatus(ctx context.Context, userID string) (err er
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetBackyardUserCacheInfo get backyard user cache info
|
||||
func (ar *authRepo) GetBackyardUserCacheInfo(ctx context.Context, accessToken string) (userInfo *entity.UserCacheInfo, err error) {
|
||||
// GetAdminUserCacheInfo get admin user cache info
|
||||
func (ar *authRepo) GetAdminUserCacheInfo(ctx context.Context, accessToken string) (userInfo *entity.UserCacheInfo, err error) {
|
||||
userInfoCache, err := ar.data.Cache.GetString(ctx, constant.AdminTokenCacheKey+accessToken)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
|
@ -110,8 +110,8 @@ func (ar *authRepo) GetBackyardUserCacheInfo(ctx context.Context, accessToken st
|
|||
return userInfo, nil
|
||||
}
|
||||
|
||||
// SetBackyardUserCacheInfo set backyard user cache info
|
||||
func (ar *authRepo) SetBackyardUserCacheInfo(ctx context.Context, accessToken string, userInfo *entity.UserCacheInfo) (err error) {
|
||||
// SetAdminUserCacheInfo set admin user cache info
|
||||
func (ar *authRepo) SetAdminUserCacheInfo(ctx context.Context, accessToken string, userInfo *entity.UserCacheInfo) (err error) {
|
||||
userInfoCache, err := json.Marshal(userInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -125,8 +125,8 @@ func (ar *authRepo) SetBackyardUserCacheInfo(ctx context.Context, accessToken st
|
|||
return nil
|
||||
}
|
||||
|
||||
// RemoveBackyardUserCacheInfo remove backyard user cache info
|
||||
func (ar *authRepo) RemoveBackyardUserCacheInfo(ctx context.Context, accessToken string) (err error) {
|
||||
// RemoveAdminUserCacheInfo remove admin user cache info
|
||||
func (ar *authRepo) RemoveAdminUserCacheInfo(ctx context.Context, accessToken string) (err error) {
|
||||
err = ar.data.Cache.Del(ctx, constant.AdminTokenCacheKey+accessToken)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
|
|
|
@ -25,7 +25,7 @@ func NewCaptchaRepo(data *data.Data) action.CaptchaRepo {
|
|||
}
|
||||
|
||||
func (cr *captchaRepo) SetActionType(ctx context.Context, ip, actionType string, amount int) (err error) {
|
||||
cacheKey := fmt.Sprintf("ActionRecord:%s@%s", ip, actionType)
|
||||
cacheKey := fmt.Sprintf("ActionRecord:%s@", ip)
|
||||
err = cr.data.Cache.SetInt64(ctx, cacheKey, int64(amount), 6*time.Minute)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
|
@ -34,17 +34,16 @@ func (cr *captchaRepo) SetActionType(ctx context.Context, ip, actionType string,
|
|||
}
|
||||
|
||||
func (cr *captchaRepo) GetActionType(ctx context.Context, ip, actionType string) (amount int, err error) {
|
||||
cacheKey := fmt.Sprintf("ActionRecord:%s@%s", ip, actionType)
|
||||
cacheKey := fmt.Sprintf("ActionRecord:%s@", ip)
|
||||
res, err := cr.data.Cache.GetInt64(ctx, cacheKey)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
// TODO: cache reflect should return empty when key not found
|
||||
return int(res), nil
|
||||
}
|
||||
|
||||
func (cr *captchaRepo) DelActionType(ctx context.Context, ip, actionType string) (err error) {
|
||||
cacheKey := fmt.Sprintf("ActionRecord:%s@%s", ip, actionType)
|
||||
cacheKey := fmt.Sprintf("ActionRecord:%s@", ip)
|
||||
err = cr.data.Cache.Del(ctx, cacheKey)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
|
@ -54,7 +53,6 @@ func (cr *captchaRepo) DelActionType(ctx context.Context, ip, actionType string)
|
|||
|
||||
// SetCaptcha set captcha to cache
|
||||
func (cr *captchaRepo) SetCaptcha(ctx context.Context, key, captcha string) (err error) {
|
||||
// TODO make cache time to config
|
||||
err = cr.data.Cache.SetString(ctx, key, captcha, 6*time.Minute)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
|
@ -68,6 +66,5 @@ func (cr *captchaRepo) GetCaptcha(ctx context.Context, key string) (captcha stri
|
|||
if err != nil {
|
||||
log.Debug(err)
|
||||
}
|
||||
// TODO: cache reflect should return empty when key not found
|
||||
return captcha, nil
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ var ProviderSetRepo = wire.NewSet(
|
|||
activity_common.NewVoteRepo,
|
||||
config.NewConfigRepo,
|
||||
user.NewUserRepo,
|
||||
user.NewUserBackyardRepo,
|
||||
user.NewUserAdminRepo,
|
||||
rank.NewUserRankRepo,
|
||||
question.NewQuestionRepo,
|
||||
answer.NewAnswerRepo,
|
||||
|
|
|
@ -271,7 +271,7 @@ func (qr *questionRepo) SearchList(ctx context.Context, search *schema.QuestionS
|
|||
return rows, count, nil
|
||||
}
|
||||
|
||||
func (qr *questionRepo) CmsSearchList(ctx context.Context, search *schema.CmsQuestionSearch) ([]*entity.Question, int64, error) {
|
||||
func (qr *questionRepo) AdminSearchList(ctx context.Context, search *schema.AdminQuestionSearch) ([]*entity.Question, int64, error) {
|
||||
var (
|
||||
count int64
|
||||
err error
|
||||
|
|
|
@ -61,26 +61,26 @@ func Test_authRepo_RemoveUserStatus(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func Test_authRepo_SetBackyardUserCacheInfo(t *testing.T) {
|
||||
func Test_authRepo_SetAdminUserCacheInfo(t *testing.T) {
|
||||
authRepo := auth.NewAuthRepo(testDataSource)
|
||||
|
||||
err := authRepo.SetBackyardUserCacheInfo(context.TODO(), token, &entity.UserCacheInfo{UserID: userID})
|
||||
err := authRepo.SetAdminUserCacheInfo(context.TODO(), token, &entity.UserCacheInfo{UserID: userID})
|
||||
assert.NoError(t, err)
|
||||
|
||||
cacheInfo, err := authRepo.GetBackyardUserCacheInfo(context.TODO(), token)
|
||||
cacheInfo, err := authRepo.GetAdminUserCacheInfo(context.TODO(), token)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, userID, cacheInfo.UserID)
|
||||
}
|
||||
|
||||
func Test_authRepo_RemoveBackyardUserCacheInfo(t *testing.T) {
|
||||
func Test_authRepo_RemoveAdminUserCacheInfo(t *testing.T) {
|
||||
authRepo := auth.NewAuthRepo(testDataSource)
|
||||
|
||||
err := authRepo.SetBackyardUserCacheInfo(context.TODO(), token, &entity.UserCacheInfo{UserID: userID})
|
||||
err := authRepo.SetAdminUserCacheInfo(context.TODO(), token, &entity.UserCacheInfo{UserID: userID})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = authRepo.RemoveBackyardUserCacheInfo(context.TODO(), token)
|
||||
err = authRepo.RemoveAdminUserCacheInfo(context.TODO(), token)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = authRepo.GetBackyardUserCacheInfo(context.TODO(), token)
|
||||
_, err = authRepo.GetAdminUserCacheInfo(context.TODO(), token)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
|
|
@ -10,43 +10,43 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_userBackyardRepo_GetUserInfo(t *testing.T) {
|
||||
userBackyardRepo := user.NewUserBackyardRepo(testDataSource, auth.NewAuthRepo(testDataSource))
|
||||
got, exist, err := userBackyardRepo.GetUserInfo(context.TODO(), "1")
|
||||
func Test_userAdminRepo_GetUserInfo(t *testing.T) {
|
||||
userAdminRepo := user.NewUserAdminRepo(testDataSource, auth.NewAuthRepo(testDataSource))
|
||||
got, exist, err := userAdminRepo.GetUserInfo(context.TODO(), "1")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
assert.Equal(t, "1", got.ID)
|
||||
}
|
||||
|
||||
func Test_userBackyardRepo_GetUserPage(t *testing.T) {
|
||||
userBackyardRepo := user.NewUserBackyardRepo(testDataSource, auth.NewAuthRepo(testDataSource))
|
||||
got, total, err := userBackyardRepo.GetUserPage(context.TODO(), 1, 1, &entity.User{Username: "admin"}, "", false)
|
||||
func Test_userAdminRepo_GetUserPage(t *testing.T) {
|
||||
userAdminRepo := user.NewUserAdminRepo(testDataSource, auth.NewAuthRepo(testDataSource))
|
||||
got, total, err := userAdminRepo.GetUserPage(context.TODO(), 1, 1, &entity.User{Username: "admin"}, "", false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Equal(t, "1", got[0].ID)
|
||||
}
|
||||
|
||||
func Test_userBackyardRepo_UpdateUserStatus(t *testing.T) {
|
||||
userBackyardRepo := user.NewUserBackyardRepo(testDataSource, auth.NewAuthRepo(testDataSource))
|
||||
got, exist, err := userBackyardRepo.GetUserInfo(context.TODO(), "1")
|
||||
func Test_userAdminRepo_UpdateUserStatus(t *testing.T) {
|
||||
userAdminRepo := user.NewUserAdminRepo(testDataSource, auth.NewAuthRepo(testDataSource))
|
||||
got, exist, err := userAdminRepo.GetUserInfo(context.TODO(), "1")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
assert.Equal(t, entity.UserStatusAvailable, got.Status)
|
||||
|
||||
err = userBackyardRepo.UpdateUserStatus(context.TODO(), "1", entity.UserStatusSuspended, entity.EmailStatusAvailable,
|
||||
err = userAdminRepo.UpdateUserStatus(context.TODO(), "1", entity.UserStatusSuspended, entity.EmailStatusAvailable,
|
||||
"admin@admin.com")
|
||||
assert.NoError(t, err)
|
||||
|
||||
got, exist, err = userBackyardRepo.GetUserInfo(context.TODO(), "1")
|
||||
got, exist, err = userAdminRepo.GetUserInfo(context.TODO(), "1")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
assert.Equal(t, entity.UserStatusSuspended, got.Status)
|
||||
|
||||
err = userBackyardRepo.UpdateUserStatus(context.TODO(), "1", entity.UserStatusAvailable, entity.EmailStatusAvailable,
|
||||
err = userAdminRepo.UpdateUserStatus(context.TODO(), "1", entity.UserStatusAvailable, entity.EmailStatusAvailable,
|
||||
"admin@admin.com")
|
||||
assert.NoError(t, err)
|
||||
|
||||
got, exist, err = userBackyardRepo.GetUserInfo(context.TODO(), "1")
|
||||
got, exist, err = userAdminRepo.GetUserInfo(context.TODO(), "1")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
assert.Equal(t, entity.UserStatusAvailable, got.Status)
|
||||
|
|
|
@ -371,8 +371,8 @@ func (sr *searchRepo) SearchAnswers(ctx context.Context, words []string, tagIDs
|
|||
|
||||
// check limit accepted
|
||||
if accepted {
|
||||
b.Where(builder.Eq{"adopted": schema.AnswerAdoptedEnable})
|
||||
args = append(args, schema.AnswerAdoptedEnable)
|
||||
b.Where(builder.Eq{"adopted": schema.AnswerAcceptedEnable})
|
||||
args = append(args, schema.AnswerAcceptedEnable)
|
||||
}
|
||||
|
||||
// check question id
|
||||
|
@ -475,14 +475,14 @@ func (sr *searchRepo) parseResult(ctx context.Context, res []map[string][]byte)
|
|||
_ = copier.Copy(&tags, tagsEntity)
|
||||
switch objectKey {
|
||||
case "question":
|
||||
for k, v := range entity.CmsQuestionSearchStatus {
|
||||
for k, v := range entity.AdminQuestionSearchStatus {
|
||||
if v == converter.StringToInt(string(r["status"])) {
|
||||
status = k
|
||||
break
|
||||
}
|
||||
}
|
||||
case "answer":
|
||||
for k, v := range entity.CmsAnswerSearchStatus {
|
||||
for k, v := range entity.AdminAnswerSearchStatus {
|
||||
if v == converter.StringToInt(string(r["status"])) {
|
||||
status = k
|
||||
break
|
||||
|
|
|
@ -12,27 +12,27 @@ import (
|
|||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/service/auth"
|
||||
"github.com/answerdev/answer/internal/service/user_backyard"
|
||||
"github.com/answerdev/answer/internal/service/user_admin"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
// userBackyardRepo user repository
|
||||
type userBackyardRepo struct {
|
||||
// userAdminRepo user repository
|
||||
type userAdminRepo struct {
|
||||
data *data.Data
|
||||
authRepo auth.AuthRepo
|
||||
}
|
||||
|
||||
// NewUserBackyardRepo new repository
|
||||
func NewUserBackyardRepo(data *data.Data, authRepo auth.AuthRepo) user_backyard.UserBackyardRepo {
|
||||
return &userBackyardRepo{
|
||||
// NewUserAdminRepo new repository
|
||||
func NewUserAdminRepo(data *data.Data, authRepo auth.AuthRepo) user_admin.UserAdminRepo {
|
||||
return &userAdminRepo{
|
||||
data: data,
|
||||
authRepo: authRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateUserStatus update user status
|
||||
func (ur *userBackyardRepo) UpdateUserStatus(ctx context.Context, userID string, userStatus, mailStatus int,
|
||||
func (ur *userAdminRepo) UpdateUserStatus(ctx context.Context, userID string, userStatus, mailStatus int,
|
||||
email string,
|
||||
) (err error) {
|
||||
cond := &entity.User{Status: userStatus, MailStatus: mailStatus, EMail: email}
|
||||
|
@ -62,7 +62,7 @@ func (ur *userBackyardRepo) UpdateUserStatus(ctx context.Context, userID string,
|
|||
}
|
||||
|
||||
// AddUser add user
|
||||
func (ur *userBackyardRepo) AddUser(ctx context.Context, user *entity.User) (err error) {
|
||||
func (ur *userAdminRepo) AddUser(ctx context.Context, user *entity.User) (err error) {
|
||||
_, err = ur.data.DB.Insert(user)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
|
@ -71,7 +71,7 @@ func (ur *userBackyardRepo) AddUser(ctx context.Context, user *entity.User) (err
|
|||
}
|
||||
|
||||
// UpdateUserPassword update user password
|
||||
func (ur *userBackyardRepo) UpdateUserPassword(ctx context.Context, userID string, password string) (err error) {
|
||||
func (ur *userAdminRepo) UpdateUserPassword(ctx context.Context, userID string, password string) (err error) {
|
||||
_, err = ur.data.DB.ID(userID).Update(&entity.User{Pass: password})
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
|
@ -80,7 +80,7 @@ func (ur *userBackyardRepo) UpdateUserPassword(ctx context.Context, userID strin
|
|||
}
|
||||
|
||||
// GetUserInfo get user info
|
||||
func (ur *userBackyardRepo) GetUserInfo(ctx context.Context, userID string) (user *entity.User, exist bool, err error) {
|
||||
func (ur *userAdminRepo) GetUserInfo(ctx context.Context, userID string) (user *entity.User, exist bool, err error) {
|
||||
user = &entity.User{}
|
||||
exist, err = ur.data.DB.ID(userID).Get(user)
|
||||
if err != nil {
|
||||
|
@ -90,7 +90,7 @@ func (ur *userBackyardRepo) GetUserInfo(ctx context.Context, userID string) (use
|
|||
}
|
||||
|
||||
// GetUserInfoByEmail get user info
|
||||
func (ur *userBackyardRepo) GetUserInfoByEmail(ctx context.Context, email string) (user *entity.User, exist bool, err error) {
|
||||
func (ur *userAdminRepo) GetUserInfoByEmail(ctx context.Context, email string) (user *entity.User, exist bool, err error) {
|
||||
userInfo := &entity.User{}
|
||||
exist, err = ur.data.DB.Where("e_mail = ?", email).
|
||||
Where("status != ?", entity.UserStatusDeleted).Get(userInfo)
|
||||
|
@ -101,7 +101,7 @@ func (ur *userBackyardRepo) GetUserInfoByEmail(ctx context.Context, email string
|
|||
}
|
||||
|
||||
// GetUserPage get user page
|
||||
func (ur *userBackyardRepo) GetUserPage(ctx context.Context, page, pageSize int, user *entity.User,
|
||||
func (ur *userAdminRepo) GetUserPage(ctx context.Context, page, pageSize int, user *entity.User,
|
||||
usernameOrDisplayName string, isStaff bool) (users []*entity.User, total int64, err error) {
|
||||
users = make([]*entity.User, 0)
|
||||
session := ur.data.DB.NewSession()
|
||||
|
|
|
@ -2,35 +2,35 @@ package router
|
|||
|
||||
import (
|
||||
"github.com/answerdev/answer/internal/controller"
|
||||
"github.com/answerdev/answer/internal/controller_backyard"
|
||||
"github.com/answerdev/answer/internal/controller_admin"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AnswerAPIRouter struct {
|
||||
langController *controller.LangController
|
||||
userController *controller.UserController
|
||||
commentController *controller.CommentController
|
||||
reportController *controller.ReportController
|
||||
voteController *controller.VoteController
|
||||
tagController *controller.TagController
|
||||
followController *controller.FollowController
|
||||
collectionController *controller.CollectionController
|
||||
questionController *controller.QuestionController
|
||||
answerController *controller.AnswerController
|
||||
searchController *controller.SearchController
|
||||
revisionController *controller.RevisionController
|
||||
rankController *controller.RankController
|
||||
backyardReportController *controller_backyard.ReportController
|
||||
backyardUserController *controller_backyard.UserBackyardController
|
||||
reasonController *controller.ReasonController
|
||||
themeController *controller_backyard.ThemeController
|
||||
siteInfoController *controller_backyard.SiteInfoController
|
||||
siteinfoController *controller.SiteinfoController
|
||||
notificationController *controller.NotificationController
|
||||
dashboardController *controller.DashboardController
|
||||
uploadController *controller.UploadController
|
||||
activityController *controller.ActivityController
|
||||
roleController *controller_backyard.RoleController
|
||||
langController *controller.LangController
|
||||
userController *controller.UserController
|
||||
commentController *controller.CommentController
|
||||
reportController *controller.ReportController
|
||||
voteController *controller.VoteController
|
||||
tagController *controller.TagController
|
||||
followController *controller.FollowController
|
||||
collectionController *controller.CollectionController
|
||||
questionController *controller.QuestionController
|
||||
answerController *controller.AnswerController
|
||||
searchController *controller.SearchController
|
||||
revisionController *controller.RevisionController
|
||||
rankController *controller.RankController
|
||||
adminReportController *controller_admin.ReportController
|
||||
adminUserController *controller_admin.UserAdminController
|
||||
reasonController *controller.ReasonController
|
||||
themeController *controller_admin.ThemeController
|
||||
siteInfoController *controller_admin.SiteInfoController
|
||||
siteinfoController *controller.SiteinfoController
|
||||
notificationController *controller.NotificationController
|
||||
dashboardController *controller.DashboardController
|
||||
uploadController *controller.UploadController
|
||||
activityController *controller.ActivityController
|
||||
roleController *controller_admin.RoleController
|
||||
}
|
||||
|
||||
func NewAnswerAPIRouter(
|
||||
|
@ -47,43 +47,43 @@ func NewAnswerAPIRouter(
|
|||
searchController *controller.SearchController,
|
||||
revisionController *controller.RevisionController,
|
||||
rankController *controller.RankController,
|
||||
backyardReportController *controller_backyard.ReportController,
|
||||
backyardUserController *controller_backyard.UserBackyardController,
|
||||
adminReportController *controller_admin.ReportController,
|
||||
adminUserController *controller_admin.UserAdminController,
|
||||
reasonController *controller.ReasonController,
|
||||
themeController *controller_backyard.ThemeController,
|
||||
siteInfoController *controller_backyard.SiteInfoController,
|
||||
themeController *controller_admin.ThemeController,
|
||||
siteInfoController *controller_admin.SiteInfoController,
|
||||
siteinfoController *controller.SiteinfoController,
|
||||
notificationController *controller.NotificationController,
|
||||
dashboardController *controller.DashboardController,
|
||||
uploadController *controller.UploadController,
|
||||
activityController *controller.ActivityController,
|
||||
roleController *controller_backyard.RoleController,
|
||||
roleController *controller_admin.RoleController,
|
||||
) *AnswerAPIRouter {
|
||||
return &AnswerAPIRouter{
|
||||
langController: langController,
|
||||
userController: userController,
|
||||
commentController: commentController,
|
||||
reportController: reportController,
|
||||
voteController: voteController,
|
||||
tagController: tagController,
|
||||
followController: followController,
|
||||
collectionController: collectionController,
|
||||
questionController: questionController,
|
||||
answerController: answerController,
|
||||
searchController: searchController,
|
||||
revisionController: revisionController,
|
||||
rankController: rankController,
|
||||
backyardReportController: backyardReportController,
|
||||
backyardUserController: backyardUserController,
|
||||
reasonController: reasonController,
|
||||
themeController: themeController,
|
||||
siteInfoController: siteInfoController,
|
||||
notificationController: notificationController,
|
||||
siteinfoController: siteinfoController,
|
||||
dashboardController: dashboardController,
|
||||
uploadController: uploadController,
|
||||
activityController: activityController,
|
||||
roleController: roleController,
|
||||
langController: langController,
|
||||
userController: userController,
|
||||
commentController: commentController,
|
||||
reportController: reportController,
|
||||
voteController: voteController,
|
||||
tagController: tagController,
|
||||
followController: followController,
|
||||
collectionController: collectionController,
|
||||
questionController: questionController,
|
||||
answerController: answerController,
|
||||
searchController: searchController,
|
||||
revisionController: revisionController,
|
||||
rankController: rankController,
|
||||
adminReportController: adminReportController,
|
||||
adminUserController: adminUserController,
|
||||
reasonController: reasonController,
|
||||
themeController: themeController,
|
||||
siteInfoController: siteInfoController,
|
||||
notificationController: notificationController,
|
||||
siteinfoController: siteinfoController,
|
||||
dashboardController: dashboardController,
|
||||
uploadController: uploadController,
|
||||
activityController: activityController,
|
||||
roleController: roleController,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,7 +194,7 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
|
|||
// answer
|
||||
r.POST("/answer", a.answerController.Add)
|
||||
r.PUT("/answer", a.answerController.Update)
|
||||
r.POST("/answer/acceptance", a.answerController.Adopted)
|
||||
r.POST("/answer/acceptance", a.answerController.Accepted)
|
||||
r.DELETE("/answer", a.answerController.RemoveAnswer)
|
||||
|
||||
// user
|
||||
|
@ -225,22 +225,22 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
|
|||
|
||||
}
|
||||
|
||||
func (a *AnswerAPIRouter) RegisterAnswerCmsAPIRouter(r *gin.RouterGroup) {
|
||||
r.GET("/question/page", a.questionController.CmsSearchList)
|
||||
func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) {
|
||||
r.GET("/question/page", a.questionController.AdminSearchList)
|
||||
r.PUT("/question/status", a.questionController.AdminSetQuestionStatus)
|
||||
r.GET("/answer/page", a.questionController.CmsSearchAnswerList)
|
||||
r.GET("/answer/page", a.questionController.AdminSearchAnswerList)
|
||||
r.PUT("/answer/status", a.answerController.AdminSetAnswerStatus)
|
||||
|
||||
// report
|
||||
r.GET("/reports/page", a.backyardReportController.ListReportPage)
|
||||
r.PUT("/report", a.backyardReportController.Handle)
|
||||
r.GET("/reports/page", a.adminReportController.ListReportPage)
|
||||
r.PUT("/report", a.adminReportController.Handle)
|
||||
|
||||
// user
|
||||
r.GET("/users/page", a.backyardUserController.GetUserPage)
|
||||
r.PUT("/user/status", a.backyardUserController.UpdateUserStatus)
|
||||
r.PUT("/user/role", a.backyardUserController.UpdateUserRole)
|
||||
r.POST("/user", a.backyardUserController.AddUser)
|
||||
r.PUT("/user/password", a.backyardUserController.UpdateUserPassword)
|
||||
r.GET("/users/page", a.adminUserController.GetUserPage)
|
||||
r.PUT("/user/status", a.adminUserController.UpdateUserStatus)
|
||||
r.PUT("/user/role", a.adminUserController.UpdateUserRole)
|
||||
r.POST("/user", a.adminUserController.AddUser)
|
||||
r.PUT("/user/password", a.adminUserController.UpdateUserPassword)
|
||||
|
||||
// reason
|
||||
r.GET("/reasons", a.reasonController.Reasons)
|
||||
|
|
|
@ -3,20 +3,20 @@ package router
|
|||
import (
|
||||
"github.com/answerdev/answer/internal/controller"
|
||||
templaterender "github.com/answerdev/answer/internal/controller/template_render"
|
||||
"github.com/answerdev/answer/internal/controller_backyard"
|
||||
"github.com/answerdev/answer/internal/controller_admin"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type TemplateRouter struct {
|
||||
templateController *controller.TemplateController
|
||||
templateRenderController *templaterender.TemplateRenderController
|
||||
siteInfoController *controller_backyard.SiteInfoController
|
||||
siteInfoController *controller_admin.SiteInfoController
|
||||
}
|
||||
|
||||
func NewTemplateRouter(
|
||||
templateController *controller.TemplateController,
|
||||
templateRenderController *templaterender.TemplateRenderController,
|
||||
siteInfoController *controller_backyard.SiteInfoController,
|
||||
siteInfoController *controller_admin.SiteInfoController,
|
||||
|
||||
) *TemplateRouter {
|
||||
return &TemplateRouter{
|
||||
|
|
|
@ -15,8 +15,8 @@ type RemoveAnswerReq struct {
|
|||
}
|
||||
|
||||
const (
|
||||
AnswerAdoptedFailed = 1
|
||||
AnswerAdoptedEnable = 2
|
||||
AnswerAcceptedFailed = 1
|
||||
AnswerAcceptedEnable = 2
|
||||
)
|
||||
|
||||
type AnswerAddReq struct {
|
||||
|
@ -74,7 +74,7 @@ type AnswerInfo struct {
|
|||
HTML string `json:"html" xorm:"html"` // html
|
||||
CreateTime int64 `json:"create_time" xorm:"created"` // create_time
|
||||
UpdateTime int64 `json:"update_time" xorm:"updated"` // update_time
|
||||
Adopted int `json:"adopted"` // 1 Failed 2 Adopted
|
||||
Accepted int `json:"accepted"` // 1 Failed 2 accepted
|
||||
UserID string `json:"-" `
|
||||
UpdateUserID string `json:"-" `
|
||||
UserInfo *UserBasicInfo `json:"user_info,omitempty"`
|
||||
|
@ -94,7 +94,7 @@ type AdminAnswerInfo struct {
|
|||
Description string `json:"description"`
|
||||
CreateTime int64 `json:"create_time"`
|
||||
UpdateTime int64 `json:"update_time"`
|
||||
Adopted int `json:"adopted"`
|
||||
Accepted int `json:"accepted"`
|
||||
UserID string `json:"-" `
|
||||
UpdateUserID string `json:"-" `
|
||||
UserInfo *UserBasicInfo `json:"user_info"`
|
||||
|
@ -104,7 +104,7 @@ type AdminAnswerInfo struct {
|
|||
} `json:"question_info"`
|
||||
}
|
||||
|
||||
type AnswerAdoptedReq struct {
|
||||
type AnswerAcceptedReq struct {
|
||||
QuestionID string `json:"question_id"`
|
||||
AnswerID string `json:"answer_id"`
|
||||
UserID string `json:"-" `
|
||||
|
|
|
@ -199,7 +199,7 @@ type GetCloseTypeResp struct {
|
|||
type UserAnswerInfo struct {
|
||||
AnswerID string `json:"answer_id"`
|
||||
QuestionID string `json:"question_id"`
|
||||
Adopted int `json:"adopted"`
|
||||
Accepted int `json:"accepted"`
|
||||
VoteCount int `json:"vote_count"`
|
||||
CreateTime int `json:"create_time"`
|
||||
UpdateTime int `json:"update_time"`
|
||||
|
@ -233,7 +233,7 @@ type QuestionSearch struct {
|
|||
UserID string `json:"-" form:"-"`
|
||||
}
|
||||
|
||||
type CmsQuestionSearch struct {
|
||||
type AdminQuestionSearch struct {
|
||||
Page int `json:"page" form:"page"` // Query number of pages
|
||||
PageSize int `json:"page_size" form:"page_size"` // Search page size
|
||||
Status int `json:"-" form:"-"`
|
||||
|
|
|
@ -36,7 +36,6 @@ func (r *SiteGeneralReq) FormatSiteUrl() {
|
|||
|
||||
// SiteInterfaceReq site interface request
|
||||
type SiteInterfaceReq struct {
|
||||
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"`
|
||||
}
|
||||
|
|
|
@ -15,11 +15,11 @@ type AnswerRepo interface {
|
|||
GetAnswer(ctx context.Context, id string) (answer *entity.Answer, exist bool, err error)
|
||||
GetAnswerList(ctx context.Context, answer *entity.Answer) (answerList []*entity.Answer, err error)
|
||||
GetAnswerPage(ctx context.Context, page, pageSize int, answer *entity.Answer) (answerList []*entity.Answer, total int64, err error)
|
||||
UpdateAdopted(ctx context.Context, id string, questionID string) error
|
||||
UpdateAccepted(ctx context.Context, id string, questionID string) error
|
||||
GetByID(ctx context.Context, id string) (*entity.Answer, bool, error)
|
||||
GetByUserIDQuestionID(ctx context.Context, userID string, questionID string) (*entity.Answer, bool, error)
|
||||
SearchList(ctx context.Context, search *entity.AnswerSearch) ([]*entity.Answer, int64, error)
|
||||
CmsSearchList(ctx context.Context, search *entity.CmsAnswerSearch) ([]*entity.Answer, int64, error)
|
||||
AdminSearchList(ctx context.Context, search *entity.AdminAnswerSearch) ([]*entity.Answer, int64, error)
|
||||
UpdateAnswerStatus(ctx context.Context, answer *entity.Answer) (err error)
|
||||
GetAnswerCount(ctx context.Context) (count int64, err error)
|
||||
}
|
||||
|
@ -43,11 +43,11 @@ func (as *AnswerCommon) SearchAnswered(ctx context.Context, userID, questionID s
|
|||
return has, nil
|
||||
}
|
||||
|
||||
func (as *AnswerCommon) CmsSearchList(ctx context.Context, search *entity.CmsAnswerSearch) ([]*entity.Answer, int64, error) {
|
||||
func (as *AnswerCommon) AdminSearchList(ctx context.Context, search *entity.AdminAnswerSearch) ([]*entity.Answer, int64, error) {
|
||||
if search.Status == 0 {
|
||||
search.Status = 1
|
||||
}
|
||||
return as.answerRepo.CmsSearchList(ctx, search)
|
||||
return as.answerRepo.AdminSearchList(ctx, search)
|
||||
}
|
||||
|
||||
func (as *AnswerCommon) Search(ctx context.Context, search *entity.AnswerSearch) ([]*entity.Answer, int64, error) {
|
||||
|
@ -64,7 +64,7 @@ func (as *AnswerCommon) ShowFormat(ctx context.Context, data *entity.Answer) *sc
|
|||
info.QuestionID = data.QuestionID
|
||||
info.Content = data.OriginalText
|
||||
info.HTML = data.ParsedText
|
||||
info.Adopted = data.Adopted
|
||||
info.Accepted = data.Accepted
|
||||
info.VoteCount = data.VoteCount
|
||||
info.CreateTime = data.CreatedAt.Unix()
|
||||
info.UpdateTime = data.UpdatedAt.Unix()
|
||||
|
@ -80,7 +80,7 @@ func (as *AnswerCommon) AdminShowFormat(ctx context.Context, data *entity.Answer
|
|||
info := schema.AdminAnswerInfo{}
|
||||
info.ID = data.ID
|
||||
info.QuestionID = data.QuestionID
|
||||
info.Adopted = data.Adopted
|
||||
info.Accepted = data.Accepted
|
||||
info.VoteCount = data.VoteCount
|
||||
info.CreateTime = data.CreatedAt.Unix()
|
||||
info.UpdateTime = data.UpdatedAt.Unix()
|
||||
|
|
|
@ -80,7 +80,7 @@ func (as *AnswerService) RemoveAnswer(ctx context.Context, req *schema.RemoveAns
|
|||
if answerInfo.VoteCount > 0 {
|
||||
return errors.BadRequest(reason.AnswerCannotDeleted)
|
||||
}
|
||||
if answerInfo.Adopted == schema.AnswerAdoptedEnable {
|
||||
if answerInfo.Accepted == schema.AnswerAcceptedEnable {
|
||||
return errors.BadRequest(reason.AnswerCannotDeleted)
|
||||
}
|
||||
questionInfo, exist, err := as.questionRepo.GetQuestion(ctx, answerInfo.QuestionID)
|
||||
|
@ -138,7 +138,7 @@ func (as *AnswerService) Insert(ctx context.Context, req *schema.AnswerAddReq) (
|
|||
insertData.UserID = req.UserID
|
||||
insertData.OriginalText = req.Content
|
||||
insertData.ParsedText = req.HTML
|
||||
insertData.Adopted = schema.AnswerAdoptedFailed
|
||||
insertData.Accepted = schema.AnswerAcceptedFailed
|
||||
insertData.QuestionID = req.QuestionID
|
||||
insertData.RevisionID = "0"
|
||||
insertData.LastEditUserID = "0"
|
||||
|
@ -285,8 +285,8 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq
|
|||
return insertData.ID, nil
|
||||
}
|
||||
|
||||
// UpdateAdopted
|
||||
func (as *AnswerService) UpdateAdopted(ctx context.Context, req *schema.AnswerAdoptedReq) error {
|
||||
// UpdateAccepted
|
||||
func (as *AnswerService) UpdateAccepted(ctx context.Context, req *schema.AnswerAcceptedReq) error {
|
||||
if req.AnswerID == "" {
|
||||
req.AnswerID = "0"
|
||||
}
|
||||
|
@ -330,7 +330,7 @@ func (as *AnswerService) UpdateAdopted(ctx context.Context, req *schema.AnswerAd
|
|||
}
|
||||
}
|
||||
|
||||
err = as.answerRepo.UpdateAdopted(ctx, req.AnswerID, req.QuestionID)
|
||||
err = as.answerRepo.UpdateAccepted(ctx, req.AnswerID, req.QuestionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -413,7 +413,7 @@ func (as *AnswerService) Get(ctx context.Context, answerID, loginUserID string)
|
|||
}
|
||||
|
||||
func (as *AnswerService) AdminSetAnswerStatus(ctx context.Context, req *schema.AdminSetAnswerStatusRequest) error {
|
||||
setStatus, ok := entity.CmsAnswerSearchStatus[req.StatusStr]
|
||||
setStatus, ok := entity.AdminAnswerSearchStatus[req.StatusStr]
|
||||
if !ok {
|
||||
return fmt.Errorf("question status does not exist")
|
||||
}
|
||||
|
|
|
@ -16,9 +16,9 @@ type AuthRepo interface {
|
|||
SetUserStatus(ctx context.Context, userID string, userInfo *entity.UserCacheInfo) (err error)
|
||||
GetUserStatus(ctx context.Context, userID string) (userInfo *entity.UserCacheInfo, err error)
|
||||
RemoveUserStatus(ctx context.Context, userID string) (err error)
|
||||
GetBackyardUserCacheInfo(ctx context.Context, accessToken string) (userInfo *entity.UserCacheInfo, err error)
|
||||
SetBackyardUserCacheInfo(ctx context.Context, accessToken string, userInfo *entity.UserCacheInfo) error
|
||||
RemoveBackyardUserCacheInfo(ctx context.Context, accessToken string) (err error)
|
||||
GetAdminUserCacheInfo(ctx context.Context, accessToken string) (userInfo *entity.UserCacheInfo, err error)
|
||||
SetAdminUserCacheInfo(ctx context.Context, accessToken string, userInfo *entity.UserCacheInfo) error
|
||||
RemoveAdminUserCacheInfo(ctx context.Context, accessToken string) (err error)
|
||||
AddUserTokenMapping(ctx context.Context, userID, accessToken string) (err error)
|
||||
RemoveAllUserTokens(ctx context.Context, userID string)
|
||||
}
|
||||
|
@ -90,17 +90,17 @@ func (as *AuthService) RemoveAllUserTokens(ctx context.Context, userID string) {
|
|||
as.authRepo.RemoveAllUserTokens(ctx, userID)
|
||||
}
|
||||
|
||||
//cms
|
||||
//Admin
|
||||
|
||||
func (as *AuthService) GetCmsUserCacheInfo(ctx context.Context, accessToken string) (userInfo *entity.UserCacheInfo, err error) {
|
||||
return as.authRepo.GetBackyardUserCacheInfo(ctx, accessToken)
|
||||
func (as *AuthService) GetAdminUserCacheInfo(ctx context.Context, accessToken string) (userInfo *entity.UserCacheInfo, err error) {
|
||||
return as.authRepo.GetAdminUserCacheInfo(ctx, accessToken)
|
||||
}
|
||||
|
||||
func (as *AuthService) SetCmsUserCacheInfo(ctx context.Context, accessToken string, userInfo *entity.UserCacheInfo) (err error) {
|
||||
err = as.authRepo.SetBackyardUserCacheInfo(ctx, accessToken, userInfo)
|
||||
func (as *AuthService) SetAdminUserCacheInfo(ctx context.Context, accessToken string, userInfo *entity.UserCacheInfo) (err error) {
|
||||
err = as.authRepo.SetAdminUserCacheInfo(ctx, accessToken, userInfo)
|
||||
return err
|
||||
}
|
||||
|
||||
func (as *AuthService) RemoveCmsUserCacheInfo(ctx context.Context, accessToken string) (err error) {
|
||||
return as.authRepo.RemoveBackyardUserCacheInfo(ctx, accessToken)
|
||||
func (as *AuthService) RemoveAdminUserCacheInfo(ctx context.Context, accessToken string) (err error) {
|
||||
return as.authRepo.RemoveAdminUserCacheInfo(ctx, accessToken)
|
||||
}
|
||||
|
|
|
@ -168,17 +168,11 @@ func (cs *CommentService) AddComment(ctx context.Context, req *schema.AddComment
|
|||
|
||||
// RemoveComment delete comment
|
||||
func (cs *CommentService) RemoveComment(ctx context.Context, req *schema.RemoveCommentReq) (err error) {
|
||||
if err := cs.checkCommentWhetherOwner(ctx, req.UserID, req.CommentID); err != nil {
|
||||
return err
|
||||
}
|
||||
return cs.commentRepo.RemoveComment(ctx, req.CommentID)
|
||||
}
|
||||
|
||||
// UpdateComment update comment
|
||||
func (cs *CommentService) UpdateComment(ctx context.Context, req *schema.UpdateCommentReq) (err error) {
|
||||
if err := cs.checkCommentWhetherOwner(ctx, req.UserID, req.CommentID); err != nil {
|
||||
return err
|
||||
}
|
||||
comment := &entity.Comment{}
|
||||
_ = copier.Copy(comment, req)
|
||||
comment.ID = req.CommentID
|
||||
|
|
|
@ -191,7 +191,7 @@ func (ns *NotificationCommon) SendNotificationToAllFollower(ctx context.Context,
|
|||
if msg.NotificationAction != constant.UpdateQuestion &&
|
||||
msg.NotificationAction != constant.AnswerTheQuestion &&
|
||||
msg.NotificationAction != constant.UpdateAnswer &&
|
||||
msg.NotificationAction != constant.AdoptAnswer {
|
||||
msg.NotificationAction != constant.AcceptAnswer {
|
||||
return
|
||||
}
|
||||
condObjectID := msg.ObjectID
|
||||
|
|
|
@ -20,8 +20,8 @@ import (
|
|||
"github.com/answerdev/answer/internal/service/rank"
|
||||
"github.com/answerdev/answer/internal/service/reason"
|
||||
"github.com/answerdev/answer/internal/service/report"
|
||||
"github.com/answerdev/answer/internal/service/report_backyard"
|
||||
"github.com/answerdev/answer/internal/service/report_handle_backyard"
|
||||
"github.com/answerdev/answer/internal/service/report_admin"
|
||||
"github.com/answerdev/answer/internal/service/report_handle_admin"
|
||||
"github.com/answerdev/answer/internal/service/revision_common"
|
||||
"github.com/answerdev/answer/internal/service/role"
|
||||
"github.com/answerdev/answer/internal/service/search_parser"
|
||||
|
@ -30,7 +30,7 @@ import (
|
|||
"github.com/answerdev/answer/internal/service/tag"
|
||||
tagcommon "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_admin"
|
||||
usercommon "github.com/answerdev/answer/internal/service/user_common"
|
||||
"github.com/google/wire"
|
||||
)
|
||||
|
@ -64,9 +64,9 @@ var ProviderSetService = wire.NewSet(
|
|||
NewSearchService,
|
||||
meta.NewMetaService,
|
||||
object_info.NewObjService,
|
||||
report_handle_backyard.NewReportHandle,
|
||||
report_backyard.NewReportBackyardService,
|
||||
user_backyard.NewUserBackyardService,
|
||||
report_handle_admin.NewReportHandle,
|
||||
report_admin.NewReportAdminService,
|
||||
user_admin.NewUserAdminService,
|
||||
reason.NewReasonService,
|
||||
siteinfo_common.NewSiteInfoCommonService,
|
||||
siteinfo.NewSiteInfoService,
|
||||
|
|
|
@ -40,7 +40,7 @@ type QuestionRepo interface {
|
|||
UpdateAccepted(ctx context.Context, question *entity.Question) (err error)
|
||||
UpdateLastAnswer(ctx context.Context, question *entity.Question) (err error)
|
||||
FindByID(ctx context.Context, id []string) (questionList []*entity.Question, err error)
|
||||
CmsSearchList(ctx context.Context, search *schema.CmsQuestionSearch) ([]*entity.Question, int64, error)
|
||||
AdminSearchList(ctx context.Context, search *schema.AdminQuestionSearch) ([]*entity.Question, int64, error)
|
||||
GetQuestionCount(ctx context.Context) (count int64, err error)
|
||||
GetQuestionIDsPage(ctx context.Context, page, pageSize int) (questionIDList []*schema.SiteMapQuestionInfo, err error)
|
||||
}
|
||||
|
|
|
@ -158,9 +158,66 @@ func (qs *QuestionService) AddQuestionCheckTags(ctx context.Context, Tags []*ent
|
|||
}
|
||||
return []string{}, nil
|
||||
}
|
||||
func (qs *QuestionService) CheckAddQuestion(ctx context.Context, req *schema.QuestionAdd) (errorlist any, err error) {
|
||||
if len(req.Tags) == 0 {
|
||||
errorlist := make([]*validator.FormErrorField, 0)
|
||||
errorlist = append(errorlist, &validator.FormErrorField{
|
||||
ErrorField: "tags",
|
||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), reason.TagNotFound),
|
||||
})
|
||||
err = errors.BadRequest(reason.RecommendTagEnter)
|
||||
return errorlist, err
|
||||
}
|
||||
recommendExist, err := qs.tagCommon.ExistRecommend(ctx, req.Tags)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !recommendExist {
|
||||
errorlist := make([]*validator.FormErrorField, 0)
|
||||
errorlist = append(errorlist, &validator.FormErrorField{
|
||||
ErrorField: "tags",
|
||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), reason.RecommendTagEnter),
|
||||
})
|
||||
err = errors.BadRequest(reason.RecommendTagEnter)
|
||||
return errorlist, err
|
||||
}
|
||||
|
||||
tagNameList := make([]string, 0)
|
||||
for _, tag := range req.Tags {
|
||||
tagNameList = append(tagNameList, tag.SlugName)
|
||||
}
|
||||
Tags, tagerr := qs.tagCommon.GetTagListByNames(ctx, tagNameList)
|
||||
if tagerr != nil {
|
||||
return errorlist, tagerr
|
||||
}
|
||||
if !req.QuestionPermission.CanUseReservedTag {
|
||||
taglist, err := qs.AddQuestionCheckTags(ctx, Tags)
|
||||
errMsg := fmt.Sprintf(`"%s" can only be used by moderators.`,
|
||||
strings.Join(taglist, ","))
|
||||
if err != nil {
|
||||
errorlist := make([]*validator.FormErrorField, 0)
|
||||
errorlist = append(errorlist, &validator.FormErrorField{
|
||||
ErrorField: "tags",
|
||||
ErrorMsg: errMsg,
|
||||
})
|
||||
err = errors.BadRequest(reason.RecommendTagEnter)
|
||||
return errorlist, err
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// AddQuestion add question
|
||||
func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.QuestionAdd) (questionInfo any, err error) {
|
||||
if len(req.Tags) == 0 {
|
||||
errorlist := make([]*validator.FormErrorField, 0)
|
||||
errorlist = append(errorlist, &validator.FormErrorField{
|
||||
ErrorField: "tags",
|
||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), reason.TagNotFound),
|
||||
})
|
||||
err = errors.BadRequest(reason.RecommendTagEnter)
|
||||
return errorlist, err
|
||||
}
|
||||
recommendExist, err := qs.tagCommon.ExistRecommend(ctx, req.Tags)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -610,7 +667,7 @@ func (qs *QuestionService) SearchUserList(ctx context.Context, userName, order s
|
|||
for _, item := range questionlist {
|
||||
info := &schema.UserQuestionInfo{}
|
||||
_ = copier.Copy(info, item)
|
||||
status, ok := entity.CmsQuestionSearchStatusIntToString[item.Status]
|
||||
status, ok := entity.AdminQuestionSearchStatusIntToString[item.Status]
|
||||
if ok {
|
||||
info.Status = status
|
||||
}
|
||||
|
@ -787,7 +844,7 @@ func (qs *QuestionService) SearchByTitleLike(ctx context.Context, title string,
|
|||
item.AnswerCount = question.AnswerCount
|
||||
item.CollectionCount = question.CollectionCount
|
||||
item.FollowCount = question.FollowCount
|
||||
status, ok := entity.CmsQuestionSearchStatusIntToString[question.Status]
|
||||
status, ok := entity.AdminQuestionSearchStatusIntToString[question.Status]
|
||||
if ok {
|
||||
item.Status = status
|
||||
}
|
||||
|
@ -855,7 +912,7 @@ func (qs *QuestionService) SearchList(ctx context.Context, req *schema.QuestionS
|
|||
}
|
||||
|
||||
func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, questionID string, setStatusStr string) error {
|
||||
setStatus, ok := entity.CmsQuestionSearchStatus[setStatusStr]
|
||||
setStatus, ok := entity.AdminQuestionSearchStatus[setStatusStr]
|
||||
if !ok {
|
||||
return fmt.Errorf("question status does not exist")
|
||||
}
|
||||
|
@ -910,10 +967,10 @@ func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, questionI
|
|||
return nil
|
||||
}
|
||||
|
||||
func (qs *QuestionService) CmsSearchList(ctx context.Context, search *schema.CmsQuestionSearch, loginUserID string) ([]*schema.AdminQuestionInfo, int64, error) {
|
||||
func (qs *QuestionService) AdminSearchList(ctx context.Context, search *schema.AdminQuestionSearch, loginUserID string) ([]*schema.AdminQuestionInfo, int64, error) {
|
||||
list := make([]*schema.AdminQuestionInfo, 0)
|
||||
|
||||
status, ok := entity.CmsQuestionSearchStatus[search.StatusStr]
|
||||
status, ok := entity.AdminQuestionSearchStatus[search.StatusStr]
|
||||
if ok {
|
||||
search.Status = status
|
||||
}
|
||||
|
@ -921,7 +978,7 @@ func (qs *QuestionService) CmsSearchList(ctx context.Context, search *schema.Cms
|
|||
if search.Status == 0 {
|
||||
search.Status = 1
|
||||
}
|
||||
dblist, count, err := qs.questionRepo.CmsSearchList(ctx, search)
|
||||
dblist, count, err := qs.questionRepo.AdminSearchList(ctx, search)
|
||||
if err != nil {
|
||||
return list, count, err
|
||||
}
|
||||
|
@ -949,11 +1006,11 @@ func (qs *QuestionService) CmsSearchList(ctx context.Context, search *schema.Cms
|
|||
return list, count, nil
|
||||
}
|
||||
|
||||
// CmsSearchList
|
||||
func (qs *QuestionService) CmsSearchAnswerList(ctx context.Context, search *entity.CmsAnswerSearch, loginUserID string) ([]*schema.AdminAnswerInfo, int64, error) {
|
||||
// AdminSearchList
|
||||
func (qs *QuestionService) AdminSearchAnswerList(ctx context.Context, search *entity.AdminAnswerSearch, loginUserID string) ([]*schema.AdminAnswerInfo, int64, error) {
|
||||
answerlist := make([]*schema.AdminAnswerInfo, 0)
|
||||
|
||||
status, ok := entity.CmsAnswerSearchStatus[search.StatusStr]
|
||||
status, ok := entity.AdminAnswerSearchStatus[search.StatusStr]
|
||||
if ok {
|
||||
search.Status = status
|
||||
}
|
||||
|
@ -961,7 +1018,7 @@ func (qs *QuestionService) CmsSearchAnswerList(ctx context.Context, search *enti
|
|||
if search.Status == 0 {
|
||||
search.Status = 1
|
||||
}
|
||||
dblist, count, err := qs.questioncommon.AnswerCommon.CmsSearchList(ctx, search)
|
||||
dblist, count, err := qs.questioncommon.AnswerCommon.AdminSearchList(ctx, search)
|
||||
if err != nil {
|
||||
return answerlist, count, err
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package report_backyard
|
||||
package report_admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -16,35 +16,35 @@ import (
|
|||
"github.com/answerdev/answer/internal/service/comment_common"
|
||||
questioncommon "github.com/answerdev/answer/internal/service/question_common"
|
||||
"github.com/answerdev/answer/internal/service/report_common"
|
||||
"github.com/answerdev/answer/internal/service/report_handle_backyard"
|
||||
"github.com/answerdev/answer/internal/service/report_handle_admin"
|
||||
usercommon "github.com/answerdev/answer/internal/service/user_common"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
)
|
||||
|
||||
// ReportBackyardService user service
|
||||
type ReportBackyardService struct {
|
||||
// ReportAdminService user service
|
||||
type ReportAdminService struct {
|
||||
reportRepo report_common.ReportRepo
|
||||
commonUser *usercommon.UserCommon
|
||||
commonRepo *common.CommonRepo
|
||||
answerRepo answercommon.AnswerRepo
|
||||
questionRepo questioncommon.QuestionRepo
|
||||
commentCommonRepo comment_common.CommentCommonRepo
|
||||
reportHandle *report_handle_backyard.ReportHandle
|
||||
reportHandle *report_handle_admin.ReportHandle
|
||||
configRepo config.ConfigRepo
|
||||
}
|
||||
|
||||
// NewReportBackyardService new report service
|
||||
func NewReportBackyardService(
|
||||
// NewReportAdminService new report service
|
||||
func NewReportAdminService(
|
||||
reportRepo report_common.ReportRepo,
|
||||
commonUser *usercommon.UserCommon,
|
||||
commonRepo *common.CommonRepo,
|
||||
answerRepo answercommon.AnswerRepo,
|
||||
questionRepo questioncommon.QuestionRepo,
|
||||
commentCommonRepo comment_common.CommentCommonRepo,
|
||||
reportHandle *report_handle_backyard.ReportHandle,
|
||||
configRepo config.ConfigRepo) *ReportBackyardService {
|
||||
return &ReportBackyardService{
|
||||
reportHandle *report_handle_admin.ReportHandle,
|
||||
configRepo config.ConfigRepo) *ReportAdminService {
|
||||
return &ReportAdminService{
|
||||
reportRepo: reportRepo,
|
||||
commonUser: commonUser,
|
||||
commonRepo: commonRepo,
|
||||
|
@ -57,7 +57,7 @@ func NewReportBackyardService(
|
|||
}
|
||||
|
||||
// ListReportPage list report pages
|
||||
func (rs *ReportBackyardService) ListReportPage(ctx context.Context, dto schema.GetReportListPageDTO) (pageModel *pager.PageModel, err error) {
|
||||
func (rs *ReportAdminService) ListReportPage(ctx context.Context, dto schema.GetReportListPageDTO) (pageModel *pager.PageModel, err error) {
|
||||
var (
|
||||
resp []*schema.GetReportListPageResp
|
||||
flags []entity.Report
|
||||
|
@ -105,7 +105,7 @@ func (rs *ReportBackyardService) ListReportPage(ctx context.Context, dto schema.
|
|||
}
|
||||
|
||||
// HandleReported handle the reported object
|
||||
func (rs *ReportBackyardService) HandleReported(ctx context.Context, req schema.ReportHandleReq) (err error) {
|
||||
func (rs *ReportAdminService) HandleReported(ctx context.Context, req schema.ReportHandleReq) (err error) {
|
||||
var (
|
||||
reported *entity.Report
|
||||
handleData = entity.Report{
|
||||
|
@ -139,7 +139,7 @@ func (rs *ReportBackyardService) HandleReported(ctx context.Context, req schema.
|
|||
return
|
||||
}
|
||||
|
||||
func (rs *ReportBackyardService) parseObject(ctx context.Context, resp *[]*schema.GetReportListPageResp) {
|
||||
func (rs *ReportAdminService) parseObject(ctx context.Context, resp *[]*schema.GetReportListPageResp) {
|
||||
var (
|
||||
res = *resp
|
||||
)
|
|
@ -1,4 +1,4 @@
|
|||
package report_handle_backyard
|
||||
package report_handle_admin
|
||||
|
||||
import (
|
||||
"context"
|
|
@ -114,23 +114,10 @@ func (s *SiteInfoService) SaveSiteGeneral(ctx context.Context, req schema.SiteGe
|
|||
|
||||
func (s *SiteInfoService) SaveSiteInterface(ctx context.Context, req schema.SiteInterfaceReq) (err error) {
|
||||
var (
|
||||
siteType = "interface"
|
||||
themeExist bool
|
||||
content []byte
|
||||
siteType = "interface"
|
||||
content []byte
|
||||
)
|
||||
|
||||
// check theme
|
||||
for _, theme := range schema.GetThemeOptions {
|
||||
if theme.Value == req.Theme {
|
||||
themeExist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !themeExist {
|
||||
err = errors.BadRequest(reason.ThemeNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// check language
|
||||
if !translator.CheckLanguageIsValid(req.Language) {
|
||||
err = errors.BadRequest(reason.LangNotFound)
|
||||
|
|
|
@ -320,13 +320,13 @@ func (ts *TagService) GetTagWithPage(ctx context.Context, req *schema.GetTagWith
|
|||
|
||||
resp := make([]*schema.GetTagPageResp, 0)
|
||||
for _, tag := range tags {
|
||||
excerpt := htmltext.FetchExcerpt(tag.ParsedText, "...", 240)
|
||||
//excerpt := htmltext.FetchExcerpt(tag.ParsedText, "...", 240)
|
||||
resp = append(resp, &schema.GetTagPageResp{
|
||||
TagID: tag.ID,
|
||||
SlugName: tag.SlugName,
|
||||
DisplayName: tag.DisplayName,
|
||||
OriginalText: excerpt,
|
||||
ParsedText: excerpt,
|
||||
OriginalText: tag.OriginalText,
|
||||
ParsedText: tag.ParsedText,
|
||||
FollowCount: tag.FollowCount,
|
||||
QuestionCount: tag.QuestionCount,
|
||||
IsFollower: ts.checkTagIsFollow(ctx, req.UserID, tag.ID),
|
||||
|
|
|
@ -661,7 +661,9 @@ func (ts *TagCommonService) UpdateTag(ctx context.Context, req *schema.UpdateTag
|
|||
return errors.BadRequest(reason.TagNotFound)
|
||||
}
|
||||
//If the content is the same, ignore it
|
||||
if tagInfo.OriginalText == req.OriginalText {
|
||||
if tagInfo.OriginalText == req.OriginalText &&
|
||||
tagInfo.DisplayName == req.DisplayName &&
|
||||
tagInfo.SlugName == req.SlugName {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package user_backyard
|
||||
package user_admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -21,8 +21,8 @@ import (
|
|||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// UserBackyardRepo user repository
|
||||
type UserBackyardRepo interface {
|
||||
// UserAdminRepo user repository
|
||||
type UserAdminRepo interface {
|
||||
UpdateUserStatus(ctx context.Context, userID string, userStatus, mailStatus int, email string) (err error)
|
||||
GetUserInfo(ctx context.Context, userID string) (user *entity.User, exist bool, err error)
|
||||
GetUserInfoByEmail(ctx context.Context, email string) (user *entity.User, exist bool, err error)
|
||||
|
@ -32,22 +32,22 @@ type UserBackyardRepo interface {
|
|||
UpdateUserPassword(ctx context.Context, userID string, password string) (err error)
|
||||
}
|
||||
|
||||
// UserBackyardService user service
|
||||
type UserBackyardService struct {
|
||||
userRepo UserBackyardRepo
|
||||
// UserAdminService user service
|
||||
type UserAdminService struct {
|
||||
userRepo UserAdminRepo
|
||||
userRoleRelService *role.UserRoleRelService
|
||||
authService *auth.AuthService
|
||||
userCommonService *usercommon.UserCommon
|
||||
}
|
||||
|
||||
// NewUserBackyardService new user backyard service
|
||||
func NewUserBackyardService(
|
||||
userRepo UserBackyardRepo,
|
||||
// NewUserAdminService new user admin service
|
||||
func NewUserAdminService(
|
||||
userRepo UserAdminRepo,
|
||||
userRoleRelService *role.UserRoleRelService,
|
||||
authService *auth.AuthService,
|
||||
userCommonService *usercommon.UserCommon,
|
||||
) *UserBackyardService {
|
||||
return &UserBackyardService{
|
||||
) *UserAdminService {
|
||||
return &UserAdminService{
|
||||
userRepo: userRepo,
|
||||
userRoleRelService: userRoleRelService,
|
||||
authService: authService,
|
||||
|
@ -56,7 +56,7 @@ func NewUserBackyardService(
|
|||
}
|
||||
|
||||
// UpdateUserStatus update user
|
||||
func (us *UserBackyardService) UpdateUserStatus(ctx context.Context, req *schema.UpdateUserStatusReq) (err error) {
|
||||
func (us *UserAdminService) UpdateUserStatus(ctx context.Context, req *schema.UpdateUserStatusReq) (err error) {
|
||||
userInfo, exist, err := us.userRepo.GetUserInfo(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -87,7 +87,7 @@ func (us *UserBackyardService) UpdateUserStatus(ctx context.Context, req *schema
|
|||
}
|
||||
|
||||
// UpdateUserRole update user role
|
||||
func (us *UserBackyardService) UpdateUserRole(ctx context.Context, req *schema.UpdateUserRoleReq) (err error) {
|
||||
func (us *UserAdminService) UpdateUserRole(ctx context.Context, req *schema.UpdateUserRoleReq) (err error) {
|
||||
// Users cannot modify their roles
|
||||
if req.UserID == req.LoginUserID {
|
||||
return errors.BadRequest(reason.UserCannotUpdateYourRole)
|
||||
|
@ -103,7 +103,7 @@ func (us *UserBackyardService) UpdateUserRole(ctx context.Context, req *schema.U
|
|||
}
|
||||
|
||||
// AddUser add user
|
||||
func (us *UserBackyardService) AddUser(ctx context.Context, req *schema.AddUserReq) (err error) {
|
||||
func (us *UserAdminService) AddUser(ctx context.Context, req *schema.AddUserReq) (err error) {
|
||||
_, has, err := us.userRepo.GetUserInfoByEmail(ctx, req.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -138,7 +138,7 @@ func (us *UserBackyardService) AddUser(ctx context.Context, req *schema.AddUserR
|
|||
}
|
||||
|
||||
// UpdateUserPassword update user password
|
||||
func (us *UserBackyardService) UpdateUserPassword(ctx context.Context, req *schema.UpdateUserPasswordReq) (err error) {
|
||||
func (us *UserAdminService) UpdateUserPassword(ctx context.Context, req *schema.UpdateUserPasswordReq) (err error) {
|
||||
userInfo, exist, err := us.userRepo.GetUserInfo(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -162,7 +162,7 @@ func (us *UserBackyardService) UpdateUserPassword(ctx context.Context, req *sche
|
|||
}
|
||||
|
||||
// GetUserInfo get user one
|
||||
func (us *UserBackyardService) GetUserInfo(ctx context.Context, userID string) (resp *schema.GetUserInfoResp, err error) {
|
||||
func (us *UserAdminService) GetUserInfo(ctx context.Context, userID string) (resp *schema.GetUserInfoResp, err error) {
|
||||
user, exist, err := us.userRepo.GetUserInfo(ctx, userID)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -177,7 +177,7 @@ func (us *UserBackyardService) GetUserInfo(ctx context.Context, userID string) (
|
|||
}
|
||||
|
||||
// GetUserPage get user list page
|
||||
func (us *UserBackyardService) GetUserPage(ctx context.Context, req *schema.GetUserPageReq) (pageModel *pager.PageModel, err error) {
|
||||
func (us *UserAdminService) GetUserPage(ctx context.Context, req *schema.GetUserPageReq) (pageModel *pager.PageModel, err error) {
|
||||
user := &entity.User{}
|
||||
_ = copier.Copy(user, req)
|
||||
|
||||
|
@ -246,7 +246,7 @@ func (us *UserBackyardService) GetUserPage(ctx context.Context, req *schema.GetU
|
|||
return pager.NewPageModel(total, resp), nil
|
||||
}
|
||||
|
||||
func (us *UserBackyardService) setUserRoleInfo(ctx context.Context, resp []*schema.GetUserPageResp) {
|
||||
func (us *UserAdminService) setUserRoleInfo(ctx context.Context, resp []*schema.GetUserPageResp) {
|
||||
var userIDs []string
|
||||
for _, u := range resp {
|
||||
userIDs = append(userIDs, u.UserID)
|
|
@ -138,7 +138,7 @@ func (us *UserService) EmailLogin(ctx context.Context, req *schema.UserEmailLogi
|
|||
}
|
||||
resp.IsAdmin = userCacheInfo.IsAdmin
|
||||
if resp.IsAdmin {
|
||||
err = us.authService.SetCmsUserCacheInfo(ctx, resp.AccessToken, userCacheInfo)
|
||||
err = us.authService.SetAdminUserCacheInfo(ctx, resp.AccessToken, userCacheInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -355,7 +355,7 @@ func (us *UserService) UserRegisterByEmail(ctx context.Context, registerUserInfo
|
|||
}
|
||||
resp.IsAdmin = userCacheInfo.IsAdmin
|
||||
if resp.IsAdmin {
|
||||
err = us.authService.SetCmsUserCacheInfo(ctx, resp.AccessToken, &entity.UserCacheInfo{UserID: userInfo.ID})
|
||||
err = us.authService.SetAdminUserCacheInfo(ctx, resp.AccessToken, &entity.UserCacheInfo{UserID: userInfo.ID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -451,7 +451,7 @@ func (us *UserService) UserVerifyEmail(ctx context.Context, req *schema.UserVeri
|
|||
}
|
||||
resp.IsAdmin = userCacheInfo.IsAdmin
|
||||
if resp.IsAdmin {
|
||||
err = us.authService.SetCmsUserCacheInfo(ctx, resp.AccessToken, &entity.UserCacheInfo{UserID: userInfo.ID})
|
||||
err = us.authService.SetAdminUserCacheInfo(ctx, resp.AccessToken, &entity.UserCacheInfo{UserID: userInfo.ID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -1,11 +1,30 @@
|
|||
package converter
|
||||
|
||||
import (
|
||||
"github.com/gomarkdown/markdown"
|
||||
"bytes"
|
||||
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
"github.com/yuin/goldmark/renderer/html"
|
||||
)
|
||||
|
||||
// Markdown2HTML convert markdown to html
|
||||
func Markdown2HTML(md string) string {
|
||||
html := markdown.ToHTML([]byte(md), nil, nil)
|
||||
return string(html)
|
||||
func Markdown2HTML(source string) string {
|
||||
mdConverter := goldmark.New(
|
||||
goldmark.WithExtensions(extension.GFM),
|
||||
goldmark.WithParserOptions(
|
||||
parser.WithAutoHeadingID(),
|
||||
),
|
||||
goldmark.WithRendererOptions(
|
||||
html.WithHardWraps(),
|
||||
),
|
||||
)
|
||||
var buf bytes.Buffer
|
||||
if err := mdConverter.Convert([]byte(source), &buf); err != nil {
|
||||
log.Error(err)
|
||||
return source
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
|
|
@ -102,9 +102,9 @@ All our translations are placed in the i18n directory.
|
|||
|
||||
## 📱Environment Support
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Safari |
|
||||
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br />Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br />Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br />Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br />Safari |
|
||||
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
||||
|
||||
## ⭐ Built with
|
||||
- [TypeScript](https://www.typescriptlang.org/) - strongly typed JavaScript
|
||||
|
|
|
@ -1,44 +1 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<title>Answer</title>
|
||||
<script defer="defer" src="/static/js/main.9de9552b.js"></script>
|
||||
<link href="/static/css/main.d4180d41.css" rel="stylesheet">
|
||||
<link href="/custom.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root">
|
||||
<style>
|
||||
@keyframes _doc-spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
#doc-spinner {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
#doc-spinner .spinner {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
vertical-align: -0.125em;
|
||||
border: 0.25rem solid currentColor;
|
||||
border-right-color: transparent;
|
||||
color: rgba(108, 117, 125, 0.75);
|
||||
border-radius: 50%;
|
||||
animation: 0.75s linear infinite _doc-spin;
|
||||
}
|
||||
</style>
|
||||
<div id="doc-spinner"><div class="spinner"></div></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<!doctype html><html><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><script defer="defer" src="/static/js/main.554b9f62.js"></script><link href="/static/css/main.401dc3ca.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><div id="spin-mask" hidden><script>try{document.querySelector("#spin-mask").removeAttribute("hidden")}catch(e){}</script><style>@keyframes _doc-spin{to{transform:rotate(360deg)}}#spin-mask{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#fff;z-index:9999}#spin-container{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}#spin-container .spinner{box-sizing:border-box;display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25rem solid currentColor;border-right-color:transparent;color:rgba(108,117,125,.75);border-radius:50%;animation:.75s linear infinite _doc-spin}</style><div id="spin-container"><div class="spinner"></div></div></div></div></body></html>
|
|
@ -29,18 +29,18 @@ module.exports = {
|
|||
devServer: function(configFunction) {
|
||||
return function(proxy, allowedHost) {
|
||||
const config = configFunction(proxy, allowedHost);
|
||||
config.proxy = {
|
||||
'/answer': {
|
||||
config.proxy = [
|
||||
{
|
||||
context: ['/answer', '/installation'],
|
||||
target: process.env.REACT_APP_API_URL,
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
'/installation': {
|
||||
{
|
||||
context: ['/custom.css'],
|
||||
target: process.env.REACT_APP_API_URL,
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
];
|
||||
return config;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,12 +9,14 @@
|
|||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root">
|
||||
<div id="spin-mask" hidden>
|
||||
<script>
|
||||
try {
|
||||
document.querySelector('#spin-mask').removeAttribute('hidden');
|
||||
} catch (ex){}
|
||||
</script>
|
||||
<div id="spin-mask">
|
||||
<noscript>
|
||||
<style>
|
||||
#spin-mask {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
</noscript>
|
||||
<style>
|
||||
@keyframes _doc-spin {
|
||||
to { transform: rotate(360deg) }
|
||||
|
|
|
@ -10,6 +10,39 @@ const makeMarker = (mark) => {
|
|||
return `<!--${mark}-->`;
|
||||
};
|
||||
|
||||
const ActivateScriptNodes = (el, part) => {
|
||||
let startMarkNode;
|
||||
const scriptList: HTMLScriptElement[] = [];
|
||||
const { childNodes } = el;
|
||||
for (let i = 0; i < childNodes.length; i += 1) {
|
||||
const node = childNodes[i];
|
||||
if (node.nodeType === 8 && node.nodeValue === part) {
|
||||
if (!startMarkNode) {
|
||||
startMarkNode = node;
|
||||
} else {
|
||||
// this is the endMarkNode
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (
|
||||
startMarkNode &&
|
||||
node.nodeType === 1 &&
|
||||
node.nodeName.toLowerCase() === 'script'
|
||||
) {
|
||||
scriptList.push(node);
|
||||
}
|
||||
}
|
||||
scriptList.forEach((so) => {
|
||||
const script = document.createElement('script');
|
||||
script.text = so.text;
|
||||
for (let i = 0; i < so.attributes.length; i += 1) {
|
||||
const attr = so.attributes[i];
|
||||
script.setAttribute(attr.name, attr.value);
|
||||
}
|
||||
el.replaceChild(script, so);
|
||||
});
|
||||
};
|
||||
|
||||
type pos = 'afterbegin' | 'beforeend';
|
||||
const renderCustomArea = (el, part, pos: pos, content: string = '') => {
|
||||
let startMarkNode;
|
||||
|
@ -43,6 +76,7 @@ const renderCustomArea = (el, part, pos: pos, content: string = '') => {
|
|||
el.insertAdjacentHTML(pos, makeMarker(part));
|
||||
el.insertAdjacentHTML(pos, content);
|
||||
el.insertAdjacentHTML(pos, makeMarker(part));
|
||||
ActivateScriptNodes(el, part);
|
||||
};
|
||||
const handleCustomHead = (content) => {
|
||||
const el = document.head;
|
||||
|
@ -70,12 +104,7 @@ const Index: FC = () => {
|
|||
handleCustomHeader(custom_header);
|
||||
handleCustomFooter(custom_footer);
|
||||
}, [custom_head, custom_header, custom_footer]);
|
||||
return (
|
||||
<>
|
||||
{null}
|
||||
{/* App customize */}
|
||||
</>
|
||||
);
|
||||
return null;
|
||||
};
|
||||
|
||||
export default memo(Index);
|
||||
|
|
|
@ -9,7 +9,7 @@ import { themeSettingStore } from '@/stores';
|
|||
const Index: FC = () => {
|
||||
const { theme, theme_config } = themeSettingStore((_) => _);
|
||||
let primaryColor;
|
||||
if (theme_config[theme]?.primary_color) {
|
||||
if (theme_config?.[theme]?.primary_color) {
|
||||
primaryColor = Color(theme_config[theme].primary_color);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { FC, memo } from 'react';
|
||||
|
||||
import clssnames from 'classnames';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { Tag } from '@/components';
|
||||
import { diffText } from '@/utils';
|
||||
|
@ -90,7 +90,7 @@ const Index: FC<Props> = ({
|
|||
)}
|
||||
{objectType === 'tag' && opts?.showTagUrlSlug && (
|
||||
<div
|
||||
className={clssnames(
|
||||
className={classnames(
|
||||
'fs-14 font-monospace',
|
||||
newData.original_text && 'mb-4',
|
||||
)}>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { FC, memo, useState } from 'react';
|
||||
import { Card, Button } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
import { TagSelector, Tag } from '@/components';
|
||||
import { tryLoggedAndActivated } from '@/utils/guard';
|
||||
|
@ -78,11 +77,13 @@ const Index: FC = () => {
|
|||
) : (
|
||||
<>
|
||||
<div className="text-muted">{t('follow_tag_tip')}</div>
|
||||
<NavLink className="d-inline-block mt-3" to="/tags">
|
||||
<Button size="sm" variant="outline-primary">
|
||||
{t('follow_a_tag')}
|
||||
</Button>
|
||||
</NavLink>
|
||||
<Button
|
||||
size="sm"
|
||||
className="mt-3"
|
||||
variant="outline-primary"
|
||||
onClick={() => setEditState(true)}>
|
||||
{t('follow_a_tag')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Card.Body>
|
||||
|
|
|
@ -72,17 +72,17 @@ const Header: FC = () => {
|
|||
}
|
||||
}, [location.pathname]);
|
||||
|
||||
let navbarStyle = 'theme-colored';
|
||||
const { theme, theme_config } = themeSettingStore((_) => _);
|
||||
let themeType = 'theme-colored';
|
||||
if (theme && theme_config[theme]) {
|
||||
themeType = `theme-${theme_config[theme].navbar_style}`;
|
||||
if (theme_config?.[theme]?.navbar_style) {
|
||||
navbarStyle = `theme-${theme_config[theme].navbar_style}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<Navbar
|
||||
variant={themeType === 'theme-colored' ? 'dark' : ''}
|
||||
variant={navbarStyle === 'theme-colored' ? 'dark' : ''}
|
||||
expand="lg"
|
||||
className={classnames('sticky-top', themeType)}
|
||||
className={classnames('sticky-top', navbarStyle)}
|
||||
id="header">
|
||||
<Container className="d-flex align-items-center">
|
||||
<Navbar.Toggle
|
||||
|
@ -124,7 +124,7 @@ const Header: FC = () => {
|
|||
{loginSetting.allow_new_registrations && (
|
||||
<Button
|
||||
variant={
|
||||
themeType === 'theme-colored' ? 'light' : 'primary'
|
||||
navbarStyle === 'theme-colored' ? 'light' : 'primary'
|
||||
}
|
||||
href="/users/register">
|
||||
{t('btns.signup')}
|
||||
|
@ -181,8 +181,8 @@ const Header: FC = () => {
|
|||
<Link
|
||||
to="/questions/ask"
|
||||
className={classnames('text-capitalize text-nowrap btn', {
|
||||
'btn-light': themeType !== 'theme-light',
|
||||
'btn-primary': themeType === 'theme-light',
|
||||
'btn-light': navbarStyle !== 'theme-light',
|
||||
'btn-primary': navbarStyle === 'theme-light',
|
||||
})}>
|
||||
{t('btns.add_question')}
|
||||
</Link>
|
||||
|
@ -199,8 +199,8 @@ const Header: FC = () => {
|
|||
<Button
|
||||
variant="link"
|
||||
className={classnames('me-2', {
|
||||
'link-light': themeType === 'theme-colored',
|
||||
'link-primary': themeType !== 'theme-colored',
|
||||
'link-light': navbarStyle === 'theme-colored',
|
||||
'link-primary': navbarStyle !== 'theme-colored',
|
||||
})}
|
||||
href="/users/login">
|
||||
{t('btns.login')}
|
||||
|
@ -208,7 +208,7 @@ const Header: FC = () => {
|
|||
{loginSetting.allow_new_registrations && (
|
||||
<Button
|
||||
variant={
|
||||
themeType === 'theme-colored' ? 'light' : 'primary'
|
||||
navbarStyle === 'theme-colored' ? 'light' : 'primary'
|
||||
}
|
||||
href="/users/register">
|
||||
{t('btns.signup')}
|
||||
|
|
|
@ -117,12 +117,12 @@ const QuestionList: FC<Props> = ({ source }) => {
|
|||
i18nKeyPrefix="question"
|
||||
/>
|
||||
</div>
|
||||
<ListGroup variant="flush" className="border-top border-bottom-0">
|
||||
<ListGroup className="rounded-0">
|
||||
{listData?.list?.map((li) => {
|
||||
return (
|
||||
<ListGroup.Item
|
||||
key={li.id}
|
||||
className="border-bottom bg-transparent py-3 px-0">
|
||||
className="bg-transparent py-3 px-0 border-start-0 border-end-0">
|
||||
<h5 className="text-wrap text-break">
|
||||
<NavLink
|
||||
to={pathFactory.questionLanding(li.id, li.url_title)}
|
||||
|
|
|
@ -22,7 +22,6 @@ const Index: FC<IProps> = ({ type, qid, aid, title, slugTitle = '' }) => {
|
|||
const [showTip, setShowTip] = useState(false);
|
||||
const [canSystemShare, setSystemShareState] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
// FIXME: pathFactory
|
||||
let baseUrl =
|
||||
type === 'question'
|
||||
? `${window.location.origin}${pathFactory.questionLanding(
|
||||
|
@ -46,7 +45,11 @@ const Index: FC<IProps> = ({ type, qid, aid, title, slugTitle = '' }) => {
|
|||
const handleCopy = (evt) => {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
copy(baseUrl);
|
||||
let copyText = baseUrl;
|
||||
if (title) {
|
||||
copyText = `${title} ${baseUrl}`;
|
||||
}
|
||||
copy(copyText);
|
||||
setShowTip(true);
|
||||
setTimeout(closeShare, 1000);
|
||||
};
|
||||
|
|
|
@ -56,7 +56,7 @@ const Answers: FC = () => {
|
|||
Modal.confirm({
|
||||
title: t('title', { keyPrefix: 'delete' }),
|
||||
content:
|
||||
item.adopted === 2
|
||||
item.accepted === 2
|
||||
? t('answer_accepted', { keyPrefix: 'delete' })
|
||||
: `<p>${t('other', { keyPrefix: 'delete' })}</p>`,
|
||||
cancelBtnVariant: 'link',
|
||||
|
@ -138,7 +138,7 @@ const Answers: FC = () => {
|
|||
rel="noreferrer">
|
||||
{li.question_info.title}
|
||||
</a>
|
||||
{li.adopted === 2 && (
|
||||
{li.accepted === 2 && (
|
||||
<Icon
|
||||
name="check-circle-fill"
|
||||
className="ms-2 text-success"
|
||||
|
|
|
@ -23,7 +23,7 @@ const Index: FC = () => {
|
|||
description: t('themes.text'),
|
||||
enum: themeSetting?.theme_options?.map((_) => _.value),
|
||||
enumNames: themeSetting?.theme_options?.map((_) => _.label),
|
||||
default: 'default',
|
||||
default: themeSetting?.theme_options?.[0]?.value,
|
||||
},
|
||||
navbar_style: {
|
||||
type: 'string',
|
||||
|
@ -31,12 +31,13 @@ const Index: FC = () => {
|
|||
description: t('navbar_style.text'),
|
||||
enum: ['colored', 'light'],
|
||||
enumNames: ['Colored', 'Light'],
|
||||
default: 'colored',
|
||||
},
|
||||
primary_color: {
|
||||
type: 'string',
|
||||
title: t('primary_color.label'),
|
||||
description: t('primary_color.text'),
|
||||
default: '#ffffff',
|
||||
default: '#0033FF',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -68,6 +69,7 @@ const Index: FC = () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
|
||||
putThemeSetting(reqParams)
|
||||
.then(() => {
|
||||
Toast.onShow({
|
||||
|
|
|
@ -44,7 +44,7 @@ const Index: FC<Props> = ({
|
|||
const acceptAnswer = () => {
|
||||
acceptanceAnswer({
|
||||
question_id: data.question_id,
|
||||
answer_id: data.adopted === 2 ? '0' : data.id,
|
||||
answer_id: data.accepted === 2 ? '0' : data.id,
|
||||
}).then(() => {
|
||||
callback?.('');
|
||||
});
|
||||
|
@ -85,7 +85,7 @@ const Index: FC<Props> = ({
|
|||
}}
|
||||
/>
|
||||
|
||||
{data?.adopted === 2 && (
|
||||
{data?.accepted === 2 && (
|
||||
<Button
|
||||
disabled={!isAuthor}
|
||||
variant="outline-success"
|
||||
|
@ -96,7 +96,7 @@ const Index: FC<Props> = ({
|
|||
</Button>
|
||||
)}
|
||||
|
||||
{isAuthor && data.adopted === 1 && (
|
||||
{isAuthor && data.accepted === 1 && (
|
||||
<Button
|
||||
variant="outline-success"
|
||||
className="ms-3"
|
||||
|
@ -114,7 +114,7 @@ const Index: FC<Props> = ({
|
|||
aid={data.id}
|
||||
memberActions={data?.member_actions}
|
||||
type="answer"
|
||||
isAccepted={data.adopted === 2}
|
||||
isAccepted={data.accepted === 2}
|
||||
title={questionTitle}
|
||||
slugTitle={slugTitle}
|
||||
callback={callback}
|
||||
|
|
|
@ -37,7 +37,7 @@ const Index: FC<Props> = ({ data }) => {
|
|||
<div className="mb-5">
|
||||
<h3 className="mb-3">{t('title')}</h3>
|
||||
<p>
|
||||
<span className="text-secondary">{t('keywords')}</span>
|
||||
<span className="text-secondary me-1">{t('keywords')}</span>
|
||||
{q?.replace(reg, '')}
|
||||
<br />
|
||||
{options?.length && (
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { FC, memo } from 'react';
|
||||
import { ListGroupItem } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { QueryGroup } from '@/components';
|
||||
|
@ -16,7 +15,7 @@ const Index: FC<Props> = ({ sort, count = 0 }) => {
|
|||
});
|
||||
|
||||
return (
|
||||
<ListGroupItem className="d-flex flex-wrap align-items-center justify-content-between divide-line pb-3 border-bottom px-0">
|
||||
<div className="d-flex flex-wrap align-items-center justify-content-between pt-2 pb-3">
|
||||
<h5 className="mb-0">{t('counts', { count, keyPrefix: 'search' })}</h5>
|
||||
<QueryGroup
|
||||
data={sortBtns}
|
||||
|
@ -24,7 +23,7 @@ const Index: FC<Props> = ({ sort, count = 0 }) => {
|
|||
sortKey="order"
|
||||
i18nKeyPrefix="search.sort_btns"
|
||||
/>
|
||||
</ListGroupItem>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ const Index: FC<Props> = ({ data }) => {
|
|||
});
|
||||
}
|
||||
return (
|
||||
<ListGroupItem className="py-3 px-0">
|
||||
<ListGroupItem className="py-3 px-0 border-start-0 border-end-0 bg-transparent">
|
||||
<div className="mb-2 clearfix">
|
||||
<span
|
||||
className="float-start me-2 badge text-bg-dark"
|
||||
|
|
|
@ -36,10 +36,8 @@ const Index = () => {
|
|||
<Row className="justify-content-center">
|
||||
<Col xxl={7} lg={8} sm={12} className="mb-3">
|
||||
<Head data={extra} />
|
||||
|
||||
<ListGroup variant="flush" className="mb-5">
|
||||
<SearchHead sort={order} count={count} />
|
||||
|
||||
<SearchHead sort={order} count={count} />
|
||||
<ListGroup className="rounded-0 mb-5">
|
||||
{list?.map((item) => {
|
||||
return <SearchItem key={item.object.id} data={item} />;
|
||||
})}
|
||||
|
|
|
@ -81,7 +81,7 @@ const Tags = () => {
|
|||
<Card.Body className="d-flex flex-column align-items-start">
|
||||
<Tag className="mb-3" data={tag} />
|
||||
|
||||
<p className="fs-14 flex-fill text-break text-wrap text-truncate-4">
|
||||
<p className="fs-14 flex-fill text-break text-wrap text-truncate-3">
|
||||
{tag.original_text}
|
||||
</p>
|
||||
<div className="d-flex align-items-center">
|
||||
|
|
|
@ -16,9 +16,7 @@ const Achievements = ({ data, handleReadNotification }) => {
|
|||
return <Empty />;
|
||||
}
|
||||
return (
|
||||
<ListGroup
|
||||
className="border-top border-bottom achievement-wrap"
|
||||
variant="flush">
|
||||
<ListGroup className="achievement-wrap rounded-0">
|
||||
{data.map((item) => {
|
||||
const { comment, question, answer } =
|
||||
item?.object_info?.object_map || {};
|
||||
|
@ -39,7 +37,10 @@ const Achievements = ({ data, handleReadNotification }) => {
|
|||
return (
|
||||
<ListGroup.Item
|
||||
key={item.id}
|
||||
className={classNames('d-flex', !item.is_read && 'warning')}>
|
||||
className={classNames(
|
||||
'd-flex border-start-0 border-end-0',
|
||||
!item.is_read && 'warning',
|
||||
)}>
|
||||
{item.rank > 0 && (
|
||||
<div className="text-success num text-end">{`+${item.rank}`}</div>
|
||||
)}
|
||||
|
|
|
@ -14,7 +14,7 @@ const Inbox = ({ data, handleReadNotification }) => {
|
|||
return <Empty />;
|
||||
}
|
||||
return (
|
||||
<ListGroup className="border-top border-bottom" variant="flush">
|
||||
<ListGroup className="rounded-0">
|
||||
{data.map((item) => {
|
||||
const { comment, question, answer } =
|
||||
item?.object_info?.object_map || {};
|
||||
|
@ -35,7 +35,10 @@ const Inbox = ({ data, handleReadNotification }) => {
|
|||
return (
|
||||
<ListGroup.Item
|
||||
key={item.id}
|
||||
className={classNames('py-3', !item.is_read && 'warning')}>
|
||||
className={classNames(
|
||||
'py-3 border-start-0 border-end-0',
|
||||
!item.is_read && 'warning',
|
||||
)}>
|
||||
<div>
|
||||
{item.user_info.status !== 'deleted' ? (
|
||||
<Link to={`/users/${item.user_info.username}`}>
|
||||
|
|
|
@ -15,10 +15,12 @@ const Index: FC<Props> = ({ visible, data }) => {
|
|||
return null;
|
||||
}
|
||||
return (
|
||||
<ListGroup variant="flush">
|
||||
<ListGroup className="rounded-0">
|
||||
{data.map((item) => {
|
||||
return (
|
||||
<ListGroupItem className="py-3 px-0" key={item.answer_id}>
|
||||
<ListGroupItem
|
||||
className="py-3 px-0 bg-transparent border-start-0 border-end-0"
|
||||
key={item.answer_id}>
|
||||
<h6 className="mb-2">
|
||||
<a
|
||||
href={pathFactory.answerLanding({
|
||||
|
@ -42,7 +44,7 @@ const Index: FC<Props> = ({ visible, data }) => {
|
|||
<span>{item?.vote_count}</span>
|
||||
</div>
|
||||
|
||||
{item.adopted === 2 && (
|
||||
{item.accepted === 2 && (
|
||||
<div className="d-flex align-items-center me-3 text-success">
|
||||
<Icon name="check-circle-fill me-1" />
|
||||
<span>{t('accepted')}</span>
|
||||
|
|
|
@ -14,10 +14,12 @@ const Index: FC<Props> = ({ visible, data }) => {
|
|||
return null;
|
||||
}
|
||||
return (
|
||||
<ListGroup variant="flush">
|
||||
<ListGroup className="rounded-0">
|
||||
{data.map((item) => {
|
||||
return (
|
||||
<ListGroupItem className="py-3 px-0" key={item.comment_id}>
|
||||
<ListGroupItem
|
||||
className="py-3 px-0 bg-transparent border-start-0 border-end-0"
|
||||
key={item.comment_id}>
|
||||
<a
|
||||
className="text-break"
|
||||
href={
|
||||
|
|
|
@ -18,11 +18,11 @@ const Index: FC<Props> = ({ visible, tabName, data }) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<ListGroup variant="flush">
|
||||
<ListGroup className="rounded-0">
|
||||
{data.map((item) => {
|
||||
return (
|
||||
<ListGroupItem
|
||||
className="py-3 px-0"
|
||||
className="py-3 px-0 bg-transparent border-start-0 border-end-0"
|
||||
key={tabName === 'questions' ? item.question_id : item.id}>
|
||||
<h6 className="mb-2">
|
||||
<a
|
||||
|
|
|
@ -24,7 +24,7 @@ const Index: FC<Props> = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="d-flex align-items-center justify-content-between pb-3 border-bottom">
|
||||
<div className="d-flex align-items-center justify-content-between pb-3">
|
||||
<h5 className="mb-0">
|
||||
{count} {t(tabName)}
|
||||
</h5>
|
||||
|
|
|
@ -16,10 +16,12 @@ const Index: FC<Props> = ({ visible, data }) => {
|
|||
return null;
|
||||
}
|
||||
return (
|
||||
<ListGroup variant="flush">
|
||||
<ListGroup className="rounded-0">
|
||||
{data.map((item) => {
|
||||
return (
|
||||
<ListGroupItem className="d-flex py-3 px-0" key={item.object_id}>
|
||||
<ListGroupItem
|
||||
className="d-flex py-3 px-0 bg-transparent border-start-0 border-end-0"
|
||||
key={item.object_id}>
|
||||
<div
|
||||
className={`me-3 text-end ${
|
||||
item.reputation > 0 ? 'text-success' : 'text-danger'
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue