diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go
index 1c237435..429771a2 100644
--- a/cmd/wire_gen.go
+++ b/cmd/wire_gen.go
@@ -45,7 +45,7 @@ import (
"github.com/answerdev/answer/internal/service"
"github.com/answerdev/answer/internal/service/action"
activity2 "github.com/answerdev/answer/internal/service/activity"
- activity_common2 "github.com/answerdev/answer/internal/service/activity_common"
+ "github.com/answerdev/answer/internal/service/activity_queue"
"github.com/answerdev/answer/internal/service/answer_common"
auth2 "github.com/answerdev/answer/internal/service/auth"
"github.com/answerdev/answer/internal/service/collection_common"
@@ -56,6 +56,7 @@ import (
export2 "github.com/answerdev/answer/internal/service/export"
"github.com/answerdev/answer/internal/service/follow"
meta2 "github.com/answerdev/answer/internal/service/meta"
+ "github.com/answerdev/answer/internal/service/notice_queue"
notification2 "github.com/answerdev/answer/internal/service/notification"
"github.com/answerdev/answer/internal/service/notification_common"
"github.com/answerdev/answer/internal/service/object_info"
@@ -128,8 +129,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
userService := service.NewUserService(userRepo, userActiveActivityRepo, activityRepo, emailService, authService, siteInfoCommonService, userRoleRelService, userCommon, userExternalLoginService)
captchaRepo := captcha.NewCaptchaRepo(dataData)
captchaService := action.NewCaptchaService(captchaRepo)
- uploaderService := uploader.NewUploaderService(serviceConf, siteInfoCommonService)
- userController := controller.NewUserController(authService, userService, captchaService, emailService, uploaderService, siteInfoCommonService)
+ userController := controller.NewUserController(authService, userService, captchaService, emailService, siteInfoCommonService)
commentRepo := comment.NewCommentRepo(dataData, uniqueIDRepo)
commentCommonRepo := comment.NewCommentCommonRepo(dataData, uniqueIDRepo)
answerRepo := answer.NewAnswerRepo(dataData, uniqueIDRepo, userRankRepo, activityRepo)
@@ -139,10 +139,12 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
tagRepo := tag.NewTagRepo(dataData, uniqueIDRepo)
revisionRepo := revision.NewRevisionRepo(dataData, uniqueIDRepo)
revisionService := revision_common.NewRevisionService(revisionRepo, userRepo)
- tagCommonService := tag_common2.NewTagCommonService(tagCommonRepo, tagRelRepo, tagRepo, revisionService, siteInfoCommonService)
+ activityQueueService := activity_queue.NewActivityQueueService()
+ tagCommonService := tag_common2.NewTagCommonService(tagCommonRepo, tagRelRepo, tagRepo, revisionService, siteInfoCommonService, activityQueueService)
objService := object_info.NewObjService(answerRepo, questionRepo, commentCommonRepo, tagCommonRepo, tagCommonService)
voteRepo := activity_common.NewVoteRepo(dataData, activityRepo)
- commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userCommon, objService, voteRepo, emailService, userRepo)
+ notificationQueueService := notice_queue.NewNotificationQueueService()
+ commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userCommon, objService, voteRepo, emailService, userRepo, notificationQueueService, activityQueueService)
rolePowerRelRepo := role.NewRolePowerRelRepo(dataData)
rolePowerRelService := role2.NewRolePowerRelService(rolePowerRelRepo, userRoleRelService)
rankService := rank2.NewRankService(userCommon, userRankRepo, objService, userRoleRelService, rolePowerRelService, configService)
@@ -150,11 +152,11 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
reportRepo := report.NewReportRepo(dataData, uniqueIDRepo)
reportService := report2.NewReportService(reportRepo, objService)
reportController := controller.NewReportController(reportService, rankService)
- serviceVoteRepo := activity.NewVoteRepo(dataData, uniqueIDRepo, configService, activityRepo, userRankRepo, voteRepo)
- voteService := service.NewVoteService(serviceVoteRepo, uniqueIDRepo, configService, questionRepo, answerRepo, commentCommonRepo, objService)
+ serviceVoteRepo := activity.NewVoteRepo(dataData, activityRepo, userRankRepo, notificationQueueService)
+ voteService := service.NewVoteService(serviceVoteRepo, configService, questionRepo, answerRepo, commentCommonRepo, objService)
voteController := controller.NewVoteController(voteService, rankService)
followRepo := activity_common.NewFollowRepo(dataData, uniqueIDRepo, activityRepo)
- tagService := tag2.NewTagService(tagRepo, tagCommonService, revisionService, followRepo, siteInfoCommonService)
+ tagService := tag2.NewTagService(tagRepo, tagCommonService, revisionService, followRepo, siteInfoCommonService, activityQueueService)
tagController := controller.NewTagController(tagService, tagCommonService, rankService)
followFollowRepo := activity.NewFollowRepo(dataData, uniqueIDRepo, activityRepo)
followService := follow.NewFollowService(followFollowRepo, followRepo, tagCommonRepo)
@@ -165,25 +167,23 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
answerCommon := answercommon.NewAnswerCommon(answerRepo)
metaRepo := meta.NewMetaRepo(dataData)
metaService := meta2.NewMetaService(metaRepo)
- questionCommon := questioncommon.NewQuestionCommon(questionRepo, answerRepo, voteRepo, followRepo, tagCommonService, userCommon, collectionCommon, answerCommon, metaService, configService, dataData)
+ questionCommon := questioncommon.NewQuestionCommon(questionRepo, answerRepo, voteRepo, followRepo, tagCommonService, userCommon, collectionCommon, answerCommon, metaService, configService, activityQueueService, dataData)
collectionService := service.NewCollectionService(collectionRepo, collectionGroupRepo, questionCommon)
collectionController := controller.NewCollectionController(collectionService)
- answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo)
- questionActivityRepo := activity.NewQuestionActivityRepo(dataData, activityRepo, userRankRepo)
- answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, questionActivityRepo)
- questionService := service.NewQuestionService(questionRepo, tagCommonService, questionCommon, userCommon, userRepo, revisionService, metaService, collectionCommon, answerActivityService, dataData, emailService)
- answerService := service.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService)
- questionController := controller.NewQuestionController(questionService, answerService, rankService)
- dashboardService := dashboard.NewDashboardService(questionRepo, answerRepo, commentCommonRepo, voteRepo, userRepo, reportRepo, configService, siteInfoCommonService, serviceConf, dataData)
- answerController := controller.NewAnswerController(answerService, rankService, dashboardService)
+ answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo, notificationQueueService)
+ answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, configService)
+ questionService := service.NewQuestionService(questionRepo, tagCommonService, questionCommon, userCommon, userRepo, revisionService, metaService, collectionCommon, answerActivityService, emailService, notificationQueueService, activityQueueService, siteInfoCommonService)
+ answerService := service.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService, notificationQueueService, activityQueueService)
+ questionController := controller.NewQuestionController(questionService, answerService, rankService, siteInfoCommonService)
+ answerController := controller.NewAnswerController(answerService, rankService)
searchParser := search_parser.NewSearchParser(tagCommonService, userCommon)
searchRepo := search_common.NewSearchRepo(dataData, uniqueIDRepo, userCommon)
searchService := service.NewSearchService(searchParser, searchRepo)
searchController := controller.NewSearchController(searchService)
- serviceRevisionService := service.NewRevisionService(revisionRepo, userCommon, questionCommon, answerService, objService, questionRepo, answerRepo, tagRepo, tagCommonService)
+ serviceRevisionService := service.NewRevisionService(revisionRepo, userCommon, questionCommon, answerService, objService, questionRepo, answerRepo, tagRepo, tagCommonService, notificationQueueService, activityQueueService)
revisionController := controller.NewRevisionController(serviceRevisionService, rankService)
rankController := controller.NewRankController(rankService)
- reportHandle := report_handle_admin.NewReportHandle(questionCommon, commentRepo, configService)
+ reportHandle := report_handle_admin.NewReportHandle(questionCommon, commentRepo, configService, notificationQueueService)
reportAdminService := report_admin.NewReportAdminService(reportRepo, userCommon, answerRepo, questionRepo, commentCommonRepo, reportHandle, configService, objService)
controller_adminReportController := controller_admin.NewReportController(reportAdminService)
userAdminRepo := user.NewUserAdminRepo(dataData, authRepo)
@@ -195,36 +195,38 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
themeController := controller_admin.NewThemeController()
siteInfoService := siteinfo.NewSiteInfoService(siteInfoRepo, siteInfoCommonService, emailService, tagCommonService, configService, questionCommon)
siteInfoController := controller_admin.NewSiteInfoController(siteInfoService)
- siteinfoController := controller.NewSiteinfoController(siteInfoCommonService)
+ controllerSiteInfoController := controller.NewSiteInfoController(siteInfoCommonService)
notificationRepo := notification.NewNotificationRepo(dataData)
- notificationCommon := notificationcommon.NewNotificationCommon(dataData, notificationRepo, userCommon, activityRepo, followRepo, objService)
+ notificationCommon := notificationcommon.NewNotificationCommon(dataData, notificationRepo, userCommon, activityRepo, followRepo, objService, notificationQueueService)
notificationService := notification2.NewNotificationService(dataData, notificationRepo, notificationCommon, revisionService)
notificationController := controller.NewNotificationController(notificationService, rankService)
+ dashboardService := dashboard.NewDashboardService(questionRepo, answerRepo, commentCommonRepo, voteRepo, userRepo, reportRepo, configService, siteInfoCommonService, serviceConf, dataData)
dashboardController := controller.NewDashboardController(dashboardService)
+ uploaderService := uploader.NewUploaderService(serviceConf, siteInfoCommonService)
uploadController := controller.NewUploadController(uploaderService)
- activityCommon := activity_common2.NewActivityCommon(activityRepo)
activityActivityRepo := activity.NewActivityRepo(dataData, configService)
commentCommonService := comment_common.NewCommentCommonService(commentCommonRepo)
- activityService := activity2.NewActivityService(activityActivityRepo, userCommon, activityCommon, tagCommonService, objService, commentCommonService, revisionService, metaService, configService)
- activityController := controller.NewActivityController(activityCommon, activityService)
+ activityService := activity2.NewActivityService(activityActivityRepo, userCommon, tagCommonService, objService, commentCommonService, revisionService, metaService, configService)
+ activityController := controller.NewActivityController(activityService)
roleController := controller_admin.NewRoleController(roleService)
pluginConfigRepo := plugin_config.NewPluginConfigRepo(dataData)
pluginCommonService := plugin_common.NewPluginCommonService(pluginConfigRepo, configService)
pluginController := controller_admin.NewPluginController(pluginCommonService)
permissionController := controller.NewPermissionController(rankService)
- 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, pluginController, permissionController)
+ answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, controller_adminReportController, userAdminController, reasonController, themeController, siteInfoController, controllerSiteInfoController, notificationController, dashboardController, uploadController, activityController, roleController, pluginController, permissionController)
swaggerRouter := router.NewSwaggerRouter(swaggerConf)
- uiRouter := router.NewUIRouter(siteinfoController, siteInfoCommonService)
+ uiRouter := router.NewUIRouter(controllerSiteInfoController, siteInfoCommonService)
authUserMiddleware := middleware.NewAuthUserMiddleware(authService, siteInfoCommonService)
avatarMiddleware := middleware.NewAvatarMiddleware(serviceConf, uploaderService)
- templateRenderController := templaterender.NewTemplateRenderController(questionService, userService, tagService, answerService, commentService, dataData, siteInfoCommonService)
+ shortIDMiddleware := middleware.NewShortIDMiddleware(siteInfoCommonService)
+ templateRenderController := templaterender.NewTemplateRenderController(questionService, userService, tagService, answerService, commentService, siteInfoCommonService, questionRepo)
templateController := controller.NewTemplateController(templateRenderController, siteInfoCommonService)
templateRouter := router.NewTemplateRouter(templateController, templateRenderController, siteInfoController)
connectorController := controller.NewConnectorController(siteInfoCommonService, emailService, userExternalLoginService)
userCenterLoginService := user_external_login2.NewUserCenterLoginService(userRepo, userCommon, userExternalLoginRepo, userActiveActivityRepo, siteInfoCommonService)
userCenterController := controller.NewUserCenterController(userCenterLoginService, siteInfoCommonService)
pluginAPIRouter := router.NewPluginAPIRouter(connectorController, userCenterController)
- ginEngine := server.NewHTTPServer(debug, staticRouter, answerAPIRouter, swaggerRouter, uiRouter, authUserMiddleware, avatarMiddleware, templateRouter, pluginAPIRouter)
+ ginEngine := server.NewHTTPServer(debug, staticRouter, answerAPIRouter, swaggerRouter, uiRouter, authUserMiddleware, avatarMiddleware, shortIDMiddleware, templateRouter, pluginAPIRouter)
scheduledTaskManager := cron.NewScheduledTaskManager(siteInfoCommonService, questionService)
application := newApplication(serverConf, ginEngine, scheduledTaskManager)
return application, func() {
diff --git a/docs/docs.go b/docs/docs.go
index 96f9cf1a..780e41dd 100644
--- a/docs/docs.go
+++ b/docs/docs.go
@@ -49,7 +49,7 @@ const docTemplate = `{
"tags": [
"admin"
],
- "summary": "AdminSearchAnswerList",
+ "summary": "AdminAnswerPage admin answer page",
"parameters": [
{
"type": "integer",
@@ -379,7 +379,7 @@ const docTemplate = `{
"tags": [
"admin"
],
- "summary": "AdminSearchList",
+ "summary": "AdminQuestionPage admin question page",
"parameters": [
{
"type": "integer",
@@ -8186,7 +8186,7 @@ const docTemplate = `{
"type": "string"
},
"site_seo": {
- "$ref": "#/definitions/schema.SiteSeoReq"
+ "$ref": "#/definitions/schema.SiteSeoResp"
},
"site_users": {
"$ref": "#/definitions/schema.SiteUsersResp"
diff --git a/docs/swagger.json b/docs/swagger.json
index 8a0db0e1..df3eee70 100644
--- a/docs/swagger.json
+++ b/docs/swagger.json
@@ -37,7 +37,7 @@
"tags": [
"admin"
],
- "summary": "AdminSearchAnswerList",
+ "summary": "AdminAnswerPage admin answer page",
"parameters": [
{
"type": "integer",
@@ -367,7 +367,7 @@
"tags": [
"admin"
],
- "summary": "AdminSearchList",
+ "summary": "AdminQuestionPage admin question page",
"parameters": [
{
"type": "integer",
@@ -8174,7 +8174,7 @@
"type": "string"
},
"site_seo": {
- "$ref": "#/definitions/schema.SiteSeoReq"
+ "$ref": "#/definitions/schema.SiteSeoResp"
},
"site_users": {
"$ref": "#/definitions/schema.SiteUsersResp"
diff --git a/docs/swagger.yaml b/docs/swagger.yaml
index 4f73fe51..e2b9c829 100644
--- a/docs/swagger.yaml
+++ b/docs/swagger.yaml
@@ -1507,7 +1507,7 @@ definitions:
revision:
type: string
site_seo:
- $ref: '#/definitions/schema.SiteSeoReq'
+ $ref: '#/definitions/schema.SiteSeoResp'
site_users:
$ref: '#/definitions/schema.SiteUsersResp'
theme:
@@ -2335,7 +2335,7 @@ paths:
$ref: '#/definitions/handler.RespBody'
security:
- ApiKeyAuth: []
- summary: AdminSearchAnswerList
+ summary: AdminAnswerPage admin answer page
tags:
- admin
/answer/admin/api/answer/status:
@@ -2533,7 +2533,7 @@ paths:
$ref: '#/definitions/handler.RespBody'
security:
- ApiKeyAuth: []
- summary: AdminSearchList
+ summary: AdminQuestionPage admin question page
tags:
- admin
/answer/admin/api/question/status:
diff --git a/go.mod b/go.mod
index 1e861790..e7c08a67 100644
--- a/go.mod
+++ b/go.mod
@@ -48,7 +48,6 @@ require (
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.14.2
xorm.io/builder v0.3.12
- xorm.io/core v0.7.3
xorm.io/xorm v1.3.2
)
diff --git a/go.sum b/go.sum
index e26ab91c..7ece6c5c 100644
--- a/go.sum
+++ b/go.sum
@@ -221,7 +221,6 @@ github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXS
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
-github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@@ -493,7 +492,6 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
-github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
@@ -1084,7 +1082,6 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
@@ -1329,7 +1326,5 @@ sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/builder v0.3.12 h1:ASZYX7fQmy+o8UJdhlLHSW57JDOkM8DNhcAF5d0LiJM=
xorm.io/builder v0.3.12/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
-xorm.io/core v0.7.3 h1:W8ws1PlrnkS1CZU1YWaYLMQcQilwAmQXU0BJDJon+H0=
-xorm.io/core v0.7.3/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
xorm.io/xorm v1.3.2 h1:uTRRKF2jYzbZ5nsofXVUx6ncMaek+SHjWYtCXyZo1oM=
xorm.io/xorm v1.3.2/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw=
diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml
index 7ca9c6fa..ebbc9e35 100644
--- a/i18n/en_US.yaml
+++ b/i18n/en_US.yaml
@@ -1417,6 +1417,7 @@ ui:
change: Change
all: All
staff: Staff
+ more: More
inactive: Inactive
suspended: Suspended
deleted: Deleted
diff --git a/internal/base/constant/cache_key.go b/internal/base/constant/cache_key.go
index 9e9f9949..c4788dac 100644
--- a/internal/base/constant/cache_key.go
+++ b/internal/base/constant/cache_key.go
@@ -16,4 +16,7 @@ const (
ConfigKEY2ContentCacheKeyPrefix = "answer:config:key:"
ConnectorUserExternalInfoCacheKey = "answer:connector:"
ConnectorUserExternalInfoCacheTime = 10 * time.Minute
+ SiteMapQuestionCacheKeyPrefix = "answer:sitemap:question:%d"
+ SiteMapQuestionCacheTime = time.Hour
+ SitemapMaxSize = 50000
)
diff --git a/internal/base/constant/http_header.go b/internal/base/constant/ctx_flag.go
similarity index 62%
rename from internal/base/constant/http_header.go
rename to internal/base/constant/ctx_flag.go
index a68db8b1..822c1a01 100644
--- a/internal/base/constant/http_header.go
+++ b/internal/base/constant/ctx_flag.go
@@ -2,4 +2,5 @@ package constant
const (
AcceptLanguageFlag = "Accept-Language"
+ ShortIDFlag = "Short-ID-Enabled"
)
diff --git a/internal/base/constant/site_info.go b/internal/base/constant/site_info.go
index dfcee76c..d3c4e52a 100644
--- a/internal/base/constant/site_info.go
+++ b/internal/base/constant/site_info.go
@@ -7,3 +7,14 @@ const (
AvatarTypeGravatar = "gravatar"
AvatarTypeCustom = "custom"
)
+
+const (
+ // PermaLinkQuestionIDAndTitle /questions/10010000000000001/post-title
+ PermaLinkQuestionIDAndTitle = iota + 1
+ // PermaLinkQuestionID /questions/10010000000000001
+ PermaLinkQuestionID
+ // PermaLinkQuestionIDAndTitleByShortID /questions/11/post-title
+ PermaLinkQuestionIDAndTitleByShortID
+ // PermaLinkQuestionIDByShortID /questions/11
+ PermaLinkQuestionIDByShortID
+)
diff --git a/internal/base/cron/cron.go b/internal/base/cron/cron.go
index 79c34597..04f396b0 100644
--- a/internal/base/cron/cron.go
+++ b/internal/base/cron/cron.go
@@ -12,13 +12,13 @@ import (
// ScheduledTaskManager scheduled task manager
type ScheduledTaskManager struct {
- siteInfoService *siteinfo_common.SiteInfoCommonService
+ siteInfoService siteinfo_common.SiteInfoCommonService
questionService *service.QuestionService
}
// NewScheduledTaskManager new scheduled task manager
func NewScheduledTaskManager(
- siteInfoService *siteinfo_common.SiteInfoCommonService,
+ siteInfoService siteinfo_common.SiteInfoCommonService,
questionService *service.QuestionService,
) *ScheduledTaskManager {
manager := &ScheduledTaskManager{
diff --git a/internal/base/data/data.go b/internal/base/data/data.go
index 113d8d48..e8a32637 100644
--- a/internal/base/data/data.go
+++ b/internal/base/data/data.go
@@ -12,9 +12,9 @@ import (
"github.com/segmentfault/pacman/contrib/cache/memory"
"github.com/segmentfault/pacman/log"
_ "modernc.org/sqlite"
- "xorm.io/core"
"xorm.io/xorm"
ormlog "xorm.io/xorm/log"
+ "xorm.io/xorm/names"
"xorm.io/xorm/schemas"
)
@@ -71,7 +71,7 @@ func NewDB(debug bool, dataConf *Database) (*xorm.Engine, error) {
if dataConf.ConnMaxLifeTime > 0 {
engine.SetConnMaxLifetime(time.Duration(dataConf.ConnMaxLifeTime) * time.Second)
}
- engine.SetColumnMapper(core.GonicMapper{})
+ engine.SetColumnMapper(names.GonicMapper{})
return engine, nil
}
diff --git a/internal/base/handler/short_id.go b/internal/base/handler/short_id.go
new file mode 100644
index 00000000..35d62e62
--- /dev/null
+++ b/internal/base/handler/short_id.go
@@ -0,0 +1,16 @@
+package handler
+
+import (
+ "context"
+
+ "github.com/answerdev/answer/internal/base/constant"
+)
+
+// GetEnableShortID get language from header
+func GetEnableShortID(ctx context.Context) bool {
+ flag, ok := ctx.Value(constant.ShortIDFlag).(bool)
+ if ok {
+ return flag
+ }
+ return false
+}
diff --git a/internal/base/middleware/auth.go b/internal/base/middleware/auth.go
index ed941c0f..64e59dc1 100644
--- a/internal/base/middleware/auth.go
+++ b/internal/base/middleware/auth.go
@@ -6,13 +6,13 @@ import (
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service/role"
"github.com/answerdev/answer/internal/service/siteinfo_common"
+ "github.com/gin-gonic/gin"
"github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/service/auth"
"github.com/answerdev/answer/pkg/converter"
- "github.com/gin-gonic/gin"
"github.com/segmentfault/pacman/errors"
)
@@ -21,13 +21,13 @@ var ctxUUIDKey = "ctxUuidKey"
// AuthUserMiddleware auth user middleware
type AuthUserMiddleware struct {
authService *auth.AuthService
- siteInfoCommonService *siteinfo_common.SiteInfoCommonService
+ siteInfoCommonService siteinfo_common.SiteInfoCommonService
}
// NewAuthUserMiddleware new auth user middleware
func NewAuthUserMiddleware(
authService *auth.AuthService,
- siteInfoCommonService *siteinfo_common.SiteInfoCommonService) *AuthUserMiddleware {
+ siteInfoCommonService siteinfo_common.SiteInfoCommonService) *AuthUserMiddleware {
return &AuthUserMiddleware{
authService: authService,
siteInfoCommonService: siteInfoCommonService,
diff --git a/internal/base/middleware/avatar.go b/internal/base/middleware/avatar.go
index 82e6ceab..dadb93c3 100644
--- a/internal/base/middleware/avatar.go
+++ b/internal/base/middleware/avatar.go
@@ -32,31 +32,28 @@ func NewAvatarMiddleware(serviceConfig *service_config.ServiceConfig,
func (am *AvatarMiddleware) AvatarThumb() gin.HandlerFunc {
return func(ctx *gin.Context) {
- u := ctx.Request.RequestURI
- if strings.Contains(u, "/uploads/avatar/") {
- sizeStr := ctx.Query("s")
- size := converter.StringToInt(sizeStr)
- uUrl, err := url.Parse(u)
+ uri := ctx.Request.RequestURI
+ if strings.Contains(uri, "/uploads/avatar/") {
+ size := converter.StringToInt(ctx.Query("s"))
+ uriWithoutQuery, _ := url.Parse(uri)
+ filename := filepath.Base(uriWithoutQuery.Path)
+ filePath := fmt.Sprintf("%s/avatar/%s", am.serviceConfig.UploadPath, filename)
+ var err error
+ if size != 0 {
+ filePath, err = am.uploaderService.AvatarThumbFile(ctx, filename, size)
+ if err != nil {
+ log.Error(err)
+ ctx.Abort()
+ }
+ }
+ avatarFile, err := os.ReadFile(filePath)
if err != nil {
- ctx.Next()
+ log.Error(err)
+ ctx.Abort()
return
}
- _, urlfileName := filepath.Split(uUrl.Path)
- uploadPath := am.serviceConfig.UploadPath
- filePath := fmt.Sprintf("%s/avatar/%s", uploadPath, urlfileName)
- var avatarfile []byte
- if size == 0 {
- avatarfile, err = os.ReadFile(filePath)
- } else {
- avatarfile, err = am.uploaderService.AvatarThumbFile(ctx, uploadPath, urlfileName, size)
- }
- if err != nil {
- ctx.Next()
- return
- }
- ext := strings.ToLower(path.Ext(filePath)[1:])
- ctx.Header("content-type", fmt.Sprintf("image/%s", ext))
- _, err = ctx.Writer.WriteString(string(avatarfile))
+ ctx.Header("content-type", fmt.Sprintf("image/%s", strings.TrimLeft(path.Ext(filePath), ".")))
+ _, err = ctx.Writer.Write(avatarFile)
if err != nil {
log.Error(err)
}
@@ -64,7 +61,7 @@ func (am *AvatarMiddleware) AvatarThumb() gin.HandlerFunc {
return
} else {
- uUrl, err := url.Parse(u)
+ uUrl, err := url.Parse(uri)
if err != nil {
ctx.Next()
return
diff --git a/internal/base/middleware/provider.go b/internal/base/middleware/provider.go
index db89854f..7e699c91 100644
--- a/internal/base/middleware/provider.go
+++ b/internal/base/middleware/provider.go
@@ -8,4 +8,5 @@ import (
var ProviderSetMiddleware = wire.NewSet(
NewAuthUserMiddleware,
NewAvatarMiddleware,
+ NewShortIDMiddleware,
)
diff --git a/internal/base/middleware/short_id.go b/internal/base/middleware/short_id.go
new file mode 100644
index 00000000..1d1f7f6f
--- /dev/null
+++ b/internal/base/middleware/short_id.go
@@ -0,0 +1,29 @@
+package middleware
+
+import (
+ "github.com/answerdev/answer/internal/base/constant"
+ "github.com/answerdev/answer/internal/service/siteinfo_common"
+ "github.com/gin-gonic/gin"
+ "github.com/segmentfault/pacman/log"
+)
+
+type ShortIDMiddleware struct {
+ siteInfoService siteinfo_common.SiteInfoCommonService
+}
+
+func NewShortIDMiddleware(siteInfoService siteinfo_common.SiteInfoCommonService) *ShortIDMiddleware {
+ return &ShortIDMiddleware{
+ siteInfoService: siteInfoService,
+ }
+}
+
+func (sm *ShortIDMiddleware) SetShortIDFlag() gin.HandlerFunc {
+ return func(ctx *gin.Context) {
+ siteSeo, err := sm.siteInfoService.GetSiteSeo(ctx)
+ if err != nil {
+ log.Error(err)
+ return
+ }
+ ctx.Set(constant.ShortIDFlag, siteSeo.IsShortLink())
+ }
+}
diff --git a/internal/base/server/http.go b/internal/base/server/http.go
index 1c79dbee..faa9e046 100644
--- a/internal/base/server/http.go
+++ b/internal/base/server/http.go
@@ -20,6 +20,7 @@ func NewHTTPServer(debug bool,
viewRouter *router.UIRouter,
authUserMiddleware *middleware.AuthUserMiddleware,
avatarMiddleware *middleware.AvatarMiddleware,
+ shortIDMiddleware *middleware.ShortIDMiddleware,
templateRouter *router.TemplateRouter,
pluginAPIRouter *router.PluginAPIRouter,
) *gin.Engine {
@@ -30,7 +31,7 @@ func NewHTTPServer(debug bool,
gin.SetMode(gin.ReleaseMode)
}
r := gin.New()
- r.Use(brotli.Brotli(brotli.DefaultCompression), middleware.ExtractAndSetAcceptLanguage)
+ r.Use(brotli.Brotli(brotli.DefaultCompression), middleware.ExtractAndSetAcceptLanguage, shortIDMiddleware.SetShortIDFlag())
r.GET("/healthz", func(ctx *gin.Context) { ctx.String(200, "OK") })
html, _ := fs.Sub(ui.Template, "template")
diff --git a/internal/controller/activity_controller.go b/internal/controller/activity_controller.go
index bb7260cc..df1d0b02 100644
--- a/internal/controller/activity_controller.go
+++ b/internal/controller/activity_controller.go
@@ -5,22 +5,19 @@ import (
"github.com/answerdev/answer/internal/base/middleware"
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service/activity"
- "github.com/answerdev/answer/internal/service/activity_common"
"github.com/answerdev/answer/internal/service/role"
"github.com/answerdev/answer/pkg/uid"
"github.com/gin-gonic/gin"
)
type ActivityController struct {
- activityCommonService *activity_common.ActivityCommon
- activityService *activity.ActivityService
+ activityService *activity.ActivityService
}
// NewActivityController new activity controller.
func NewActivityController(
- activityCommonService *activity_common.ActivityCommon,
activityService *activity.ActivityService) *ActivityController {
- return &ActivityController{activityCommonService: activityCommonService, activityService: activityService}
+ return &ActivityController{activityService: activityService}
}
// GetObjectTimeline get object timeline
diff --git a/internal/controller/answer_controller.go b/internal/controller/answer_controller.go
index 93d34f5b..bf343616 100644
--- a/internal/controller/answer_controller.go
+++ b/internal/controller/answer_controller.go
@@ -8,7 +8,6 @@ import (
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service"
- "github.com/answerdev/answer/internal/service/dashboard"
"github.com/answerdev/answer/internal/service/permission"
"github.com/answerdev/answer/internal/service/rank"
"github.com/answerdev/answer/pkg/uid"
@@ -18,20 +17,17 @@ import (
// AnswerController answer controller
type AnswerController struct {
- answerService *service.AnswerService
- rankService *rank.RankService
- dashboardService *dashboard.DashboardService
+ answerService *service.AnswerService
+ rankService *rank.RankService
}
// NewAnswerController new controller
func NewAnswerController(answerService *service.AnswerService,
rankService *rank.RankService,
- dashboardService *dashboard.DashboardService,
) *AnswerController {
return &AnswerController{
- answerService: answerService,
- rankService: rankService,
- dashboardService: dashboardService,
+ answerService: answerService,
+ rankService: rankService,
}
}
diff --git a/internal/controller/connector_controller.go b/internal/controller/connector_controller.go
index f3bfdc5a..f6c35d70 100644
--- a/internal/controller/connector_controller.go
+++ b/internal/controller/connector_controller.go
@@ -23,14 +23,14 @@ const (
// ConnectorController comment controller
type ConnectorController struct {
- siteInfoService *siteinfo_common.SiteInfoCommonService
+ siteInfoService siteinfo_common.SiteInfoCommonService
userExternalService *user_external_login.UserExternalLoginService
emailService *export.EmailService
}
// NewConnectorController new controller
func NewConnectorController(
- siteInfoService *siteinfo_common.SiteInfoCommonService,
+ siteInfoService siteinfo_common.SiteInfoCommonService,
emailService *export.EmailService,
userExternalService *user_external_login.UserExternalLoginService,
) *ConnectorController {
diff --git a/internal/controller/controller.go b/internal/controller/controller.go
index 6b1c2fad..db5f98c7 100644
--- a/internal/controller/controller.go
+++ b/internal/controller/controller.go
@@ -19,7 +19,7 @@ var ProviderSetController = wire.NewSet(
NewRankController,
NewReasonController,
NewNotificationController,
- NewSiteinfoController,
+ NewSiteInfoController,
NewDashboardController,
NewUploadController,
NewActivityController,
diff --git a/internal/controller/dashboard_controller.go b/internal/controller/dashboard_controller.go
index a525adeb..fe7c0b26 100644
--- a/internal/controller/dashboard_controller.go
+++ b/internal/controller/dashboard_controller.go
@@ -7,12 +7,12 @@ import (
)
type DashboardController struct {
- dashboardService *dashboard.DashboardService
+ dashboardService dashboard.DashboardService
}
// NewDashboardController new controller
func NewDashboardController(
- dashboardService *dashboard.DashboardService,
+ dashboardService dashboard.DashboardService,
) *DashboardController {
return &DashboardController{
dashboardService: dashboardService,
@@ -29,7 +29,7 @@ func NewDashboardController(
// @Router /answer/admin/api/dashboard [get]
// @Success 200 {object} handler.RespBody
func (ac *DashboardController) DashboardInfo(ctx *gin.Context) {
- info, err := ac.dashboardService.StatisticalByCache(ctx)
+ info, err := ac.dashboardService.Statistical(ctx)
handler.HandleResponse(ctx, err, gin.H{
"info": info,
})
diff --git a/internal/controller/lang_controller.go b/internal/controller/lang_controller.go
index 0cf2637a..f2717fa8 100644
--- a/internal/controller/lang_controller.go
+++ b/internal/controller/lang_controller.go
@@ -12,11 +12,11 @@ import (
type LangController struct {
translator i18n.Translator
- siteInfoService *siteinfo_common.SiteInfoCommonService
+ siteInfoService siteinfo_common.SiteInfoCommonService
}
// NewLangController new language controller.
-func NewLangController(tr i18n.Translator, siteInfoService *siteinfo_common.SiteInfoCommonService) *LangController {
+func NewLangController(tr i18n.Translator, siteInfoService siteinfo_common.SiteInfoCommonService) *LangController {
return &LangController{translator: tr, siteInfoService: siteInfoService}
}
diff --git a/internal/controller/plugin_user_center_controller.go b/internal/controller/plugin_user_center_controller.go
index 024a0692..c4476065 100644
--- a/internal/controller/plugin_user_center_controller.go
+++ b/internal/controller/plugin_user_center_controller.go
@@ -22,13 +22,13 @@ const (
// UserCenterController comment controller
type UserCenterController struct {
userCenterLoginService *user_external_login.UserCenterLoginService
- siteInfoService *siteinfo_common.SiteInfoCommonService
+ siteInfoService siteinfo_common.SiteInfoCommonService
}
// NewUserCenterController new controller
func NewUserCenterController(
userCenterLoginService *user_external_login.UserCenterLoginService,
- siteInfoService *siteinfo_common.SiteInfoCommonService,
+ siteInfoService siteinfo_common.SiteInfoCommonService,
) *UserCenterController {
return &UserCenterController{
userCenterLoginService: userCenterLoginService,
diff --git a/internal/controller/question_controller.go b/internal/controller/question_controller.go
index a0c35be6..aa24fd32 100644
--- a/internal/controller/question_controller.go
+++ b/internal/controller/question_controller.go
@@ -7,11 +7,11 @@ import (
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/base/translator"
"github.com/answerdev/answer/internal/base/validator"
- "github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service"
"github.com/answerdev/answer/internal/service/permission"
"github.com/answerdev/answer/internal/service/rank"
+ "github.com/answerdev/answer/internal/service/siteinfo_common"
"github.com/answerdev/answer/pkg/uid"
"github.com/gin-gonic/gin"
"github.com/jinzhu/copier"
@@ -23,6 +23,7 @@ type QuestionController struct {
questionService *service.QuestionService
answerService *service.AnswerService
rankService *rank.RankService
+ siteInfoService siteinfo_common.SiteInfoCommonService
}
// NewQuestionController new controller
@@ -30,11 +31,13 @@ func NewQuestionController(
questionService *service.QuestionService,
answerService *service.AnswerService,
rankService *rank.RankService,
+ siteInfoService siteinfo_common.SiteInfoCommonService,
) *QuestionController {
return &QuestionController{
questionService: questionService,
answerService: answerService,
rankService: rankService,
+ siteInfoService: siteInfoService,
}
}
@@ -220,7 +223,9 @@ func (qc *QuestionController) GetQuestion(ctx *gin.Context) {
handler.HandleResponse(ctx, err, nil)
return
}
- info.ID = uid.EnShortID(info.ID)
+ if handler.GetEnableShortID(ctx) {
+ info.ID = uid.EnShortID(info.ID)
+ }
handler.HandleResponse(ctx, nil, info)
}
@@ -703,8 +708,8 @@ func (qc *QuestionController) PersonalCollectionPage(ctx *gin.Context) {
handler.HandleResponse(ctx, err, resp)
}
-// AdminSearchList godoc
-// @Summary AdminSearchList
+// AdminQuestionPage admin question page
+// @Summary AdminQuestionPage admin question page
// @Description Status:[available,closed,deleted]
// @Tags admin
// @Accept json
@@ -716,21 +721,19 @@ func (qc *QuestionController) PersonalCollectionPage(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) AdminSearchList(ctx *gin.Context) {
- req := &schema.AdminQuestionSearch{}
+func (qc *QuestionController) AdminQuestionPage(ctx *gin.Context) {
+ req := &schema.AdminQuestionPageReq{}
if handler.BindAndCheck(ctx, req) {
return
}
- userID := middleware.GetLoginUserIDFromContext(ctx)
- questionList, count, err := qc.questionService.AdminSearchList(ctx, req, userID)
- handler.HandleResponse(ctx, err, gin.H{
- "list": questionList,
- "count": count,
- })
+
+ req.LoginUserID = middleware.GetLoginUserIDFromContext(ctx)
+ resp, err := qc.questionService.AdminQuestionPage(ctx, req)
+ handler.HandleResponse(ctx, err, resp)
}
-// AdminSearchAnswerList godoc
-// @Summary AdminSearchAnswerList
+// AdminAnswerPage admin answer page
+// @Summary AdminAnswerPage admin answer page
// @Description Status:[available,deleted]
// @Tags admin
// @Accept json
@@ -743,21 +746,15 @@ func (qc *QuestionController) AdminSearchList(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) AdminSearchAnswerList(ctx *gin.Context) {
- req := &entity.AdminAnswerSearch{}
+func (qc *QuestionController) AdminAnswerPage(ctx *gin.Context) {
+ req := &schema.AdminAnswerPageReq{}
if handler.BindAndCheck(ctx, req) {
return
}
- req.QuestionID = uid.DeShortID(req.QuestionID)
- if req.QuestionID == "0" {
- req.QuestionID = ""
- }
- userID := middleware.GetLoginUserIDFromContext(ctx)
- questionList, count, err := qc.questionService.AdminSearchAnswerList(ctx, req, userID)
- handler.HandleResponse(ctx, err, gin.H{
- "list": questionList,
- "count": count,
- })
+
+ req.LoginUserID = middleware.GetLoginUserIDFromContext(ctx)
+ resp, err := qc.questionService.AdminAnswerPage(ctx, req)
+ handler.HandleResponse(ctx, err, resp)
}
// AdminSetQuestionStatus godoc
diff --git a/internal/controller/siteinfo_controller.go b/internal/controller/siteinfo_controller.go
index f815bdb9..4cdba56f 100644
--- a/internal/controller/siteinfo_controller.go
+++ b/internal/controller/siteinfo_controller.go
@@ -11,13 +11,13 @@ import (
"github.com/segmentfault/pacman/log"
)
-type SiteinfoController struct {
- siteInfoService *siteinfo_common.SiteInfoCommonService
+type SiteInfoController struct {
+ siteInfoService siteinfo_common.SiteInfoCommonService
}
-// NewSiteinfoController new siteinfo controller.
-func NewSiteinfoController(siteInfoService *siteinfo_common.SiteInfoCommonService) *SiteinfoController {
- return &SiteinfoController{
+// NewSiteInfoController new site info controller.
+func NewSiteInfoController(siteInfoService siteinfo_common.SiteInfoCommonService) *SiteInfoController {
+ return &SiteInfoController{
siteInfoService: siteInfoService,
}
}
@@ -29,7 +29,7 @@ func NewSiteinfoController(siteInfoService *siteinfo_common.SiteInfoCommonServic
// @Produce json
// @Success 200 {object} handler.RespBody{data=schema.SiteInfoResp}
// @Router /answer/api/v1/siteinfo [get]
-func (sc *SiteinfoController) GetSiteInfo(ctx *gin.Context) {
+func (sc *SiteInfoController) GetSiteInfo(ctx *gin.Context) {
var err error
resp := &schema.SiteInfoResp{Version: constant.Version, Revision: constant.Revision}
resp.General, err = sc.siteInfoService.GetSiteGeneral(ctx)
@@ -80,7 +80,7 @@ func (sc *SiteinfoController) GetSiteInfo(ctx *gin.Context) {
// @Produce json
// @Success 200 {object} handler.RespBody{data=schema.GetSiteLegalInfoResp}
// @Router /answer/api/v1/siteinfo/legal [get]
-func (sc *SiteinfoController) GetSiteLegalInfo(ctx *gin.Context) {
+func (sc *SiteInfoController) GetSiteLegalInfo(ctx *gin.Context) {
req := &schema.GetSiteLegalInfoReq{}
if handler.BindAndCheck(ctx, req) {
return
@@ -102,7 +102,7 @@ func (sc *SiteinfoController) GetSiteLegalInfo(ctx *gin.Context) {
}
// GetManifestJson get manifest.json
-func (sc *SiteinfoController) GetManifestJson(ctx *gin.Context) {
+func (sc *SiteInfoController) GetManifestJson(ctx *gin.Context) {
favicon := "favicon.ico"
resp := &schema.GetManifestJsonResp{
ManifestVersion: 3,
diff --git a/internal/controller/template_controller.go b/internal/controller/template_controller.go
index cf9dd5ee..a291b7d8 100644
--- a/internal/controller/template_controller.go
+++ b/internal/controller/template_controller.go
@@ -30,13 +30,13 @@ type TemplateController struct {
scriptPath string
cssPath string
templateRenderController *templaterender.TemplateRenderController
- siteInfoService *siteinfo_common.SiteInfoCommonService
+ siteInfoService siteinfo_common.SiteInfoCommonService
}
// NewTemplateController new controller
func NewTemplateController(
templateRenderController *templaterender.TemplateRenderController,
- siteInfoService *siteinfo_common.SiteInfoCommonService,
+ siteInfoService siteinfo_common.SiteInfoCommonService,
) *TemplateController {
script, css := GetStyle()
return &TemplateController{
@@ -116,7 +116,7 @@ func (tc *TemplateController) Index(ctx *gin.Context) {
siteInfo.Canonical = siteInfo.General.SiteUrl
UrlUseTitle := false
- if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionIDAndTitle {
+ if siteInfo.SiteSeo.PermaLink == constant.PermaLinkQuestionIDAndTitle {
UrlUseTitle = true
}
siteInfo.Title = ""
@@ -149,7 +149,7 @@ func (tc *TemplateController) QuestionList(ctx *gin.Context) {
}
UrlUseTitle := false
- if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionIDAndTitle {
+ if siteInfo.SiteSeo.PermaLink == constant.PermaLinkQuestionIDAndTitle {
UrlUseTitle = true
}
siteInfo.Title = fmt.Sprintf("Questions - %s", siteInfo.General.Name)
@@ -164,16 +164,21 @@ func (tc *TemplateController) QuestionInfoeRdirect(ctx *gin.Context, siteInfo *s
id := ctx.Param("id")
title := ctx.Param("title")
titleIsAnswerID := false
- NeedChangeShortID := false
+ needChangeShortID := false
+
+ siteSeo, err := tc.siteInfoService.GetSiteSeo(ctx)
+ if err != nil {
+ return false, ""
+ }
isShortID := uid.IsShortID(id)
- if uid.ShortIDSwitch {
+ if siteSeo.IsShortLink() {
if !isShortID {
id = uid.EnShortID(id)
- NeedChangeShortID = true
+ needChangeShortID = true
}
} else {
if isShortID {
- NeedChangeShortID = true
+ needChangeShortID = true
id = uid.DeShortID(id)
}
}
@@ -186,11 +191,11 @@ func (tc *TemplateController) QuestionInfoeRdirect(ctx *gin.Context, siteInfo *s
}
siteInfo = tc.SiteInfo(ctx)
url = fmt.Sprintf("%s/questions/%s", siteInfo.General.SiteUrl, id)
- if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionID || siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionIDByShortID {
+ if siteInfo.SiteSeo.PermaLink == constant.PermaLinkQuestionID || siteInfo.SiteSeo.PermaLink == constant.PermaLinkQuestionIDByShortID {
if len(ctx.Request.URL.Query()) > 0 {
url = fmt.Sprintf("%s?%s", url, ctx.Request.URL.RawQuery)
}
- if NeedChangeShortID {
+ if needChangeShortID {
return true, url
}
//not have title
@@ -216,7 +221,7 @@ func (tc *TemplateController) QuestionInfoeRdirect(ctx *gin.Context, siteInfo *s
}
//have title
if len(title) > 0 && !titleIsAnswerID && correctTitle {
- if NeedChangeShortID {
+ if needChangeShortID {
return true, url
}
return false, ""
@@ -277,9 +282,11 @@ func (tc *TemplateController) QuestionInfo(ctx *gin.Context) {
}
// comments
- objectIDs := []string{id}
+
+ objectIDs := []string{uid.DeShortID(id)}
for _, answer := range answers {
- objectIDs = append(objectIDs, answer.ID)
+ answerID := uid.DeShortID(answer.ID)
+ objectIDs = append(objectIDs, answerID)
}
comments, err := tc.templateRenderController.CommentList(ctx, objectIDs)
if err != nil {
@@ -287,7 +294,7 @@ func (tc *TemplateController) QuestionInfo(ctx *gin.Context) {
return
}
siteInfo.Canonical = fmt.Sprintf("%s/questions/%s/%s", siteInfo.General.SiteUrl, id, encodeTitle)
- if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionID {
+ if siteInfo.SiteSeo.PermaLink == constant.PermaLinkQuestionID {
siteInfo.Canonical = fmt.Sprintf("%s/questions/%s", siteInfo.General.SiteUrl, id)
}
jsonLD := &schema.QAPageJsonLD{}
@@ -402,7 +409,7 @@ func (tc *TemplateController) TagInfo(ctx *gin.Context) {
siteInfo.Keywords = taginifo.DisplayName
UrlUseTitle := false
- if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionIDAndTitle {
+ if siteInfo.SiteSeo.PermaLink == constant.PermaLinkQuestionIDAndTitle {
UrlUseTitle = true
}
siteInfo.Title = fmt.Sprintf("'%s' Questions - %s", taginifo.DisplayName, siteInfo.General.Name)
diff --git a/internal/controller/template_render/controller.go b/internal/controller/template_render/controller.go
index f44c1ea6..51a0fdd3 100644
--- a/internal/controller/template_render/controller.go
+++ b/internal/controller/template_render/controller.go
@@ -1,16 +1,16 @@
package templaterender
import (
+ questioncommon "github.com/answerdev/answer/internal/service/question_common"
"math"
- "github.com/answerdev/answer/internal/base/data"
"github.com/answerdev/answer/internal/service/comment"
"github.com/answerdev/answer/internal/service/siteinfo_common"
+ "github.com/google/wire"
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service"
"github.com/answerdev/answer/internal/service/tag"
- "github.com/google/wire"
)
// ProviderSetTemplateRenderController is template render controller providers.
@@ -24,8 +24,8 @@ type TemplateRenderController struct {
tagService *tag.TagService
answerService *service.AnswerService
commentService *comment.CommentService
- data *data.Data
- siteInfoService *siteinfo_common.SiteInfoCommonService
+ siteInfoService siteinfo_common.SiteInfoCommonService
+ questionRepo questioncommon.QuestionRepo
}
func NewTemplateRenderController(
@@ -34,9 +34,8 @@ func NewTemplateRenderController(
tagService *tag.TagService,
answerService *service.AnswerService,
commentService *comment.CommentService,
- data *data.Data,
- siteInfoService *siteinfo_common.SiteInfoCommonService,
-
+ siteInfoService siteinfo_common.SiteInfoCommonService,
+ questionRepo questioncommon.QuestionRepo,
) *TemplateRenderController {
return &TemplateRenderController{
questionService: questionService,
@@ -44,7 +43,7 @@ func NewTemplateRenderController(
tagService: tagService,
answerService: answerService,
commentService: commentService,
- data: data,
+ questionRepo: questionRepo,
siteInfoService: siteInfoService,
}
}
diff --git a/internal/controller/template_render/index.go b/internal/controller/template_render/index.go
deleted file mode 100644
index 362739ec..00000000
--- a/internal/controller/template_render/index.go
+++ /dev/null
@@ -1 +0,0 @@
-package templaterender
diff --git a/internal/controller/template_render/question.go b/internal/controller/template_render/question.go
index 6504d7e9..5629245d 100644
--- a/internal/controller/template_render/question.go
+++ b/internal/controller/template_render/question.go
@@ -1,11 +1,11 @@
package templaterender
import (
- "encoding/json"
- "fmt"
"html/template"
+ "math"
"net/http"
+ "github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/schema"
"github.com/gin-gonic/gin"
"github.com/segmentfault/pacman/log"
@@ -31,48 +31,46 @@ func (t *TemplateRenderController) Sitemap(ctx *gin.Context) {
return
}
- sitemapInfo := &schema.SiteMapList{}
- infoStr, err := t.data.Cache.GetString(ctx, schema.SitemapCachekey)
+ questions, err := t.questionRepo.SitemapQuestions(ctx, 1, constant.SitemapMaxSize)
if err != nil {
- log.Errorf("get Cache failed: %s", err)
- return
- }
- hasTitle := false
- if siteInfo.PermaLink == schema.PermaLinkQuestionIDAndTitle || siteInfo.PermaLink == schema.PermaLinkQuestionIDAndTitleByShortID {
- hasTitle = true
- }
- if err = json.Unmarshal([]byte(infoStr), sitemapInfo); err != nil {
- log.Errorf("get sitemap info failed: %s", err)
+ log.Errorf("get sitemap questions failed: %s", err)
return
}
- if len(sitemapInfo.QuestionIDs) > 0 {
- //question url list
- ctx.Header("Content-Type", "application/xml")
+ ctx.Header("Content-Type", "application/xml")
+ if len(questions) < constant.SitemapMaxSize {
ctx.HTML(
http.StatusOK, "sitemap.xml", gin.H{
"xmlHeader": template.HTML(``),
- "list": sitemapInfo.QuestionIDs,
- "general": general,
- "hastitle": hasTitle,
- },
- )
- } else {
- //question list page
- ctx.Header("Content-Type", "application/xml")
- ctx.HTML(
- http.StatusOK, "sitemap-list.xml", gin.H{
- "xmlHeader": template.HTML(``),
- "page": sitemapInfo.MaxPageNum,
+ "list": questions,
"general": general,
+ "hastitle": siteInfo.PermaLink == constant.PermaLinkQuestionIDAndTitle ||
+ siteInfo.PermaLink == constant.PermaLinkQuestionIDAndTitleByShortID,
},
)
return
}
+
+ questionNum, err := t.questionRepo.GetQuestionCount(ctx)
+ if err != nil {
+ log.Error("GetQuestionCount error", err)
+ return
+ }
+ var pageList []int
+ totalPages := int(math.Ceil(float64(questionNum) / float64(constant.SitemapMaxSize)))
+ for i := 1; i <= totalPages; i++ {
+ pageList = append(pageList, i)
+ }
+ ctx.HTML(
+ http.StatusOK, "sitemap-list.xml", gin.H{
+ "xmlHeader": template.HTML(``),
+ "page": pageList,
+ "general": general,
+ },
+ )
}
func (t *TemplateRenderController) SitemapPage(ctx *gin.Context, page int) error {
- sitemapInfo := &schema.SiteMapPageList{}
general, err := t.siteInfoService.GetSiteGeneral(ctx)
if err != nil {
log.Error("get site general failed:", err)
@@ -83,28 +81,20 @@ func (t *TemplateRenderController) SitemapPage(ctx *gin.Context, page int) error
log.Error("get site GetSiteSeo failed:", err)
return err
}
- hasTitle := false
- if siteInfo.PermaLink == schema.PermaLinkQuestionIDAndTitle || siteInfo.PermaLink == schema.PermaLinkQuestionIDAndTitleByShortID {
- hasTitle = true
- }
- cachekey := fmt.Sprintf(schema.SitemapPageCachekey, page)
- infoStr, err := t.data.Cache.GetString(ctx, cachekey)
+ questions, err := t.questionRepo.SitemapQuestions(ctx, page, constant.SitemapMaxSize)
if err != nil {
- //If there is no cache, return directly.
- return nil
- }
- if err = json.Unmarshal([]byte(infoStr), sitemapInfo); err != nil {
- log.Errorf("get sitemap info failed: %s", err)
+ log.Errorf("get sitemap questions failed: %s", err)
return err
}
ctx.Header("Content-Type", "application/xml")
ctx.HTML(
http.StatusOK, "sitemap.xml", gin.H{
"xmlHeader": template.HTML(``),
- "list": sitemapInfo.PageData,
+ "list": questions,
"general": general,
- "hastitle": hasTitle,
+ "hastitle": siteInfo.PermaLink == constant.PermaLinkQuestionIDAndTitle ||
+ siteInfo.PermaLink == constant.PermaLinkQuestionIDAndTitleByShortID,
},
)
return nil
diff --git a/internal/controller/user_controller.go b/internal/controller/user_controller.go
index 598d7008..ee216481 100644
--- a/internal/controller/user_controller.go
+++ b/internal/controller/user_controller.go
@@ -12,7 +12,6 @@ import (
"github.com/answerdev/answer/internal/service/auth"
"github.com/answerdev/answer/internal/service/export"
"github.com/answerdev/answer/internal/service/siteinfo_common"
- "github.com/answerdev/answer/internal/service/uploader"
"github.com/answerdev/answer/pkg/checker"
"github.com/gin-gonic/gin"
"github.com/segmentfault/pacman/errors"
@@ -24,9 +23,8 @@ type UserController struct {
userService *service.UserService
authService *auth.AuthService
actionService *action.CaptchaService
- uploaderService uploader.UploaderService
emailService *export.EmailService
- siteInfoCommonService *siteinfo_common.SiteInfoCommonService
+ siteInfoCommonService siteinfo_common.SiteInfoCommonService
}
// NewUserController new controller
@@ -35,14 +33,12 @@ func NewUserController(
userService *service.UserService,
actionService *action.CaptchaService,
emailService *export.EmailService,
- uploaderService uploader.UploaderService,
- siteInfoCommonService *siteinfo_common.SiteInfoCommonService,
+ siteInfoCommonService siteinfo_common.SiteInfoCommonService,
) *UserController {
return &UserController{
authService: authService,
userService: userService,
actionService: actionService,
- uploaderService: uploaderService,
emailService: emailService,
siteInfoCommonService: siteInfoCommonService,
}
diff --git a/internal/controller/vote_controller.go b/internal/controller/vote_controller.go
index 6af80e35..3548e053 100644
--- a/internal/controller/vote_controller.go
+++ b/internal/controller/vote_controller.go
@@ -10,7 +10,6 @@ import (
"github.com/answerdev/answer/internal/service/rank"
"github.com/answerdev/answer/pkg/uid"
"github.com/gin-gonic/gin"
- "github.com/jinzhu/copier"
"github.com/segmentfault/pacman/errors"
)
@@ -54,9 +53,7 @@ func (vc *VoteController) VoteUp(ctx *gin.Context) {
return
}
- dto := &schema.VoteDTO{}
- _ = copier.Copy(dto, req)
- resp, err := vc.VoteService.VoteUp(ctx, dto)
+ resp, err := vc.VoteService.VoteUp(ctx, req)
if err != nil {
handler.HandleResponse(ctx, err, schema.ErrTypeToast)
} else {
@@ -93,9 +90,7 @@ func (vc *VoteController) VoteDown(ctx *gin.Context) {
return
}
- dto := &schema.VoteDTO{}
- _ = copier.Copy(dto, req)
- resp, err := vc.VoteService.VoteDown(ctx, dto)
+ resp, err := vc.VoteService.VoteDown(ctx, req)
if err != nil {
handler.HandleResponse(ctx, err, schema.ErrTypeToast)
} else {
@@ -103,9 +98,9 @@ func (vc *VoteController) VoteDown(ctx *gin.Context) {
}
}
-// UserVotes godoc
-// @Summary user's votes
-// @Description user's vote
+// UserVotes user votes
+// @Summary get user personal votes
+// @Description get user personal votes
// @Tags Activity
// @Accept json
// @Produce json
@@ -116,21 +111,12 @@ func (vc *VoteController) VoteDown(ctx *gin.Context) {
// @Router /answer/api/v1/personal/vote/page [get]
func (vc *VoteController) UserVotes(ctx *gin.Context) {
req := schema.GetVoteWithPageReq{}
- req.UserID = middleware.GetLoginUserIDFromContext(ctx)
if handler.BindAndCheck(ctx, &req) {
return
}
- if req.Page == 0 {
- req.Page = 1
- }
- if req.PageSize == 0 {
- req.PageSize = 30
- }
+
+ req.UserID = middleware.GetLoginUserIDFromContext(ctx)
resp, err := vc.VoteService.ListUserVotes(ctx, req)
- if err != nil {
- handler.HandleResponse(ctx, err, schema.ErrTypeModal)
- } else {
- handler.HandleResponse(ctx, err, resp)
- }
+ handler.HandleResponse(ctx, err, resp)
}
diff --git a/internal/entity/answer_entity.go b/internal/entity/answer_entity.go
index 43567efd..74b599dc 100644
--- a/internal/entity/answer_entity.go
+++ b/internal/entity/answer_entity.go
@@ -42,15 +42,6 @@ type AnswerSearch struct {
PageSize int `json:"page_size" form:"page_size"` // Search page size
}
-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:"-"`
- StatusStr string `json:"status" form:"status"` // Status 1 Available 2 closed 10 Deleted
- Query string `validate:"omitempty,gt=0,lte=100" json:"query" form:"query" ` //Query string
- QuestionID string `validate:"omitempty,gt=0,lte=24" json:"question_id" form:"question_id" ` //Query string
-}
-
// TableName answer table name
func (Answer) TableName() string {
return "answer"
diff --git a/internal/install/install_controller.go b/internal/install/install_controller.go
index 465378f7..154976c2 100644
--- a/internal/install/install_controller.go
+++ b/internal/install/install_controller.go
@@ -16,6 +16,7 @@ import (
"github.com/answerdev/answer/internal/migrations"
"github.com/answerdev/answer/internal/schema"
"github.com/gin-gonic/gin"
+ "github.com/jinzhu/copier"
"github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/log"
)
@@ -184,17 +185,17 @@ func InitBaseInfo(ctx *gin.Context) {
return
}
- if err := migrations.InitDB(c.Data.Database); err != nil {
- log.Error("init database error: ", err.Error())
- handler.HandleResponse(ctx, errors.BadRequest(reason.InstallCreateTableFailed), schema.ErrTypeAlert)
- return
+ engine, err := data.NewDB(false, c.Data.Database)
+ if err != nil {
+ log.Errorf("init database failed %s", err)
+ handler.HandleResponse(ctx, errors.BadRequest(reason.InstallCreateTableFailed), nil)
}
- err = migrations.UpdateInstallInfo(c.Data.Database, req.Language, req.SiteName, req.SiteURL, req.ContactEmail,
- req.AdminName, req.AdminPassword, req.AdminEmail)
- if err != nil {
- log.Error(err)
- handler.HandleResponse(ctx, errors.BadRequest(reason.InstallConfigFailed), nil)
+ inputData := &migrations.InitNeedUserInputData{}
+ _ = copier.Copy(inputData, req)
+ if err := migrations.NewMentor(ctx, engine, inputData).InitDB(); err != nil {
+ log.Error("init database error: ", err.Error())
+ handler.HandleResponse(ctx, errors.BadRequest(reason.InstallConfigFailed), schema.ErrTypeAlert)
return
}
diff --git a/internal/migrations/init.go b/internal/migrations/init.go
index 0df92b56..19b03925 100644
--- a/internal/migrations/init.go
+++ b/internal/migrations/init.go
@@ -1,188 +1,183 @@
package migrations
import (
+ "context"
"encoding/json"
"fmt"
- "github.com/answerdev/answer/internal/base/data"
"github.com/answerdev/answer/internal/entity"
- "github.com/answerdev/answer/internal/service/permission"
"golang.org/x/crypto/bcrypt"
"xorm.io/xorm"
)
-const (
- defaultSEORobotTxt = `User-agent: *
-Disallow: /admin
-Disallow: /search
-Disallow: /install
-Disallow: /review
-Disallow: /users/login
-Disallow: /users/register
-Disallow: /users/account-recovery
-Disallow: /users/oauth/*
-Disallow: /users/*/*
-Disallow: /answer/api
-Disallow: /*?code*
-
-Sitemap: `
-)
-
-var tables = []interface{}{
- &entity.Activity{},
- &entity.Answer{},
- &entity.Collection{},
- &entity.CollectionGroup{},
- &entity.Comment{},
- &entity.Config{},
- &entity.Meta{},
- &entity.Notification{},
- &entity.Question{},
- &entity.Report{},
- &entity.Revision{},
- &entity.SiteInfo{},
- &entity.Tag{},
- &entity.TagRel{},
- &entity.Uniqid{},
- &entity.User{},
- &entity.Version{},
- &entity.Role{},
- &entity.RolePowerRel{},
- &entity.Power{},
- &entity.UserRoleRel{},
- &entity.PluginConfig{},
- &entity.UserExternalLogin{},
+type Mentor struct {
+ ctx context.Context
+ engine *xorm.Engine
+ userData *InitNeedUserInputData
+ err error
+ Done bool
}
-// InitDB init db
-func InitDB(dataConf *data.Database) (err error) {
- engine, err := data.NewDB(false, dataConf)
- if err != nil {
- fmt.Println("new database failed: ", err.Error())
- return err
- }
+func NewMentor(ctx context.Context, engine *xorm.Engine, data *InitNeedUserInputData) *Mentor {
+ return &Mentor{ctx: ctx, engine: engine, userData: data}
+}
- exist, err := engine.IsTableExist(&entity.Version{})
- if err != nil {
- return fmt.Errorf("check table exists failed: %s", err)
+type InitNeedUserInputData struct {
+ Language string
+ SiteName string
+ SiteURL string
+ ContactEmail string
+ AdminName string
+ AdminPassword string
+ AdminEmail string
+}
+
+func (m *Mentor) InitDB() error {
+ m.do("check table exist", m.checkTableExist)
+ m.do("sync table", m.syncTable)
+ m.do("init version table", m.initVersionTable)
+ m.do("init admin user", m.initAdminUser)
+ m.do("init config", m.initConfig)
+ m.do("init role", m.initRole)
+ m.do("init power", m.initPower)
+ m.do("init role power rel", m.initRolePowerRel)
+ m.do("init admin user role rel", m.initAdminUserRoleRel)
+ m.do("init site info interface", m.initSiteInfoInterface)
+ m.do("init site info general config", m.initSiteInfoGeneralData)
+ m.do("init site info login config", m.initSiteInfoLoginConfig)
+ m.do("init site info theme config", m.initSiteInfoThemeConfig)
+ m.do("init site info seo config", m.initSiteInfoSEOConfig)
+ m.do("init site info user config", m.initSiteInfoUsersConfig)
+ return m.err
+}
+
+func (m *Mentor) do(taskName string, fn func()) {
+ if m.err != nil || m.Done {
+ return
}
- if exist {
+ fn()
+ if m.err != nil {
+ m.err = fmt.Errorf("%s failed: %s", taskName, m.err)
+ }
+}
+
+func (m *Mentor) checkTableExist() {
+ m.Done, m.err = m.engine.Context(m.ctx).IsTableExist(&entity.Version{})
+ if m.Done {
fmt.Println("[database] already exists")
- return nil
}
-
- err = engine.Sync(tables...)
- if err != nil {
- return fmt.Errorf("sync table failed: %s", err)
- }
- _, err = engine.InsertOne(&entity.Version{ID: 1, VersionNumber: ExpectedVersion()})
- if err != nil {
- return fmt.Errorf("init version table failed: %s", err)
- }
-
- err = initAdminUser(engine)
- if err != nil {
- return fmt.Errorf("init admin user failed: %s", err)
- }
-
- err = initConfigTable(engine)
- if err != nil {
- return fmt.Errorf("init config table: %s", err)
- }
-
- err = initRolePower(engine)
- if err != nil {
- return fmt.Errorf("init role and power failed: %s", err)
- }
- return nil
}
-func initAdminUser(engine *xorm.Engine) error {
- _, err := engine.InsertOne(&entity.User{
+func (m *Mentor) syncTable() {
+ m.err = m.engine.Context(m.ctx).Sync(tables...)
+}
+
+func (m *Mentor) initVersionTable() {
+ _, m.err = m.engine.Context(m.ctx).Insert(&entity.Version{ID: 1, VersionNumber: ExpectedVersion()})
+}
+
+func (m *Mentor) initAdminUser() {
+ generateFromPassword, _ := bcrypt.GenerateFromPassword([]byte(m.userData.AdminPassword), bcrypt.DefaultCost)
+ _, m.err = m.engine.Context(m.ctx).Insert(&entity.User{
ID: "1",
- Username: "admin",
- Pass: "$2a$10$.gnUnpW.8ssRNaEvx.XwvOR2NuPsGzFLWWX2rqSIVAdIvLNZZYs5y", // admin
- EMail: "admin@admin.com",
+ Username: m.userData.AdminName,
+ Pass: string(generateFromPassword),
+ EMail: m.userData.AdminEmail,
MailStatus: 1,
NoticeStatus: 1,
Status: 1,
Rank: 1,
- DisplayName: "admin",
+ DisplayName: m.userData.AdminName,
})
- return err
}
-func initSiteInfo(engine *xorm.Engine, language, siteName, siteURL, contactEmail string) error {
+func (m *Mentor) initConfig() {
+ _, m.err = m.engine.Context(m.ctx).Insert(defaultConfigTable)
+}
+
+func (m *Mentor) initRole() {
+ _, m.err = m.engine.Context(m.ctx).Insert(roles)
+}
+
+func (m *Mentor) initPower() {
+ _, m.err = m.engine.Context(m.ctx).Insert(powers)
+}
+
+func (m *Mentor) initRolePowerRel() {
+ _, m.err = m.engine.Context(m.ctx).Insert(rolePowerRels)
+}
+
+func (m *Mentor) initAdminUserRoleRel() {
+ _, m.err = m.engine.Context(m.ctx).Insert(adminUserRoleRel)
+}
+
+func (m *Mentor) initSiteInfoInterface() {
interfaceData := map[string]string{
- "language": language,
+ "language": m.userData.Language,
"time_zone": "UTC",
}
interfaceDataBytes, _ := json.Marshal(interfaceData)
- _, err := engine.InsertOne(&entity.SiteInfo{
+ _, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
Type: "interface",
Content: string(interfaceDataBytes),
Status: 1,
})
- if err != nil {
- return err
- }
+}
+func (m *Mentor) initSiteInfoGeneralData() {
generalData := map[string]string{
- "name": siteName,
- "site_url": siteURL,
- "contact_email": contactEmail,
+ "name": m.userData.SiteName,
+ "site_url": m.userData.SiteURL,
+ "contact_email": m.userData.ContactEmail,
}
generalDataBytes, _ := json.Marshal(generalData)
- _, err = engine.InsertOne(&entity.SiteInfo{
+ _, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
Type: "general",
Content: string(generalDataBytes),
Status: 1,
})
- if err != nil {
- return err
- }
+}
+func (m *Mentor) initSiteInfoLoginConfig() {
loginConfig := map[string]bool{
"allow_new_registrations": true,
"allow_email_registrations": true,
"login_required": false,
}
loginConfigDataBytes, _ := json.Marshal(loginConfig)
- _, err = engine.InsertOne(&entity.SiteInfo{
+ _, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
Type: "login",
Content: string(loginConfigDataBytes),
Status: 1,
})
- if err != nil {
- return err
- }
+}
+func (m *Mentor) initSiteInfoThemeConfig() {
themeConfig := `{"theme":"default","theme_config":{"default":{"navbar_style":"colored","primary_color":"#0033ff"}}}`
- _, err = engine.InsertOne(&entity.SiteInfo{
+ _, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
Type: "theme",
Content: themeConfig,
Status: 1,
})
- if err != nil {
- return err
- }
+}
- seoData := map[string]string{
- "robots": defaultSEORobotTxt + siteURL + "/sitemap.xml",
+func (m *Mentor) initSiteInfoSEOConfig() {
+ seoData := map[string]interface{}{
+ "permalink": 1,
+ "robots": defaultSEORobotTxt + m.userData.SiteURL + "/sitemap.xml",
}
seoDataBytes, _ := json.Marshal(seoData)
- _, err = engine.InsertOne(&entity.SiteInfo{
+ _, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
Type: "seo",
Content: string(seoDataBytes),
Status: 1,
})
- if err != nil {
- return err
- }
+}
+func (m *Mentor) initSiteInfoUsersConfig() {
usersData := map[string]any{
"default_avatar": "gravatar",
- "default_gravatar_base_url": "https://www.gravatar.com/avatar/",
+ "gravatar_base_url": "https://www.gravatar.com/avatar/",
"allow_update_display_name": true,
"allow_update_username": true,
"allow_update_avatar": true,
@@ -191,344 +186,9 @@ func initSiteInfo(engine *xorm.Engine, language, siteName, siteURL, contactEmail
"allow_update_location": true,
}
usersDataBytes, _ := json.Marshal(usersData)
- _, err = engine.InsertOne(&entity.SiteInfo{
+ _, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
Type: "users",
Content: string(usersDataBytes),
Status: 1,
})
- if err != nil {
- return err
- }
- return err
-}
-
-func updateAdminInfo(engine *xorm.Engine, adminName, adminPassword, adminEmail string) error {
- generateFromPassword, err := bcrypt.GenerateFromPassword([]byte(adminPassword), bcrypt.DefaultCost)
- if err != nil {
- return err
- }
- adminPassword = string(generateFromPassword)
-
- // update admin info
- _, err = engine.ID("1").Update(&entity.User{
- Username: adminName,
- Pass: adminPassword,
- EMail: adminEmail,
- DisplayName: adminName,
- })
- if err != nil {
- return fmt.Errorf("update admin user info failed: %s", err)
- }
- return nil
-}
-
-// UpdateInstallInfo update some init data about the admin interface and admin password
-func UpdateInstallInfo(dataConf *data.Database, language string,
- siteName string,
- siteURL string,
- contactEmail string,
- adminName string,
- adminPassword string,
- adminEmail string) error {
-
- engine, err := data.NewDB(false, dataConf)
- if err != nil {
- return fmt.Errorf("database connection error: %s", err)
- }
-
- err = updateAdminInfo(engine, adminName, adminPassword, adminEmail)
- if err != nil {
- return fmt.Errorf("update admin info failed: %s", err)
- }
-
- err = initSiteInfo(engine, language, siteName, siteURL, contactEmail)
- if err != nil {
- return fmt.Errorf("init site info failed: %s", err)
- }
- return err
-}
-
-func initConfigTable(engine *xorm.Engine) error {
- defaultConfigTable := []*entity.Config{
- {ID: 1, Key: "answer.accepted", Value: `15`},
- {ID: 2, Key: "answer.voted_up", Value: `10`},
- {ID: 3, Key: "question.voted_up", Value: `10`},
- {ID: 4, Key: "tag.edit_accepted", Value: `2`},
- {ID: 5, Key: "answer.accept", Value: `2`},
- {ID: 6, Key: "answer.voted_down_cancel", Value: `2`},
- {ID: 7, Key: "question.voted_down_cancel", Value: `2`},
- {ID: 8, Key: "answer.vote_down_cancel", Value: `1`},
- {ID: 9, Key: "question.vote_down_cancel", Value: `1`},
- {ID: 10, Key: "user.activated", Value: `1`},
- {ID: 11, Key: "edit.accepted", Value: `2`},
- {ID: 12, Key: "answer.vote_down", Value: `-1`},
- {ID: 13, Key: "question.voted_down", Value: `-2`},
- {ID: 14, Key: "answer.voted_down", Value: `-2`},
- {ID: 15, Key: "answer.accept_cancel", Value: `-2`},
- {ID: 16, Key: "answer.deleted", Value: `-5`},
- {ID: 17, Key: "question.voted_up_cancel", Value: `-10`},
- {ID: 18, Key: "answer.voted_up_cancel", Value: `-10`},
- {ID: 19, Key: "answer.accepted_cancel", Value: `-15`},
- {ID: 20, Key: "object.reported", Value: `-100`},
- {ID: 21, Key: "edit.rejected", Value: `-2`},
- {ID: 22, Key: "daily_rank_limit", Value: `200`},
- {ID: 23, Key: "daily_rank_limit.exclude", Value: `["answer.accepted"]`},
- {ID: 24, Key: "user.follow", Value: `0`},
- {ID: 25, Key: "comment.vote_up", Value: `0`},
- {ID: 26, Key: "comment.vote_up_cancel", Value: `0`},
- {ID: 27, Key: "question.vote_down", Value: `0`},
- {ID: 28, Key: "question.vote_up", Value: `0`},
- {ID: 29, Key: "question.vote_up_cancel", Value: `0`},
- {ID: 30, Key: "answer.vote_up", Value: `0`},
- {ID: 31, Key: "answer.vote_up_cancel", Value: `0`},
- {ID: 32, Key: "question.follow", Value: `0`},
- {ID: 33, Key: "email.config", Value: `{"from_name":"","from_email":"","smtp_host":"","smtp_port":465,"smtp_password":"","smtp_username":"","smtp_authentication":true,"encryption":"","register_title":"[{{.SiteName}}] Confirm your new account","register_body":"Welcome to {{.SiteName}}
\n\nClick the following link to confirm and activate your new account:
\n{{.RegisterUrl}}
\n\nIf the above link is not clickable, try copying and pasting it into the address bar of your web browser.\n","pass_reset_title":"[{{.SiteName }}] Password reset","pass_reset_body":"Somebody asked to reset your password on [{{.SiteName}}].
\n\nIf it was not you, you can safely ignore this email.
\n\nClick the following link to choose a new password:
\n{{.PassResetUrl}}\n","change_title":"[{{.SiteName}}] Confirm your new email address","change_body":"Confirm your new email address for {{.SiteName}} by clicking on the following link:
\n\n{{.ChangeEmailUrl}}
\n\nIf you did not request this change, please ignore this email.\n","test_title":"[{{.SiteName}}] Test Email","test_body":"This is a test email.","new_answer_title":"[{{.SiteName}}] {{.DisplayName}} answered your question","new_answer_body":"{{.QuestionTitle}}
\n\n{{.DisplayName}}:
\n
{{.AnswerSummary}}
\nView it on {{.SiteName}}
\n\nYou are receiving this because you authored the thread. Unsubscribe","new_comment_title":"[{{.SiteName}}] {{.DisplayName}} commented on your post","new_comment_body":"{{.QuestionTitle}}
\n\n{{.DisplayName}}:
\n{{.CommentSummary}}
\nView it on {{.SiteName}}
\n\nYou are receiving this because you authored the thread. Unsubscribe"}`},
- {ID: 35, Key: "tag.follow", Value: `0`},
- {ID: 36, Key: "rank.question.add", Value: `1`},
- {ID: 37, Key: "rank.question.edit", Value: `200`},
- {ID: 38, Key: "rank.question.delete", Value: `-1`},
- {ID: 39, Key: "rank.question.vote_up", Value: `15`},
- {ID: 40, Key: "rank.question.vote_down", Value: `125`},
- {ID: 41, Key: "rank.answer.add", Value: `1`},
- {ID: 42, Key: "rank.answer.edit", Value: `200`},
- {ID: 43, Key: "rank.answer.delete", Value: `-1`},
- {ID: 44, Key: "rank.answer.accept", Value: `-1`},
- {ID: 45, Key: "rank.answer.vote_up", Value: `15`},
- {ID: 46, Key: "rank.answer.vote_down", Value: `125`},
- {ID: 47, Key: "rank.comment.add", Value: `1`},
- {ID: 48, Key: "rank.comment.edit", Value: `-1`},
- {ID: 49, Key: "rank.comment.delete", Value: `-1`},
- {ID: 50, Key: "rank.report.add", Value: `1`},
- {ID: 51, Key: "rank.tag.add", Value: `1`},
- {ID: 52, Key: "rank.tag.edit", Value: `100`},
- {ID: 53, Key: "rank.tag.delete", Value: `-1`},
- {ID: 54, Key: "rank.tag.synonym", Value: `20000`},
- {ID: 55, Key: "rank.link.url_limit", Value: `10`},
- {ID: 56, Key: "rank.vote.detail", Value: `0`},
- {ID: 57, Key: "reason.spam", Value: `{"name":"spam","description":"This post is an advertisement, or vandalism. It is not useful or relevant to the current topic."}`},
- {ID: 58, Key: "reason.rude_or_abusive", Value: `{"name":"rude or abusive","description":"A reasonable person would find this content inappropriate for respectful discourse."}`},
- {ID: 59, Key: "reason.something", Value: `{"name":"something else","description":"This post requires staff attention for another reason not listed above.","content_type":"textarea"}`},
- {ID: 60, Key: "reason.a_duplicate", Value: `{"name":"a duplicate","description":"This question has been asked before and already has an answer.","content_type":"text"}`},
- {ID: 61, Key: "reason.not_a_answer", Value: `{"name":"not a answer","description":"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.","content_type":""}`},
- {ID: 62, Key: "reason.no_longer_needed", Value: `{"name":"no longer needed","description":"This comment is outdated, conversational or not relevant to this post."}`},
- {ID: 63, Key: "reason.community_specific", Value: `{"name":"a community-specific reason","description":"This question doesn’t meet a community guideline."}`},
- {ID: 64, Key: "reason.not_clarity", Value: `{"name":"needs details or clarity","description":"This question currently includes multiple questions in one. It should focus on one problem only.","content_type":"text"}`},
- {ID: 65, Key: "reason.normal", Value: `{"name":"normal","description":"A normal post available to everyone."}`},
- {ID: 66, Key: "reason.normal.user", Value: `{"name":"normal","description":"A normal user can ask and answer questions."}`},
- {ID: 67, Key: "reason.closed", Value: `{"name":"closed","description":"A closed question can’t answer, but still can edit, vote and comment."}`},
- {ID: 68, Key: "reason.deleted", Value: `{"name":"deleted","description":"All reputation gained and lost will be restored."}`},
- {ID: 69, Key: "reason.deleted.user", Value: `{"name":"deleted","description":"Delete profile, authentication associations."}`},
- {ID: 70, Key: "reason.suspended", Value: `{"name":"suspended","description":"A suspended user can’t log in."}`},
- {ID: 71, Key: "reason.inactive", Value: `{"name":"inactive","description":"An inactive user must re-validate their email."}`},
- {ID: 72, Key: "reason.looks_ok", Value: `{"name":"looks ok","description":"This post is good as-is and not low quality."}`},
- {ID: 73, Key: "reason.needs_edit", Value: `{"name":"needs edit, and I did it","description":"Improve and correct problems with this post yourself."}`},
- {ID: 74, Key: "reason.needs_close", Value: `{"name":"needs close","description":"A closed question can’t answer, but still can edit, vote and comment."}`},
- {ID: 75, Key: "reason.needs_delete", Value: `{"name":"needs delete","description":"All reputation gained and lost will be restored."}`},
- {ID: 76, Key: "question.flag.reasons", Value: `["reason.spam","reason.rude_or_abusive","reason.something","reason.a_duplicate"]`},
- {ID: 77, Key: "answer.flag.reasons", Value: `["reason.spam","reason.rude_or_abusive","reason.something","reason.not_a_answer"]`},
- {ID: 78, Key: "comment.flag.reasons", Value: `["reason.spam","reason.rude_or_abusive","reason.something","reason.no_longer_needed"]`},
- {ID: 79, Key: "question.close.reasons", Value: `["reason.a_duplicate","reason.community_specific","reason.not_clarity","reason.something"]`},
- {ID: 80, Key: "question.status.reasons", Value: `["reason.normal","reason.closed","reason.deleted"]`},
- {ID: 81, Key: "answer.status.reasons", Value: `["reason.normal","reason.deleted"]`},
- {ID: 82, Key: "comment.status.reasons", Value: `["reason.normal","reason.deleted"]`},
- {ID: 83, Key: "user.status.reasons", Value: `["reason.normal.user","reason.suspended","reason.deleted.user","reason.inactive"]`},
- {ID: 84, Key: "question.review.reasons", Value: `["reason.looks_ok","reason.needs_edit","reason.needs_close","reason.needs_delete"]`},
- {ID: 85, Key: "answer.review.reasons", Value: `["reason.looks_ok","reason.needs_edit","reason.needs_delete"]`},
- {ID: 86, Key: "comment.review.reasons", Value: `["reason.looks_ok","reason.needs_edit","reason.needs_delete"]`},
- {ID: 87, Key: "question.asked", Value: `0`},
- {ID: 88, Key: "question.closed", Value: `0`},
- {ID: 89, Key: "question.reopened", Value: `0`},
- {ID: 90, Key: "question.answered", Value: `0`},
- {ID: 91, Key: "question.commented", Value: `0`},
- {ID: 92, Key: "question.accept", Value: `0`},
- {ID: 93, Key: "question.edited", Value: `0`},
- {ID: 94, Key: "question.rollback", Value: `0`},
- {ID: 95, Key: "question.deleted", Value: `0`},
- {ID: 96, Key: "question.undeleted", Value: `0`},
- {ID: 97, Key: "answer.answered", Value: `0`},
- {ID: 98, Key: "answer.commented", Value: `0`},
- {ID: 99, Key: "answer.edited", Value: `0`},
- {ID: 100, Key: "answer.rollback", Value: `0`},
- {ID: 101, Key: "answer.undeleted", Value: `0`},
- {ID: 102, Key: "tag.created", Value: `0`},
- {ID: 103, Key: "tag.edited", Value: `0`},
- {ID: 104, Key: "tag.rollback", Value: `0`},
- {ID: 105, Key: "tag.deleted", Value: `0`},
- {ID: 106, Key: "tag.undeleted", Value: `0`},
- {ID: 107, Key: "rank.comment.vote_up", Value: `1`},
- {ID: 108, Key: "rank.comment.vote_down", Value: `1`},
- {ID: 109, Key: "rank.question.edit_without_review", Value: `2000`},
- {ID: 110, Key: "rank.answer.edit_without_review", Value: `2000`},
- {ID: 111, Key: "rank.tag.edit_without_review", Value: `20000`},
- {ID: 112, Key: "rank.answer.audit", Value: `2000`},
- {ID: 113, Key: "rank.question.audit", Value: `2000`},
- {ID: 114, Key: "rank.tag.audit", Value: `20000`},
- {ID: 115, Key: "rank.question.close", Value: `-1`},
- {ID: 116, Key: "rank.question.reopen", Value: `-1`},
- {ID: 117, Key: "rank.tag.use_reserved_tag", Value: `-1`},
- {ID: 118, Key: "plugin.status", Value: `{}`},
- {ID: 119, Key: "question.pin", Value: `0`},
- {ID: 120, Key: "question.unpin", Value: `0`},
- {ID: 121, Key: "question.show", Value: `0`},
- {ID: 122, Key: "question.hide", Value: `0`},
- {ID: 123, Key: "rank.question.pin", Value: `-1`},
- {ID: 124, Key: "rank.question.unpin", Value: `-1`},
- {ID: 125, Key: "rank.question.show", Value: `-1`},
- {ID: 126, Key: "rank.question.hide", Value: `-1`},
- {ID: 127, Key: "rank.answer.invite_someone_to_answer", Value: `1000`},
- }
- _, err := engine.Insert(defaultConfigTable)
- return err
-}
-
-func initRolePower(engine *xorm.Engine) (err error) {
- roles := []*entity.Role{
- {ID: 1, Name: "User", Description: "Default with no special access."},
- {ID: 2, Name: "Admin", Description: "Have the full power to access the site."},
- {ID: 3, Name: "Moderator", Description: "Has access to all posts except admin settings."},
- }
- _, err = engine.Insert(roles)
- if err != nil {
- return err
- }
-
- powers := []*entity.Power{
- {ID: 1, Name: "admin access", PowerType: permission.AdminAccess, Description: "admin access"},
- {ID: 2, Name: "question add", PowerType: permission.QuestionAdd, Description: "question add"},
- {ID: 3, Name: "question edit", PowerType: permission.QuestionEdit, Description: "question edit"},
- {ID: 4, Name: "question edit without review", PowerType: permission.QuestionEditWithoutReview, Description: "question edit without review"},
- {ID: 5, Name: "question delete", PowerType: permission.QuestionDelete, Description: "question delete"},
- {ID: 6, Name: "question close", PowerType: permission.QuestionClose, Description: "question close"},
- {ID: 7, Name: "question reopen", PowerType: permission.QuestionReopen, Description: "question reopen"},
- {ID: 8, Name: "question vote up", PowerType: permission.QuestionVoteUp, Description: "question vote up"},
- {ID: 9, Name: "question vote down", PowerType: permission.QuestionVoteDown, Description: "question vote down"},
- {ID: 10, Name: "answer add", PowerType: permission.AnswerAdd, Description: "answer add"},
- {ID: 11, Name: "answer edit", PowerType: permission.AnswerEdit, Description: "answer edit"},
- {ID: 12, Name: "answer edit without review", PowerType: permission.AnswerEditWithoutReview, Description: "answer edit without review"},
- {ID: 13, Name: "answer delete", PowerType: permission.AnswerDelete, Description: "answer delete"},
- {ID: 14, Name: "answer accept", PowerType: permission.AnswerAccept, Description: "answer accept"},
- {ID: 15, Name: "answer vote up", PowerType: permission.AnswerVoteUp, Description: "answer vote up"},
- {ID: 16, Name: "answer vote down", PowerType: permission.AnswerVoteDown, Description: "answer vote down"},
- {ID: 17, Name: "comment add", PowerType: permission.CommentAdd, Description: "comment add"},
- {ID: 18, Name: "comment edit", PowerType: permission.CommentEdit, Description: "comment edit"},
- {ID: 19, Name: "comment delete", PowerType: permission.CommentDelete, Description: "comment delete"},
- {ID: 20, Name: "comment vote up", PowerType: permission.CommentVoteUp, Description: "comment vote up"},
- {ID: 21, Name: "comment vote down", PowerType: permission.CommentVoteDown, Description: "comment vote down"},
- {ID: 22, Name: "report add", PowerType: permission.ReportAdd, Description: "report add"},
- {ID: 23, Name: "tag add", PowerType: permission.TagAdd, Description: "tag add"},
- {ID: 24, Name: "tag edit", PowerType: permission.TagEdit, Description: "tag edit"},
- {ID: 25, Name: "tag edit without review", PowerType: permission.TagEditWithoutReview, Description: "tag edit without review"},
- {ID: 26, Name: "tag edit slug name", PowerType: permission.TagEditSlugName, Description: "tag edit slug name"},
- {ID: 27, Name: "tag delete", PowerType: permission.TagDelete, Description: "tag delete"},
- {ID: 28, Name: "tag synonym", PowerType: permission.TagSynonym, Description: "tag synonym"},
- {ID: 29, Name: "link url limit", PowerType: permission.LinkUrlLimit, Description: "link url limit"},
- {ID: 30, Name: "vote detail", PowerType: permission.VoteDetail, Description: "vote detail"},
- {ID: 31, Name: "answer audit", PowerType: permission.AnswerAudit, Description: "answer audit"},
- {ID: 32, Name: "question audit", PowerType: permission.QuestionAudit, Description: "question audit"},
- {ID: 33, Name: "tag audit", PowerType: permission.TagAudit, Description: "tag audit"},
- {ID: 34, Name: "question pin", PowerType: permission.QuestionPin, Description: "top the question"},
- {ID: 35, Name: "question hide", PowerType: permission.QuestionHide, Description: "hide the question"},
- {ID: 36, Name: "question unpin", PowerType: permission.QuestionUnPin, Description: "untop the question"},
- {ID: 37, Name: "question show", PowerType: permission.QuestionShow, Description: "show the question"},
- {ID: 38, Name: "invite someone to answer", PowerType: permission.AnswerInviteSomeoneToAnswer, Description: "invite someone to answer"},
- }
- _, err = engine.Insert(powers)
- if err != nil {
- return err
- }
-
- rolePowerRels := []*entity.RolePowerRel{
- {RoleID: 2, PowerType: permission.AdminAccess},
- {RoleID: 2, PowerType: permission.QuestionAdd},
- {RoleID: 2, PowerType: permission.QuestionEdit},
- {RoleID: 2, PowerType: permission.QuestionEditWithoutReview},
- {RoleID: 2, PowerType: permission.QuestionDelete},
- {RoleID: 2, PowerType: permission.QuestionClose},
- {RoleID: 2, PowerType: permission.QuestionReopen},
- {RoleID: 2, PowerType: permission.QuestionVoteUp},
- {RoleID: 2, PowerType: permission.QuestionVoteDown},
- {RoleID: 2, PowerType: permission.AnswerAdd},
- {RoleID: 2, PowerType: permission.AnswerEdit},
- {RoleID: 2, PowerType: permission.AnswerEditWithoutReview},
- {RoleID: 2, PowerType: permission.AnswerDelete},
- {RoleID: 2, PowerType: permission.AnswerAccept},
- {RoleID: 2, PowerType: permission.AnswerVoteUp},
- {RoleID: 2, PowerType: permission.AnswerVoteDown},
- {RoleID: 2, PowerType: permission.CommentAdd},
- {RoleID: 2, PowerType: permission.CommentEdit},
- {RoleID: 2, PowerType: permission.CommentDelete},
- {RoleID: 2, PowerType: permission.CommentVoteUp},
- {RoleID: 2, PowerType: permission.CommentVoteDown},
- {RoleID: 2, PowerType: permission.ReportAdd},
- {RoleID: 2, PowerType: permission.TagAdd},
- {RoleID: 2, PowerType: permission.TagEdit},
- {RoleID: 2, PowerType: permission.TagEditSlugName},
- {RoleID: 2, PowerType: permission.TagEditWithoutReview},
- {RoleID: 2, PowerType: permission.TagDelete},
- {RoleID: 2, PowerType: permission.TagSynonym},
- {RoleID: 2, PowerType: permission.LinkUrlLimit},
- {RoleID: 2, PowerType: permission.VoteDetail},
- {RoleID: 2, PowerType: permission.AnswerAudit},
- {RoleID: 2, PowerType: permission.QuestionAudit},
- {RoleID: 2, PowerType: permission.TagAudit},
- {RoleID: 2, PowerType: permission.TagUseReservedTag},
- {RoleID: 2, PowerType: permission.QuestionPin},
- {RoleID: 2, PowerType: permission.QuestionHide},
- {RoleID: 2, PowerType: permission.QuestionUnPin},
- {RoleID: 2, PowerType: permission.QuestionShow},
- {RoleID: 2, PowerType: permission.AnswerInviteSomeoneToAnswer},
-
- {RoleID: 3, PowerType: permission.QuestionAdd},
- {RoleID: 3, PowerType: permission.QuestionEdit},
- {RoleID: 3, PowerType: permission.QuestionEditWithoutReview},
- {RoleID: 3, PowerType: permission.QuestionDelete},
- {RoleID: 3, PowerType: permission.QuestionClose},
- {RoleID: 3, PowerType: permission.QuestionReopen},
- {RoleID: 3, PowerType: permission.QuestionVoteUp},
- {RoleID: 3, PowerType: permission.QuestionVoteDown},
- {RoleID: 3, PowerType: permission.AnswerAdd},
- {RoleID: 3, PowerType: permission.AnswerEdit},
- {RoleID: 3, PowerType: permission.AnswerEditWithoutReview},
- {RoleID: 3, PowerType: permission.AnswerDelete},
- {RoleID: 3, PowerType: permission.AnswerAccept},
- {RoleID: 3, PowerType: permission.AnswerVoteUp},
- {RoleID: 3, PowerType: permission.AnswerVoteDown},
- {RoleID: 3, PowerType: permission.CommentAdd},
- {RoleID: 3, PowerType: permission.CommentEdit},
- {RoleID: 3, PowerType: permission.CommentDelete},
- {RoleID: 3, PowerType: permission.CommentVoteUp},
- {RoleID: 3, PowerType: permission.CommentVoteDown},
- {RoleID: 3, PowerType: permission.ReportAdd},
- {RoleID: 3, PowerType: permission.TagAdd},
- {RoleID: 3, PowerType: permission.TagEdit},
- {RoleID: 3, PowerType: permission.TagEditSlugName},
- {RoleID: 3, PowerType: permission.TagEditWithoutReview},
- {RoleID: 3, PowerType: permission.TagDelete},
- {RoleID: 3, PowerType: permission.TagSynonym},
- {RoleID: 3, PowerType: permission.LinkUrlLimit},
- {RoleID: 3, PowerType: permission.VoteDetail},
- {RoleID: 3, PowerType: permission.AnswerAudit},
- {RoleID: 3, PowerType: permission.QuestionAudit},
- {RoleID: 3, PowerType: permission.TagAudit},
- {RoleID: 3, PowerType: permission.TagUseReservedTag},
- {RoleID: 3, PowerType: permission.QuestionPin},
- {RoleID: 3, PowerType: permission.QuestionHide},
- {RoleID: 3, PowerType: permission.QuestionUnPin},
- {RoleID: 3, PowerType: permission.QuestionShow},
- {RoleID: 3, PowerType: permission.AnswerInviteSomeoneToAnswer},
- }
- _, err = engine.Insert(rolePowerRels)
- if err != nil {
- return err
- }
-
- adminUserRoleRel := &entity.UserRoleRel{
- UserID: "1",
- RoleID: 2,
- }
- _, err = engine.Insert(adminUserRoleRel)
- if err != nil {
- return err
- }
- return nil
}
diff --git a/internal/migrations/init_data.go b/internal/migrations/init_data.go
new file mode 100644
index 00000000..623fa5d2
--- /dev/null
+++ b/internal/migrations/init_data.go
@@ -0,0 +1,313 @@
+package migrations
+
+import (
+ "github.com/answerdev/answer/internal/entity"
+ "github.com/answerdev/answer/internal/service/permission"
+)
+
+const (
+ defaultSEORobotTxt = `User-agent: *
+Disallow: /admin
+Disallow: /search
+Disallow: /install
+Disallow: /review
+Disallow: /users/login
+Disallow: /users/register
+Disallow: /users/account-recovery
+Disallow: /users/oauth/*
+Disallow: /users/*/*
+Disallow: /answer/api
+Disallow: /*?code*
+
+Sitemap: `
+)
+
+var (
+ tables = []interface{}{
+ &entity.Activity{},
+ &entity.Answer{},
+ &entity.Collection{},
+ &entity.CollectionGroup{},
+ &entity.Comment{},
+ &entity.Config{},
+ &entity.Meta{},
+ &entity.Notification{},
+ &entity.Question{},
+ &entity.Report{},
+ &entity.Revision{},
+ &entity.SiteInfo{},
+ &entity.Tag{},
+ &entity.TagRel{},
+ &entity.Uniqid{},
+ &entity.User{},
+ &entity.Version{},
+ &entity.Role{},
+ &entity.RolePowerRel{},
+ &entity.Power{},
+ &entity.UserRoleRel{},
+ &entity.PluginConfig{},
+ &entity.UserExternalLogin{},
+ }
+
+ roles = []*entity.Role{
+ {ID: 1, Name: "User", Description: "Default with no special access."},
+ {ID: 2, Name: "Admin", Description: "Have the full power to access the site."},
+ {ID: 3, Name: "Moderator", Description: "Has access to all posts except admin settings."},
+ }
+
+ powers = []*entity.Power{
+ {ID: 1, Name: "admin access", PowerType: permission.AdminAccess, Description: "admin access"},
+ {ID: 2, Name: "question add", PowerType: permission.QuestionAdd, Description: "question add"},
+ {ID: 3, Name: "question edit", PowerType: permission.QuestionEdit, Description: "question edit"},
+ {ID: 4, Name: "question edit without review", PowerType: permission.QuestionEditWithoutReview, Description: "question edit without review"},
+ {ID: 5, Name: "question delete", PowerType: permission.QuestionDelete, Description: "question delete"},
+ {ID: 6, Name: "question close", PowerType: permission.QuestionClose, Description: "question close"},
+ {ID: 7, Name: "question reopen", PowerType: permission.QuestionReopen, Description: "question reopen"},
+ {ID: 8, Name: "question vote up", PowerType: permission.QuestionVoteUp, Description: "question vote up"},
+ {ID: 9, Name: "question vote down", PowerType: permission.QuestionVoteDown, Description: "question vote down"},
+ {ID: 10, Name: "answer add", PowerType: permission.AnswerAdd, Description: "answer add"},
+ {ID: 11, Name: "answer edit", PowerType: permission.AnswerEdit, Description: "answer edit"},
+ {ID: 12, Name: "answer edit without review", PowerType: permission.AnswerEditWithoutReview, Description: "answer edit without review"},
+ {ID: 13, Name: "answer delete", PowerType: permission.AnswerDelete, Description: "answer delete"},
+ {ID: 14, Name: "answer accept", PowerType: permission.AnswerAccept, Description: "answer accept"},
+ {ID: 15, Name: "answer vote up", PowerType: permission.AnswerVoteUp, Description: "answer vote up"},
+ {ID: 16, Name: "answer vote down", PowerType: permission.AnswerVoteDown, Description: "answer vote down"},
+ {ID: 17, Name: "comment add", PowerType: permission.CommentAdd, Description: "comment add"},
+ {ID: 18, Name: "comment edit", PowerType: permission.CommentEdit, Description: "comment edit"},
+ {ID: 19, Name: "comment delete", PowerType: permission.CommentDelete, Description: "comment delete"},
+ {ID: 20, Name: "comment vote up", PowerType: permission.CommentVoteUp, Description: "comment vote up"},
+ {ID: 21, Name: "comment vote down", PowerType: permission.CommentVoteDown, Description: "comment vote down"},
+ {ID: 22, Name: "report add", PowerType: permission.ReportAdd, Description: "report add"},
+ {ID: 23, Name: "tag add", PowerType: permission.TagAdd, Description: "tag add"},
+ {ID: 24, Name: "tag edit", PowerType: permission.TagEdit, Description: "tag edit"},
+ {ID: 25, Name: "tag edit without review", PowerType: permission.TagEditWithoutReview, Description: "tag edit without review"},
+ {ID: 26, Name: "tag edit slug name", PowerType: permission.TagEditSlugName, Description: "tag edit slug name"},
+ {ID: 27, Name: "tag delete", PowerType: permission.TagDelete, Description: "tag delete"},
+ {ID: 28, Name: "tag synonym", PowerType: permission.TagSynonym, Description: "tag synonym"},
+ {ID: 29, Name: "link url limit", PowerType: permission.LinkUrlLimit, Description: "link url limit"},
+ {ID: 30, Name: "vote detail", PowerType: permission.VoteDetail, Description: "vote detail"},
+ {ID: 31, Name: "answer audit", PowerType: permission.AnswerAudit, Description: "answer audit"},
+ {ID: 32, Name: "question audit", PowerType: permission.QuestionAudit, Description: "question audit"},
+ {ID: 33, Name: "tag audit", PowerType: permission.TagAudit, Description: "tag audit"},
+ {ID: 34, Name: "question pin", PowerType: permission.QuestionPin, Description: "top the question"},
+ {ID: 35, Name: "question hide", PowerType: permission.QuestionHide, Description: "hide the question"},
+ {ID: 36, Name: "question unpin", PowerType: permission.QuestionUnPin, Description: "untop the question"},
+ {ID: 37, Name: "question show", PowerType: permission.QuestionShow, Description: "show the question"},
+ {ID: 38, Name: "invite someone to answer", PowerType: permission.AnswerInviteSomeoneToAnswer, Description: "invite someone to answer"},
+ }
+
+ rolePowerRels = []*entity.RolePowerRel{
+ {RoleID: 2, PowerType: permission.AdminAccess},
+ {RoleID: 2, PowerType: permission.QuestionAdd},
+ {RoleID: 2, PowerType: permission.QuestionEdit},
+ {RoleID: 2, PowerType: permission.QuestionEditWithoutReview},
+ {RoleID: 2, PowerType: permission.QuestionDelete},
+ {RoleID: 2, PowerType: permission.QuestionClose},
+ {RoleID: 2, PowerType: permission.QuestionReopen},
+ {RoleID: 2, PowerType: permission.QuestionVoteUp},
+ {RoleID: 2, PowerType: permission.QuestionVoteDown},
+ {RoleID: 2, PowerType: permission.AnswerAdd},
+ {RoleID: 2, PowerType: permission.AnswerEdit},
+ {RoleID: 2, PowerType: permission.AnswerEditWithoutReview},
+ {RoleID: 2, PowerType: permission.AnswerDelete},
+ {RoleID: 2, PowerType: permission.AnswerAccept},
+ {RoleID: 2, PowerType: permission.AnswerVoteUp},
+ {RoleID: 2, PowerType: permission.AnswerVoteDown},
+ {RoleID: 2, PowerType: permission.CommentAdd},
+ {RoleID: 2, PowerType: permission.CommentEdit},
+ {RoleID: 2, PowerType: permission.CommentDelete},
+ {RoleID: 2, PowerType: permission.CommentVoteUp},
+ {RoleID: 2, PowerType: permission.CommentVoteDown},
+ {RoleID: 2, PowerType: permission.ReportAdd},
+ {RoleID: 2, PowerType: permission.TagAdd},
+ {RoleID: 2, PowerType: permission.TagEdit},
+ {RoleID: 2, PowerType: permission.TagEditSlugName},
+ {RoleID: 2, PowerType: permission.TagEditWithoutReview},
+ {RoleID: 2, PowerType: permission.TagDelete},
+ {RoleID: 2, PowerType: permission.TagSynonym},
+ {RoleID: 2, PowerType: permission.LinkUrlLimit},
+ {RoleID: 2, PowerType: permission.VoteDetail},
+ {RoleID: 2, PowerType: permission.AnswerAudit},
+ {RoleID: 2, PowerType: permission.QuestionAudit},
+ {RoleID: 2, PowerType: permission.TagAudit},
+ {RoleID: 2, PowerType: permission.TagUseReservedTag},
+ {RoleID: 2, PowerType: permission.QuestionPin},
+ {RoleID: 2, PowerType: permission.QuestionHide},
+ {RoleID: 2, PowerType: permission.QuestionUnPin},
+ {RoleID: 2, PowerType: permission.QuestionShow},
+ {RoleID: 2, PowerType: permission.AnswerInviteSomeoneToAnswer},
+
+ {RoleID: 3, PowerType: permission.QuestionAdd},
+ {RoleID: 3, PowerType: permission.QuestionEdit},
+ {RoleID: 3, PowerType: permission.QuestionEditWithoutReview},
+ {RoleID: 3, PowerType: permission.QuestionDelete},
+ {RoleID: 3, PowerType: permission.QuestionClose},
+ {RoleID: 3, PowerType: permission.QuestionReopen},
+ {RoleID: 3, PowerType: permission.QuestionVoteUp},
+ {RoleID: 3, PowerType: permission.QuestionVoteDown},
+ {RoleID: 3, PowerType: permission.AnswerAdd},
+ {RoleID: 3, PowerType: permission.AnswerEdit},
+ {RoleID: 3, PowerType: permission.AnswerEditWithoutReview},
+ {RoleID: 3, PowerType: permission.AnswerDelete},
+ {RoleID: 3, PowerType: permission.AnswerAccept},
+ {RoleID: 3, PowerType: permission.AnswerVoteUp},
+ {RoleID: 3, PowerType: permission.AnswerVoteDown},
+ {RoleID: 3, PowerType: permission.CommentAdd},
+ {RoleID: 3, PowerType: permission.CommentEdit},
+ {RoleID: 3, PowerType: permission.CommentDelete},
+ {RoleID: 3, PowerType: permission.CommentVoteUp},
+ {RoleID: 3, PowerType: permission.CommentVoteDown},
+ {RoleID: 3, PowerType: permission.ReportAdd},
+ {RoleID: 3, PowerType: permission.TagAdd},
+ {RoleID: 3, PowerType: permission.TagEdit},
+ {RoleID: 3, PowerType: permission.TagEditSlugName},
+ {RoleID: 3, PowerType: permission.TagEditWithoutReview},
+ {RoleID: 3, PowerType: permission.TagDelete},
+ {RoleID: 3, PowerType: permission.TagSynonym},
+ {RoleID: 3, PowerType: permission.LinkUrlLimit},
+ {RoleID: 3, PowerType: permission.VoteDetail},
+ {RoleID: 3, PowerType: permission.AnswerAudit},
+ {RoleID: 3, PowerType: permission.QuestionAudit},
+ {RoleID: 3, PowerType: permission.TagAudit},
+ {RoleID: 3, PowerType: permission.TagUseReservedTag},
+ {RoleID: 3, PowerType: permission.QuestionPin},
+ {RoleID: 3, PowerType: permission.QuestionHide},
+ {RoleID: 3, PowerType: permission.QuestionUnPin},
+ {RoleID: 3, PowerType: permission.QuestionShow},
+ {RoleID: 3, PowerType: permission.AnswerInviteSomeoneToAnswer},
+ }
+
+ adminUserRoleRel = &entity.UserRoleRel{
+ UserID: "1",
+ RoleID: 2,
+ }
+
+ defaultConfigTable = []*entity.Config{
+ {ID: 1, Key: "answer.accepted", Value: `15`},
+ {ID: 2, Key: "answer.voted_up", Value: `10`},
+ {ID: 3, Key: "question.voted_up", Value: `10`},
+ {ID: 4, Key: "tag.edit_accepted", Value: `2`},
+ {ID: 5, Key: "answer.accept", Value: `2`},
+ {ID: 6, Key: "answer.voted_down_cancel", Value: `2`},
+ {ID: 7, Key: "question.voted_down_cancel", Value: `2`},
+ {ID: 8, Key: "answer.vote_down_cancel", Value: `1`},
+ {ID: 9, Key: "question.vote_down_cancel", Value: `1`},
+ {ID: 10, Key: "user.activated", Value: `1`},
+ {ID: 11, Key: "edit.accepted", Value: `2`},
+ {ID: 12, Key: "answer.vote_down", Value: `-1`},
+ {ID: 13, Key: "question.voted_down", Value: `-2`},
+ {ID: 14, Key: "answer.voted_down", Value: `-2`},
+ {ID: 15, Key: "answer.accept_cancel", Value: `-2`},
+ {ID: 16, Key: "answer.deleted", Value: `-5`},
+ {ID: 17, Key: "question.voted_up_cancel", Value: `-10`},
+ {ID: 18, Key: "answer.voted_up_cancel", Value: `-10`},
+ {ID: 19, Key: "answer.accepted_cancel", Value: `-15`},
+ {ID: 20, Key: "object.reported", Value: `-100`},
+ {ID: 21, Key: "edit.rejected", Value: `-2`},
+ {ID: 22, Key: "daily_rank_limit", Value: `200`},
+ {ID: 23, Key: "daily_rank_limit.exclude", Value: `["answer.accepted"]`},
+ {ID: 24, Key: "user.follow", Value: `0`},
+ {ID: 25, Key: "comment.vote_up", Value: `0`},
+ {ID: 26, Key: "comment.vote_up_cancel", Value: `0`},
+ {ID: 27, Key: "question.vote_down", Value: `0`},
+ {ID: 28, Key: "question.vote_up", Value: `0`},
+ {ID: 29, Key: "question.vote_up_cancel", Value: `0`},
+ {ID: 30, Key: "answer.vote_up", Value: `0`},
+ {ID: 31, Key: "answer.vote_up_cancel", Value: `0`},
+ {ID: 32, Key: "question.follow", Value: `0`},
+ {ID: 33, Key: "email.config", Value: `{"from_name":"","from_email":"","smtp_host":"","smtp_port":465,"smtp_password":"","smtp_username":"","smtp_authentication":true,"encryption":"","register_title":"[{{.SiteName}}] Confirm your new account","register_body":"Welcome to {{.SiteName}}
\n\nClick the following link to confirm and activate your new account:
\n{{.RegisterUrl}}
\n\nIf the above link is not clickable, try copying and pasting it into the address bar of your web browser.\n","pass_reset_title":"[{{.SiteName }}] Password reset","pass_reset_body":"Somebody asked to reset your password on [{{.SiteName}}].
\n\nIf it was not you, you can safely ignore this email.
\n\nClick the following link to choose a new password:
\n{{.PassResetUrl}}\n","change_title":"[{{.SiteName}}] Confirm your new email address","change_body":"Confirm your new email address for {{.SiteName}} by clicking on the following link:
\n\n{{.ChangeEmailUrl}}
\n\nIf you did not request this change, please ignore this email.\n","test_title":"[{{.SiteName}}] Test Email","test_body":"This is a test email.","new_answer_title":"[{{.SiteName}}] {{.DisplayName}} answered your question","new_answer_body":"{{.QuestionTitle}}
\n\n{{.DisplayName}}:
\n{{.AnswerSummary}}
\nView it on {{.SiteName}}
\n\nYou are receiving this because you authored the thread. Unsubscribe","new_comment_title":"[{{.SiteName}}] {{.DisplayName}} commented on your post","new_comment_body":"{{.QuestionTitle}}
\n\n{{.DisplayName}}:
\n{{.CommentSummary}}
\nView it on {{.SiteName}}
\n\nYou are receiving this because you authored the thread. Unsubscribe"}`},
+ {ID: 35, Key: "tag.follow", Value: `0`},
+ {ID: 36, Key: "rank.question.add", Value: `1`},
+ {ID: 37, Key: "rank.question.edit", Value: `200`},
+ {ID: 38, Key: "rank.question.delete", Value: `-1`},
+ {ID: 39, Key: "rank.question.vote_up", Value: `15`},
+ {ID: 40, Key: "rank.question.vote_down", Value: `125`},
+ {ID: 41, Key: "rank.answer.add", Value: `1`},
+ {ID: 42, Key: "rank.answer.edit", Value: `200`},
+ {ID: 43, Key: "rank.answer.delete", Value: `-1`},
+ {ID: 44, Key: "rank.answer.accept", Value: `-1`},
+ {ID: 45, Key: "rank.answer.vote_up", Value: `15`},
+ {ID: 46, Key: "rank.answer.vote_down", Value: `125`},
+ {ID: 47, Key: "rank.comment.add", Value: `1`},
+ {ID: 48, Key: "rank.comment.edit", Value: `-1`},
+ {ID: 49, Key: "rank.comment.delete", Value: `-1`},
+ {ID: 50, Key: "rank.report.add", Value: `1`},
+ {ID: 51, Key: "rank.tag.add", Value: `1`},
+ {ID: 52, Key: "rank.tag.edit", Value: `100`},
+ {ID: 53, Key: "rank.tag.delete", Value: `-1`},
+ {ID: 54, Key: "rank.tag.synonym", Value: `20000`},
+ {ID: 55, Key: "rank.link.url_limit", Value: `10`},
+ {ID: 56, Key: "rank.vote.detail", Value: `0`},
+ {ID: 57, Key: "reason.spam", Value: `{"name":"spam","description":"This post is an advertisement, or vandalism. It is not useful or relevant to the current topic."}`},
+ {ID: 58, Key: "reason.rude_or_abusive", Value: `{"name":"rude or abusive","description":"A reasonable person would find this content inappropriate for respectful discourse."}`},
+ {ID: 59, Key: "reason.something", Value: `{"name":"something else","description":"This post requires staff attention for another reason not listed above.","content_type":"textarea"}`},
+ {ID: 60, Key: "reason.a_duplicate", Value: `{"name":"a duplicate","description":"This question has been asked before and already has an answer.","content_type":"text"}`},
+ {ID: 61, Key: "reason.not_a_answer", Value: `{"name":"not a answer","description":"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.","content_type":""}`},
+ {ID: 62, Key: "reason.no_longer_needed", Value: `{"name":"no longer needed","description":"This comment is outdated, conversational or not relevant to this post."}`},
+ {ID: 63, Key: "reason.community_specific", Value: `{"name":"a community-specific reason","description":"This question doesn’t meet a community guideline."}`},
+ {ID: 64, Key: "reason.not_clarity", Value: `{"name":"needs details or clarity","description":"This question currently includes multiple questions in one. It should focus on one problem only.","content_type":"text"}`},
+ {ID: 65, Key: "reason.normal", Value: `{"name":"normal","description":"A normal post available to everyone."}`},
+ {ID: 66, Key: "reason.normal.user", Value: `{"name":"normal","description":"A normal user can ask and answer questions."}`},
+ {ID: 67, Key: "reason.closed", Value: `{"name":"closed","description":"A closed question can’t answer, but still can edit, vote and comment."}`},
+ {ID: 68, Key: "reason.deleted", Value: `{"name":"deleted","description":"All reputation gained and lost will be restored."}`},
+ {ID: 69, Key: "reason.deleted.user", Value: `{"name":"deleted","description":"Delete profile, authentication associations."}`},
+ {ID: 70, Key: "reason.suspended", Value: `{"name":"suspended","description":"A suspended user can’t log in."}`},
+ {ID: 71, Key: "reason.inactive", Value: `{"name":"inactive","description":"An inactive user must re-validate their email."}`},
+ {ID: 72, Key: "reason.looks_ok", Value: `{"name":"looks ok","description":"This post is good as-is and not low quality."}`},
+ {ID: 73, Key: "reason.needs_edit", Value: `{"name":"needs edit, and I did it","description":"Improve and correct problems with this post yourself."}`},
+ {ID: 74, Key: "reason.needs_close", Value: `{"name":"needs close","description":"A closed question can’t answer, but still can edit, vote and comment."}`},
+ {ID: 75, Key: "reason.needs_delete", Value: `{"name":"needs delete","description":"All reputation gained and lost will be restored."}`},
+ {ID: 76, Key: "question.flag.reasons", Value: `["reason.spam","reason.rude_or_abusive","reason.something","reason.a_duplicate"]`},
+ {ID: 77, Key: "answer.flag.reasons", Value: `["reason.spam","reason.rude_or_abusive","reason.something","reason.not_a_answer"]`},
+ {ID: 78, Key: "comment.flag.reasons", Value: `["reason.spam","reason.rude_or_abusive","reason.something","reason.no_longer_needed"]`},
+ {ID: 79, Key: "question.close.reasons", Value: `["reason.a_duplicate","reason.community_specific","reason.not_clarity","reason.something"]`},
+ {ID: 80, Key: "question.status.reasons", Value: `["reason.normal","reason.closed","reason.deleted"]`},
+ {ID: 81, Key: "answer.status.reasons", Value: `["reason.normal","reason.deleted"]`},
+ {ID: 82, Key: "comment.status.reasons", Value: `["reason.normal","reason.deleted"]`},
+ {ID: 83, Key: "user.status.reasons", Value: `["reason.normal.user","reason.suspended","reason.deleted.user","reason.inactive"]`},
+ {ID: 84, Key: "question.review.reasons", Value: `["reason.looks_ok","reason.needs_edit","reason.needs_close","reason.needs_delete"]`},
+ {ID: 85, Key: "answer.review.reasons", Value: `["reason.looks_ok","reason.needs_edit","reason.needs_delete"]`},
+ {ID: 86, Key: "comment.review.reasons", Value: `["reason.looks_ok","reason.needs_edit","reason.needs_delete"]`},
+ {ID: 87, Key: "question.asked", Value: `0`},
+ {ID: 88, Key: "question.closed", Value: `0`},
+ {ID: 89, Key: "question.reopened", Value: `0`},
+ {ID: 90, Key: "question.answered", Value: `0`},
+ {ID: 91, Key: "question.commented", Value: `0`},
+ {ID: 92, Key: "question.accept", Value: `0`},
+ {ID: 93, Key: "question.edited", Value: `0`},
+ {ID: 94, Key: "question.rollback", Value: `0`},
+ {ID: 95, Key: "question.deleted", Value: `0`},
+ {ID: 96, Key: "question.undeleted", Value: `0`},
+ {ID: 97, Key: "answer.answered", Value: `0`},
+ {ID: 98, Key: "answer.commented", Value: `0`},
+ {ID: 99, Key: "answer.edited", Value: `0`},
+ {ID: 100, Key: "answer.rollback", Value: `0`},
+ {ID: 101, Key: "answer.undeleted", Value: `0`},
+ {ID: 102, Key: "tag.created", Value: `0`},
+ {ID: 103, Key: "tag.edited", Value: `0`},
+ {ID: 104, Key: "tag.rollback", Value: `0`},
+ {ID: 105, Key: "tag.deleted", Value: `0`},
+ {ID: 106, Key: "tag.undeleted", Value: `0`},
+ {ID: 107, Key: "rank.comment.vote_up", Value: `1`},
+ {ID: 108, Key: "rank.comment.vote_down", Value: `1`},
+ {ID: 109, Key: "rank.question.edit_without_review", Value: `2000`},
+ {ID: 110, Key: "rank.answer.edit_without_review", Value: `2000`},
+ {ID: 111, Key: "rank.tag.edit_without_review", Value: `20000`},
+ {ID: 112, Key: "rank.answer.audit", Value: `2000`},
+ {ID: 113, Key: "rank.question.audit", Value: `2000`},
+ {ID: 114, Key: "rank.tag.audit", Value: `20000`},
+ {ID: 115, Key: "rank.question.close", Value: `-1`},
+ {ID: 116, Key: "rank.question.reopen", Value: `-1`},
+ {ID: 117, Key: "rank.tag.use_reserved_tag", Value: `-1`},
+ {ID: 118, Key: "plugin.status", Value: `{}`},
+ {ID: 119, Key: "question.pin", Value: `0`},
+ {ID: 120, Key: "question.unpin", Value: `0`},
+ {ID: 121, Key: "question.show", Value: `0`},
+ {ID: 122, Key: "question.hide", Value: `0`},
+ {ID: 123, Key: "rank.question.pin", Value: `-1`},
+ {ID: 124, Key: "rank.question.unpin", Value: `-1`},
+ {ID: 125, Key: "rank.question.show", Value: `-1`},
+ {ID: 126, Key: "rank.question.hide", Value: `-1`},
+ {ID: 127, Key: "rank.answer.invite_someone_to_answer", Value: `1000`},
+ }
+)
diff --git a/internal/repo/activity/answer_repo.go b/internal/repo/activity/answer_repo.go
index 6ed8bad4..349fbb7a 100644
--- a/internal/repo/activity/answer_repo.go
+++ b/internal/repo/activity/answer_repo.go
@@ -2,7 +2,10 @@ package activity
import (
"context"
+ "fmt"
+ "github.com/segmentfault/pacman/log"
"time"
+ "xorm.io/builder"
"github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/base/data"
@@ -15,343 +18,338 @@ import (
"github.com/answerdev/answer/internal/service/rank"
"github.com/answerdev/answer/pkg/converter"
"github.com/segmentfault/pacman/errors"
- "github.com/segmentfault/pacman/log"
"xorm.io/xorm"
)
// AnswerActivityRepo answer accepted
type AnswerActivityRepo struct {
- data *data.Data
- activityRepo activity_common.ActivityRepo
- userRankRepo rank.UserRankRepo
+ data *data.Data
+ activityRepo activity_common.ActivityRepo
+ userRankRepo rank.UserRankRepo
+ notificationQueueService notice_queue.NotificationQueueService
}
-const (
- acceptAction = "accept"
- acceptedAction = "accepted"
-)
-
-var (
- acceptActionList = []string{acceptAction, acceptedAction}
-)
-
// NewAnswerActivityRepo new repository
func NewAnswerActivityRepo(
data *data.Data,
activityRepo activity_common.ActivityRepo,
userRankRepo rank.UserRankRepo,
+ notificationQueueService notice_queue.NotificationQueueService,
) activity.AnswerActivityRepo {
return &AnswerActivityRepo{
- data: data,
- activityRepo: activityRepo,
- userRankRepo: userRankRepo,
+ data: data,
+ activityRepo: activityRepo,
+ userRankRepo: userRankRepo,
+ notificationQueueService: notificationQueueService,
}
}
-// NewQuestionActivityRepo new repository
-func NewQuestionActivityRepo(
- data *data.Data,
- activityRepo activity_common.ActivityRepo,
- userRankRepo rank.UserRankRepo,
-) activity.QuestionActivityRepo {
- return &AnswerActivityRepo{
- data: data,
- activityRepo: activityRepo,
- userRankRepo: userRankRepo,
- }
-}
-
-func (ar *AnswerActivityRepo) DeleteQuestion(ctx context.Context, questionID string) (err error) {
- questionInfo := &entity.Question{}
- exist, err := ar.data.DB.Context(ctx).Where("id = ?", questionID).Get(questionInfo)
+func (ar *AnswerActivityRepo) SaveAcceptAnswerActivity(ctx context.Context, op *schema.AcceptAnswerOperationInfo) (
+ err error) {
+ // pre check
+ noNeedToDo, err := ar.activityPreCheck(ctx, op)
if err != nil {
- return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ return err
}
- if !exist {
+ if noNeedToDo {
return nil
}
- // get all this object activity
- activityList := make([]*entity.Activity, 0)
- session := ar.data.DB.Context(ctx).Where("has_rank = 1")
- session.Where("cancelled = ?", entity.ActivityAvailable)
- err = session.Find(&activityList, &entity.Activity{ObjectID: questionID})
- if err != nil {
- return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
- }
- if len(activityList) == 0 {
- return nil
- }
-
- log.Infof("questionInfo %s deleted will rollback activity %d", questionID, len(activityList))
-
+ // save activity
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
session = session.Context(ctx)
- for _, act := range activityList {
- log.Infof("user %s rollback rank %d", act.UserID, -act.Rank)
- _, e := ar.userRankRepo.TriggerUserRank(
- ctx, session, act.UserID, -act.Rank, act.ActivityType)
- if e != nil {
- return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
- }
- if _, e := session.Where("id = ?", act.ID).Cols("cancelled", "cancelled_at").
- Update(&entity.Activity{Cancelled: entity.ActivityCancelled, CancelledAt: time.Now()}); e != nil {
- return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
- }
+ userInfoMapping, err := ar.acquireUserInfo(session, op.GetUserIDs())
+ if err != nil {
+ return nil, err
+ }
+
+ err = ar.saveActivitiesAvailable(session, op)
+ if err != nil {
+ return nil, err
+ }
+
+ err = ar.changeUserRank(ctx, session, op, userInfoMapping)
+ if err != nil {
+ return nil, err
}
return nil, nil
})
if err != nil {
- return err
+ return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
- // get all answers
- answerList := make([]*entity.Answer, 0)
- err = ar.data.DB.Context(ctx).Find(&answerList, &entity.Answer{QuestionID: questionID})
+ // notification
+ ar.sendAcceptAnswerNotification(ctx, op)
+ return nil
+}
+
+func (ar *AnswerActivityRepo) SaveCancelAcceptAnswerActivity(ctx context.Context, op *schema.AcceptAnswerOperationInfo) (
+ err error) {
+ // pre check
+ activities, err := ar.getExistActivity(ctx, op)
+ if err != nil {
+ return err
+ }
+ var userIDs []string
+ for _, act := range activities {
+ if act.Cancelled == entity.ActivityCancelled {
+ continue
+ }
+ userIDs = append(userIDs, act.UserID)
+ }
+ if len(userIDs) == 0 {
+ return nil
+ }
+
+ // save activity
+ _, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
+ session = session.Context(ctx)
+
+ userInfoMapping, err := ar.acquireUserInfo(session, userIDs)
+ if err != nil {
+ return nil, err
+ }
+
+ err = ar.cancelActivities(session, activities)
+ if err != nil {
+ return nil, err
+ }
+
+ err = ar.rollbackUserRank(ctx, session, activities, userInfoMapping)
+ if err != nil {
+ return nil, err
+ }
+ return nil, nil
+ })
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
- for _, answerInfo := range answerList {
- err = ar.DeleteAnswer(ctx, answerInfo.ID)
+
+ // notification
+ ar.sendCancelAcceptAnswerNotification(ctx, op)
+ return nil
+}
+
+func (ar *AnswerActivityRepo) activityPreCheck(ctx context.Context, op *schema.AcceptAnswerOperationInfo) (
+ noNeedToDo bool, err error) {
+ activities, err := ar.getExistActivity(ctx, op)
+ if err != nil {
+ return false, err
+ }
+ done := 0
+ for _, act := range activities {
+ if act.Cancelled == entity.ActivityAvailable {
+ done++
+ }
+ }
+ return done == len(op.Activities), nil
+}
+
+func (ar *AnswerActivityRepo) acquireUserInfo(session *xorm.Session, userIDs []string) (map[string]*entity.User, error) {
+ us := make([]*entity.User, 0)
+ err := session.In("id", userIDs).ForUpdate().Find(&us)
+ if err != nil {
+ log.Error(err)
+ return nil, err
+ }
+
+ users := make(map[string]*entity.User, 0)
+ for _, u := range us {
+ users[u.ID] = u
+ }
+ return users, nil
+}
+
+// saveActivitiesAvailable save activities
+// If activity not exist it will be created or else will be updated
+// If this activity is already exist, set activity rank to 0
+// So after this function, the activity rank will be correct for update user rank
+func (ar *AnswerActivityRepo) saveActivitiesAvailable(session *xorm.Session, op *schema.AcceptAnswerOperationInfo) (
+ err error) {
+ for _, act := range op.Activities {
+ existsActivity := &entity.Activity{}
+ exist, err := session.
+ Where(builder.Eq{"object_id": op.AnswerObjectID}).
+ And(builder.Eq{"user_id": act.ActivityUserID}).
+ And(builder.Eq{"trigger_user_id": act.TriggerUserID}).
+ And(builder.Eq{"activity_type": act.ActivityType}).
+ Get(existsActivity)
+ if err != nil {
+ return err
+ }
+ if exist && existsActivity.Cancelled == entity.ActivityAvailable {
+ act.Rank = 0
+ continue
+ }
+ if exist {
+ if _, err = session.Where("id = ?", existsActivity.ID).Cols("`cancelled`").
+ Update(&entity.Activity{Cancelled: entity.ActivityAvailable}); err != nil {
+ return err
+ }
+ } else {
+ insertActivity := entity.Activity{
+ ObjectID: op.AnswerObjectID,
+ OriginalObjectID: act.OriginalObjectID,
+ UserID: act.ActivityUserID,
+ TriggerUserID: converter.StringToInt64(act.TriggerUserID),
+ ActivityType: act.ActivityType,
+ Rank: act.Rank,
+ HasRank: act.HasRank(),
+ Cancelled: entity.ActivityAvailable,
+ }
+ _, err = session.Insert(&insertActivity)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+// cancelActivities cancel activities
+// If this activity is already cancelled, set activity rank to 0
+// So after this function, the activity rank will be correct for update user rank
+func (ar *AnswerActivityRepo) cancelActivities(session *xorm.Session, activities []*entity.Activity) (err error) {
+ for _, act := range activities {
+ t := &entity.Activity{}
+ exist, err := session.ID(act.ID).Get(t)
if err != nil {
log.Error(err)
+ return err
+ }
+ if !exist {
+ log.Error(fmt.Errorf("%s activity not exist", act.ID))
+ return fmt.Errorf("%s activity not exist", act.ID)
+ }
+ // If this activity is already cancelled, set activity rank to 0
+ if t.Cancelled == entity.ActivityCancelled {
+ act.Rank = 0
+ }
+ if _, err = session.ID(act.ID).Cols("cancelled", "cancelled_at").
+ Update(&entity.Activity{
+ Cancelled: entity.ActivityCancelled,
+ CancelledAt: time.Now(),
+ }); err != nil {
+ log.Error(err)
+ return err
}
}
- return
+ return nil
}
-// AcceptAnswer accept other answer
-func (ar *AnswerActivityRepo) AcceptAnswer(ctx context.Context,
- answerObjID, questionObjID, questionUserID, answerUserID string, isSelf bool,
-) (err error) {
- addActivityList := make([]*entity.Activity, 0)
- for _, action := range acceptActionList {
- // get accept answer need add rank amount
- activityType, deltaRank, hasRank, e := ar.activityRepo.GetActivityTypeByObjID(ctx, answerObjID, action)
- if e != nil {
- return errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
+func (ar *AnswerActivityRepo) changeUserRank(ctx context.Context, session *xorm.Session,
+ op *schema.AcceptAnswerOperationInfo,
+ userInfoMapping map[string]*entity.User) (err error) {
+ for _, act := range op.Activities {
+ if act.Rank == 0 {
+ continue
}
- addActivity := &entity.Activity{
- ObjectID: answerObjID,
- OriginalObjectID: questionObjID,
- ActivityType: activityType,
- Rank: deltaRank,
- HasRank: hasRank,
+ user := userInfoMapping[act.ActivityUserID]
+ if user == nil {
+ continue
}
- if action == acceptAction {
- addActivity.UserID = questionUserID
- addActivity.TriggerUserID = converter.StringToInt64(answerUserID)
- addActivity.OriginalObjectID = questionObjID // if activity is 'accept' means this question is accept the answer.
- } else {
- addActivity.UserID = answerUserID
- addActivity.TriggerUserID = converter.StringToInt64(answerUserID)
- addActivity.OriginalObjectID = answerObjID // if activity is 'accepted' means this answer was accepted.
+ if err = ar.userRankRepo.ChangeUserRank(ctx, session,
+ act.ActivityUserID, user.Rank, act.Rank); err != nil {
+ log.Error(err)
+ return err
}
- if isSelf {
- addActivity.Rank = 0
- addActivity.HasRank = 0
- }
- addActivityList = append(addActivityList, addActivity)
}
+ return nil
+}
- _, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
- session = session.Context(ctx)
- for _, addActivity := range addActivityList {
- existsActivity, exists, e := ar.activityRepo.GetActivity(
- ctx, session, answerObjID, addActivity.UserID, addActivity.ActivityType)
- if e != nil {
- return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
- }
- if exists && existsActivity.Cancelled == entity.ActivityAvailable {
- continue
- }
-
- // trigger user rank and send notification
- if addActivity.Rank != 0 {
- reachStandard, e := ar.userRankRepo.TriggerUserRank(
- ctx, session, addActivity.UserID, addActivity.Rank, addActivity.ActivityType)
- if e != nil {
- return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
- }
- if reachStandard {
- addActivity.Rank = 0
- }
- }
-
- if exists {
- if _, e = session.Where("id = ?", existsActivity.ID).Cols("`cancelled`").
- Update(&entity.Activity{Cancelled: entity.ActivityAvailable}); e != nil {
- return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
- }
- } else {
- if _, e = session.Insert(addActivity); e != nil {
- return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
- }
- }
+func (ar *AnswerActivityRepo) rollbackUserRank(ctx context.Context, session *xorm.Session,
+ activities []*entity.Activity,
+ userInfoMapping map[string]*entity.User) (err error) {
+ for _, act := range activities {
+ if act.Rank == 0 {
+ continue
+ }
+ user := userInfoMapping[act.UserID]
+ if user == nil {
+ continue
+ }
+ if err = ar.userRankRepo.ChangeUserRank(ctx, session,
+ act.UserID, user.Rank, -act.Rank); err != nil {
+ log.Error(err)
+ return err
}
- return nil, nil
- })
- if err != nil {
- return err
}
- for _, act := range addActivityList {
+ return nil
+}
+
+func (ar *AnswerActivityRepo) getExistActivity(ctx context.Context, op *schema.AcceptAnswerOperationInfo) ([]*entity.Activity, error) {
+ var activities []*entity.Activity
+ for _, action := range op.Activities {
+ t := &entity.Activity{}
+ exist, err := ar.data.DB.Context(ctx).
+ Where(builder.Eq{"user_id": action.ActivityUserID}).
+ And(builder.Eq{"trigger_user_id": action.TriggerUserID}).
+ And(builder.Eq{"activity_type": action.ActivityType}).
+ And(builder.Eq{"object_id": op.AnswerObjectID}).
+ Get(t)
+ if err != nil {
+ return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ }
+ if exist {
+ activities = append(activities, t)
+ }
+ }
+ return activities, nil
+}
+
+func (ar *AnswerActivityRepo) sendAcceptAnswerNotification(
+ ctx context.Context, op *schema.AcceptAnswerOperationInfo) {
+ for _, act := range op.Activities {
msg := &schema.NotificationMsg{
Type: schema.NotificationTypeAchievement,
- ObjectID: act.ObjectID,
- ReceiverUserID: act.UserID,
+ ObjectID: op.AnswerObjectID,
+ ReceiverUserID: act.ActivityUserID,
}
- if act.UserID == questionUserID {
- msg.TriggerUserID = answerUserID
+ if act.ActivityUserID == op.QuestionUserID {
+ msg.TriggerUserID = op.AnswerUserID
msg.ObjectType = constant.AnswerObjectType
} else {
- msg.TriggerUserID = questionUserID
+ msg.TriggerUserID = op.QuestionUserID
msg.ObjectType = constant.AnswerObjectType
}
if msg.TriggerUserID != msg.ReceiverUserID {
- notice_queue.AddNotification(msg)
+ ar.notificationQueueService.Send(ctx, msg)
}
}
- for _, act := range addActivityList {
+ for _, act := range op.Activities {
msg := &schema.NotificationMsg{
- ReceiverUserID: act.UserID,
+ ReceiverUserID: act.ActivityUserID,
Type: schema.NotificationTypeInbox,
- ObjectID: act.ObjectID,
+ ObjectID: op.AnswerObjectID,
}
- if act.UserID != questionUserID {
- msg.TriggerUserID = questionUserID
+ if act.ActivityUserID != op.QuestionUserID {
+ msg.TriggerUserID = op.QuestionUserID
msg.ObjectType = constant.AnswerObjectType
msg.NotificationAction = constant.NotificationAcceptAnswer
- notice_queue.AddNotification(msg)
+ ar.notificationQueueService.Send(ctx, msg)
}
}
- return err
}
-// CancelAcceptAnswer accept other answer
-func (ar *AnswerActivityRepo) CancelAcceptAnswer(ctx context.Context,
- answerObjID, questionObjID, questionUserID, answerUserID string,
-) (err error) {
- addActivityList := make([]*entity.Activity, 0)
- for _, action := range acceptActionList {
- // get accept answer need add rank amount
- activityType, deltaRank, hasRank, e := ar.activityRepo.GetActivityTypeByObjID(ctx, answerObjID, action)
- if e != nil {
- return errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
- }
- addActivity := &entity.Activity{
- ObjectID: answerObjID,
- ActivityType: activityType,
- Rank: -deltaRank,
- HasRank: hasRank,
- }
- if action == acceptAction {
- addActivity.UserID = questionUserID
- addActivity.OriginalObjectID = questionObjID
- } else {
- addActivity.UserID = answerUserID
- addActivity.OriginalObjectID = answerObjID
- }
- addActivityList = append(addActivityList, addActivity)
- }
-
- _, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
- session = session.Context(ctx)
- for _, addActivity := range addActivityList {
- existsActivity, exists, e := ar.activityRepo.GetActivity(
- ctx, session, answerObjID, addActivity.UserID, addActivity.ActivityType)
- if e != nil {
- return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
- }
- if exists && existsActivity.Cancelled == entity.ActivityCancelled {
- continue
- }
- if !exists {
- continue
- }
-
- if existsActivity.Rank != 0 {
- _, e = ar.userRankRepo.TriggerUserRank(
- ctx, session, addActivity.UserID, addActivity.Rank, addActivity.ActivityType)
- if e != nil {
- return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
- }
- }
-
- if _, e := session.Where("id = ?", existsActivity.ID).Cols("cancelled", "cancelled_at").
- Update(&entity.Activity{Cancelled: entity.ActivityCancelled, CancelledAt: time.Now()}); e != nil {
- return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
- }
- }
- return nil, nil
- })
- if err != nil {
- return err
- }
- for _, act := range addActivityList {
+func (ar *AnswerActivityRepo) sendCancelAcceptAnswerNotification(
+ ctx context.Context, op *schema.AcceptAnswerOperationInfo) {
+ for _, act := range op.Activities {
msg := &schema.NotificationMsg{
- ReceiverUserID: act.UserID,
+ ReceiverUserID: act.ActivityUserID,
Type: schema.NotificationTypeAchievement,
- ObjectID: act.ObjectID,
+ ObjectID: op.AnswerObjectID,
}
- if act.UserID == questionUserID {
- msg.TriggerUserID = answerUserID
+ if act.ActivityUserID == op.QuestionObjectID {
+ msg.TriggerUserID = op.AnswerObjectID
msg.ObjectType = constant.QuestionObjectType
} else {
- msg.TriggerUserID = questionUserID
+ msg.TriggerUserID = op.QuestionObjectID
msg.ObjectType = constant.AnswerObjectType
}
if msg.TriggerUserID != msg.ReceiverUserID {
- notice_queue.AddNotification(msg)
+ ar.notificationQueueService.Send(ctx, msg)
}
}
- return err
-}
-
-func (ar *AnswerActivityRepo) DeleteAnswer(ctx context.Context, answerID string) (err error) {
- answerInfo := &entity.Answer{}
- exist, err := ar.data.DB.Context(ctx).Where("id = ?", answerID).Get(answerInfo)
- if err != nil {
- return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
- }
- if !exist {
- return nil
- }
-
- // get all this object activity
- activityList := make([]*entity.Activity, 0)
- session := ar.data.DB.Context(ctx).Where("has_rank = 1")
- session.Where("cancelled = ?", entity.ActivityAvailable)
- err = session.Find(&activityList, &entity.Activity{ObjectID: answerID})
- if err != nil {
- return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
- }
- if len(activityList) == 0 {
- return nil
- }
-
- log.Infof("answerInfo %s deleted will rollback activity %d", answerID, len(activityList))
-
- _, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
- session = session.Context(ctx)
- for _, act := range activityList {
- log.Infof("user %s rollback rank %d", act.UserID, -act.Rank)
- _, e := ar.userRankRepo.TriggerUserRank(
- ctx, session, act.UserID, -act.Rank, act.ActivityType)
- if e != nil {
- return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
- }
-
- if _, e := session.Where("id = ?", act.ID).Cols("cancelled", "cancelled_at").
- Update(&entity.Activity{Cancelled: entity.ActivityCancelled, CancelledAt: time.Now()}); e != nil {
- return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
- }
- }
- return nil, nil
- })
- if err != nil {
- return err
- }
- return
}
diff --git a/internal/repo/activity/follow_repo.go b/internal/repo/activity/follow_repo.go
index 8f9ade89..02f025cd 100644
--- a/internal/repo/activity/follow_repo.go
+++ b/internal/repo/activity/follow_repo.go
@@ -43,7 +43,7 @@ func (ar *FollowRepo) Follow(ctx context.Context, objectID, userID string) error
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
- activityType, err := ar.activityRepo.GetActivityTypeByObjKey(ctx, objectTypeStr, "follow")
+ activityType, err := ar.activityRepo.GetActivityTypeByObjectType(ctx, objectTypeStr, "follow")
if err != nil {
return err
}
@@ -110,7 +110,7 @@ func (ar *FollowRepo) FollowCancel(ctx context.Context, objectID, userID string)
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
- activityType, err := ar.activityRepo.GetActivityTypeByObjKey(ctx, objectTypeStr, "follow")
+ activityType, err := ar.activityRepo.GetActivityTypeByObjectType(ctx, objectTypeStr, "follow")
if err != nil {
return err
}
diff --git a/internal/repo/activity/user_active_repo.go b/internal/repo/activity/user_active_repo.go
index 0f6099d1..06c587c3 100644
--- a/internal/repo/activity/user_active_repo.go
+++ b/internal/repo/activity/user_active_repo.go
@@ -2,6 +2,8 @@ package activity
import (
"context"
+ "fmt"
+ "xorm.io/builder"
"github.com/answerdev/answer/internal/base/data"
"github.com/answerdev/answer/internal/base/reason"
@@ -41,43 +43,58 @@ func NewUserActiveActivityRepo(
}
}
-// UserActive accept other answer
+// UserActive user active
func (ar *UserActiveActivityRepo) UserActive(ctx context.Context, userID string) (err error) {
cfg, err := ar.configService.GetConfigByKey(ctx, UserActivated)
if err != nil {
return err
}
- activityType := cfg.ID
- deltaRank := cfg.GetIntValue()
addActivity := &entity.Activity{
UserID: userID,
ObjectID: "0",
OriginalObjectID: "0",
- ActivityType: activityType,
- Rank: deltaRank,
+ ActivityType: cfg.ID,
+ Rank: cfg.GetIntValue(),
HasRank: 1,
}
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
session = session.Context(ctx)
- _, exists, err := ar.activityRepo.GetActivity(ctx, session, "0", addActivity.UserID, activityType)
+ user := &entity.User{}
+ exist, err := session.ID(userID).ForUpdate().Get(user)
if err != nil {
- return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ return nil, err
}
- if exists {
+ if !exist {
+ return nil, fmt.Errorf("user not exist")
+ }
+
+ existsActivity := &entity.Activity{}
+ exist, err = session.
+ And(builder.Eq{"user_id": addActivity.UserID}).
+ And(builder.Eq{"activity_type": addActivity.ActivityType}).
+ Get(existsActivity)
+ if err != nil {
+ return nil, err
+ }
+ if exist {
return nil, nil
}
- _, err = ar.userRankRepo.TriggerUserRank(ctx, session, addActivity.UserID, addActivity.Rank, activityType)
+ err = ar.userRankRepo.ChangeUserRank(ctx, session, addActivity.UserID, user.Rank, addActivity.Rank)
if err != nil {
- return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ return nil, err
}
+
_, err = session.Insert(addActivity)
if err != nil {
- return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ return nil, err
}
return nil, nil
})
- return err
+ if err != nil {
+ return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ }
+ return nil
}
diff --git a/internal/repo/activity/vote_repo.go b/internal/repo/activity/vote_repo.go
index 58bf1510..120d392b 100644
--- a/internal/repo/activity/vote_repo.go
+++ b/internal/repo/activity/vote_repo.go
@@ -2,395 +2,174 @@ package activity
import (
"context"
- "strings"
+ "fmt"
+ "github.com/segmentfault/pacman/log"
"time"
"github.com/answerdev/answer/internal/base/constant"
+ "github.com/answerdev/answer/internal/service/notice_queue"
"github.com/answerdev/answer/pkg/converter"
"github.com/answerdev/answer/internal/base/pager"
- "github.com/answerdev/answer/internal/service/config"
- "github.com/answerdev/answer/internal/service/notice_queue"
"github.com/answerdev/answer/internal/service/rank"
"github.com/answerdev/answer/pkg/obj"
"xorm.io/builder"
- "github.com/answerdev/answer/internal/service/activity_common"
- "github.com/answerdev/answer/internal/service/unique"
-
"github.com/answerdev/answer/internal/base/data"
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service"
+ "github.com/answerdev/answer/internal/service/activity_common"
"github.com/segmentfault/pacman/errors"
"xorm.io/xorm"
)
// VoteRepo activity repository
type VoteRepo struct {
- data *data.Data
- uniqueIDRepo unique.UniqueIDRepo
- configService *config.ConfigService
- activityRepo activity_common.ActivityRepo
- userRankRepo rank.UserRankRepo
- voteCommon activity_common.VoteRepo
+ data *data.Data
+ activityRepo activity_common.ActivityRepo
+ userRankRepo rank.UserRankRepo
+ notificationQueueService notice_queue.NotificationQueueService
}
// NewVoteRepo new repository
func NewVoteRepo(
data *data.Data,
- uniqueIDRepo unique.UniqueIDRepo,
- configService *config.ConfigService,
activityRepo activity_common.ActivityRepo,
userRankRepo rank.UserRankRepo,
- voteCommon activity_common.VoteRepo,
+ notificationQueueService notice_queue.NotificationQueueService,
) service.VoteRepo {
return &VoteRepo{
- data: data,
- uniqueIDRepo: uniqueIDRepo,
- configService: configService,
- activityRepo: activityRepo,
- userRankRepo: userRankRepo,
- voteCommon: voteCommon,
+ data: data,
+ activityRepo: activityRepo,
+ userRankRepo: userRankRepo,
+ notificationQueueService: notificationQueueService,
}
}
-var LimitUpActions = map[string][]string{
- "question": {"vote_up", "voted_up"},
- "answer": {"vote_up", "voted_up"},
- "comment": {"vote_up"},
-}
+func (vr *VoteRepo) Vote(ctx context.Context, op *schema.VoteOperationInfo) (err error) {
+ noNeedToVote, err := vr.votePreCheck(ctx, op)
+ if err != nil {
+ return err
+ }
+ if noNeedToVote {
+ return nil
+ }
-var LimitDownActions = map[string][]string{
- "question": {"vote_down", "voted_down"},
- "answer": {"vote_down", "voted_down"},
- "comment": {"vote_down"},
-}
-
-func (vr *VoteRepo) vote(ctx context.Context, objectID string, userID, objectUserID string, actions []string) (resp *schema.VoteResp, err error) {
- resp = &schema.VoteResp{}
- achievementNotificationUserIDs := make([]string, 0)
sendInboxNotification := false
- upVote := false
+ maxDailyRank, err := vr.userRankRepo.GetMaxDailyRank(ctx)
+ if err != nil {
+ return err
+ }
+ var userIDs []string
+ for _, activity := range op.Activities {
+ userIDs = append(userIDs, activity.ActivityUserID)
+ }
+
_, err = vr.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
session = session.Context(ctx)
- result = nil
- for _, action := range actions {
- var (
- existsActivity entity.Activity
- insertActivity entity.Activity
- has bool
- triggerUserID,
- activityUserID string
- activityType, deltaRank, hasRank int
- )
- activityUserID, activityType, deltaRank, hasRank, err = vr.CheckRank(ctx, objectID, objectUserID, userID, action)
- if err != nil {
- return
- }
-
- triggerUserID = userID
- if userID == activityUserID {
- triggerUserID = "0"
- }
-
- // check is voted up
- has, _ = session.
- Where(builder.Eq{"object_id": objectID}).
- And(builder.Eq{"user_id": activityUserID}).
- And(builder.Eq{"trigger_user_id": triggerUserID}).
- And(builder.Eq{"activity_type": activityType}).
- Get(&existsActivity)
-
- // is is voted,return
- if has && existsActivity.Cancelled == entity.ActivityAvailable {
- return
- }
-
- insertActivity = entity.Activity{
- ObjectID: objectID,
- OriginalObjectID: objectID,
- UserID: activityUserID,
- TriggerUserID: converter.StringToInt64(triggerUserID),
- ActivityType: activityType,
- Rank: deltaRank,
- HasRank: hasRank,
- Cancelled: entity.ActivityAvailable,
- }
-
- // trigger user rank and send notification
- if hasRank != 0 {
- var isReachStandard bool
- isReachStandard, err = vr.userRankRepo.TriggerUserRank(ctx, session, activityUserID, deltaRank, activityType)
- if err != nil {
- return nil, err
- }
- if isReachStandard {
- insertActivity.Rank = 0
- }
- achievementNotificationUserIDs = append(achievementNotificationUserIDs, activityUserID)
- }
-
- if has {
- if _, err = session.Where("id = ?", existsActivity.ID).Cols("`cancelled`").
- Update(&entity.Activity{
- Cancelled: entity.ActivityAvailable,
- }); err != nil {
- return
- }
- } else {
- _, err = session.Insert(&insertActivity)
- if err != nil {
- return nil, err
- }
- sendInboxNotification = true
- }
-
- // update votes
- if action == constant.ActVoteDown || action == constant.ActVoteUp {
- votes := 1
- if action == constant.ActVoteDown {
- upVote = false
- votes = -1
- } else {
- upVote = true
- }
- err = vr.updateVotes(ctx, session, objectID, votes)
- if err != nil {
- return
- }
- }
+ userInfoMapping, err := vr.acquireUserInfo(session, userIDs)
+ if err != nil {
+ return nil, err
}
- return
+
+ err = vr.setActivityRankToZeroIfUserReachLimit(ctx, session, op, maxDailyRank)
+ if err != nil {
+ return nil, err
+ }
+
+ sendInboxNotification, err = vr.saveActivitiesAvailable(session, op)
+ if err != nil {
+ return nil, err
+ }
+
+ err = vr.changeUserRank(ctx, session, op, userInfoMapping)
+ if err != nil {
+ return nil, err
+ }
+ return nil, nil
})
if err != nil {
- return
+ return err
}
- resp, err = vr.GetVoteResultByObjectId(ctx, objectID)
- resp.VoteStatus = vr.voteCommon.GetVoteStatus(ctx, objectID, userID)
-
- for _, activityUserID := range achievementNotificationUserIDs {
- vr.sendNotification(ctx, activityUserID, objectUserID, objectID)
+ for _, activity := range op.Activities {
+ if activity.Rank == 0 {
+ continue
+ }
+ vr.sendAchievementNotification(ctx, activity.ActivityUserID, op.ObjectCreatorUserID, op.ObjectID)
}
if sendInboxNotification {
- vr.sendVoteInboxNotification(userID, objectUserID, objectID, upVote)
+ vr.sendVoteInboxNotification(ctx, op.OperatingUserID, op.ObjectCreatorUserID, op.ObjectID, op.VoteUp)
}
- return
+ return nil
}
-func (vr *VoteRepo) voteCancel(ctx context.Context, objectID string, userID, objectUserID string, actions []string) (resp *schema.VoteResp, err error) {
- resp = &schema.VoteResp{}
- notificationUserIDs := make([]string, 0)
+func (vr *VoteRepo) CancelVote(ctx context.Context, op *schema.VoteOperationInfo) (err error) {
+ // Pre-Check
+ // 1. check if the activity exist
+ // 2. check if the activity is not cancelled
+ // 3. if all activities are cancelled, return directly
+ activities, err := vr.getExistActivity(ctx, op)
+ if err != nil {
+ return err
+ }
+ var userIDs []string
+ for _, activity := range activities {
+ if activity.Cancelled == entity.ActivityCancelled {
+ continue
+ }
+ userIDs = append(userIDs, activity.UserID)
+ }
+ if len(userIDs) == 0 {
+ return nil
+ }
+
_, err = vr.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
session = session.Context(ctx)
- for _, action := range actions {
- var (
- existsActivity entity.Activity
- has bool
- triggerUserID,
- activityUserID string
- activityType,
- deltaRank, hasRank int
- )
- result = nil
- activityUserID, activityType, deltaRank, hasRank, err = vr.CheckRank(ctx, objectID, objectUserID, userID, action)
- if err != nil {
- return
- }
-
- triggerUserID = userID
- if userID == activityUserID {
- triggerUserID = "0"
- }
-
- has, err = session.
- Where(builder.Eq{"user_id": activityUserID}).
- And(builder.Eq{"trigger_user_id": triggerUserID}).
- And(builder.Eq{"activity_type": activityType}).
- And(builder.Eq{"object_id": objectID}).
- Get(&existsActivity)
-
- if !has {
- return
- }
-
- if existsActivity.Cancelled == entity.ActivityCancelled {
- return
- }
-
- if _, err = session.Where("id = ?", existsActivity.ID).Cols("cancelled", "cancelled_at").
- Update(&entity.Activity{
- Cancelled: entity.ActivityCancelled,
- CancelledAt: time.Now(),
- }); err != nil {
- return
- }
-
- // trigger user rank and send notification
- if hasRank != 0 && existsActivity.Rank != 0 {
- _, err = vr.userRankRepo.TriggerUserRank(ctx, session, activityUserID, -deltaRank, activityType)
- if err != nil {
- return
- }
- notificationUserIDs = append(notificationUserIDs, activityUserID)
- }
-
- // update votes
- if action == "vote_down" || action == "vote_up" {
- votes := -1
- if action == "vote_down" {
- votes = 1
- }
- err = vr.updateVotes(ctx, session, objectID, votes)
- if err != nil {
- return
- }
- }
+ userInfoMapping, err := vr.acquireUserInfo(session, userIDs)
+ if err != nil {
+ return nil, err
}
- return
+ err = vr.cancelActivities(session, activities)
+ if err != nil {
+ return nil, err
+ }
+
+ err = vr.rollbackUserRank(ctx, session, activities, userInfoMapping)
+ if err != nil {
+ return nil, err
+ }
+ return nil, nil
})
if err != nil {
- return
+ return err
}
- resp, err = vr.GetVoteResultByObjectId(ctx, objectID)
- resp.VoteStatus = vr.voteCommon.GetVoteStatus(ctx, objectID, userID)
- for _, activityUserID := range notificationUserIDs {
- vr.sendNotification(ctx, activityUserID, objectUserID, objectID)
+ for _, activity := range activities {
+ if activity.Rank == 0 {
+ continue
+ }
+ vr.sendAchievementNotification(ctx, activity.UserID, op.ObjectCreatorUserID, op.ObjectID)
}
+ return nil
+}
+
+func (vr *VoteRepo) GetAndSaveVoteResult(ctx context.Context, objectID, objectType string) (
+ up, down int64, err error) {
+ up = vr.countVoteUp(ctx, objectID, objectType)
+ down = vr.countVoteDown(ctx, objectID, objectType)
+ err = vr.updateVotes(ctx, objectID, objectType, int(up-down))
return
}
-func (vr *VoteRepo) VoteUp(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) {
- resp = &schema.VoteResp{}
- objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
- if err != nil {
- err = errors.BadRequest(reason.ObjectNotFound)
- return
- }
-
- actions, ok := LimitUpActions[objectType]
- if !ok {
- err = errors.BadRequest(reason.DisallowVote)
- return
- }
-
- _, _ = vr.VoteDownCancel(ctx, objectID, userID, objectUserID)
- return vr.vote(ctx, objectID, userID, objectUserID, actions)
-}
-
-func (vr *VoteRepo) VoteDown(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) {
- resp = &schema.VoteResp{}
- objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
- if err != nil {
- err = errors.BadRequest(reason.ObjectNotFound)
- return
- }
- actions, ok := LimitDownActions[objectType]
- if !ok {
- err = errors.BadRequest(reason.DisallowVote)
- return
- }
-
- _, _ = vr.VoteUpCancel(ctx, objectID, userID, objectUserID)
- return vr.vote(ctx, objectID, userID, objectUserID, actions)
-}
-
-func (vr *VoteRepo) VoteUpCancel(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) {
- var objectType string
- resp = &schema.VoteResp{}
-
- objectType, err = obj.GetObjectTypeStrByObjectID(objectID)
- if err != nil {
- err = errors.BadRequest(reason.ObjectNotFound)
- return
- }
- actions, ok := LimitUpActions[objectType]
- if !ok {
- err = errors.BadRequest(reason.DisallowVote)
- return
- }
-
- return vr.voteCancel(ctx, objectID, userID, objectUserID, actions)
-}
-
-func (vr *VoteRepo) VoteDownCancel(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) {
- var objectType string
- resp = &schema.VoteResp{}
-
- objectType, err = obj.GetObjectTypeStrByObjectID(objectID)
- if err != nil {
- err = errors.BadRequest(reason.ObjectNotFound)
- return
- }
- actions, ok := LimitDownActions[objectType]
- if !ok {
- err = errors.BadRequest(reason.DisallowVote)
- return
- }
-
- return vr.voteCancel(ctx, objectID, userID, objectUserID, actions)
-}
-
-func (vr *VoteRepo) CheckRank(ctx context.Context, objectID, objectUserID, userID string, action string) (activityUserID string, activityType, rank, hasRank int, err error) {
- activityType, rank, hasRank, err = vr.activityRepo.GetActivityTypeByObjID(ctx, objectID, action)
-
- if err != nil {
- return
- }
-
- activityUserID = userID
- if strings.Contains(action, "voted") {
- activityUserID = objectUserID
- }
-
- return activityUserID, activityType, rank, hasRank, nil
-}
-
-func (vr *VoteRepo) GetVoteResultByObjectId(ctx context.Context, objectID string) (resp *schema.VoteResp, err error) {
- resp = &schema.VoteResp{}
- for _, action := range []string{"vote_up", "vote_down"} {
- var (
- activity entity.Activity
- votes int64
- activityType int
- )
-
- activityType, _, _, _ = vr.activityRepo.GetActivityTypeByObjID(ctx, objectID, action)
-
- votes, err = vr.data.DB.Context(ctx).Where(builder.Eq{"object_id": objectID}).
- And(builder.Eq{"activity_type": activityType}).
- And(builder.Eq{"cancelled": 0}).
- Count(&activity)
-
- if err != nil {
- return
- }
-
- if action == "vote_up" {
- resp.UpVotes = int(votes)
- } else {
- resp.DownVotes = int(votes)
- }
- }
-
- resp.Votes = resp.UpVotes - resp.DownVotes
-
- return resp, nil
-}
-
-func (vr *VoteRepo) ListUserVotes(
- ctx context.Context,
- userID string,
- req schema.GetVoteWithPageReq,
- activityTypes []int,
-) (voteList []entity.Activity, total int64, err error) {
+func (vr *VoteRepo) ListUserVotes(ctx context.Context, userID string,
+ page int, pageSize int, activityTypes []int) (voteList []*entity.Activity, total int64, err error) {
session := vr.data.DB.Context(ctx)
cond := builder.
And(
@@ -399,46 +178,243 @@ func (vr *VoteRepo) ListUserVotes(
builder.In("activity_type", activityTypes),
)
- session.Where(cond).OrderBy("updated_at desc")
+ session.Where(cond).Desc("updated_at")
- total, err = pager.Help(req.Page, req.PageSize, &voteList, &entity.Activity{}, session)
+ total, err = pager.Help(page, pageSize, &voteList, &entity.Activity{}, session)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return
}
-// updateVotes
-// if votes < 0 Decr object vote_count,otherwise Incr object vote_count
-func (vr *VoteRepo) updateVotes(ctx context.Context, session *xorm.Session, objectID string, votes int) (err error) {
- var (
- objectType string
- e error
- )
+func (vr *VoteRepo) votePreCheck(ctx context.Context, op *schema.VoteOperationInfo) (noNeedToVote bool, err error) {
+ activities, err := vr.getExistActivity(ctx, op)
+ if err != nil {
+ return false, err
+ }
+ done := 0
+ for _, activity := range activities {
+ if activity.Cancelled == entity.ActivityAvailable {
+ done++
+ }
+ }
+ return done == len(op.Activities), nil
+}
- objectType, err = obj.GetObjectTypeStrByObjectID(objectID)
+func (vr *VoteRepo) acquireUserInfo(session *xorm.Session, userIDs []string) (map[string]*entity.User, error) {
+ us := make([]*entity.User, 0)
+ err := session.In("id", userIDs).ForUpdate().Find(&us)
+ if err != nil {
+ log.Error(err)
+ return nil, err
+ }
+
+ users := make(map[string]*entity.User, 0)
+ for _, u := range us {
+ users[u.ID] = u
+ }
+ return users, nil
+}
+
+func (vr *VoteRepo) setActivityRankToZeroIfUserReachLimit(ctx context.Context, session *xorm.Session,
+ op *schema.VoteOperationInfo, maxDailyRank int) (err error) {
+ // check if user reach daily rank limit
+ for _, activity := range op.Activities {
+ reach, err := vr.userRankRepo.CheckReachLimit(ctx, session, activity.ActivityUserID, maxDailyRank)
+ if err != nil {
+ log.Error(err)
+ return err
+ }
+ if reach {
+ activity.Rank = 0
+ }
+ }
+ return nil
+}
+
+func (vr *VoteRepo) changeUserRank(ctx context.Context, session *xorm.Session,
+ op *schema.VoteOperationInfo,
+ userInfoMapping map[string]*entity.User) (err error) {
+ for _, activity := range op.Activities {
+ if activity.Rank == 0 {
+ continue
+ }
+ user := userInfoMapping[activity.ActivityUserID]
+ if user == nil {
+ continue
+ }
+ if err = vr.userRankRepo.ChangeUserRank(ctx, session,
+ activity.ActivityUserID, user.Rank, activity.Rank); err != nil {
+ log.Error(err)
+ return err
+ }
+ }
+ return nil
+}
+
+func (vr *VoteRepo) rollbackUserRank(ctx context.Context, session *xorm.Session,
+ activities []*entity.Activity,
+ userInfoMapping map[string]*entity.User) (err error) {
+ for _, activity := range activities {
+ if activity.Rank == 0 {
+ continue
+ }
+ user := userInfoMapping[activity.UserID]
+ if user == nil {
+ continue
+ }
+ if err = vr.userRankRepo.ChangeUserRank(ctx, session,
+ activity.UserID, user.Rank, -activity.Rank); err != nil {
+ log.Error(err)
+ return err
+ }
+ }
+ return nil
+}
+
+// saveActivitiesAvailable save activities
+// If activity not exist it will be created or else will be updated
+// If this activity is already exist, set activity rank to 0
+// So after this function, the activity rank will be correct for update user rank
+func (vr *VoteRepo) saveActivitiesAvailable(session *xorm.Session, op *schema.VoteOperationInfo) (newAct bool, err error) {
+ for _, activity := range op.Activities {
+ existsActivity := &entity.Activity{}
+ exist, err := session.
+ Where(builder.Eq{"object_id": op.ObjectID}).
+ And(builder.Eq{"user_id": activity.ActivityUserID}).
+ And(builder.Eq{"trigger_user_id": activity.TriggerUserID}).
+ And(builder.Eq{"activity_type": activity.ActivityType}).
+ Get(existsActivity)
+ if err != nil {
+ return false, err
+ }
+ if exist && existsActivity.Cancelled == entity.ActivityAvailable {
+ activity.Rank = 0
+ continue
+ }
+ if exist {
+ if _, err = session.Where("id = ?", existsActivity.ID).Cols("`cancelled`").
+ Update(&entity.Activity{Cancelled: entity.ActivityAvailable}); err != nil {
+ return false, err
+ }
+ } else {
+ insertActivity := entity.Activity{
+ ObjectID: op.ObjectID,
+ OriginalObjectID: op.ObjectID,
+ UserID: activity.ActivityUserID,
+ TriggerUserID: converter.StringToInt64(activity.TriggerUserID),
+ ActivityType: activity.ActivityType,
+ Rank: activity.Rank,
+ HasRank: activity.HasRank(),
+ Cancelled: entity.ActivityAvailable,
+ }
+ _, err = session.Insert(&insertActivity)
+ if err != nil {
+ return false, err
+ }
+ newAct = true
+ }
+ }
+ return newAct, nil
+}
+
+// cancelActivities cancel activities
+// If this activity is already cancelled, set activity rank to 0
+// So after this function, the activity rank will be correct for update user rank
+func (vr *VoteRepo) cancelActivities(session *xorm.Session, activities []*entity.Activity) (err error) {
+ for _, activity := range activities {
+ t := &entity.Activity{}
+ exist, err := session.ID(activity.ID).Get(t)
+ if err != nil {
+ log.Error(err)
+ return err
+ }
+ if !exist {
+ log.Error(fmt.Errorf("%s activity not exist", activity.ID))
+ return fmt.Errorf("%s activity not exist", activity.ID)
+ }
+ // If this activity is already cancelled, set activity rank to 0
+ if t.Cancelled == entity.ActivityCancelled {
+ activity.Rank = 0
+ }
+ if _, err = session.ID(activity.ID).Cols("cancelled", "cancelled_at").
+ Update(&entity.Activity{
+ Cancelled: entity.ActivityCancelled,
+ CancelledAt: time.Now(),
+ }); err != nil {
+ log.Error(err)
+ return err
+ }
+ }
+ return nil
+}
+
+func (vr *VoteRepo) getExistActivity(ctx context.Context, op *schema.VoteOperationInfo) ([]*entity.Activity, error) {
+ var activities []*entity.Activity
+ for _, action := range op.Activities {
+ t := &entity.Activity{}
+ exist, err := vr.data.DB.Context(ctx).
+ Where(builder.Eq{"user_id": action.ActivityUserID}).
+ And(builder.Eq{"trigger_user_id": action.TriggerUserID}).
+ And(builder.Eq{"activity_type": action.ActivityType}).
+ And(builder.Eq{"object_id": op.ObjectID}).
+ Get(t)
+ if err != nil {
+ return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ }
+ if exist {
+ activities = append(activities, t)
+ }
+ }
+ return activities, nil
+}
+
+func (vr *VoteRepo) countVoteUp(ctx context.Context, objectID, objectType string) (count int64) {
+ count, err := vr.countVote(ctx, objectID, objectType, constant.ActVoteUp)
+ if err != nil {
+ log.Errorf("get vote up count error: %v", err)
+ }
+ return count
+}
+
+func (vr *VoteRepo) countVoteDown(ctx context.Context, objectID, objectType string) (count int64) {
+ count, err := vr.countVote(ctx, objectID, objectType, constant.ActVoteDown)
+ if err != nil {
+ log.Errorf("get vote down count error: %v", err)
+ }
+ return count
+}
+
+func (vr *VoteRepo) countVote(ctx context.Context, objectID, objectType, action string) (count int64, err error) {
+ activity := &entity.Activity{}
+ activityType, _ := vr.activityRepo.GetActivityTypeByObjectType(ctx, objectType, action)
+ count, err = vr.data.DB.Context(ctx).Where(builder.Eq{"object_id": objectID}).
+ And(builder.Eq{"activity_type": activityType}).
+ And(builder.Eq{"cancelled": 0}).
+ Count(activity)
+ if err != nil {
+ err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ }
+ return count, err
+}
+
+func (vr *VoteRepo) updateVotes(ctx context.Context, objectID, objectType string, voteCount int) (err error) {
+ session := vr.data.DB.Context(ctx)
switch objectType {
- case "question":
- _, err = session.Where("id = ?", objectID).Incr("vote_count", votes).Update(&entity.Question{})
- case "answer":
- _, err = session.Where("id = ?", objectID).Incr("vote_count", votes).Update(&entity.Answer{})
- case "comment":
- _, err = session.Where("id = ?", objectID).Incr("vote_count", votes).Update(&entity.Comment{})
- default:
- e = errors.BadRequest(reason.DisallowVote)
+ case constant.QuestionObjectType:
+ _, err = session.ID(objectID).Cols("vote_count").Update(&entity.Question{VoteCount: voteCount})
+ case constant.AnswerObjectType:
+ _, err = session.ID(objectID).Cols("vote_count").Update(&entity.Answer{VoteCount: voteCount})
+ case constant.CommentObjectType:
+ _, err = session.ID(objectID).Cols("vote_count").Update(&entity.Comment{VoteCount: voteCount})
}
-
- if e != nil {
- err = e
- } else if err != nil {
- err = errors.BadRequest(reason.DatabaseError).WithError(err).WithStack()
+ if err != nil {
+ log.Error(err)
}
-
return
}
-// sendNotification send rank triggered notification
-func (vr *VoteRepo) sendNotification(ctx context.Context, activityUserID, objectUserID, objectID string) {
+func (vr *VoteRepo) sendAchievementNotification(ctx context.Context, activityUserID, objectUserID, objectID string) {
objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
if err != nil {
return
@@ -451,10 +427,10 @@ func (vr *VoteRepo) sendNotification(ctx context.Context, activityUserID, object
ObjectID: objectID,
ObjectType: objectType,
}
- notice_queue.AddNotification(msg)
+ vr.notificationQueueService.Send(ctx, msg)
}
-func (vr *VoteRepo) sendVoteInboxNotification(triggerUserID, receiverUserID, objectID string, upvote bool) {
+func (vr *VoteRepo) sendVoteInboxNotification(ctx context.Context, triggerUserID, receiverUserID, objectID string, upvote bool) {
if triggerUserID == receiverUserID {
return
}
@@ -487,6 +463,6 @@ func (vr *VoteRepo) sendVoteInboxNotification(triggerUserID, receiverUserID, obj
}
}
if len(msg.NotificationAction) > 0 {
- notice_queue.AddNotification(msg)
+ vr.notificationQueueService.Send(ctx, msg)
}
}
diff --git a/internal/repo/activity_common/activity_repo.go b/internal/repo/activity_common/activity_repo.go
index a59b7085..ca16e482 100644
--- a/internal/repo/activity_common/activity_repo.go
+++ b/internal/repo/activity_common/activity_repo.go
@@ -41,12 +41,12 @@ func NewActivityRepo(
func (ar *ActivityRepo) GetActivityTypeByObjID(ctx context.Context, objectID string, action string) (
activityType, rank, hasRank int, err error) {
- objectKey, err := obj.GetObjectTypeStrByObjectID(objectID)
+ objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
if err != nil {
return
}
- confKey := fmt.Sprintf("%s.%s", objectKey, action)
+ confKey := fmt.Sprintf("%s.%s", objectType, action)
cfg, err := ar.configService.GetConfigByKey(ctx, confKey)
if err != nil {
return
@@ -59,11 +59,11 @@ func (ar *ActivityRepo) GetActivityTypeByObjID(ctx context.Context, objectID str
return
}
-func (ar *ActivityRepo) GetActivityTypeByObjKey(ctx context.Context, objectKey, action string) (activityType int, err error) {
- configKey := fmt.Sprintf("%s.%s", objectKey, action)
+func (ar *ActivityRepo) GetActivityTypeByObjectType(ctx context.Context, objectType, action string) (activityType int, err error) {
+ configKey := fmt.Sprintf("%s.%s", objectType, action)
cfg, err := ar.configService.GetConfigByKey(ctx, configKey)
if err != nil {
- err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ return 0, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return cfg.ID, nil
}
@@ -71,7 +71,7 @@ func (ar *ActivityRepo) GetActivityTypeByObjKey(ctx context.Context, objectKey,
func (ar *ActivityRepo) GetActivityTypeByConfigKey(ctx context.Context, configKey string) (activityType int, err error) {
cfg, err := ar.configService.GetConfigByKey(ctx, configKey)
if err != nil {
- err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ return 0, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return cfg.ID, nil
}
diff --git a/internal/repo/activity_common/follow.go b/internal/repo/activity_common/follow.go
index 11b5763c..1c3f08a2 100644
--- a/internal/repo/activity_common/follow.go
+++ b/internal/repo/activity_common/follow.go
@@ -10,6 +10,7 @@ import (
"github.com/answerdev/answer/internal/service/unique"
"github.com/answerdev/answer/pkg/obj"
"github.com/segmentfault/pacman/errors"
+ "github.com/segmentfault/pacman/log"
)
// FollowRepo follow repository
@@ -71,11 +72,12 @@ func (ar *FollowRepo) GetFollowAmount(ctx context.Context, objectID string) (fol
func (ar *FollowRepo) GetFollowUserIDs(ctx context.Context, objectID string) (userIDs []string, err error) {
objectTypeStr, err := obj.GetObjectTypeStrByObjectID(objectID)
if err != nil {
- return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ return nil, err
}
- activityType, err := ar.activityRepo.GetActivityTypeByObjKey(ctx, objectTypeStr, "follow")
+ activityType, err := ar.activityRepo.GetActivityTypeByObjectType(ctx, objectTypeStr, "follow")
if err != nil {
- return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ log.Errorf("can't get activity type by object key: %s", objectTypeStr)
+ return nil, err
}
userIDs = make([]string, 0)
@@ -94,7 +96,7 @@ func (ar *FollowRepo) GetFollowUserIDs(ctx context.Context, objectID string) (us
// GetFollowIDs get all follow id list
func (ar *FollowRepo) GetFollowIDs(ctx context.Context, userID, objectKey string) (followIDs []string, err error) {
followIDs = make([]string, 0)
- activityType, err := ar.activityRepo.GetActivityTypeByObjKey(ctx, objectKey, "follow")
+ activityType, err := ar.activityRepo.GetActivityTypeByObjectType(ctx, objectKey, "follow")
if err != nil {
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
@@ -116,7 +118,7 @@ func (ar *FollowRepo) IsFollowed(ctx context.Context, userID, objectID string) (
return false, err
}
- activityType, err := ar.activityRepo.GetActivityTypeByObjKey(ctx, objectKey, "follow")
+ activityType, err := ar.activityRepo.GetActivityTypeByObjectType(ctx, objectKey, "follow")
if err != nil {
return false, err
}
diff --git a/internal/repo/answer/answer_repo.go b/internal/repo/answer/answer_repo.go
index 76585a07..2cf18c9d 100644
--- a/internal/repo/answer/answer_repo.go
+++ b/internal/repo/answer/answer_repo.go
@@ -2,14 +2,11 @@ package answer
import (
"context"
- "strings"
"time"
- "unicode"
-
- "xorm.io/builder"
"github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/base/data"
+ "github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/pager"
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/entity"
@@ -58,8 +55,10 @@ func (ar *answerRepo) AddAnswer(ctx context.Context, answer *entity.Answer) (err
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
- answer.ID = uid.EnShortID(answer.ID)
- answer.QuestionID = uid.EnShortID(answer.QuestionID)
+ if handler.GetEnableShortID(ctx) {
+ answer.ID = uid.EnShortID(answer.ID)
+ answer.QuestionID = uid.EnShortID(answer.QuestionID)
+ }
return nil
}
@@ -109,9 +108,10 @@ func (ar *answerRepo) GetAnswer(ctx context.Context, id string) (
if err != nil {
return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
- answer.ID = uid.EnShortID(answer.ID)
- answer.QuestionID = uid.EnShortID(answer.QuestionID)
-
+ if handler.GetEnableShortID(ctx) {
+ answer.ID = uid.EnShortID(answer.ID)
+ answer.QuestionID = uid.EnShortID(answer.QuestionID)
+ }
return
}
@@ -134,9 +134,11 @@ func (ar *answerRepo) GetAnswerList(ctx context.Context, answer *entity.Answer)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
- for _, item := range answerList {
- item.ID = uid.EnShortID(item.ID)
- item.QuestionID = uid.EnShortID(item.QuestionID)
+ if handler.GetEnableShortID(ctx) {
+ for _, item := range answerList {
+ item.ID = uid.EnShortID(item.ID)
+ item.QuestionID = uid.EnShortID(item.QuestionID)
+ }
}
return
}
@@ -150,9 +152,11 @@ func (ar *answerRepo) GetAnswerPage(ctx context.Context, page, pageSize int, ans
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
- for _, item := range answerList {
- item.ID = uid.EnShortID(item.ID)
- item.QuestionID = uid.EnShortID(item.QuestionID)
+ if handler.GetEnableShortID(ctx) {
+ for _, item := range answerList {
+ item.ID = uid.EnShortID(item.ID)
+ item.QuestionID = uid.EnShortID(item.QuestionID)
+ }
}
return
}
@@ -191,8 +195,10 @@ func (ar *answerRepo) GetByID(ctx context.Context, id string) (*entity.Answer, b
if err != nil {
return &resp, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
- resp.ID = uid.EnShortID(resp.ID)
- resp.QuestionID = uid.EnShortID(resp.QuestionID)
+ if handler.GetEnableShortID(ctx) {
+ resp.ID = uid.EnShortID(resp.ID)
+ resp.QuestionID = uid.EnShortID(resp.QuestionID)
+ }
return &resp, has, nil
}
@@ -222,8 +228,10 @@ func (ar *answerRepo) GetByUserIDQuestionID(ctx context.Context, userID string,
if err != nil {
return &resp, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
- resp.ID = uid.EnShortID(resp.ID)
- resp.QuestionID = uid.EnShortID(resp.QuestionID)
+ if handler.GetEnableShortID(ctx) {
+ resp.ID = uid.EnShortID(resp.ID)
+ resp.QuestionID = uid.EnShortID(resp.QuestionID)
+ }
return &resp, has, nil
}
@@ -274,87 +282,40 @@ func (ar *answerRepo) SearchList(ctx context.Context, search *entity.AnswerSearc
if err != nil {
return rows, count, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
- for _, item := range rows {
- item.ID = uid.EnShortID(item.ID)
- item.QuestionID = uid.EnShortID(item.QuestionID)
+ if handler.GetEnableShortID(ctx) {
+ for _, item := range rows {
+ item.ID = uid.EnShortID(item.ID)
+ item.QuestionID = uid.EnShortID(item.QuestionID)
+ }
}
return rows, count, nil
}
-func (ar *answerRepo) AdminSearchList(ctx context.Context, search *entity.AdminAnswerSearch) ([]*entity.Answer, int64, error) {
- var (
- count int64
- err error
- session = ar.data.DB.Context(ctx).Table([]string{entity.Answer{}.TableName(), "a"}).Select("a.*")
- )
- if search.QuestionID != "" {
- search.QuestionID = uid.DeShortID(search.QuestionID)
- }
-
- session.Where(builder.Eq{
- "a.status": search.Status,
- })
-
- rows := make([]*entity.Answer, 0)
- if search.Page > 0 {
- search.Page = search.Page - 1
- } else {
- search.Page = 0
- }
- if search.PageSize == 0 {
- search.PageSize = constant.DefaultPageSize
- }
-
- // search by question title like or answer id
- if len(search.Query) > 0 {
- // check id search
- var (
- idSearch = false
- id = ""
- )
-
- if strings.Contains(search.Query, "answer:") {
- idSearch = true
- id = strings.TrimSpace(strings.TrimPrefix(search.Query, "answer:"))
- id = uid.DeShortID(id)
- for _, r := range id {
- if !unicode.IsDigit(r) {
- idSearch = false
- break
- }
- }
- }
-
- if idSearch {
- session.And(builder.Eq{
- "id": id,
- })
- } else {
- session.Join("LEFT", []string{entity.Question{}.TableName(), "q"}, "q.id = a.question_id")
- session.And(builder.Like{
- "q.title", search.Query,
- })
+func (ar *answerRepo) AdminSearchList(ctx context.Context, req *schema.AdminAnswerPageReq) (
+ resp []*entity.Answer, total int64, err error) {
+ cond := &entity.Answer{}
+ session := ar.data.DB.Context(ctx)
+ if len(req.QuestionID) == 0 && len(req.AnswerID) == 0 {
+ session.Join("INNER", "question", "answer.question_id = question.id")
+ if len(req.QuestionTitle) > 0 {
+ session.Where("question.title like ?", "%"+req.QuestionTitle+"%")
}
}
-
- // check search by question id
- if len(search.QuestionID) > 0 {
- session.And(builder.Eq{
- "question_id": search.QuestionID,
- })
+ if len(req.AnswerID) > 0 {
+ cond.ID = req.AnswerID
}
+ if len(req.QuestionID) > 0 {
+ session.Where("answer.question_id = ?", req.QuestionID)
+ }
+ if req.Status > 0 {
+ cond.Status = req.Status
+ }
+ session.Desc("answer.created_at")
- offset := search.Page * search.PageSize
- session.
- OrderBy("a.created_at desc").
- Limit(search.PageSize, offset)
- count, err = session.FindAndCount(&rows)
+ resp = make([]*entity.Answer, 0)
+ total, err = pager.Help(req.Page, req.PageSize, &resp, cond, session)
if err != nil {
- return rows, count, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ return nil, 0, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
- for _, item := range rows {
- item.ID = uid.EnShortID(item.ID)
- item.QuestionID = uid.EnShortID(item.QuestionID)
- }
- return rows, count, nil
+ return resp, total, nil
}
diff --git a/internal/repo/config/config_repo.go b/internal/repo/config/config_repo.go
index c5a155bd..0d426d70 100644
--- a/internal/repo/config/config_repo.go
+++ b/internal/repo/config/config_repo.go
@@ -37,7 +37,7 @@ func (cr configRepo) GetConfigByID(ctx context.Context, id int) (c *entity.Confi
}
c = &entity.Config{}
- exist, err := cr.data.DB.ID(id).Get(c)
+ exist, err := cr.data.DB.Context(ctx).ID(id).Get(c)
if err != nil {
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
@@ -80,7 +80,7 @@ func (cr configRepo) GetConfigByKey(ctx context.Context, key string) (c *entity.
func (cr configRepo) UpdateConfig(ctx context.Context, key string, value string) (err error) {
// check if key exists
- oldConfig := &entity.Config{}
+ oldConfig := &entity.Config{Key: key}
exist, err := cr.data.DB.Context(ctx).Get(oldConfig)
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
diff --git a/internal/repo/provider.go b/internal/repo/provider.go
index 10722cec..34fe1c1a 100644
--- a/internal/repo/provider.go
+++ b/internal/repo/provider.go
@@ -52,7 +52,6 @@ var ProviderSetRepo = wire.NewSet(
activity.NewVoteRepo,
activity.NewFollowRepo,
activity.NewAnswerActivityRepo,
- activity.NewQuestionActivityRepo,
activity.NewUserActiveActivityRepo,
activity.NewActivityRepo,
tag.NewTagRepo,
diff --git a/internal/repo/question/question_repo.go b/internal/repo/question/question_repo.go
index 2932e7ff..6623ae90 100644
--- a/internal/repo/question/question_repo.go
+++ b/internal/repo/question/question_repo.go
@@ -2,11 +2,14 @@ package question
import (
"context"
+ "encoding/json"
"fmt"
+ "github.com/segmentfault/pacman/log"
"strings"
"time"
"unicode"
+ "github.com/answerdev/answer/internal/base/handler"
"xorm.io/builder"
"github.com/answerdev/answer/internal/base/constant"
@@ -50,7 +53,9 @@ func (qr *questionRepo) AddQuestion(ctx context.Context, question *entity.Questi
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
- question.ID = uid.EnShortID(question.ID)
+ if handler.GetEnableShortID(ctx) {
+ question.ID = uid.EnShortID(question.ID)
+ }
return
}
@@ -71,7 +76,9 @@ func (qr *questionRepo) UpdateQuestion(ctx context.Context, question *entity.Que
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
- question.ID = uid.EnShortID(question.ID)
+ if handler.GetEnableShortID(ctx) {
+ question.ID = uid.EnShortID(question.ID)
+ }
return
}
@@ -164,7 +171,9 @@ func (qr *questionRepo) GetQuestion(ctx context.Context, id string) (
if err != nil {
return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
- question.ID = uid.EnShortID(question.ID)
+ if handler.GetEnableShortID(ctx) {
+ question.ID = uid.EnShortID(question.ID)
+ }
return
}
@@ -175,8 +184,10 @@ func (qr *questionRepo) SearchByTitleLike(ctx context.Context, title string) (qu
if err != nil {
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
- for _, item := range questionList {
- item.ID = uid.EnShortID(item.ID)
+ if handler.GetEnableShortID(ctx) {
+ for _, item := range questionList {
+ item.ID = uid.EnShortID(item.ID)
+ }
}
return
}
@@ -190,8 +201,10 @@ func (qr *questionRepo) FindByID(ctx context.Context, id []string) (questionList
if err != nil {
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
- for _, item := range questionList {
- item.ID = uid.EnShortID(item.ID)
+ if handler.GetEnableShortID(ctx) {
+ for _, item := range questionList {
+ item.ID = uid.EnShortID(item.ID)
+ }
}
return
}
@@ -211,13 +224,13 @@ func (qr *questionRepo) GetQuestionList(ctx context.Context, question *entity.Qu
}
func (qr *questionRepo) GetQuestionCount(ctx context.Context) (count int64, err error) {
- questionList := make([]*entity.Question, 0)
-
- count, err = qr.data.DB.Context(ctx).In("question.status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed}).FindAndCount(&questionList)
+ session := qr.data.DB.Context(ctx)
+ session.Where("status = ? OR status = ?", entity.QuestionStatusAvailable, entity.QuestionStatusClosed)
+ count, err = session.Count(&entity.Question{Show: entity.QuestionShow})
if err != nil {
- return count, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ return 0, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
- return
+ return count, nil
}
func (qr *questionRepo) GetUserQuestionCount(ctx context.Context, userID string) (count int64, err error) {
@@ -238,38 +251,52 @@ func (qr *questionRepo) GetQuestionCountByIDs(ctx context.Context, ids []string)
return
}
-func (qr *questionRepo) GetQuestionIDsPage(ctx context.Context, page, pageSize int) (questionIDList []*schema.SiteMapQuestionInfo, err error) {
+func (qr *questionRepo) SitemapQuestions(ctx context.Context, page, pageSize int) (
+ questionIDList []*schema.SiteMapQuestionInfo, err error) {
+ page = page - 1
questionIDList = make([]*schema.SiteMapQuestionInfo, 0)
+
+ // try to get sitemap data from cache
+ cacheKey := fmt.Sprintf(constant.SiteMapQuestionCacheKeyPrefix, page)
+ cacheData, err := qr.data.Cache.GetString(ctx, cacheKey)
+ if err == nil && len(cacheKey) > 0 {
+ _ = json.Unmarshal([]byte(cacheData), &questionIDList)
+ return questionIDList, nil
+ }
+
+ // get sitemap data from db
rows := make([]*entity.Question, 0)
- if page > 0 {
- page = page - 1
- } else {
- page = 0
- }
- if pageSize == 0 {
- pageSize = constant.DefaultPageSize
- }
- offset := page * pageSize
- session := qr.data.DB.Context(ctx).Table("question")
- session = session.In("question.status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed})
- session.And("question.show = ?", entity.QuestionShow)
- session = session.Limit(pageSize, offset)
- session = session.OrderBy("question.created_at asc")
- err = session.Select("id,title,created_at,post_update_time").Find(&rows)
+ session := qr.data.DB.Context(ctx)
+ session.Select("id,title,created_at,post_update_time")
+ session.Where("`show` = ?", entity.QuestionShow)
+ session.Where("status = ? OR status = ?", entity.QuestionStatusAvailable, entity.QuestionStatusClosed)
+ session.Limit(pageSize, page*pageSize)
+ session.Asc("created_at")
+ err = session.Find(&rows)
if err != nil {
return questionIDList, err
}
+
+ // warp data
for _, question := range rows {
- item := &schema.SiteMapQuestionInfo{}
- item.ID = uid.EnShortID(question.ID)
- item.Title = htmltext.UrlTitle(question.Title)
- updateTime := fmt.Sprintf("%v", question.PostUpdateTime.Format(time.RFC3339))
- if question.PostUpdateTime.Unix() < 1 {
- updateTime = fmt.Sprintf("%v", question.CreatedAt.Format(time.RFC3339))
+ item := &schema.SiteMapQuestionInfo{ID: question.ID}
+ if handler.GetEnableShortID(ctx) {
+ item.ID = uid.EnShortID(question.ID)
+ }
+ item.Title = htmltext.UrlTitle(question.Title)
+ if question.PostUpdateTime.IsZero() {
+ item.UpdateTime = question.CreatedAt.Format(time.RFC3339)
+ } else {
+ item.UpdateTime = question.PostUpdateTime.Format(time.RFC3339)
}
- item.UpdateTime = updateTime
questionIDList = append(questionIDList, item)
}
+
+ // set sitemap data to cache
+ cacheDataByte, _ := json.Marshal(questionIDList)
+ if err := qr.data.Cache.SetString(ctx, cacheKey, string(cacheDataByte), constant.SiteMapQuestionCacheTime); err != nil {
+ log.Error(err)
+ }
return questionIDList, nil
}
@@ -312,13 +339,15 @@ func (qr *questionRepo) GetQuestionPage(ctx context.Context, page, pageSize int,
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
- for _, item := range questionList {
- item.ID = uid.EnShortID(item.ID)
+ if handler.GetEnableShortID(ctx) {
+ for _, item := range questionList {
+ item.ID = uid.EnShortID(item.ID)
+ }
}
return questionList, total, err
}
-func (qr *questionRepo) AdminSearchList(ctx context.Context, search *schema.AdminQuestionSearch) ([]*entity.Question, int64, error) {
+func (qr *questionRepo) AdminQuestionPage(ctx context.Context, search *schema.AdminQuestionPageReq) ([]*entity.Question, int64, error) {
var (
count int64
err error
@@ -379,8 +408,10 @@ func (qr *questionRepo) AdminSearchList(ctx context.Context, search *schema.Admi
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
return rows, count, err
}
- for _, item := range rows {
- item.ID = uid.EnShortID(item.ID)
+ if handler.GetEnableShortID(ctx) {
+ for _, item := range rows {
+ item.ID = uid.EnShortID(item.ID)
+ }
}
return rows, count, nil
}
diff --git a/internal/repo/rank/user_rank_repo.go b/internal/repo/rank/user_rank_repo.go
index 06570fec..257dec8e 100644
--- a/internal/repo/rank/user_rank_repo.go
+++ b/internal/repo/rank/user_rank_repo.go
@@ -31,6 +31,56 @@ func NewUserRankRepo(data *data.Data, configService *config.ConfigService) rank.
}
}
+func (ur *UserRankRepo) GetMaxDailyRank(ctx context.Context) (maxDailyRank int, err error) {
+ maxDailyRank, err = ur.configService.GetIntValue(ctx, "daily_rank_limit")
+ if err != nil {
+ return 0, err
+ }
+ return maxDailyRank, nil
+}
+
+func (ur *UserRankRepo) CheckReachLimit(ctx context.Context, session *xorm.Session,
+ userID string, maxDailyRank int) (
+ reach bool, err error) {
+ session.Where(builder.Eq{"user_id": userID})
+ session.Where(builder.Eq{"cancelled": 0})
+ session.Where(builder.Between{
+ Col: "updated_at",
+ LessVal: now.BeginningOfDay(),
+ MoreVal: now.EndOfDay(),
+ })
+
+ earned, err := session.Sum(&entity.Activity{}, "`rank`")
+ if err != nil {
+ return false, err
+ }
+ if int(earned) <= maxDailyRank {
+ return false, nil
+ }
+ log.Infof("user %s today has rank %d is reach stand %d", userID, earned, maxDailyRank)
+ return true, nil
+}
+
+// ChangeUserRank change user rank
+func (ur *UserRankRepo) ChangeUserRank(
+ ctx context.Context, session *xorm.Session, userID string, userCurrentScore, deltaRank int) (err error) {
+ // IMPORTANT: If user center enabled the rank agent, then we should not change user rank.
+ if plugin.RankAgentEnabled() || deltaRank == 0 {
+ return nil
+ }
+
+ // If user rank is lower than 1 after this action, then user rank will be set to 1 only.
+ if deltaRank < 0 && userCurrentScore+deltaRank < 1 {
+ deltaRank = 1 - userCurrentScore
+ }
+
+ _, err = session.ID(userID).Incr("`rank`", deltaRank).Update(&entity.User{})
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
// TriggerUserRank trigger user rank change
// session is need provider, it means this action must be success or failure
// if outer action is failed then this action is need rollback
@@ -38,10 +88,7 @@ func (ur *UserRankRepo) TriggerUserRank(ctx context.Context,
session *xorm.Session, userID string, deltaRank int, activityType int,
) (isReachStandard bool, err error) {
// IMPORTANT: If user center enabled the rank agent, then we should not change user rank.
- if plugin.RankAgentEnabled() {
- return false, nil
- }
- if deltaRank == 0 {
+ if plugin.RankAgentEnabled() || deltaRank == 0 {
return false, nil
}
@@ -114,7 +161,7 @@ func (ur *UserRankRepo) checkUserTodayRank(ctx context.Context,
LessVal: start,
MoreVal: end,
})
- earned, err := session.Sum(&entity.Activity{}, "rank")
+ earned, err := session.Sum(&entity.Activity{}, "`rank`")
if err != nil {
return false, err
}
@@ -137,7 +184,7 @@ func (ur *UserRankRepo) UserRankPage(ctx context.Context, userID string, page, p
) {
rankPage = make([]*entity.Activity, 0)
- session := ur.data.DB.Context(ctx).Where(builder.Eq{"has_rank": 1}.And(builder.Eq{"cancelled": 0})).And(builder.Gt{"rank": 0})
+ session := ur.data.DB.Context(ctx).Where(builder.Eq{"has_rank": 1}.And(builder.Eq{"cancelled": 0})).And(builder.Gt{"`rank`": 0})
session.Desc("created_at")
cond := &entity.Activity{UserID: userID}
diff --git a/internal/repo/tag/tag_rel_repo.go b/internal/repo/tag/tag_rel_repo.go
index 62b3fe7a..3570cc01 100644
--- a/internal/repo/tag/tag_rel_repo.go
+++ b/internal/repo/tag/tag_rel_repo.go
@@ -4,6 +4,7 @@ import (
"context"
"github.com/answerdev/answer/internal/base/data"
+ "github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/entity"
tagcommon "github.com/answerdev/answer/internal/service/tag_common"
@@ -36,8 +37,10 @@ func (tr *tagRelRepo) AddTagRelList(ctx context.Context, tagList []*entity.TagRe
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
- for _, item := range tagList {
- item.ObjectID = uid.EnShortID(item.ObjectID)
+ if handler.GetEnableShortID(ctx) {
+ for _, item := range tagList {
+ item.ObjectID = uid.EnShortID(item.ObjectID)
+ }
}
return
}
@@ -54,7 +57,7 @@ func (tr *tagRelRepo) RemoveTagRelListByObjectID(ctx context.Context, objectID s
func (tr *tagRelRepo) HideTagRelListByObjectID(ctx context.Context, objectID string) (err error) {
objectID = uid.DeShortID(objectID)
- _, err = tr.data.DB.Where("object_id = ?", objectID).Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusHide})
+ _, err = tr.data.DB.Context(ctx).Where("object_id = ?", objectID).Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusHide})
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
@@ -63,7 +66,7 @@ func (tr *tagRelRepo) HideTagRelListByObjectID(ctx context.Context, objectID str
func (tr *tagRelRepo) ShowTagRelListByObjectID(ctx context.Context, objectID string) (err error) {
objectID = uid.DeShortID(objectID)
- _, err = tr.data.DB.Where("object_id = ?", objectID).Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusAvailable})
+ _, err = tr.data.DB.Context(ctx).Where("object_id = ?", objectID).Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusAvailable})
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
@@ -89,8 +92,11 @@ func (tr *tagRelRepo) GetObjectTagRelWithoutStatus(ctx context.Context, objectID
exist, err = session.Get(tagRel)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ return
+ }
+ if handler.GetEnableShortID(ctx) {
+ tagRel.ObjectID = uid.EnShortID(tagRel.ObjectID)
}
- tagRel.ObjectID = uid.EnShortID(tagRel.ObjectID)
return
}
@@ -112,9 +118,12 @@ func (tr *tagRelRepo) GetObjectTagRelList(ctx context.Context, objectID string)
err = session.Find(&tagListList)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ return
}
- for _, item := range tagListList {
- item.ObjectID = uid.EnShortID(item.ObjectID)
+ if handler.GetEnableShortID(ctx) {
+ for _, item := range tagListList {
+ item.ObjectID = uid.EnShortID(item.ObjectID)
+ }
}
return
}
@@ -130,9 +139,12 @@ func (tr *tagRelRepo) BatchGetObjectTagRelList(ctx context.Context, objectIds []
err = session.Find(&tagListList)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ return
}
- for _, item := range tagListList {
- item.ObjectID = uid.EnShortID(item.ObjectID)
+ if handler.GetEnableShortID(ctx) {
+ for _, item := range tagListList {
+ item.ObjectID = uid.EnShortID(item.ObjectID)
+ }
}
return
}
diff --git a/internal/repo/user/user_repo.go b/internal/repo/user/user_repo.go
index 72f7dc5c..9bf2f2ea 100644
--- a/internal/repo/user/user_repo.go
+++ b/internal/repo/user/user_repo.go
@@ -72,7 +72,7 @@ func (ur *userRepo) IncreaseQuestionCount(ctx context.Context, userID string, am
func (ur *userRepo) UpdateQuestionCount(ctx context.Context, userID string, count int64) (err error) {
user := &entity.User{}
user.QuestionCount = int(count)
- _, err = ur.data.DB.Where("id = ?", userID).Cols("question_count").Update(user)
+ _, err = ur.data.DB.Context(ctx).Where("id = ?", userID).Cols("question_count").Update(user)
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
@@ -82,7 +82,7 @@ func (ur *userRepo) UpdateQuestionCount(ctx context.Context, userID string, coun
func (ur *userRepo) UpdateAnswerCount(ctx context.Context, userID string, count int) (err error) {
user := &entity.User{}
user.AnswerCount = count
- _, err = ur.data.DB.Where("id = ?", userID).Cols("answer_count").Update(user)
+ _, err = ur.data.DB.Context(ctx).Where("id = ?", userID).Cols("answer_count").Update(user)
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
@@ -195,7 +195,7 @@ func (ur *userRepo) GetByUsername(ctx context.Context, username string) (userInf
func (ur *userRepo) GetByUsernames(ctx context.Context, usernames []string) ([]*entity.User, error) {
list := make([]*entity.User, 0)
- err := ur.data.DB.Where("status =?", entity.UserStatusAvailable).In("username", usernames).Find(&list)
+ err := ur.data.DB.Context(ctx).Where("status =?", entity.UserStatusAvailable).In("username", usernames).Find(&list)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
return list, err
diff --git a/internal/router/answer_api_router.go b/internal/router/answer_api_router.go
index 025ee594..897deace 100644
--- a/internal/router/answer_api_router.go
+++ b/internal/router/answer_api_router.go
@@ -26,7 +26,7 @@ type AnswerAPIRouter struct {
reasonController *controller.ReasonController
themeController *controller_admin.ThemeController
siteInfoController *controller_admin.SiteInfoController
- siteinfoController *controller.SiteinfoController
+ siteinfoController *controller.SiteInfoController
notificationController *controller.NotificationController
dashboardController *controller.DashboardController
uploadController *controller.UploadController
@@ -55,7 +55,7 @@ func NewAnswerAPIRouter(
reasonController *controller.ReasonController,
themeController *controller_admin.ThemeController,
siteInfoController *controller_admin.SiteInfoController,
- siteinfoController *controller.SiteinfoController,
+ siteinfoController *controller.SiteInfoController,
notificationController *controller.NotificationController,
dashboardController *controller.DashboardController,
uploadController *controller.UploadController,
@@ -244,9 +244,9 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
}
func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) {
- r.GET("/question/page", a.questionController.AdminSearchList)
+ r.GET("/question/page", a.questionController.AdminQuestionPage)
r.PUT("/question/status", a.questionController.AdminSetQuestionStatus)
- r.GET("/answer/page", a.questionController.AdminSearchAnswerList)
+ r.GET("/answer/page", a.questionController.AdminAnswerPage)
r.PUT("/answer/status", a.answerController.AdminSetAnswerStatus)
// report
diff --git a/internal/router/template_router.go b/internal/router/template_router.go
index 4ecb99aa..e90dc7ee 100644
--- a/internal/router/template_router.go
+++ b/internal/router/template_router.go
@@ -17,7 +17,6 @@ func NewTemplateRouter(
templateController *controller.TemplateController,
templateRenderController *templaterender.TemplateRenderController,
siteInfoController *controller_admin.SiteInfoController,
-
) *TemplateRouter {
return &TemplateRouter{
templateController: templateController,
@@ -26,7 +25,7 @@ func NewTemplateRouter(
}
}
-// TemplateRouter template router
+// RegisterTemplateRouter template router
func (a *TemplateRouter) RegisterTemplateRouter(r *gin.RouterGroup) {
r.GET("/sitemap.xml", a.templateController.Sitemap)
r.GET("/sitemap/:page", a.templateController.SitemapPage)
@@ -35,7 +34,6 @@ func (a *TemplateRouter) RegisterTemplateRouter(r *gin.RouterGroup) {
r.GET("/custom.css", a.siteInfoController.GetCss)
r.GET("/", a.templateController.Index)
- r.GET("/index", a.templateController.Index)
r.GET("/questions", a.templateController.QuestionList)
r.GET("/questions/:id", a.templateController.QuestionInfo)
diff --git a/internal/router/ui.go b/internal/router/ui.go
index f94bcd3f..1e8bf8ec 100644
--- a/internal/router/ui.go
+++ b/internal/router/ui.go
@@ -21,14 +21,14 @@ const UIStaticPath = "build/static"
// UIRouter is an interface that provides ui static file routers
type UIRouter struct {
- siteInfoController *controller.SiteinfoController
- siteInfoService *siteinfo_common.SiteInfoCommonService
+ siteInfoController *controller.SiteInfoController
+ siteInfoService siteinfo_common.SiteInfoCommonService
}
// NewUIRouter creates a new UIRouter instance with the embed resources
func NewUIRouter(
- siteInfoController *controller.SiteinfoController,
- siteInfoService *siteinfo_common.SiteInfoCommonService,
+ siteInfoController *controller.SiteInfoController,
+ siteInfoService siteinfo_common.SiteInfoCommonService,
) *UIRouter {
return &UIRouter{
siteInfoController: siteInfoController,
diff --git a/internal/schema/activity.go b/internal/schema/activity.go
index 6e775c88..0dfdf37d 100644
--- a/internal/schema/activity.go
+++ b/internal/schema/activity.go
@@ -4,12 +4,13 @@ import "github.com/answerdev/answer/internal/base/constant"
// ActivityMsg activity message
type ActivityMsg struct {
- UserID string `json:"user_id"`
- TriggerUserID int64 `json:"trigger_user_id"`
- ObjectID string `json:"object_id"`
- OriginalObjectID string `json:"original_object_id"`
- ActivityTypeKey constant.ActivityTypeKey `json:"activity_type_key"`
- RevisionID string `json:"revision_id"`
+ UserID string
+ TriggerUserID int64
+ ObjectID string
+ OriginalObjectID string
+ ActivityTypeKey constant.ActivityTypeKey
+ RevisionID string
+ ExtraInfo map[string]string
}
// GetObjectTimelineReq get object timeline request
diff --git a/internal/schema/answer_activity_schema.go b/internal/schema/answer_activity_schema.go
new file mode 100644
index 00000000..747f7754
--- /dev/null
+++ b/internal/schema/answer_activity_schema.go
@@ -0,0 +1,35 @@
+package schema
+
+// AcceptAnswerOperationInfo accept answer operation info
+type AcceptAnswerOperationInfo struct {
+ QuestionObjectID string
+ QuestionUserID string
+ AnswerObjectID string
+ AnswerUserID string
+
+ // vote activity info
+ Activities []*AcceptAnswerActivity
+}
+
+// AcceptAnswerActivity accept answer activity
+type AcceptAnswerActivity struct {
+ ActivityType int
+ ActivityUserID string
+ TriggerUserID string
+ OriginalObjectID string
+ Rank int
+}
+
+func (v *AcceptAnswerActivity) HasRank() int {
+ if v.Rank != 0 {
+ return 1
+ }
+ return 0
+}
+
+func (a *AcceptAnswerOperationInfo) GetUserIDs() (userIDs []string) {
+ for _, act := range a.Activities {
+ userIDs = append(userIDs, act.ActivityUserID)
+ }
+ return userIDs
+}
diff --git a/internal/schema/dashboard_schema.go b/internal/schema/dashboard_schema.go
index 88afed90..844023d4 100644
--- a/internal/schema/dashboard_schema.go
+++ b/internal/schema/dashboard_schema.go
@@ -5,8 +5,8 @@ import "time"
var AppStartTime time.Time
const (
- DashBoardCachekey = "answer@dashboard"
- DashBoardCacheTime = 60 * time.Minute
+ DashboardCacheKey = "answer:dashboard"
+ DashboardCacheTime = 60 * time.Minute
)
type DashboardInfo struct {
diff --git a/internal/schema/notification_schema.go b/internal/schema/notification_schema.go
index 4e617efd..7dc5f746 100644
--- a/internal/schema/notification_schema.go
+++ b/internal/schema/notification_schema.go
@@ -63,6 +63,8 @@ type NotificationMsg struct {
NotificationAction string
// if true no need to send notification to all followers
NoNeedPushAllFollow bool
+ // extra info
+ ExtraInfo map[string]string
}
type ObjectInfo struct {
diff --git a/internal/schema/question_schema.go b/internal/schema/question_schema.go
index acabc0d2..6fd26bab 100644
--- a/internal/schema/question_schema.go
+++ b/internal/schema/question_schema.go
@@ -1,16 +1,16 @@
package schema
import (
+ "strings"
"time"
"github.com/answerdev/answer/internal/base/validator"
+ "github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/pkg/converter"
+ "github.com/answerdev/answer/pkg/uid"
)
const (
- SitemapMaxSize = 50000
- SitemapCachekey = "answer@sitemap"
- SitemapPageCachekey = "answer@sitemap@page%d"
QuestionOperationPin = "pin"
QuestionOperationUnPin = "unpin"
QuestionOperationHide = "hide"
@@ -361,12 +361,62 @@ type QuestionPageRespOperator struct {
DisplayName string `json:"display_name"`
}
-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:"-"`
- StatusStr string `json:"status" form:"status"` // Status 1 Available 2 closed 10 UserDeleted
- Query string `validate:"omitempty,gt=0,lte=100" json:"query" form:"query" ` //Query string
+type AdminQuestionPageReq struct {
+ Page int `validate:"omitempty,min=1" form:"page"`
+ PageSize int `validate:"omitempty,min=1" form:"page_size"`
+ StatusCond string `validate:"omitempty,oneof=normal closed deleted" form:"status"`
+ Query string `validate:"omitempty,gt=0,lte=100" json:"query" form:"query" `
+ Status int `json:"-"`
+ LoginUserID string `json:"-"`
+}
+
+func (req *AdminQuestionPageReq) Check() (errField []*validator.FormErrorField, err error) {
+ status, ok := entity.AdminQuestionSearchStatus[req.StatusCond]
+ if ok {
+ req.Status = status
+ }
+ if req.Status == 0 {
+ req.Status = 1
+ }
+ return nil, nil
+}
+
+// AdminAnswerPageReq admin answer page req
+type AdminAnswerPageReq struct {
+ Page int `validate:"omitempty,min=1" form:"page"`
+ PageSize int `validate:"omitempty,min=1" form:"page_size"`
+ StatusCond string `validate:"omitempty,oneof=normal deleted" form:"status"`
+ Query string `validate:"omitempty,gt=0,lte=100" form:"query"`
+ QuestionID string `validate:"omitempty,gt=0,lte=24" form:"question_id"`
+ QuestionTitle string `json:"-"`
+ AnswerID string `json:"-"`
+ Status int `json:"-"`
+ LoginUserID string `json:"-"`
+}
+
+func (req *AdminAnswerPageReq) Check() (errField []*validator.FormErrorField, err error) {
+ req.QuestionID = uid.DeShortID(req.QuestionID)
+ if req.QuestionID == "0" {
+ req.QuestionID = ""
+ }
+
+ if status, ok := entity.AdminAnswerSearchStatus[req.StatusCond]; ok {
+ req.Status = status
+ }
+ if req.Status == 0 {
+ req.Status = 1
+ }
+
+ // parse query condition
+ if len(req.Query) > 0 {
+ prefix := "answer:"
+ if strings.Contains(req.Query, prefix) {
+ req.AnswerID = uid.DeShortID(strings.TrimSpace(strings.TrimPrefix(req.Query, prefix)))
+ } else {
+ req.QuestionTitle = strings.TrimSpace(req.Query)
+ }
+ }
+ return nil, nil
}
type AdminSetQuestionStatusRequest struct {
@@ -374,21 +424,6 @@ type AdminSetQuestionStatusRequest struct {
QuestionID string `json:"question_id" form:"question_id"`
}
-type SiteMapList struct {
- QuestionIDs []*SiteMapQuestionInfo `json:"question_ids"`
- MaxPageNum []int `json:"max_page_num"`
-}
-
-type SiteMapPageList struct {
- PageData []*SiteMapQuestionInfo `json:"page_data"`
-}
-
-type SiteMapQuestionInfo struct {
- ID string `json:"id"`
- Title string `json:"title"`
- UpdateTime string `json:"time"`
-}
-
type PersonalQuestionPageReq struct {
Page int `validate:"omitempty,min=1" form:"page"`
PageSize int `validate:"omitempty,min=1" form:"page_size"`
diff --git a/internal/schema/siteinfo_schema.go b/internal/schema/siteinfo_schema.go
index 119420cf..bef9cf8e 100644
--- a/internal/schema/siteinfo_schema.go
+++ b/internal/schema/siteinfo_schema.go
@@ -14,11 +14,6 @@ import (
"github.com/segmentfault/pacman/errors"
)
-const PermaLinkQuestionIDAndTitle = 1 // /questions/10010000000000001/post-title
-const PermaLinkQuestionID = 2 // /questions/10010000000000001
-const PermaLinkQuestionIDAndTitleByShortID = 3 // /questions/11/post-title
-const PermaLinkQuestionIDByShortID = 4 // /questions/11
-
// SiteGeneralReq site general request
type SiteGeneralReq struct {
Name string `validate:"required,sanitizer,gt=1,lte=128" form:"name" json:"name"`
@@ -28,11 +23,6 @@ type SiteGeneralReq struct {
ContactEmail string `validate:"required,sanitizer,gt=1,lte=512,email" form:"contact_email" json:"contact_email"`
}
-type SiteSeoReq struct {
- PermaLink int `validate:"required,lte=4,gte=0" form:"permalink" json:"permalink"`
- Robots string `validate:"required" form:"robots" json:"robots"`
-}
-
func (r *SiteGeneralReq) FormatSiteUrl() {
parsedUrl, err := url.Parse(r.SiteUrl)
if err != nil {
@@ -127,6 +117,16 @@ type SiteThemeReq struct {
ThemeConfig map[string]interface{} `validate:"omitempty" json:"theme_config"`
}
+type SiteSeoReq struct {
+ PermaLink int `validate:"required,lte=4,gte=0" form:"permalink" json:"permalink"`
+ Robots string `validate:"required" form:"robots" json:"robots"`
+}
+
+func (s *SiteSeoResp) IsShortLink() bool {
+ return s.PermaLink == constant.PermaLinkQuestionIDAndTitleByShortID ||
+ s.PermaLink == constant.PermaLinkQuestionIDByShortID
+}
+
// SiteGeneralResp site general response
type SiteGeneralResp SiteGeneralReq
@@ -186,7 +186,7 @@ type SiteInfoResp struct {
Login *SiteLoginResp `json:"login"`
Theme *SiteThemeResp `json:"theme"`
CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"`
- SiteSeo *SiteSeoReq `json:"site_seo"`
+ SiteSeo *SiteSeoResp `json:"site_seo"`
SiteUsers *SiteUsersResp `json:"site_users"`
Version string `json:"version"`
Revision string `json:"revision"`
@@ -195,7 +195,7 @@ type TemplateSiteInfoResp struct {
General *SiteGeneralResp `json:"general"`
Interface *SiteInterfaceResp `json:"interface"`
Branding *SiteBrandingResp `json:"branding"`
- SiteSeo *SiteSeoReq `json:"site_seo"`
+ SiteSeo *SiteSeoResp `json:"site_seo"`
CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"`
Title string
Year string
diff --git a/internal/schema/sitemap_schema.go b/internal/schema/sitemap_schema.go
new file mode 100644
index 00000000..08e27f8d
--- /dev/null
+++ b/internal/schema/sitemap_schema.go
@@ -0,0 +1,16 @@
+package schema
+
+type SiteMapList struct {
+ QuestionIDs []*SiteMapQuestionInfo `json:"question_ids"`
+ MaxPageNum []int `json:"max_page_num"`
+}
+
+type SiteMapPageList struct {
+ PageData []*SiteMapQuestionInfo `json:"page_data"`
+}
+
+type SiteMapQuestionInfo struct {
+ ID string `json:"id"`
+ Title string `json:"title"`
+ UpdateTime string `json:"time"`
+}
diff --git a/internal/schema/vote_schema.go b/internal/schema/vote_schema.go
index 4cb86c66..e41f542f 100644
--- a/internal/schema/vote_schema.go
+++ b/internal/schema/vote_schema.go
@@ -6,20 +6,44 @@ type VoteReq struct {
UserID string `json:"-"`
}
-type VoteDTO struct {
- // object TagID
- ObjectID string
- // is cancel
- IsCancel bool
- // user TagID
- UserID string
+type VoteResp struct {
+ UpVotes int64 `json:"up_votes"`
+ DownVotes int64 `json:"down_votes"`
+ Votes int64 `json:"votes"`
+ VoteStatus string `json:"vote_status"`
}
-type VoteResp struct {
- UpVotes int `json:"up_votes"`
- DownVotes int `json:"down_votes"`
- Votes int `json:"votes"`
- VoteStatus string `json:"vote_status"`
+// VoteOperationInfo vote operation info
+type VoteOperationInfo struct {
+ // operation object id
+ ObjectID string
+ // question answer comment
+ ObjectType string
+ // object owner user id
+ ObjectCreatorUserID string
+ // operation user id
+ OperatingUserID string
+ // vote up
+ VoteUp bool
+ // vote down
+ VoteDown bool
+ // vote activity info
+ Activities []*VoteActivity
+}
+
+// VoteActivity vote activity
+type VoteActivity struct {
+ ActivityType int
+ ActivityUserID string
+ TriggerUserID string
+ Rank int
+}
+
+func (v *VoteActivity) HasRank() int {
+ if v.Rank != 0 {
+ return 1
+ }
+ return 0
}
type GetVoteWithPageReq struct {
@@ -28,23 +52,7 @@ type GetVoteWithPageReq struct {
// page size
PageSize int `validate:"omitempty,min=1" form:"page_size"`
// user id
- UserID string `validate:"required" form:"user_id"`
-}
-
-type VoteQuestion struct {
- // object ID
- ID string `json:"id"`
- // title
- Title string `json:"title"`
-}
-
-type VoteAnswer struct {
- // object ID
- ID string `json:"id"`
- // question ID
- QuestionID string `json:"question_id"`
- // title
- Title string `json:"title"`
+ UserID string `json:"-"`
}
type GetVoteWithPageResp struct {
diff --git a/internal/service/activity/activity.go b/internal/service/activity/activity.go
index ebd5e6df..0b3f7dde 100644
--- a/internal/service/activity/activity.go
+++ b/internal/service/activity/activity.go
@@ -7,9 +7,9 @@ import (
"strings"
"github.com/answerdev/answer/internal/base/constant"
+ "github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/schema"
- "github.com/answerdev/answer/internal/service/activity_common"
"github.com/answerdev/answer/internal/service/comment_common"
"github.com/answerdev/answer/internal/service/config"
"github.com/answerdev/answer/internal/service/meta"
@@ -30,22 +30,20 @@ type ActivityRepo interface {
// ActivityService activity service
type ActivityService struct {
- activityRepo ActivityRepo
- userCommon *usercommon.UserCommon
- activityCommonService *activity_common.ActivityCommon
- tagCommonService *tag_common.TagCommonService
- objectInfoService *object_info.ObjService
- commentCommonService *comment_common.CommentCommonService
- revisionService *revision_common.RevisionService
- metaService *meta.MetaService
- configService *config.ConfigService
+ activityRepo ActivityRepo
+ userCommon *usercommon.UserCommon
+ tagCommonService *tag_common.TagCommonService
+ objectInfoService *object_info.ObjService
+ commentCommonService *comment_common.CommentCommonService
+ revisionService *revision_common.RevisionService
+ metaService *meta.MetaService
+ configService *config.ConfigService
}
// NewActivityService new activity service
func NewActivityService(
activityRepo ActivityRepo,
userCommon *usercommon.UserCommon,
- activityCommonService *activity_common.ActivityCommon,
tagCommonService *tag_common.TagCommonService,
objectInfoService *object_info.ObjService,
commentCommonService *comment_common.CommentCommonService,
@@ -54,15 +52,14 @@ func NewActivityService(
configService *config.ConfigService,
) *ActivityService {
return &ActivityService{
- objectInfoService: objectInfoService,
- activityRepo: activityRepo,
- userCommon: userCommon,
- activityCommonService: activityCommonService,
- tagCommonService: tagCommonService,
- commentCommonService: commentCommonService,
- revisionService: revisionService,
- metaService: metaService,
- configService: configService,
+ objectInfoService: objectInfoService,
+ activityRepo: activityRepo,
+ userCommon: userCommon,
+ tagCommonService: tagCommonService,
+ commentCommonService: commentCommonService,
+ revisionService: revisionService,
+ metaService: metaService,
+ configService: configService,
}
}
@@ -97,7 +94,9 @@ func (as *ActivityService) GetObjectTimeline(ctx context.Context, req *schema.Ge
}
if item.ObjectType == constant.QuestionObjectType || item.ObjectType == constant.AnswerObjectType {
- item.ObjectID = uid.EnShortID(act.ObjectID)
+ if handler.GetEnableShortID(ctx) {
+ item.ObjectID = uid.EnShortID(act.ObjectID)
+ }
}
cfg, err := as.configService.GetConfigByID(ctx, act.ActivityType)
diff --git a/internal/service/activity/answer_activity.go b/internal/service/activity/answer_activity.go
deleted file mode 100644
index 2a8e51d8..00000000
--- a/internal/service/activity/answer_activity.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package activity
-
-import (
- "context"
- "time"
-
- "github.com/segmentfault/pacman/log"
-)
-
-// AnswerActivityRepo answer activity
-type AnswerActivityRepo interface {
- AcceptAnswer(ctx context.Context,
- answerObjID, questionObjID, questionUserID, answerUserID string, isSelf bool) (err error)
- CancelAcceptAnswer(ctx context.Context,
- answerObjID, questionObjID, questionUserID, answerUserID string) (err error)
- DeleteAnswer(ctx context.Context, answerID string) (err error)
-}
-
-// QuestionActivityRepo answer activity
-type QuestionActivityRepo interface {
- DeleteQuestion(ctx context.Context, questionID string) (err error)
-}
-
-// AnswerActivityService user service
-type AnswerActivityService struct {
- answerActivityRepo AnswerActivityRepo
- questionActivityRepo QuestionActivityRepo
-}
-
-// NewAnswerActivityService new comment service
-func NewAnswerActivityService(
- answerActivityRepo AnswerActivityRepo, questionActivityRepo QuestionActivityRepo) *AnswerActivityService {
- return &AnswerActivityService{
- answerActivityRepo: answerActivityRepo,
- questionActivityRepo: questionActivityRepo,
- }
-}
-
-// AcceptAnswer accept answer change activity
-func (as *AnswerActivityService) AcceptAnswer(ctx context.Context,
- answerObjID, questionObjID, questionUserID, answerUserID string, isSelf bool) (err error) {
- return as.answerActivityRepo.AcceptAnswer(ctx, answerObjID, questionObjID, questionUserID, answerUserID, isSelf)
-}
-
-// CancelAcceptAnswer cancel accept answer change activity
-func (as *AnswerActivityService) CancelAcceptAnswer(ctx context.Context,
- answerObjID, questionObjID, questionUserID, answerUserID string) (err error) {
- return as.answerActivityRepo.CancelAcceptAnswer(ctx, answerObjID, questionObjID, questionUserID, answerUserID)
-}
-
-// DeleteAnswer delete answer change activity
-func (as *AnswerActivityService) DeleteAnswer(ctx context.Context, answerID string, createdAt time.Time,
- voteCount int) (err error) {
- if voteCount >= 3 {
- log.Infof("There is no need to roll back the reputation by answering likes above the target value. %s %d", answerID, voteCount)
- return nil
- }
- if createdAt.Before(time.Now().AddDate(0, 0, -60)) {
- log.Infof("There is no need to roll back the reputation by answer's existence time meets the target. %s %s", answerID, createdAt.String())
- return nil
- }
- return as.answerActivityRepo.DeleteAnswer(ctx, answerID)
-}
-
-// DeleteQuestion delete question change activity
-func (as *AnswerActivityService) DeleteQuestion(ctx context.Context, questionID string, createdAt time.Time,
- voteCount int) (err error) {
- if voteCount >= 3 {
- log.Infof("There is no need to roll back the reputation by answering likes above the target value. %s %d", questionID, voteCount)
- return nil
- }
- if createdAt.Before(time.Now().AddDate(0, 0, -60)) {
- log.Infof("There is no need to roll back the reputation by answer's existence time meets the target. %s %s", questionID, createdAt.String())
- return nil
- }
- return as.questionActivityRepo.DeleteQuestion(ctx, questionID)
-}
diff --git a/internal/service/activity/answer_activity_service.go b/internal/service/activity/answer_activity_service.go
new file mode 100644
index 00000000..56b09676
--- /dev/null
+++ b/internal/service/activity/answer_activity_service.go
@@ -0,0 +1,92 @@
+package activity
+
+import (
+ "context"
+ "github.com/answerdev/answer/internal/schema"
+ "github.com/answerdev/answer/internal/service/activity_type"
+ "github.com/answerdev/answer/internal/service/config"
+ "github.com/segmentfault/pacman/log"
+)
+
+// AnswerActivityRepo answer activity
+type AnswerActivityRepo interface {
+ SaveAcceptAnswerActivity(ctx context.Context, op *schema.AcceptAnswerOperationInfo) (err error)
+ SaveCancelAcceptAnswerActivity(ctx context.Context, op *schema.AcceptAnswerOperationInfo) (err error)
+}
+
+// AnswerActivityService answer activity service
+type AnswerActivityService struct {
+ answerActivityRepo AnswerActivityRepo
+ configService *config.ConfigService
+}
+
+// NewAnswerActivityService new comment service
+func NewAnswerActivityService(
+ answerActivityRepo AnswerActivityRepo,
+ configService *config.ConfigService,
+) *AnswerActivityService {
+ return &AnswerActivityService{
+ answerActivityRepo: answerActivityRepo,
+ configService: configService,
+ }
+}
+
+// AcceptAnswer accept answer change activity
+func (as *AnswerActivityService) AcceptAnswer(ctx context.Context,
+ answerObjID, questionObjID, questionUserID, answerUserID string, isSelf bool) (err error) {
+ operationInfo := as.createAcceptAnswerOperationInfo(ctx,
+ answerObjID, questionObjID, questionUserID, answerUserID, isSelf)
+ return as.answerActivityRepo.SaveAcceptAnswerActivity(ctx, operationInfo)
+}
+
+// CancelAcceptAnswer cancel accept answer change activity
+func (as *AnswerActivityService) CancelAcceptAnswer(ctx context.Context,
+ answerObjID, questionObjID, questionUserID, answerUserID string) (err error) {
+ operationInfo := as.createAcceptAnswerOperationInfo(ctx,
+ answerObjID, questionObjID, questionUserID, answerUserID, false)
+ return as.answerActivityRepo.SaveCancelAcceptAnswerActivity(ctx, operationInfo)
+}
+
+func (as *AnswerActivityService) createAcceptAnswerOperationInfo(ctx context.Context,
+ answerObjID, questionObjID, questionUserID, answerUserID string, isSelf bool) *schema.AcceptAnswerOperationInfo {
+ operationInfo := &schema.AcceptAnswerOperationInfo{
+ QuestionObjectID: questionObjID,
+ QuestionUserID: questionUserID,
+ AnswerObjectID: answerObjID,
+ AnswerUserID: answerUserID,
+ }
+ operationInfo.Activities = as.getActivities(ctx, operationInfo)
+ if isSelf {
+ for _, activity := range operationInfo.Activities {
+ activity.Rank = 0
+ }
+ }
+ return operationInfo
+}
+
+func (as *AnswerActivityService) getActivities(ctx context.Context, op *schema.AcceptAnswerOperationInfo) (
+ activities []*schema.AcceptAnswerActivity) {
+ activities = make([]*schema.AcceptAnswerActivity, 0)
+
+ for _, action := range []string{activity_type.AnswerAccept, activity_type.AnswerAccepted} {
+ t := &schema.AcceptAnswerActivity{}
+ cfg, err := as.configService.GetConfigByKey(ctx, action)
+ if err != nil {
+ log.Warnf("get config by key error: %v", err)
+ continue
+ }
+ t.ActivityType, t.Rank = cfg.ID, cfg.GetIntValue()
+
+ if action == activity_type.AnswerAccept {
+ t.ActivityUserID = op.QuestionUserID
+ t.TriggerUserID = op.AnswerUserID
+ t.OriginalObjectID = op.QuestionObjectID // if activity is 'accept' means this question is accept the answer.
+ } else {
+ t.ActivityUserID = op.AnswerUserID
+ t.TriggerUserID = op.AnswerUserID
+ t.OriginalObjectID = op.AnswerObjectID // if activity is 'accepted' means this answer was accepted.
+ }
+ activities = append(activities, t)
+ }
+ return activities
+}
diff --git a/internal/service/activity_common/activity.go b/internal/service/activity_common/activity.go
index cf5b78b5..adfb55ad 100644
--- a/internal/service/activity_common/activity.go
+++ b/internal/service/activity_common/activity.go
@@ -5,6 +5,7 @@ import (
"time"
"github.com/answerdev/answer/internal/entity"
+ "github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service/activity_queue"
"github.com/answerdev/answer/pkg/converter"
"github.com/answerdev/answer/pkg/uid"
@@ -14,7 +15,7 @@ import (
type ActivityRepo interface {
GetActivityTypeByObjID(ctx context.Context, objectId string, action string) (activityType, rank int, hasRank int, err error)
- GetActivityTypeByObjKey(ctx context.Context, objectKey, action string) (activityType int, err error)
+ GetActivityTypeByObjectType(ctx context.Context, objectKey, action string) (activityType int, err error)
GetActivity(ctx context.Context, session *xorm.Session, objectID, userID string, activityType int) (
existsActivity *entity.Activity, exist bool, err error)
GetUserIDObjectIDActivitySum(ctx context.Context, userID, objectID string) (int, error)
@@ -27,51 +28,44 @@ type ActivityRepo interface {
}
type ActivityCommon struct {
- activityRepo ActivityRepo
+ activityRepo ActivityRepo
+ activityQueueService activity_queue.ActivityQueueService
}
// NewActivityCommon new activity common
func NewActivityCommon(
activityRepo ActivityRepo,
+ activityQueueService activity_queue.ActivityQueueService,
) *ActivityCommon {
activity := &ActivityCommon{
- activityRepo: activityRepo,
+ activityRepo: activityRepo,
+ activityQueueService: activityQueueService,
}
- activity.HandleActivity()
+ activity.activityQueueService.RegisterHandler(activity.HandleActivity)
return activity
}
// HandleActivity handle activity message
-func (ac *ActivityCommon) HandleActivity() {
- go func() {
- defer func() {
- if err := recover(); err != nil {
- log.Error(err)
- }
- }()
+func (ac *ActivityCommon) HandleActivity(ctx context.Context, msg *schema.ActivityMsg) error {
+ activityType, err := ac.activityRepo.GetActivityTypeByConfigKey(ctx, string(msg.ActivityTypeKey))
+ if err != nil {
+ log.Errorf("error getting activity type %s, activity type is %d", err, activityType)
+ return err
+ }
- for msg := range activity_queue.ActivityQueue {
- log.Debugf("received activity %+v", msg)
-
- activityType, err := ac.activityRepo.GetActivityTypeByConfigKey(context.Background(), string(msg.ActivityTypeKey))
- if err != nil {
- log.Errorf("error getting activity type %s, activity type is %d", err, activityType)
- }
-
- act := &entity.Activity{
- UserID: msg.UserID,
- TriggerUserID: msg.TriggerUserID,
- ObjectID: uid.DeShortID(msg.ObjectID),
- OriginalObjectID: uid.DeShortID(msg.OriginalObjectID),
- ActivityType: activityType,
- Cancelled: entity.ActivityAvailable,
- }
- if len(msg.RevisionID) > 0 {
- act.RevisionID = converter.StringToInt64(msg.RevisionID)
- }
- if err := ac.activityRepo.AddActivity(context.TODO(), act); err != nil {
- log.Error(err)
- }
- }
- }()
+ act := &entity.Activity{
+ UserID: msg.UserID,
+ TriggerUserID: msg.TriggerUserID,
+ ObjectID: uid.DeShortID(msg.ObjectID),
+ OriginalObjectID: uid.DeShortID(msg.OriginalObjectID),
+ ActivityType: activityType,
+ Cancelled: entity.ActivityAvailable,
+ }
+ if len(msg.RevisionID) > 0 {
+ act.RevisionID = converter.StringToInt64(msg.RevisionID)
+ }
+ if err := ac.activityRepo.AddActivity(ctx, act); err != nil {
+ return err
+ }
+ return nil
}
diff --git a/internal/service/activity_queue/activity_queue.go b/internal/service/activity_queue/activity_queue.go
index 27897268..3561e3f5 100644
--- a/internal/service/activity_queue/activity_queue.go
+++ b/internal/service/activity_queue/activity_queue.go
@@ -1,14 +1,50 @@
package activity_queue
import (
+ "context"
+
"github.com/answerdev/answer/internal/schema"
+ "github.com/segmentfault/pacman/log"
)
-var (
- ActivityQueue = make(chan *schema.ActivityMsg, 128)
-)
-
-// AddActivity add new activity
-func AddActivity(msg *schema.ActivityMsg) {
- ActivityQueue <- msg
+type ActivityQueueService interface {
+ Send(ctx context.Context, msg *schema.ActivityMsg)
+ RegisterHandler(handler func(ctx context.Context, msg *schema.ActivityMsg) error)
+}
+
+type activityQueueService struct {
+ Queue chan *schema.ActivityMsg
+ Handler func(ctx context.Context, msg *schema.ActivityMsg) error
+}
+
+func (ns *activityQueueService) Send(ctx context.Context, msg *schema.ActivityMsg) {
+ ns.Queue <- msg
+}
+
+func (ns *activityQueueService) RegisterHandler(
+ handler func(ctx context.Context, msg *schema.ActivityMsg) error) {
+ ns.Handler = handler
+}
+
+func (ns *activityQueueService) working() {
+ go func() {
+ for msg := range ns.Queue {
+ log.Debugf("received activity %+v", msg)
+ if ns.Handler == nil {
+ log.Warnf("no handler for activity")
+ continue
+ }
+ if err := ns.Handler(context.Background(), msg); err != nil {
+ log.Error(err)
+ }
+ }
+ }()
+}
+
+// NewActivityQueueService create a new activity queue service
+func NewActivityQueueService() ActivityQueueService {
+ ns := &activityQueueService{}
+ ns.Queue = make(chan *schema.ActivityMsg, 128)
+ ns.working()
+ return ns
}
diff --git a/internal/service/answer_common/answer.go b/internal/service/answer_common/answer.go
index c23e6822..2373a6d0 100644
--- a/internal/service/answer_common/answer.go
+++ b/internal/service/answer_common/answer.go
@@ -3,9 +3,11 @@ package answercommon
import (
"context"
+ "github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/pkg/htmltext"
+ "github.com/answerdev/answer/pkg/uid"
)
type AnswerRepo interface {
@@ -21,7 +23,7 @@ type AnswerRepo interface {
GetCountByUserID(ctx context.Context, userID string) (int64, error)
GetByUserIDQuestionID(ctx context.Context, userID string, questionID string) (*entity.Answer, bool, error)
SearchList(ctx context.Context, search *entity.AnswerSearch) ([]*entity.Answer, int64, error)
- AdminSearchList(ctx context.Context, search *entity.AdminAnswerSearch) ([]*entity.Answer, int64, error)
+ AdminSearchList(ctx context.Context, search *schema.AdminAnswerPageReq) ([]*entity.Answer, int64, error)
UpdateAnswerStatus(ctx context.Context, answer *entity.Answer) (err error)
GetAnswerCount(ctx context.Context) (count int64, err error)
}
@@ -45,11 +47,16 @@ func (as *AnswerCommon) SearchAnswered(ctx context.Context, userID, questionID s
return has, nil
}
-func (as *AnswerCommon) AdminSearchList(ctx context.Context, search *entity.AdminAnswerSearch) ([]*entity.Answer, int64, error) {
- if search.Status == 0 {
- search.Status = 1
+func (as *AnswerCommon) AdminSearchList(ctx context.Context, req *schema.AdminAnswerPageReq) (
+ resp []*entity.Answer, count int64, err error) {
+ resp, count, err = as.answerRepo.AdminSearchList(ctx, req)
+ if handler.GetEnableShortID(ctx) {
+ for _, item := range resp {
+ item.ID = uid.EnShortID(item.ID)
+ item.QuestionID = uid.EnShortID(item.QuestionID)
+ }
}
- return as.answerRepo.AdminSearchList(ctx, search)
+ return resp, count, err
}
func (as *AnswerCommon) Search(ctx context.Context, search *entity.AnswerSearch) ([]*entity.Answer, int64, error) {
diff --git a/internal/service/answer_service.go b/internal/service/answer_service.go
index a090bc84..81276951 100644
--- a/internal/service/answer_service.go
+++ b/internal/service/answer_service.go
@@ -31,18 +31,20 @@ import (
// AnswerService user service
type AnswerService struct {
- answerRepo answercommon.AnswerRepo
- questionRepo questioncommon.QuestionRepo
- questionCommon *questioncommon.QuestionCommon
- answerActivityService *activity.AnswerActivityService
- userCommon *usercommon.UserCommon
- collectionCommon *collectioncommon.CollectionCommon
- userRepo usercommon.UserRepo
- revisionService *revision_common.RevisionService
- AnswerCommon *answercommon.AnswerCommon
- voteRepo activity_common.VoteRepo
- emailService *export.EmailService
- roleService *role.UserRoleRelService
+ answerRepo answercommon.AnswerRepo
+ questionRepo questioncommon.QuestionRepo
+ questionCommon *questioncommon.QuestionCommon
+ answerActivityService *activity.AnswerActivityService
+ userCommon *usercommon.UserCommon
+ collectionCommon *collectioncommon.CollectionCommon
+ userRepo usercommon.UserRepo
+ revisionService *revision_common.RevisionService
+ AnswerCommon *answercommon.AnswerCommon
+ voteRepo activity_common.VoteRepo
+ emailService *export.EmailService
+ roleService *role.UserRoleRelService
+ notificationQueueService notice_queue.NotificationQueueService
+ activityQueueService activity_queue.ActivityQueueService
}
func NewAnswerService(
@@ -58,20 +60,24 @@ func NewAnswerService(
voteRepo activity_common.VoteRepo,
emailService *export.EmailService,
roleService *role.UserRoleRelService,
+ notificationQueueService notice_queue.NotificationQueueService,
+ activityQueueService activity_queue.ActivityQueueService,
) *AnswerService {
return &AnswerService{
- answerRepo: answerRepo,
- questionRepo: questionRepo,
- userCommon: userCommon,
- collectionCommon: collectionCommon,
- questionCommon: questionCommon,
- userRepo: userRepo,
- revisionService: revisionService,
- answerActivityService: answerAcceptActivityRepo,
- AnswerCommon: answerCommon,
- voteRepo: voteRepo,
- emailService: emailService,
- roleService: roleService,
+ answerRepo: answerRepo,
+ questionRepo: questionRepo,
+ userCommon: userCommon,
+ collectionCommon: collectionCommon,
+ questionCommon: questionCommon,
+ userRepo: userRepo,
+ revisionService: revisionService,
+ answerActivityService: answerAcceptActivityRepo,
+ AnswerCommon: answerCommon,
+ voteRepo: voteRepo,
+ emailService: emailService,
+ roleService: roleService,
+ notificationQueueService: notificationQueueService,
+ activityQueueService: activityQueueService,
}
}
@@ -136,7 +142,7 @@ func (as *AnswerService) RemoveAnswer(ctx context.Context, req *schema.RemoveAns
//if err != nil {
// log.Errorf("delete answer activity change failed: %s", err.Error())
//}
- activity_queue.AddActivity(&schema.ActivityMsg{
+ as.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: req.UserID,
ObjectID: answerInfo.ID,
OriginalObjectID: answerInfo.ID,
@@ -205,14 +211,14 @@ func (as *AnswerService) Insert(ctx context.Context, req *schema.AnswerAddReq) (
as.notificationAnswerTheQuestion(ctx, questionInfo.UserID, questionInfo.ID, insertData.ID, req.UserID, questionInfo.Title,
insertData.OriginalText)
- activity_queue.AddActivity(&schema.ActivityMsg{
+ as.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: insertData.UserID,
ObjectID: insertData.ID,
OriginalObjectID: insertData.ID,
ActivityTypeKey: constant.ActAnswerAnswered,
RevisionID: revisionID,
})
- activity_queue.AddActivity(&schema.ActivityMsg{
+ as.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: insertData.UserID,
ObjectID: insertData.ID,
OriginalObjectID: questionInfo.ID,
@@ -305,7 +311,7 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq
return insertData.ID, err
}
if canUpdate {
- activity_queue.AddActivity(&schema.ActivityMsg{
+ as.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: insertData.UserID,
ObjectID: insertData.ID,
OriginalObjectID: insertData.ID,
@@ -472,7 +478,7 @@ func (as *AnswerService) AdminSetAnswerStatus(ctx context.Context, req *schema.A
//if err != nil {
// log.Errorf("admin delete question then rank rollback error %s", err.Error())
//}
- activity_queue.AddActivity(&schema.ActivityMsg{
+ as.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: req.UserID,
ObjectID: answerInfo.ID,
OriginalObjectID: answerInfo.ID,
@@ -487,7 +493,7 @@ func (as *AnswerService) AdminSetAnswerStatus(ctx context.Context, req *schema.A
msg.TriggerUserID = answerInfo.UserID
msg.ObjectType = constant.AnswerObjectType
msg.NotificationAction = constant.NotificationYourAnswerWasDeleted
- notice_queue.AddNotification(msg)
+ as.notificationQueueService.Send(ctx, msg)
return nil
}
@@ -579,7 +585,7 @@ func (as *AnswerService) notificationUpdateAnswer(ctx context.Context, questionU
}
msg.ObjectType = constant.AnswerObjectType
msg.NotificationAction = constant.NotificationUpdateAnswer
- notice_queue.AddNotification(msg)
+ as.notificationQueueService.Send(ctx, msg)
}
func (as *AnswerService) notificationAnswerTheQuestion(ctx context.Context,
@@ -596,7 +602,7 @@ func (as *AnswerService) notificationAnswerTheQuestion(ctx context.Context,
}
msg.ObjectType = constant.AnswerObjectType
msg.NotificationAction = constant.NotificationAnswerTheQuestion
- notice_queue.AddNotification(msg)
+ as.notificationQueueService.Send(ctx, msg)
userInfo, exist, err := as.userRepo.GetByUserID(ctx, questionUserID)
if err != nil {
diff --git a/internal/service/comment/comment_service.go b/internal/service/comment/comment_service.go
index a983d5d1..83970652 100644
--- a/internal/service/comment/comment_service.go
+++ b/internal/service/comment/comment_service.go
@@ -58,13 +58,15 @@ func (c *CommentQuery) GetOrderBy() string {
// CommentService user service
type CommentService struct {
- commentRepo CommentRepo
- commentCommonRepo comment_common.CommentCommonRepo
- userCommon *usercommon.UserCommon
- voteCommon activity_common.VoteRepo
- objectInfoService *object_info.ObjService
- emailService *export.EmailService
- userRepo usercommon.UserRepo
+ commentRepo CommentRepo
+ commentCommonRepo comment_common.CommentCommonRepo
+ userCommon *usercommon.UserCommon
+ voteCommon activity_common.VoteRepo
+ objectInfoService *object_info.ObjService
+ emailService *export.EmailService
+ userRepo usercommon.UserRepo
+ notificationQueueService notice_queue.NotificationQueueService
+ activityQueueService activity_queue.ActivityQueueService
}
// NewCommentService new comment service
@@ -76,15 +78,19 @@ func NewCommentService(
voteCommon activity_common.VoteRepo,
emailService *export.EmailService,
userRepo usercommon.UserRepo,
+ notificationQueueService notice_queue.NotificationQueueService,
+ activityQueueService activity_queue.ActivityQueueService,
) *CommentService {
return &CommentService{
- commentRepo: commentRepo,
- commentCommonRepo: commentCommonRepo,
- userCommon: userCommon,
- voteCommon: voteCommon,
- objectInfoService: objectInfoService,
- emailService: emailService,
- userRepo: userRepo,
+ commentRepo: commentRepo,
+ commentCommonRepo: commentCommonRepo,
+ userCommon: userCommon,
+ voteCommon: voteCommon,
+ objectInfoService: objectInfoService,
+ emailService: emailService,
+ userRepo: userRepo,
+ notificationQueueService: notificationQueueService,
+ activityQueueService: activityQueueService,
}
}
@@ -161,7 +167,7 @@ func (cs *CommentService) AddComment(ctx context.Context, req *schema.AddComment
case constant.AnswerObjectType:
activityMsg.ActivityTypeKey = constant.ActAnswerCommented
}
- activity_queue.AddActivity(activityMsg)
+ cs.activityQueueService.Send(ctx, activityMsg)
return resp, nil
}
@@ -476,7 +482,7 @@ func (cs *CommentService) notificationQuestionComment(ctx context.Context, quest
}
msg.ObjectType = constant.CommentObjectType
msg.NotificationAction = constant.NotificationCommentQuestion
- notice_queue.AddNotification(msg)
+ cs.notificationQueueService.Send(ctx, msg)
receiverUserInfo, exist, err := cs.userRepo.GetByUserID(ctx, questionUserID)
if err != nil {
@@ -535,7 +541,7 @@ func (cs *CommentService) notificationAnswerComment(ctx context.Context,
}
msg.ObjectType = constant.CommentObjectType
msg.NotificationAction = constant.NotificationCommentAnswer
- notice_queue.AddNotification(msg)
+ cs.notificationQueueService.Send(ctx, msg)
receiverUserInfo, exist, err := cs.userRepo.GetByUserID(ctx, answerUserID)
if err != nil {
@@ -591,7 +597,7 @@ func (cs *CommentService) notificationCommentReply(ctx context.Context, replyUse
}
msg.ObjectType = constant.CommentObjectType
msg.NotificationAction = constant.NotificationReplyToYou
- notice_queue.AddNotification(msg)
+ cs.notificationQueueService.Send(ctx, msg)
}
func (cs *CommentService) notificationMention(
@@ -612,7 +618,7 @@ func (cs *CommentService) notificationMention(
}
msg.ObjectType = constant.CommentObjectType
msg.NotificationAction = constant.NotificationMentionYou
- notice_queue.AddNotification(msg)
+ cs.notificationQueueService.Send(ctx, msg)
alreadyNotifiedUserIDs = append(alreadyNotifiedUserIDs, userInfo.ID)
}
}
diff --git a/internal/service/dashboard/dashboard_service.go b/internal/service/dashboard/dashboard_service.go
index fa798e69..e6b26701 100644
--- a/internal/service/dashboard/dashboard_service.go
+++ b/internal/service/dashboard/dashboard_service.go
@@ -11,7 +11,6 @@ import (
"github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/base/data"
- "github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service/activity_common"
answercommon "github.com/answerdev/answer/internal/service/answer_common"
@@ -24,11 +23,10 @@ import (
"github.com/answerdev/answer/internal/service/siteinfo_common"
usercommon "github.com/answerdev/answer/internal/service/user_common"
"github.com/answerdev/answer/pkg/dir"
- "github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/log"
)
-type DashboardService struct {
+type dashboardService struct {
questionRepo questioncommon.QuestionRepo
answerRepo answercommon.AnswerRepo
commentRepo comment_common.CommentCommonRepo
@@ -36,10 +34,9 @@ type DashboardService struct {
userRepo usercommon.UserRepo
reportRepo report_common.ReportRepo
configService *config.ConfigService
- siteInfoService *siteinfo_common.SiteInfoCommonService
+ siteInfoService siteinfo_common.SiteInfoCommonService
serviceConfig *service_config.ServiceConfig
-
- data *data.Data
+ data *data.Data
}
func NewDashboardService(
@@ -50,12 +47,11 @@ func NewDashboardService(
userRepo usercommon.UserRepo,
reportRepo report_common.ReportRepo,
configService *config.ConfigService,
- siteInfoService *siteinfo_common.SiteInfoCommonService,
+ siteInfoService siteinfo_common.SiteInfoCommonService,
serviceConfig *service_config.ServiceConfig,
-
data *data.Data,
-) *DashboardService {
- return &DashboardService{
+) DashboardService {
+ return &dashboardService{
questionRepo: questionRepo,
answerRepo: answerRepo,
commentRepo: commentRepo,
@@ -65,63 +61,102 @@ func NewDashboardService(
configService: configService,
siteInfoService: siteInfoService,
serviceConfig: serviceConfig,
-
- data: data,
+ data: data,
}
}
-func (ds *DashboardService) StatisticalByCache(ctx context.Context) (*schema.DashboardInfo, error) {
- dashboardInfo := &schema.DashboardInfo{}
- infoStr, err := ds.data.Cache.GetString(ctx, schema.DashBoardCachekey)
+type DashboardService interface {
+ Statistical(ctx context.Context) (resp *schema.DashboardInfo, err error)
+}
+
+func (ds *dashboardService) Statistical(ctx context.Context) (*schema.DashboardInfo, error) {
+ dashboardInfo, err := ds.getFromCache(ctx)
if err != nil {
- info, statisticalErr := ds.Statistical(ctx)
- if statisticalErr != nil {
- return nil, statisticalErr
- }
- if setCacheErr := ds.SetCache(ctx, info); setCacheErr != nil {
- log.Errorf("set dashboard statistical failed: %s", setCacheErr)
- }
- return info, nil
+ dashboardInfo = &schema.DashboardInfo{}
+ dashboardInfo.QuestionCount = ds.questionCount(ctx)
+ dashboardInfo.AnswerCount = ds.answerCount(ctx)
+ dashboardInfo.CommentCount = ds.commentCount(ctx)
+ dashboardInfo.UserCount = ds.userCount(ctx)
+ dashboardInfo.ReportCount = ds.reportCount(ctx)
+ dashboardInfo.VoteCount = ds.voteCount(ctx)
+ dashboardInfo.OccupyingStorageSpace = ds.calculateStorage()
+ dashboardInfo.VersionInfo.RemoteVersion = ds.remoteVersion(ctx)
}
- if err = json.Unmarshal([]byte(infoStr), dashboardInfo); err != nil {
- log.Errorf("parsing dashboard information failed: %s", err)
- return nil, errors.InternalServer(reason.UnknownError)
- }
- startTime := time.Now().Unix() - schema.AppStartTime.Unix()
- dashboardInfo.AppStartTime = fmt.Sprintf("%d", startTime)
+
+ dashboardInfo.SMTP = ds.smtpStatus(ctx)
+ dashboardInfo.HTTPS = ds.httpsStatus(ctx)
+ dashboardInfo.TimeZone = ds.getTimezone(ctx)
+ dashboardInfo.UploadingFiles = true
+ dashboardInfo.AppStartTime = fmt.Sprintf("%d", time.Now().Unix()-schema.AppStartTime.Unix())
dashboardInfo.VersionInfo.Version = constant.Version
dashboardInfo.VersionInfo.Revision = constant.Revision
+
+ ds.setCache(ctx, dashboardInfo)
return dashboardInfo, nil
}
-func (ds *DashboardService) SetCache(ctx context.Context, info *schema.DashboardInfo) error {
- infoStr, err := json.Marshal(info)
+func (ds *dashboardService) getFromCache(ctx context.Context) (*schema.DashboardInfo, error) {
+ infoStr, err := ds.data.Cache.GetString(ctx, schema.DashboardCacheKey)
if err != nil {
- return errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
+ return nil, err
}
- err = ds.data.Cache.SetString(ctx, schema.DashBoardCachekey, string(infoStr), schema.DashBoardCacheTime)
- if err != nil {
- return errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
+ dashboardInfo := &schema.DashboardInfo{}
+ if err = json.Unmarshal([]byte(infoStr), dashboardInfo); err != nil {
+ return nil, err
}
- return nil
+ return dashboardInfo, nil
}
-// Statistical
-func (ds *DashboardService) Statistical(ctx context.Context) (*schema.DashboardInfo, error) {
- dashboardInfo := &schema.DashboardInfo{}
+func (ds *dashboardService) setCache(ctx context.Context, info *schema.DashboardInfo) {
+ infoStr, _ := json.Marshal(info)
+ err := ds.data.Cache.SetString(ctx, schema.DashboardCacheKey, string(infoStr), schema.DashboardCacheTime)
+ if err != nil {
+ log.Errorf("set dashboard statistical failed: %s", err)
+ }
+}
+
+func (ds *dashboardService) questionCount(ctx context.Context) int64 {
questionCount, err := ds.questionRepo.GetQuestionCount(ctx)
if err != nil {
- return dashboardInfo, err
+ log.Errorf("get question count failed: %s", err)
}
+ return questionCount
+}
+
+func (ds *dashboardService) answerCount(ctx context.Context) int64 {
answerCount, err := ds.answerRepo.GetAnswerCount(ctx)
if err != nil {
- return dashboardInfo, err
+ log.Errorf("get answer count failed: %s", err)
}
+ return answerCount
+}
+
+func (ds *dashboardService) commentCount(ctx context.Context) int64 {
commentCount, err := ds.commentRepo.GetCommentCount(ctx)
if err != nil {
- return dashboardInfo, err
+ log.Errorf("get comment count failed: %s", err)
}
+ return commentCount
+}
+func (ds *dashboardService) userCount(ctx context.Context) int64 {
+ userCount, err := ds.userRepo.GetUserCount(ctx)
+ if err != nil {
+ log.Errorf("get user count failed: %s", err)
+ }
+ return userCount
+}
+
+func (ds *dashboardService) reportCount(ctx context.Context) int64 {
+ reportCount, err := ds.reportRepo.GetReportCount(ctx)
+ if err != nil {
+ log.Errorf("get report count failed: %s", err)
+ }
+ return reportCount
+}
+
+// count vote
+func (ds *dashboardService) voteCount(ctx context.Context) int64 {
typeKeys := []string{
"question.vote_up",
"question.vote_down",
@@ -129,7 +164,6 @@ func (ds *DashboardService) Statistical(ctx context.Context) (*schema.DashboardI
"answer.vote_down",
}
var activityTypes []int
-
for _, typeKey := range typeKeys {
cfg, err := ds.configService.GetConfigByKey(ctx, typeKey)
if err != nil {
@@ -137,69 +171,14 @@ func (ds *DashboardService) Statistical(ctx context.Context) (*schema.DashboardI
}
activityTypes = append(activityTypes, cfg.ID)
}
-
voteCount, err := ds.voteRepo.GetVoteCount(ctx, activityTypes)
if err != nil {
- return dashboardInfo, err
+ log.Errorf("get vote count failed: %s", err)
}
- userCount, err := ds.userRepo.GetUserCount(ctx)
- if err != nil {
- return dashboardInfo, err
- }
-
- reportCount, err := ds.reportRepo.GetReportCount(ctx)
- if err != nil {
- return dashboardInfo, err
- }
-
- siteInfoInterface, err := ds.siteInfoService.GetSiteInterface(ctx)
- if err != nil {
- return dashboardInfo, err
- }
-
- dashboardInfo.QuestionCount = questionCount
- dashboardInfo.AnswerCount = answerCount
- dashboardInfo.CommentCount = commentCount
- dashboardInfo.VoteCount = voteCount
- dashboardInfo.UserCount = userCount
- dashboardInfo.ReportCount = reportCount
-
- dashboardInfo.UploadingFiles = true
- emailconfig, err := ds.GetEmailConfig(ctx)
- if err != nil {
- return dashboardInfo, err
- }
- if emailconfig.SMTPHost != "" {
- dashboardInfo.SMTP = true
- }
- siteGeneral, err := ds.siteInfoService.GetSiteGeneral(ctx)
- if err != nil {
- return dashboardInfo, err
- }
- siteUrl, err := url.Parse(siteGeneral.SiteUrl)
- if err != nil {
- return dashboardInfo, err
- }
- if siteUrl.Scheme == "https" {
- dashboardInfo.HTTPS = true
- }
-
- dirSize, err := dir.DirSize(ds.serviceConfig.UploadPath)
- if err != nil {
- return dashboardInfo, err
- }
- size := dir.FormatFileSize(dirSize)
- dashboardInfo.OccupyingStorageSpace = size
- startTime := time.Now().Unix() - schema.AppStartTime.Unix()
- dashboardInfo.AppStartTime = fmt.Sprintf("%d", startTime)
- dashboardInfo.TimeZone = siteInfoInterface.TimeZone
- dashboardInfo.VersionInfo.Version = constant.Version
- dashboardInfo.VersionInfo.Revision = constant.Revision
- dashboardInfo.VersionInfo.RemoteVersion = ds.RemoteVersion(ctx)
- return dashboardInfo, nil
+ return voteCount
}
-func (ds *DashboardService) RemoteVersion(ctx context.Context) string {
+func (ds *dashboardService) remoteVersion(ctx context.Context) string {
url := "https://answer.dev/getlatest"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("User-Agent", "Answer/"+constant.Version)
@@ -224,15 +203,48 @@ func (ds *DashboardService) RemoteVersion(ctx context.Context) string {
return remoteVersion.Release.Version
}
-func (ds *DashboardService) GetEmailConfig(ctx context.Context) (ec *export.EmailConfig, err error) {
+func (ds *dashboardService) smtpStatus(ctx context.Context) (enabled bool) {
emailConf, err := ds.configService.GetStringValue(ctx, "email.config")
if err != nil {
- return nil, err
+ log.Errorf("get email config failed: %s", err)
+ return false
}
- ec = &export.EmailConfig{}
+ ec := &export.EmailConfig{}
err = json.Unmarshal([]byte(emailConf), ec)
if err != nil {
- return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ log.Errorf("parsing email config failed: %s", err)
+ return false
}
- return ec, nil
+ return ec.SMTPHost != ""
+}
+
+func (ds *dashboardService) httpsStatus(ctx context.Context) (enabled bool) {
+ siteGeneral, err := ds.siteInfoService.GetSiteGeneral(ctx)
+ if err != nil {
+ log.Errorf("get site general failed: %s", err)
+ return false
+ }
+ siteUrl, err := url.Parse(siteGeneral.SiteUrl)
+ if err != nil {
+ log.Errorf("parse site url failed: %s", err)
+ return false
+ }
+ return siteUrl.Scheme == "https"
+}
+
+func (ds *dashboardService) getTimezone(ctx context.Context) string {
+ siteInfoInterface, err := ds.siteInfoService.GetSiteInterface(ctx)
+ if err != nil {
+ return ""
+ }
+ return siteInfoInterface.TimeZone
+}
+
+func (ds *dashboardService) calculateStorage() string {
+ dirSize, err := dir.DirSize(ds.serviceConfig.UploadPath)
+ if err != nil {
+ log.Errorf("get upload dir size failed: %s", err)
+ return ""
+ }
+ return dir.FormatFileSize(dirSize)
}
diff --git a/internal/service/export/email_service.go b/internal/service/export/email_service.go
index 2812aa66..c886faee 100644
--- a/internal/service/export/email_service.go
+++ b/internal/service/export/email_service.go
@@ -106,6 +106,10 @@ func (es *EmailService) Send(ctx context.Context, toEmailAddr, subject, body str
log.Errorf("get email config failed: %s", err)
return
}
+ if len(ec.SMTPHost) == 0 {
+ log.Warnf("smtp host is empty, skip send email")
+ return
+ }
m := gomail.NewMessage()
fromName := mime.QEncoding.Encode("utf-8", ec.FromName)
diff --git a/internal/service/notice_queue/notice_queue.go b/internal/service/notice_queue/notice_queue.go
index 2281a7ee..78f6fa50 100644
--- a/internal/service/notice_queue/notice_queue.go
+++ b/internal/service/notice_queue/notice_queue.go
@@ -1,13 +1,50 @@
package notice_queue
import (
+ "context"
+
"github.com/answerdev/answer/internal/schema"
+ "github.com/segmentfault/pacman/log"
)
-var (
- NotificationQueue = make(chan *schema.NotificationMsg, 128)
-)
-
-func AddNotification(msg *schema.NotificationMsg) {
- NotificationQueue <- msg
+type NotificationQueueService interface {
+ Send(ctx context.Context, msg *schema.NotificationMsg)
+ RegisterHandler(handler func(ctx context.Context, msg *schema.NotificationMsg) error)
+}
+
+type notificationQueueService struct {
+ Queue chan *schema.NotificationMsg
+ Handler func(ctx context.Context, msg *schema.NotificationMsg) error
+}
+
+func (ns *notificationQueueService) Send(ctx context.Context, msg *schema.NotificationMsg) {
+ ns.Queue <- msg
+}
+
+func (ns *notificationQueueService) RegisterHandler(
+ handler func(ctx context.Context, msg *schema.NotificationMsg) error) {
+ ns.Handler = handler
+}
+
+func (ns *notificationQueueService) working() {
+ go func() {
+ for msg := range ns.Queue {
+ log.Debugf("received notification %+v", msg)
+ if ns.Handler == nil {
+ log.Warnf("no handler for notification")
+ continue
+ }
+ if err := ns.Handler(context.Background(), msg); err != nil {
+ log.Error(err)
+ }
+ }
+ }()
+}
+
+// NewNotificationQueueService create a new notification queue service
+func NewNotificationQueueService() NotificationQueueService {
+ ns := ¬ificationQueueService{}
+ ns.Queue = make(chan *schema.NotificationMsg, 128)
+ ns.working()
+ return ns
}
diff --git a/internal/service/notification/notification_service.go b/internal/service/notification/notification_service.go
index 6e7d09d6..25d8444e 100644
--- a/internal/service/notification/notification_service.go
+++ b/internal/service/notification/notification_service.go
@@ -146,6 +146,7 @@ func (ns *NotificationService) GetNotificationPage(ctx context.Context, searchCo
func (ns *NotificationService) formatNotificationPage(ctx context.Context, notifications []*entity.Notification) (
resp []*schema.NotificationContent, err error) {
lang := handler.GetLangByCtx(ctx)
+ enableShortID := handler.GetEnableShortID(ctx)
for _, notificationInfo := range notifications {
item := &schema.NotificationContent{}
if err := json.Unmarshal([]byte(notificationInfo.Content), item); err != nil {
@@ -163,17 +164,19 @@ func (ns *NotificationService) formatNotificationPage(ctx context.Context, notif
item.UpdateTime = notificationInfo.UpdatedAt.Unix()
item.IsRead = notificationInfo.IsRead == schema.NotificationRead
- if answerID, ok := item.ObjectInfo.ObjectMap["answer"]; ok {
- if item.ObjectInfo.ObjectID == answerID {
- item.ObjectInfo.ObjectID = uid.EnShortID(item.ObjectInfo.ObjectMap["answer"])
+ if enableShortID {
+ if answerID, ok := item.ObjectInfo.ObjectMap["answer"]; ok {
+ if item.ObjectInfo.ObjectID == answerID {
+ item.ObjectInfo.ObjectID = uid.EnShortID(item.ObjectInfo.ObjectMap["answer"])
+ }
+ item.ObjectInfo.ObjectMap["answer"] = uid.EnShortID(item.ObjectInfo.ObjectMap["answer"])
}
- item.ObjectInfo.ObjectMap["answer"] = uid.EnShortID(item.ObjectInfo.ObjectMap["answer"])
- }
- if questionID, ok := item.ObjectInfo.ObjectMap["question"]; ok {
- if item.ObjectInfo.ObjectID == questionID {
- item.ObjectInfo.ObjectID = uid.EnShortID(item.ObjectInfo.ObjectMap["question"])
+ if questionID, ok := item.ObjectInfo.ObjectMap["question"]; ok {
+ if item.ObjectInfo.ObjectID == questionID {
+ item.ObjectInfo.ObjectID = uid.EnShortID(item.ObjectInfo.ObjectMap["question"])
+ }
+ item.ObjectInfo.ObjectMap["question"] = uid.EnShortID(item.ObjectInfo.ObjectMap["question"])
}
- item.ObjectInfo.ObjectMap["question"] = uid.EnShortID(item.ObjectInfo.ObjectMap["question"])
}
resp = append(resp, item)
diff --git a/internal/service/notification_common/notification.go b/internal/service/notification_common/notification.go
index 2bf0cf8c..ed7001a9 100644
--- a/internal/service/notification_common/notification.go
+++ b/internal/service/notification_common/notification.go
@@ -33,12 +33,13 @@ type NotificationRepo interface {
}
type NotificationCommon struct {
- data *data.Data
- notificationRepo NotificationRepo
- activityRepo activity_common.ActivityRepo
- followRepo activity_common.FollowRepo
- userCommon *usercommon.UserCommon
- objectInfoService *object_info.ObjService
+ data *data.Data
+ notificationRepo NotificationRepo
+ activityRepo activity_common.ActivityRepo
+ followRepo activity_common.FollowRepo
+ userCommon *usercommon.UserCommon
+ objectInfoService *object_info.ObjService
+ notificationQueueService notice_queue.NotificationQueueService
}
func NewNotificationCommon(
@@ -48,31 +49,21 @@ func NewNotificationCommon(
activityRepo activity_common.ActivityRepo,
followRepo activity_common.FollowRepo,
objectInfoService *object_info.ObjService,
+ notificationQueueService notice_queue.NotificationQueueService,
) *NotificationCommon {
notification := &NotificationCommon{
- data: data,
- notificationRepo: notificationRepo,
- activityRepo: activityRepo,
- followRepo: followRepo,
- userCommon: userCommon,
- objectInfoService: objectInfoService,
+ data: data,
+ notificationRepo: notificationRepo,
+ activityRepo: activityRepo,
+ followRepo: followRepo,
+ userCommon: userCommon,
+ objectInfoService: objectInfoService,
+ notificationQueueService: notificationQueueService,
}
- notification.HandleNotification()
+ notificationQueueService.RegisterHandler(notification.AddNotification)
return notification
}
-func (ns *NotificationCommon) HandleNotification() {
- go func() {
- for msg := range notice_queue.NotificationQueue {
- log.Debugf("received notification %+v", msg)
- err := ns.AddNotification(context.TODO(), msg)
- if err != nil {
- log.Error(err)
- }
- }
- }()
-}
-
// AddNotification
// need set
// LoginUserID
@@ -172,7 +163,7 @@ func (ns *NotificationCommon) AddNotification(ctx context.Context, msg *schema.N
log.Error("addRedDot Error", err.Error())
}
- go ns.SendNotificationToAllFollower(context.Background(), msg, questionID)
+ go ns.SendNotificationToAllFollower(ctx, msg, questionID)
return nil
}
@@ -213,6 +204,6 @@ func (ns *NotificationCommon) SendNotificationToAllFollower(ctx context.Context,
t.ReceiverUserID = userID
t.TriggerUserID = msg.TriggerUserID
t.NoNeedPushAllFollow = true
- notice_queue.AddNotification(t)
+ ns.notificationQueueService.Send(ctx, t)
}
}
diff --git a/internal/service/object_info/object_info.go b/internal/service/object_info/object_info.go
index feda14f9..09e59882 100644
--- a/internal/service/object_info/object_info.go
+++ b/internal/service/object_info/object_info.go
@@ -4,6 +4,7 @@ import (
"context"
"github.com/answerdev/answer/internal/base/constant"
+ "github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/schema"
answercommon "github.com/answerdev/answer/internal/service/answer_common"
@@ -51,7 +52,9 @@ func (os *ObjService) GetUnreviewedRevisionInfo(ctx context.Context, objectID st
if err != nil {
return nil, err
}
- questionInfo.ID = uid.EnShortID(questionInfo.ID)
+ if handler.GetEnableShortID(ctx) {
+ questionInfo.ID = uid.EnShortID(questionInfo.ID)
+ }
if !exist {
break
}
@@ -87,7 +90,9 @@ func (os *ObjService) GetUnreviewedRevisionInfo(ctx context.Context, objectID st
if !exist {
break
}
- questionInfo.ID = uid.EnShortID(questionInfo.ID)
+ if handler.GetEnableShortID(ctx) {
+ questionInfo.ID = uid.EnShortID(questionInfo.ID)
+ }
objInfo = &schema.UnreviewedRevisionInfoInfo{
ObjectID: answerInfo.ID,
Title: questionInfo.Title,
diff --git a/internal/service/provider.go b/internal/service/provider.go
index 69c30047..53990b91 100644
--- a/internal/service/provider.go
+++ b/internal/service/provider.go
@@ -4,6 +4,7 @@ import (
"github.com/answerdev/answer/internal/service/action"
"github.com/answerdev/answer/internal/service/activity"
"github.com/answerdev/answer/internal/service/activity_common"
+ "github.com/answerdev/answer/internal/service/activity_queue"
answercommon "github.com/answerdev/answer/internal/service/answer_common"
"github.com/answerdev/answer/internal/service/auth"
collectioncommon "github.com/answerdev/answer/internal/service/collection_common"
@@ -14,6 +15,7 @@ import (
"github.com/answerdev/answer/internal/service/export"
"github.com/answerdev/answer/internal/service/follow"
"github.com/answerdev/answer/internal/service/meta"
+ "github.com/answerdev/answer/internal/service/notice_queue"
"github.com/answerdev/answer/internal/service/notification"
notficationcommon "github.com/answerdev/answer/internal/service/notification_common"
"github.com/answerdev/answer/internal/service/object_info"
@@ -86,4 +88,6 @@ var ProviderSetService = wire.NewSet(
user_external_login.NewUserCenterLoginService,
plugin_common.NewPluginCommonService,
config.NewConfigService,
+ notice_queue.NewNotificationQueueService,
+ activity_queue.NewActivityQueueService,
)
diff --git a/internal/service/question_common/question.go b/internal/service/question_common/question.go
index b0ccc4cc..dee28b5b 100644
--- a/internal/service/question_common/question.go
+++ b/internal/service/question_common/question.go
@@ -3,7 +3,6 @@ package questioncommon
import (
"context"
"encoding/json"
- "fmt"
"math"
"time"
@@ -48,26 +47,27 @@ 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)
- AdminSearchList(ctx context.Context, search *schema.AdminQuestionSearch) ([]*entity.Question, int64, error)
+ AdminQuestionPage(ctx context.Context, search *schema.AdminQuestionPageReq) ([]*entity.Question, int64, error)
GetQuestionCount(ctx context.Context) (count int64, err error)
GetUserQuestionCount(ctx context.Context, userID string) (count int64, err error)
GetQuestionCountByIDs(ctx context.Context, ids []string) (count int64, err error)
- GetQuestionIDsPage(ctx context.Context, page, pageSize int) (questionIDList []*schema.SiteMapQuestionInfo, err error)
+ SitemapQuestions(ctx context.Context, page, pageSize int) (questionIDList []*schema.SiteMapQuestionInfo, err error)
}
// QuestionCommon user service
type QuestionCommon struct {
- questionRepo QuestionRepo
- answerRepo answercommon.AnswerRepo
- voteRepo activity_common.VoteRepo
- followCommon activity_common.FollowRepo
- tagCommon *tagcommon.TagCommonService
- userCommon *usercommon.UserCommon
- collectionCommon *collectioncommon.CollectionCommon
- AnswerCommon *answercommon.AnswerCommon
- metaService *meta.MetaService
- configService *config.ConfigService
- data *data.Data
+ questionRepo QuestionRepo
+ answerRepo answercommon.AnswerRepo
+ voteRepo activity_common.VoteRepo
+ followCommon activity_common.FollowRepo
+ tagCommon *tagcommon.TagCommonService
+ userCommon *usercommon.UserCommon
+ collectionCommon *collectioncommon.CollectionCommon
+ AnswerCommon *answercommon.AnswerCommon
+ metaService *meta.MetaService
+ configService *config.ConfigService
+ activityQueueService activity_queue.ActivityQueueService
+ data *data.Data
}
func NewQuestionCommon(questionRepo QuestionRepo,
@@ -80,21 +80,22 @@ func NewQuestionCommon(questionRepo QuestionRepo,
answerCommon *answercommon.AnswerCommon,
metaService *meta.MetaService,
configService *config.ConfigService,
+ activityQueueService activity_queue.ActivityQueueService,
data *data.Data,
-
) *QuestionCommon {
return &QuestionCommon{
- questionRepo: questionRepo,
- answerRepo: answerRepo,
- voteRepo: voteRepo,
- followCommon: followCommon,
- tagCommon: tagCommon,
- userCommon: userCommon,
- collectionCommon: collectionCommon,
- AnswerCommon: answerCommon,
- metaService: metaService,
- configService: configService,
- data: data,
+ questionRepo: questionRepo,
+ answerRepo: answerRepo,
+ voteRepo: voteRepo,
+ followCommon: followCommon,
+ tagCommon: tagCommon,
+ userCommon: userCommon,
+ collectionCommon: collectionCommon,
+ AnswerCommon: answerCommon,
+ metaService: metaService,
+ configService: configService,
+ activityQueueService: activityQueueService,
+ data: data,
}
}
@@ -513,7 +514,7 @@ func (qs *QuestionCommon) CloseQuestion(ctx context.Context, req *schema.CloseQu
return err
}
- activity_queue.AddActivity(&schema.ActivityMsg{
+ qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: questionInfo.UserID,
ObjectID: questionInfo.ID,
OriginalObjectID: questionInfo.ID,
@@ -551,40 +552,26 @@ func (as *QuestionCommon) RemoveAnswer(ctx context.Context, id string) (err erro
}
func (qs *QuestionCommon) SitemapCron(ctx context.Context) {
- data := &schema.SiteMapList{}
questionNum, err := qs.questionRepo.GetQuestionCount(ctx)
if err != nil {
- log.Error("GetQuestionCount error", err)
+ log.Error(err)
return
}
- if questionNum <= schema.SitemapMaxSize {
- questionIDList, err := qs.questionRepo.GetQuestionIDsPage(ctx, 0, int(questionNum))
+ if questionNum <= constant.SitemapMaxSize {
+ _, err = qs.questionRepo.SitemapQuestions(ctx, 1, int(questionNum))
if err != nil {
- log.Error("GetQuestionIDsPage error", err)
+ log.Errorf("get site map question error: %v", err)
+ }
+ return
+ }
+
+ totalPages := int(math.Ceil(float64(questionNum) / float64(constant.SitemapMaxSize)))
+ for i := 1; i <= totalPages; i++ {
+ _, err = qs.questionRepo.SitemapQuestions(ctx, i, constant.SitemapMaxSize)
+ if err != nil {
+ log.Errorf("get site map question error: %v", err)
return
}
- data.QuestionIDs = questionIDList
-
- } else {
- nums := make([]int, 0)
- totalpages := int(math.Ceil(float64(questionNum) / float64(schema.SitemapMaxSize)))
- for i := 1; i <= totalpages; i++ {
- siteMapPagedata := &schema.SiteMapPageList{}
- nums = append(nums, i)
- questionIDList, err := qs.questionRepo.GetQuestionIDsPage(ctx, i, int(schema.SitemapMaxSize))
- if err != nil {
- log.Error("GetQuestionIDsPage error", err)
- return
- }
- siteMapPagedata.PageData = questionIDList
- if setCacheErr := qs.SetCache(ctx, fmt.Sprintf(schema.SitemapPageCachekey, i), siteMapPagedata); setCacheErr != nil {
- log.Errorf("set sitemap cron SetCache failed: %s", setCacheErr)
- }
- }
- data.MaxPageNum = nums
- }
- if setCacheErr := qs.SetCache(ctx, schema.SitemapCachekey, data); setCacheErr != nil {
- log.Errorf("set sitemap cron SetCache failed: %s", setCacheErr)
}
}
@@ -594,7 +581,7 @@ func (qs *QuestionCommon) SetCache(ctx context.Context, cachekey string, info in
return errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
}
- err = qs.data.Cache.SetString(ctx, cachekey, string(infoStr), schema.DashBoardCacheTime)
+ err = qs.data.Cache.SetString(ctx, cachekey, string(infoStr), schema.DashboardCacheTime)
if err != nil {
return errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
}
diff --git a/internal/service/question_service.go b/internal/service/question_service.go
index 378b27df..f8f939b7 100644
--- a/internal/service/question_service.go
+++ b/internal/service/question_service.go
@@ -3,11 +3,11 @@ package service
import (
"encoding/json"
"fmt"
+ "github.com/answerdev/answer/internal/service/siteinfo_common"
"strings"
"time"
"github.com/answerdev/answer/internal/base/constant"
- "github.com/answerdev/answer/internal/base/data"
"github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/pager"
"github.com/answerdev/answer/internal/base/reason"
@@ -41,17 +41,19 @@ import (
// QuestionService user service
type QuestionService struct {
- questionRepo questioncommon.QuestionRepo
- tagCommon *tagcommon.TagCommonService
- questioncommon *questioncommon.QuestionCommon
- userCommon *usercommon.UserCommon
- userRepo usercommon.UserRepo
- revisionService *revision_common.RevisionService
- metaService *meta.MetaService
- collectionCommon *collectioncommon.CollectionCommon
- answerActivityService *activity.AnswerActivityService
- data *data.Data
- emailService *export.EmailService
+ questionRepo questioncommon.QuestionRepo
+ tagCommon *tagcommon.TagCommonService
+ questioncommon *questioncommon.QuestionCommon
+ userCommon *usercommon.UserCommon
+ userRepo usercommon.UserRepo
+ revisionService *revision_common.RevisionService
+ metaService *meta.MetaService
+ collectionCommon *collectioncommon.CollectionCommon
+ answerActivityService *activity.AnswerActivityService
+ emailService *export.EmailService
+ notificationQueueService notice_queue.NotificationQueueService
+ activityQueueService activity_queue.ActivityQueueService
+ siteInfoService siteinfo_common.SiteInfoCommonService
}
func NewQuestionService(
@@ -64,21 +66,25 @@ func NewQuestionService(
metaService *meta.MetaService,
collectionCommon *collectioncommon.CollectionCommon,
answerActivityService *activity.AnswerActivityService,
- data *data.Data,
emailService *export.EmailService,
+ notificationQueueService notice_queue.NotificationQueueService,
+ activityQueueService activity_queue.ActivityQueueService,
+ siteInfoService siteinfo_common.SiteInfoCommonService,
) *QuestionService {
return &QuestionService{
- questionRepo: questionRepo,
- tagCommon: tagCommon,
- questioncommon: questioncommon,
- userCommon: userCommon,
- userRepo: userRepo,
- revisionService: revisionService,
- metaService: metaService,
- collectionCommon: collectionCommon,
- answerActivityService: answerActivityService,
- data: data,
- emailService: emailService,
+ questionRepo: questionRepo,
+ tagCommon: tagCommon,
+ questioncommon: questioncommon,
+ userCommon: userCommon,
+ userRepo: userRepo,
+ revisionService: revisionService,
+ metaService: metaService,
+ collectionCommon: collectionCommon,
+ answerActivityService: answerActivityService,
+ emailService: emailService,
+ notificationQueueService: notificationQueueService,
+ activityQueueService: activityQueueService,
+ siteInfoService: siteInfoService,
}
}
@@ -106,7 +112,7 @@ func (qs *QuestionService) CloseQuestion(ctx context.Context, req *schema.CloseQ
return err
}
- activity_queue.AddActivity(&schema.ActivityMsg{
+ qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: req.UserID,
ObjectID: questionInfo.ID,
OriginalObjectID: questionInfo.ID,
@@ -130,7 +136,7 @@ func (qs *QuestionService) ReopenQuestion(ctx context.Context, req *schema.Reope
if err != nil {
return err
}
- activity_queue.AddActivity(&schema.ActivityMsg{
+ qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: req.UserID,
ObjectID: questionInfo.ID,
OriginalObjectID: questionInfo.ID,
@@ -312,7 +318,7 @@ func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.Question
}
}
- activity_queue.AddActivity(&schema.ActivityMsg{
+ qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: question.UserID,
ObjectID: question.ID,
OriginalObjectID: question.ID,
@@ -381,7 +387,7 @@ func (qs *QuestionService) OperationQuestion(ctx context.Context, req *schema.Op
actMap[schema.QuestionOperationShow] = constant.ActQuestionShow
_, ok := actMap[req.Operation]
if ok {
- activity_queue.AddActivity(&schema.ActivityMsg{
+ qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: req.UserID,
ObjectID: questionInfo.ID,
OriginalObjectID: questionInfo.ID,
@@ -473,7 +479,7 @@ func (qs *QuestionService) RemoveQuestion(ctx context.Context, req *schema.Remov
// if err != nil {
// log.Errorf("user DeleteQuestion rank rollback error %s", err.Error())
// }
- activity_queue.AddActivity(&schema.ActivityMsg{
+ qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: req.UserID,
ObjectID: questionInfo.ID,
OriginalObjectID: questionInfo.ID,
@@ -632,7 +638,7 @@ func (qs *QuestionService) notificationInviteUser(
}
msg.ObjectType = constant.QuestionObjectType
msg.NotificationAction = constant.NotificationInvitedYouToAnswer
- notice_queue.AddNotification(msg)
+ qs.notificationQueueService.Send(ctx, msg)
userInfo, ok := invitee[userID]
if !ok {
@@ -822,7 +828,7 @@ func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.Quest
return
}
if canUpdate {
- activity_queue.AddActivity(&schema.ActivityMsg{
+ qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: req.UserID,
ObjectID: question.ID,
ActivityTypeKey: constant.ActQuestionEdited,
@@ -1017,16 +1023,19 @@ func (qs *QuestionService) PersonalCollectionPage(ctx context.Context, req *sche
return nil, err
}
for _, id := range questionIDs {
- _, ok := questionMaps[uid.EnShortID(id)]
+ if handler.GetEnableShortID(ctx) {
+ id = uid.EnShortID(id)
+ }
+ _, ok := questionMaps[id]
if ok {
- questionMaps[uid.EnShortID(id)].LastAnsweredUserInfo = nil
- questionMaps[uid.EnShortID(id)].UpdateUserInfo = nil
- questionMaps[uid.EnShortID(id)].Content = ""
- questionMaps[uid.EnShortID(id)].HTML = ""
- if questionMaps[uid.EnShortID(id)].Status == entity.QuestionStatusDeleted {
- questionMaps[uid.EnShortID(id)].Title = "Deleted question"
+ questionMaps[id].LastAnsweredUserInfo = nil
+ questionMaps[id].UpdateUserInfo = nil
+ questionMaps[id].Content = ""
+ questionMaps[id].HTML = ""
+ if questionMaps[id].Status == entity.QuestionStatusDeleted {
+ questionMaps[id].Title = "Deleted question"
}
- list = append(list, questionMaps[uid.EnShortID(id)])
+ list = append(list, questionMaps[id])
}
}
@@ -1213,7 +1222,7 @@ func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, questionI
//if err != nil {
// log.Errorf("admin delete question then rank rollback error %s", err.Error())
//}
- activity_queue.AddActivity(&schema.ActivityMsg{
+ qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: questionInfo.UserID,
ObjectID: questionInfo.ID,
OriginalObjectID: questionInfo.ID,
@@ -1221,7 +1230,7 @@ func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, questionI
})
}
if setStatus == entity.QuestionStatusAvailable && questionInfo.Status == entity.QuestionStatusClosed {
- activity_queue.AddActivity(&schema.ActivityMsg{
+ qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: questionInfo.UserID,
ObjectID: questionInfo.ID,
OriginalObjectID: questionInfo.ID,
@@ -1229,7 +1238,7 @@ func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, questionI
})
}
if setStatus == entity.QuestionStatusClosed && questionInfo.Status != entity.QuestionStatusClosed {
- activity_queue.AddActivity(&schema.ActivityMsg{
+ qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: questionInfo.UserID,
ObjectID: questionInfo.ID,
OriginalObjectID: questionInfo.ID,
@@ -1243,93 +1252,77 @@ func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, questionI
msg.TriggerUserID = questionInfo.UserID
msg.ObjectType = constant.QuestionObjectType
msg.NotificationAction = constant.NotificationYourQuestionWasDeleted
- notice_queue.AddNotification(msg)
+ qs.notificationQueueService.Send(ctx, msg)
return nil
}
-func (qs *QuestionService) AdminSearchList(ctx context.Context, search *schema.AdminQuestionSearch, loginUserID string) ([]*schema.AdminQuestionInfo, int64, error) {
+func (qs *QuestionService) AdminQuestionPage(
+ ctx context.Context, req *schema.AdminQuestionPageReq) (
+ resp *pager.PageModel, err error) {
+
list := make([]*schema.AdminQuestionInfo, 0)
-
- status, ok := entity.AdminQuestionSearchStatus[search.StatusStr]
- if ok {
- search.Status = status
- }
-
- if search.Status == 0 {
- search.Status = 1
- }
- dblist, count, err := qs.questionRepo.AdminSearchList(ctx, search)
+ questionList, count, err := qs.questionRepo.AdminQuestionPage(ctx, req)
if err != nil {
- return list, count, err
+ return nil, err
}
+
userIds := make([]string, 0)
- for _, dbitem := range dblist {
+ for _, info := range questionList {
item := &schema.AdminQuestionInfo{}
- _ = copier.Copy(item, dbitem)
- item.CreateTime = dbitem.CreatedAt.Unix()
- item.UpdateTime = dbitem.PostUpdateTime.Unix()
- item.EditTime = dbitem.UpdatedAt.Unix()
+ _ = copier.Copy(item, info)
+ item.CreateTime = info.CreatedAt.Unix()
+ item.UpdateTime = info.PostUpdateTime.Unix()
+ item.EditTime = info.UpdatedAt.Unix()
list = append(list, item)
- userIds = append(userIds, dbitem.UserID)
+ userIds = append(userIds, info.UserID)
}
userInfoMap, err := qs.userCommon.BatchUserBasicInfoByID(ctx, userIds)
if err != nil {
- return list, count, err
+ return nil, err
}
for _, item := range list {
- _, ok = userInfoMap[item.UserID]
- if ok {
- item.UserInfo = userInfoMap[item.UserID]
+ if u, ok := userInfoMap[item.UserID]; ok {
+ item.UserInfo = u
}
}
-
- return list, count, nil
+ return pager.NewPageModel(count, list), nil
}
-// 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.AdminAnswerSearchStatus[search.StatusStr]
- if ok {
- search.Status = status
- }
-
- if search.Status == 0 {
- search.Status = 1
- }
- dblist, count, err := qs.questioncommon.AnswerCommon.AdminSearchList(ctx, search)
+// AdminAnswerPage search answer list
+func (qs *QuestionService) AdminAnswerPage(ctx context.Context, req *schema.AdminAnswerPageReq) (
+ resp *pager.PageModel, err error) {
+ answerList, count, err := qs.questioncommon.AnswerCommon.AdminSearchList(ctx, req)
if err != nil {
- return answerlist, count, err
+ return nil, err
}
+
questionIDs := make([]string, 0)
userIds := make([]string, 0)
- for _, item := range dblist {
- answerinfo := qs.questioncommon.AnswerCommon.AdminShowFormat(ctx, item)
- answerlist = append(answerlist, answerinfo)
+ answerResp := make([]*schema.AdminAnswerInfo, 0)
+ for _, item := range answerList {
+ answerInfo := qs.questioncommon.AnswerCommon.AdminShowFormat(ctx, item)
+ answerResp = append(answerResp, answerInfo)
questionIDs = append(questionIDs, item.QuestionID)
userIds = append(userIds, item.UserID)
}
userInfoMap, err := qs.userCommon.BatchUserBasicInfoByID(ctx, userIds)
if err != nil {
- return answerlist, count, err
+ return nil, err
+ }
+ questionMaps, err := qs.questioncommon.FindInfoByID(ctx, questionIDs, req.LoginUserID)
+ if err != nil {
+ return nil, err
}
- questionMaps, err := qs.questioncommon.FindInfoByID(ctx, questionIDs, loginUserID)
- if err != nil {
- return answerlist, count, err
- }
- for _, item := range answerlist {
- _, ok := questionMaps[item.QuestionID]
- if ok {
- item.QuestionInfo.Title = questionMaps[item.QuestionID].Title
+ for _, item := range answerResp {
+ if q, ok := questionMaps[item.QuestionID]; ok {
+ item.QuestionInfo.Title = q.Title
}
- _, ok = userInfoMap[item.UserID]
- if ok {
- item.UserInfo = userInfoMap[item.UserID]
+ if u, ok := userInfoMap[item.UserID]; ok {
+ item.UserInfo = u
}
}
- return answerlist, count, nil
+ return pager.NewPageModel(count, answerResp), nil
}
func (qs *QuestionService) changeQuestionToRevision(ctx context.Context, questionInfo *entity.Question, tags []*entity.Tag) (
@@ -1346,5 +1339,11 @@ func (qs *QuestionService) changeQuestionToRevision(ctx context.Context, questio
}
func (qs *QuestionService) SitemapCron(ctx context.Context) {
+ siteSeo, err := qs.siteInfoService.GetSiteSeo(ctx)
+ if err != nil {
+ log.Error(err)
+ return
+ }
+ ctx = context.WithValue(ctx, constant.ShortIDFlag, siteSeo.IsShortLink())
qs.questioncommon.SitemapCron(ctx)
}
diff --git a/internal/service/rank/rank_service.go b/internal/service/rank/rank_service.go
index 307bc591..2bf222a4 100644
--- a/internal/service/rank/rank_service.go
+++ b/internal/service/rank/rank_service.go
@@ -29,6 +29,10 @@ const (
)
type UserRankRepo interface {
+ GetMaxDailyRank(ctx context.Context) (maxDailyRank int, err error)
+ CheckReachLimit(ctx context.Context, session *xorm.Session, userID string, maxDailyRank int) (reach bool, err error)
+ ChangeUserRank(ctx context.Context, session *xorm.Session,
+ userID string, userCurrentScore, deltaRank int) (err error)
TriggerUserRank(ctx context.Context, session *xorm.Session, userId string, rank int, activityType int) (isReachStandard bool, err error)
UserRankPage(ctx context.Context, userId string, page, pageSize int) (rankPage []*entity.Activity, total int64, err error)
}
diff --git a/internal/service/report_handle_admin/report_handle.go b/internal/service/report_handle_admin/report_handle.go
index 65601aaa..bafe8729 100644
--- a/internal/service/report_handle_admin/report_handle.go
+++ b/internal/service/report_handle_admin/report_handle.go
@@ -4,30 +4,34 @@ import (
"context"
"github.com/answerdev/answer/internal/service/config"
+ "github.com/answerdev/answer/internal/service/notice_queue"
"github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service/comment"
- "github.com/answerdev/answer/internal/service/notice_queue"
questioncommon "github.com/answerdev/answer/internal/service/question_common"
"github.com/answerdev/answer/pkg/obj"
)
type ReportHandle struct {
- questionCommon *questioncommon.QuestionCommon
- commentRepo comment.CommentRepo
- configService *config.ConfigService
+ questionCommon *questioncommon.QuestionCommon
+ commentRepo comment.CommentRepo
+ configService *config.ConfigService
+ notificationQueueService notice_queue.NotificationQueueService
}
func NewReportHandle(
questionCommon *questioncommon.QuestionCommon,
commentRepo comment.CommentRepo,
- configService *config.ConfigService) *ReportHandle {
+ configService *config.ConfigService,
+ notificationQueueService notice_queue.NotificationQueueService,
+) *ReportHandle {
return &ReportHandle{
- questionCommon: questionCommon,
- commentRepo: commentRepo,
- configService: configService,
+ questionCommon: questionCommon,
+ commentRepo: commentRepo,
+ configService: configService,
+ notificationQueueService: notificationQueueService,
}
}
@@ -88,5 +92,5 @@ func (rh *ReportHandle) sendNotification(ctx context.Context, reportedUserID, ob
ObjectType: constant.ReportObjectType,
NotificationAction: notificationAction,
}
- notice_queue.AddNotification(msg)
+ rh.notificationQueueService.Send(ctx, msg)
}
diff --git a/internal/service/revision_service.go b/internal/service/revision_service.go
index 20d3c9ca..b8ea66d2 100644
--- a/internal/service/revision_service.go
+++ b/internal/service/revision_service.go
@@ -28,15 +28,17 @@ import (
// RevisionService user service
type RevisionService struct {
- revisionRepo revision.RevisionRepo
- userCommon *usercommon.UserCommon
- questionCommon *questioncommon.QuestionCommon
- answerService *AnswerService
- objectInfoService *object_info.ObjService
- questionRepo questioncommon.QuestionRepo
- answerRepo answercommon.AnswerRepo
- tagRepo tag_common.TagRepo
- tagCommon *tagcommon.TagCommonService
+ revisionRepo revision.RevisionRepo
+ userCommon *usercommon.UserCommon
+ questionCommon *questioncommon.QuestionCommon
+ answerService *AnswerService
+ objectInfoService *object_info.ObjService
+ questionRepo questioncommon.QuestionRepo
+ answerRepo answercommon.AnswerRepo
+ tagRepo tag_common.TagRepo
+ tagCommon *tagcommon.TagCommonService
+ notificationQueueService notice_queue.NotificationQueueService
+ activityQueueService activity_queue.ActivityQueueService
}
func NewRevisionService(
@@ -49,17 +51,21 @@ func NewRevisionService(
answerRepo answercommon.AnswerRepo,
tagRepo tag_common.TagRepo,
tagCommon *tagcommon.TagCommonService,
+ notificationQueueService notice_queue.NotificationQueueService,
+ activityQueueService activity_queue.ActivityQueueService,
) *RevisionService {
return &RevisionService{
- revisionRepo: revisionRepo,
- userCommon: userCommon,
- questionCommon: questionCommon,
- answerService: answerService,
- objectInfoService: objectInfoService,
- questionRepo: questionRepo,
- answerRepo: answerRepo,
- tagRepo: tagRepo,
- tagCommon: tagCommon,
+ revisionRepo: revisionRepo,
+ userCommon: userCommon,
+ questionCommon: questionCommon,
+ answerService: answerService,
+ objectInfoService: objectInfoService,
+ questionRepo: questionRepo,
+ answerRepo: answerRepo,
+ tagRepo: tagRepo,
+ tagCommon: tagCommon,
+ notificationQueueService: notificationQueueService,
+ activityQueueService: activityQueueService,
}
}
@@ -155,7 +161,7 @@ func (rs *RevisionService) revisionAuditQuestion(ctx context.Context, revisionit
if saveerr != nil {
return saveerr
}
- activity_queue.AddActivity(&schema.ActivityMsg{
+ rs.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: revisionitem.UserID,
ObjectID: revisionitem.ObjectID,
ActivityTypeKey: constant.ActQuestionEdited,
@@ -210,9 +216,9 @@ func (rs *RevisionService) revisionAuditAnswer(ctx context.Context, revisionitem
}
msg.ObjectType = constant.AnswerObjectType
msg.NotificationAction = constant.NotificationUpdateAnswer
- notice_queue.AddNotification(msg)
+ rs.notificationQueueService.Send(ctx, msg)
- activity_queue.AddActivity(&schema.ActivityMsg{
+ rs.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: revisionitem.UserID,
ObjectID: insertData.ID,
OriginalObjectID: insertData.ID,
@@ -258,7 +264,7 @@ func (rs *RevisionService) revisionAuditTag(ctx context.Context, revisionitem *s
}
}
- activity_queue.AddActivity(&schema.ActivityMsg{
+ rs.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: revisionitem.UserID,
ObjectID: taginfo.TagID,
OriginalObjectID: taginfo.TagID,
diff --git a/internal/service/siteinfo/siteinfo_service.go b/internal/service/siteinfo/siteinfo_service.go
index 5d0fc294..70ddadba 100644
--- a/internal/service/siteinfo/siteinfo_service.go
+++ b/internal/service/siteinfo/siteinfo_service.go
@@ -16,7 +16,6 @@ import (
questioncommon "github.com/answerdev/answer/internal/service/question_common"
"github.com/answerdev/answer/internal/service/siteinfo_common"
tagcommon "github.com/answerdev/answer/internal/service/tag_common"
- "github.com/answerdev/answer/pkg/uid"
"github.com/answerdev/answer/plugin"
"github.com/jinzhu/copier"
"github.com/segmentfault/pacman/errors"
@@ -25,7 +24,7 @@ import (
type SiteInfoService struct {
siteInfoRepo siteinfo_common.SiteInfoRepo
- siteInfoCommonService *siteinfo_common.SiteInfoCommonService
+ siteInfoCommonService siteinfo_common.SiteInfoCommonService
emailService *export.EmailService
tagCommonService *tagcommon.TagCommonService
configService *config.ConfigService
@@ -34,7 +33,7 @@ type SiteInfoService struct {
func NewSiteInfoService(
siteInfoRepo siteinfo_common.SiteInfoRepo,
- siteInfoCommonService *siteinfo_common.SiteInfoCommonService,
+ siteInfoCommonService siteinfo_common.SiteInfoCommonService,
emailService *export.EmailService,
tagCommonService *tagcommon.TagCommonService,
configService *config.ConfigService,
@@ -280,28 +279,12 @@ func (s *SiteInfoService) GetSeo(ctx context.Context) (resp *schema.SiteSeoReq,
}
func (s *SiteInfoService) SaveSeo(ctx context.Context, req schema.SiteSeoReq) (err error) {
- var (
- siteType = constant.SiteTypeSeo
- content []byte
- )
- content, _ = json.Marshal(req)
-
+ content, _ := json.Marshal(req)
data := entity.SiteInfo{
- Type: siteType,
+ Type: constant.SiteTypeSeo,
Content: string(content),
}
-
- err = s.siteInfoRepo.SaveByType(ctx, siteType, &data)
- if err != nil {
- return
- }
- if req.PermaLink == schema.PermaLinkQuestionIDAndTitleByShortID || req.PermaLink == schema.PermaLinkQuestionIDByShortID {
- uid.ShortIDSwitch = true
- } else {
- uid.ShortIDSwitch = false
- }
- s.questioncommon.SitemapCron(ctx)
- return
+ return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeSeo, &data)
}
func (s *SiteInfoService) GetPrivilegesConfig(ctx context.Context) (resp *schema.GetPrivilegesConfigResp, err error) {
diff --git a/internal/service/siteinfo_common/siteinfo_service.go b/internal/service/siteinfo_common/siteinfo_service.go
index fff82a8d..cf7a8be8 100644
--- a/internal/service/siteinfo_common/siteinfo_service.go
+++ b/internal/service/siteinfo_common/siteinfo_service.go
@@ -8,7 +8,6 @@ import (
"github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/pkg/gravatar"
- "github.com/answerdev/answer/pkg/uid"
"github.com/segmentfault/pacman/log"
)
@@ -18,31 +17,36 @@ type SiteInfoRepo interface {
GetByType(ctx context.Context, siteType string) (siteInfo *entity.SiteInfo, exist bool, err error)
}
-// SiteInfoCommonService site info common service
-type SiteInfoCommonService struct {
+// siteInfoCommonService site info common service
+type siteInfoCommonService struct {
siteInfoRepo SiteInfoRepo
}
+type SiteInfoCommonService interface {
+ GetSiteGeneral(ctx context.Context) (resp *schema.SiteGeneralResp, err error)
+ GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error)
+ GetSiteBranding(ctx context.Context) (resp *schema.SiteBrandingResp, err error)
+ GetSiteUsers(ctx context.Context) (resp *schema.SiteUsersResp, err error)
+ FormatAvatar(ctx context.Context, originalAvatarData, email string) *schema.AvatarInfo
+ FormatListAvatar(ctx context.Context, userList []*entity.User) (userID2AvatarMapping map[string]*schema.AvatarInfo)
+ GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error)
+ GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error)
+ GetSiteLogin(ctx context.Context) (resp *schema.SiteLoginResp, err error)
+ GetSiteCustomCssHTML(ctx context.Context) (resp *schema.SiteCustomCssHTMLResp, err error)
+ GetSiteTheme(ctx context.Context) (resp *schema.SiteThemeResp, err error)
+ GetSiteSeo(ctx context.Context) (resp *schema.SiteSeoResp, err error)
+ GetSiteInfoByType(ctx context.Context, siteType string, resp interface{}) (err error)
+}
+
// NewSiteInfoCommonService new site info common service
-func NewSiteInfoCommonService(siteInfoRepo SiteInfoRepo) *SiteInfoCommonService {
- siteInfo := &SiteInfoCommonService{
+func NewSiteInfoCommonService(siteInfoRepo SiteInfoRepo) SiteInfoCommonService {
+ return &siteInfoCommonService{
siteInfoRepo: siteInfoRepo,
}
- seoinfo, err := siteInfo.GetSiteSeo(context.Background())
- if err != nil {
- log.Error("seoinfo error", err)
- }
- if seoinfo.PermaLink == schema.PermaLinkQuestionIDAndTitleByShortID || seoinfo.PermaLink == schema.PermaLinkQuestionIDByShortID {
- uid.ShortIDSwitch = true
- } else {
- uid.ShortIDSwitch = false
- }
-
- return siteInfo
}
// GetSiteGeneral get site info general
-func (s *SiteInfoCommonService) GetSiteGeneral(ctx context.Context) (resp *schema.SiteGeneralResp, err error) {
+func (s *siteInfoCommonService) GetSiteGeneral(ctx context.Context) (resp *schema.SiteGeneralResp, err error) {
resp = &schema.SiteGeneralResp{}
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeGeneral, resp); err != nil {
return nil, err
@@ -51,7 +55,7 @@ func (s *SiteInfoCommonService) GetSiteGeneral(ctx context.Context) (resp *schem
}
// GetSiteInterface get site info interface
-func (s *SiteInfoCommonService) GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error) {
+func (s *siteInfoCommonService) GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error) {
resp = &schema.SiteInterfaceResp{}
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeInterface, resp); err != nil {
return nil, err
@@ -60,7 +64,7 @@ func (s *SiteInfoCommonService) GetSiteInterface(ctx context.Context) (resp *sch
}
// GetSiteBranding get site info branding
-func (s *SiteInfoCommonService) GetSiteBranding(ctx context.Context) (resp *schema.SiteBrandingResp, err error) {
+func (s *siteInfoCommonService) GetSiteBranding(ctx context.Context) (resp *schema.SiteBrandingResp, err error) {
resp = &schema.SiteBrandingResp{}
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeBranding, resp); err != nil {
return nil, err
@@ -69,7 +73,7 @@ func (s *SiteInfoCommonService) GetSiteBranding(ctx context.Context) (resp *sche
}
// GetSiteUsers get site info about users
-func (s *SiteInfoCommonService) GetSiteUsers(ctx context.Context) (resp *schema.SiteUsersResp, err error) {
+func (s *siteInfoCommonService) GetSiteUsers(ctx context.Context) (resp *schema.SiteUsersResp, err error) {
resp = &schema.SiteUsersResp{}
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeUsers, resp); err != nil {
return nil, err
@@ -78,13 +82,13 @@ func (s *SiteInfoCommonService) GetSiteUsers(ctx context.Context) (resp *schema.
}
// FormatAvatar format avatar
-func (s *SiteInfoCommonService) FormatAvatar(ctx context.Context, originalAvatarData, email string) *schema.AvatarInfo {
+func (s *siteInfoCommonService) FormatAvatar(ctx context.Context, originalAvatarData, email string) *schema.AvatarInfo {
gravatarBaseURL, defaultAvatar := s.getAvatarDefaultConfig(ctx)
return s.selectedAvatar(originalAvatarData, defaultAvatar, gravatarBaseURL, email)
}
// FormatListAvatar format avatar
-func (s *SiteInfoCommonService) FormatListAvatar(ctx context.Context, userList []*entity.User) (
+func (s *siteInfoCommonService) FormatListAvatar(ctx context.Context, userList []*entity.User) (
avatarMapping map[string]*schema.AvatarInfo) {
gravatarBaseURL, defaultAvatar := s.getAvatarDefaultConfig(ctx)
avatarMapping = make(map[string]*schema.AvatarInfo)
@@ -94,19 +98,22 @@ func (s *SiteInfoCommonService) FormatListAvatar(ctx context.Context, userList [
return avatarMapping
}
-func (s *SiteInfoCommonService) getAvatarDefaultConfig(ctx context.Context) (string, string) {
+func (s *siteInfoCommonService) getAvatarDefaultConfig(ctx context.Context) (string, string) {
gravatarBaseURL, defaultAvatar := constant.DefaultGravatarBaseURL, constant.DefaultAvatar
usersConfig, err := s.GetSiteUsers(ctx)
if err != nil {
log.Error(err)
- } else {
+ }
+ if len(usersConfig.GravatarBaseURL) > 0 {
gravatarBaseURL = usersConfig.GravatarBaseURL
+ }
+ if len(usersConfig.DefaultAvatar) > 0 {
defaultAvatar = usersConfig.DefaultAvatar
}
return gravatarBaseURL, defaultAvatar
}
-func (s *SiteInfoCommonService) selectedAvatar(
+func (s *siteInfoCommonService) selectedAvatar(
originalAvatarData string, defaultAvatar string, gravatarBaseURL string, email string) *schema.AvatarInfo {
avatarInfo := &schema.AvatarInfo{}
_ = json.Unmarshal([]byte(originalAvatarData), avatarInfo)
@@ -121,7 +128,7 @@ func (s *SiteInfoCommonService) selectedAvatar(
}
// GetSiteWrite get site info write
-func (s *SiteInfoCommonService) GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error) {
+func (s *siteInfoCommonService) GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error) {
resp = &schema.SiteWriteResp{}
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeWrite, resp); err != nil {
return nil, err
@@ -130,7 +137,7 @@ func (s *SiteInfoCommonService) GetSiteWrite(ctx context.Context) (resp *schema.
}
// GetSiteLegal get site info write
-func (s *SiteInfoCommonService) GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) {
+func (s *siteInfoCommonService) GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) {
resp = &schema.SiteLegalResp{}
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeLegal, resp); err != nil {
return nil, err
@@ -139,7 +146,7 @@ func (s *SiteInfoCommonService) GetSiteLegal(ctx context.Context) (resp *schema.
}
// GetSiteLogin get site login config
-func (s *SiteInfoCommonService) GetSiteLogin(ctx context.Context) (resp *schema.SiteLoginResp, err error) {
+func (s *siteInfoCommonService) GetSiteLogin(ctx context.Context) (resp *schema.SiteLoginResp, err error) {
resp = &schema.SiteLoginResp{}
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeLogin, resp); err != nil {
return nil, err
@@ -148,7 +155,7 @@ func (s *SiteInfoCommonService) GetSiteLogin(ctx context.Context) (resp *schema.
}
// GetSiteCustomCssHTML get site custom css html config
-func (s *SiteInfoCommonService) GetSiteCustomCssHTML(ctx context.Context) (resp *schema.SiteCustomCssHTMLResp, err error) {
+func (s *siteInfoCommonService) GetSiteCustomCssHTML(ctx context.Context) (resp *schema.SiteCustomCssHTMLResp, err error) {
resp = &schema.SiteCustomCssHTMLResp{}
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeCustomCssHTML, resp); err != nil {
return nil, err
@@ -157,7 +164,7 @@ func (s *SiteInfoCommonService) GetSiteCustomCssHTML(ctx context.Context) (resp
}
// GetSiteTheme get site theme
-func (s *SiteInfoCommonService) GetSiteTheme(ctx context.Context) (resp *schema.SiteThemeResp, err error) {
+func (s *siteInfoCommonService) GetSiteTheme(ctx context.Context) (resp *schema.SiteThemeResp, err error) {
resp = &schema.SiteThemeResp{
ThemeOptions: schema.GetThemeOptions,
}
@@ -169,15 +176,24 @@ func (s *SiteInfoCommonService) GetSiteTheme(ctx context.Context) (resp *schema.
}
// GetSiteSeo get site seo
-func (s *SiteInfoCommonService) GetSiteSeo(ctx context.Context) (resp *schema.SiteSeoReq, err error) {
- resp = &schema.SiteSeoReq{}
+func (s *siteInfoCommonService) GetSiteSeo(ctx context.Context) (resp *schema.SiteSeoResp, err error) {
+ resp = &schema.SiteSeoResp{}
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeSeo, resp); err != nil {
return nil, err
}
return resp, nil
}
-func (s *SiteInfoCommonService) GetSiteInfoByType(ctx context.Context, siteType string, resp interface{}) (err error) {
+func (s *siteInfoCommonService) EnableShortID(ctx context.Context) (enabled bool) {
+ siteSeo, err := s.GetSiteSeo(ctx)
+ if err != nil {
+ log.Error(err)
+ return false
+ }
+ return siteSeo.IsShortLink()
+}
+
+func (s *siteInfoCommonService) GetSiteInfoByType(ctx context.Context, siteType string, resp interface{}) (err error) {
siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, siteType)
if err != nil {
return err
diff --git a/internal/service/tag/tag_service.go b/internal/service/tag/tag_service.go
index 23dc25de..6d2884a6 100644
--- a/internal/service/tag/tag_service.go
+++ b/internal/service/tag/tag_service.go
@@ -10,6 +10,7 @@ import (
"github.com/answerdev/answer/internal/service/siteinfo_common"
tagcommonser "github.com/answerdev/answer/internal/service/tag_common"
"github.com/answerdev/answer/pkg/htmltext"
+ "github.com/jinzhu/copier"
"github.com/answerdev/answer/internal/base/pager"
"github.com/answerdev/answer/internal/base/reason"
@@ -18,18 +19,18 @@ import (
"github.com/answerdev/answer/internal/service/activity_common"
"github.com/answerdev/answer/internal/service/permission"
"github.com/answerdev/answer/pkg/converter"
- "github.com/jinzhu/copier"
"github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/log"
)
// TagService user service
type TagService struct {
- tagRepo tagcommonser.TagRepo
- tagCommonService *tagcommonser.TagCommonService
- revisionService *revision_common.RevisionService
- followCommon activity_common.FollowRepo
- siteInfoService *siteinfo_common.SiteInfoCommonService
+ tagRepo tagcommonser.TagRepo
+ tagCommonService *tagcommonser.TagCommonService
+ revisionService *revision_common.RevisionService
+ followCommon activity_common.FollowRepo
+ siteInfoService siteinfo_common.SiteInfoCommonService
+ activityQueueService activity_queue.ActivityQueueService
}
// NewTagService new tag service
@@ -38,13 +39,16 @@ func NewTagService(
tagCommonService *tagcommonser.TagCommonService,
revisionService *revision_common.RevisionService,
followCommon activity_common.FollowRepo,
- siteInfoService *siteinfo_common.SiteInfoCommonService) *TagService {
+ siteInfoService siteinfo_common.SiteInfoCommonService,
+ activityQueueService activity_queue.ActivityQueueService,
+) *TagService {
return &TagService{
- tagRepo: tagRepo,
- tagCommonService: tagCommonService,
- revisionService: revisionService,
- followCommon: followCommon,
- siteInfoService: siteInfoService,
+ tagRepo: tagRepo,
+ tagCommonService: tagCommonService,
+ revisionService: revisionService,
+ followCommon: followCommon,
+ siteInfoService: siteInfoService,
+ activityQueueService: activityQueueService,
}
}
@@ -73,7 +77,7 @@ func (ts *TagService) RemoveTag(ctx context.Context, req *schema.RemoveTagReq) (
if err != nil {
return err
}
- activity_queue.AddActivity(&schema.ActivityMsg{
+ ts.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: req.UserID,
ObjectID: req.TagID,
OriginalObjectID: req.TagID,
@@ -298,7 +302,7 @@ func (ts *TagService) UpdateTagSynonym(ctx context.Context, req *schema.UpdateTa
if err != nil {
return err
}
- activity_queue.AddActivity(&schema.ActivityMsg{
+ ts.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: req.UserID,
ObjectID: tag.ID,
OriginalObjectID: tag.ID,
diff --git a/internal/service/tag_common/tag_common.go b/internal/service/tag_common/tag_common.go
index 7332ab80..b57eb240 100644
--- a/internal/service/tag_common/tag_common.go
+++ b/internal/service/tag_common/tag_common.go
@@ -57,11 +57,12 @@ type TagRelRepo interface {
// TagCommonService user service
type TagCommonService struct {
- revisionService *revision_common.RevisionService
- tagCommonRepo TagCommonRepo
- tagRelRepo TagRelRepo
- tagRepo TagRepo
- siteInfoService *siteinfo_common.SiteInfoCommonService
+ revisionService *revision_common.RevisionService
+ tagCommonRepo TagCommonRepo
+ tagRelRepo TagRelRepo
+ tagRepo TagRepo
+ siteInfoService siteinfo_common.SiteInfoCommonService
+ activityQueueService activity_queue.ActivityQueueService
}
// NewTagCommonService new tag service
@@ -70,14 +71,16 @@ func NewTagCommonService(
tagRelRepo TagRelRepo,
tagRepo TagRepo,
revisionService *revision_common.RevisionService,
- siteInfoService *siteinfo_common.SiteInfoCommonService,
+ siteInfoService siteinfo_common.SiteInfoCommonService,
+ activityQueueService activity_queue.ActivityQueueService,
) *TagCommonService {
return &TagCommonService{
- tagCommonRepo: tagCommonRepo,
- tagRelRepo: tagRelRepo,
- tagRepo: tagRepo,
- revisionService: revisionService,
- siteInfoService: siteInfoService,
+ tagCommonRepo: tagCommonRepo,
+ tagRelRepo: tagRelRepo,
+ tagRepo: tagRepo,
+ revisionService: revisionService,
+ siteInfoService: siteInfoService,
+ activityQueueService: activityQueueService,
}
}
@@ -645,7 +648,7 @@ func (ts *TagCommonService) ObjectChangeTag(ctx context.Context, objectTagData *
if err != nil {
return err
}
- activity_queue.AddActivity(&schema.ActivityMsg{
+ ts.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: objectTagData.UserID,
ObjectID: tag.ID,
OriginalObjectID: tag.ID,
@@ -845,7 +848,7 @@ func (ts *TagCommonService) UpdateTag(ctx context.Context, req *schema.UpdateTag
return err
}
if canUpdate {
- activity_queue.AddActivity(&schema.ActivityMsg{
+ ts.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: req.UserID,
ObjectID: tagInfo.ID,
OriginalObjectID: tagInfo.ID,
diff --git a/internal/service/uploader/upload.go b/internal/service/uploader/upload.go
index 3b978521..f6c14686 100644
--- a/internal/service/uploader/upload.go
+++ b/internal/service/uploader/upload.go
@@ -53,20 +53,20 @@ var (
type UploaderService interface {
UploadAvatarFile(ctx *gin.Context) (url string, err error)
- AvatarThumbFile(ctx *gin.Context, uploadPath, fileName string, size int) (avatarFile []byte, err error)
UploadPostFile(ctx *gin.Context) (url string, err error)
UploadBrandingFile(ctx *gin.Context) (url string, err error)
+ AvatarThumbFile(ctx *gin.Context, fileName string, size int) (url string, err error)
}
// uploaderService uploader service
type uploaderService struct {
serviceConfig *service_config.ServiceConfig
- siteInfoService *siteinfo_common.SiteInfoCommonService
+ siteInfoService siteinfo_common.SiteInfoCommonService
}
// NewUploaderService new upload service
func NewUploaderService(serviceConfig *service_config.ServiceConfig,
- siteInfoService *siteinfo_common.SiteInfoCommonService) UploaderService {
+ siteInfoService siteinfo_common.SiteInfoCommonService) UploaderService {
for _, subPath := range subPathList {
err := dir.CreateDirIfNotExist(filepath.Join(serviceConfig.UploadPath, subPath))
if err != nil {
@@ -105,26 +105,26 @@ func (us *uploaderService) UploadAvatarFile(ctx *gin.Context) (url string, err e
return us.uploadFile(ctx, file, avatarFilePath)
}
-func (us *uploaderService) AvatarThumbFile(ctx *gin.Context, uploadPath, fileName string, size int) (
- avatarfile []byte, err error) {
+func (us *uploaderService) AvatarThumbFile(ctx *gin.Context, fileName string, size int) (url string, err error) {
if size > 1024 {
size = 1024
}
+
thumbFileName := fmt.Sprintf("%d_%d@%s", size, size, fileName)
- thumbfilePath := fmt.Sprintf("%s/%s/%s", uploadPath, avatarThumbSubPath, thumbFileName)
- avatarfile, err = os.ReadFile(thumbfilePath)
+ thumbFilePath := fmt.Sprintf("%s/%s/%s", us.serviceConfig.UploadPath, avatarThumbSubPath, thumbFileName)
+ avatarfile, err := os.ReadFile(thumbFilePath)
if err == nil {
- return avatarfile, nil
+ return thumbFilePath, nil
}
- filePath := fmt.Sprintf("%s/avatar/%s", uploadPath, fileName)
+ filePath := fmt.Sprintf("%s/avatar/%s", us.serviceConfig.UploadPath, fileName)
avatarfile, err = os.ReadFile(filePath)
if err != nil {
- return avatarfile, errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
+ return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
}
reader := bytes.NewReader(avatarfile)
img, err := imaging.Decode(reader)
if err != nil {
- return avatarfile, errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
+ return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
}
new_image := imaging.Fill(img, size, size, imaging.Center, imaging.Linear)
var buf bytes.Buffer
@@ -133,29 +133,29 @@ func (us *uploaderService) AvatarThumbFile(ctx *gin.Context, uploadPath, fileNam
_, ok := FormatExts[fileSuffix]
if !ok {
- return avatarfile, fmt.Errorf("img extension not exist")
+ return "", fmt.Errorf("img extension not exist")
}
err = imaging.Encode(&buf, new_image, FormatExts[fileSuffix])
if err != nil {
- return avatarfile, errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
+ return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
}
thumbReader := bytes.NewReader(buf.Bytes())
err = dir.CreateDirIfNotExist(path.Join(us.serviceConfig.UploadPath, avatarThumbSubPath))
if err != nil {
- return nil, errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
+ return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
}
avatarFilePath := path.Join(avatarThumbSubPath, thumbFileName)
savefilePath := path.Join(us.serviceConfig.UploadPath, avatarFilePath)
out, err := os.Create(savefilePath)
if err != nil {
- return avatarfile, errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
+ return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
}
defer out.Close()
_, err = io.Copy(out, thumbReader)
if err != nil {
- return avatarfile, errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
+ return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
}
- return buf.Bytes(), nil
+ return savefilePath, nil
}
func (us *uploaderService) UploadPostFile(ctx *gin.Context) (
diff --git a/internal/service/user_admin/user_backyard.go b/internal/service/user_admin/user_backyard.go
index 86270e08..ad6ea194 100644
--- a/internal/service/user_admin/user_backyard.go
+++ b/internal/service/user_admin/user_backyard.go
@@ -41,7 +41,7 @@ type UserAdminService struct {
authService *auth.AuthService
userCommonService *usercommon.UserCommon
userActivity activity.UserActiveActivityRepo
- siteInfoCommonService *siteinfo_common.SiteInfoCommonService
+ siteInfoCommonService siteinfo_common.SiteInfoCommonService
}
// NewUserAdminService new user admin service
@@ -51,7 +51,7 @@ func NewUserAdminService(
authService *auth.AuthService,
userCommonService *usercommon.UserCommon,
userActivity activity.UserActiveActivityRepo,
- siteInfoCommonService *siteinfo_common.SiteInfoCommonService,
+ siteInfoCommonService siteinfo_common.SiteInfoCommonService,
) *UserAdminService {
return &UserAdminService{
userRepo: userRepo,
diff --git a/internal/service/user_common/user.go b/internal/service/user_common/user.go
index 9a451265..84a13f3e 100644
--- a/internal/service/user_common/user.go
+++ b/internal/service/user_common/user.go
@@ -44,14 +44,14 @@ type UserCommon struct {
userRepo UserRepo
userRoleService *role.UserRoleRelService
authService *auth.AuthService
- siteInfoCommonService *siteinfo_common.SiteInfoCommonService
+ siteInfoCommonService siteinfo_common.SiteInfoCommonService
}
func NewUserCommon(
userRepo UserRepo,
userRoleService *role.UserRoleRelService,
authService *auth.AuthService,
- siteInfoCommonService *siteinfo_common.SiteInfoCommonService,
+ siteInfoCommonService siteinfo_common.SiteInfoCommonService,
) *UserCommon {
return &UserCommon{
userRepo: userRepo,
diff --git a/internal/service/user_external_login/user_center_login_service.go b/internal/service/user_external_login/user_center_login_service.go
index 9e870381..e4f786d6 100644
--- a/internal/service/user_external_login/user_center_login_service.go
+++ b/internal/service/user_external_login/user_center_login_service.go
@@ -27,7 +27,7 @@ type UserCenterLoginService struct {
userExternalLoginRepo UserExternalLoginRepo
userCommonService *usercommon.UserCommon
userActivity activity.UserActiveActivityRepo
- siteInfoCommonService *siteinfo_common.SiteInfoCommonService
+ siteInfoCommonService siteinfo_common.SiteInfoCommonService
}
// NewUserCenterLoginService new user external login service
@@ -36,7 +36,7 @@ func NewUserCenterLoginService(
userCommonService *usercommon.UserCommon,
userExternalLoginRepo UserExternalLoginRepo,
userActivity activity.UserActiveActivityRepo,
- siteInfoCommonService *siteinfo_common.SiteInfoCommonService,
+ siteInfoCommonService siteinfo_common.SiteInfoCommonService,
) *UserCenterLoginService {
return &UserCenterLoginService{
userRepo: userRepo,
@@ -228,10 +228,10 @@ func (us *UserCenterLoginService) UserCenterAdminFunctionAgent(ctx context.Conte
desc := userCenter.Description()
// If user status agent is enabled, admin can not update user status in answer.
resp.AllowUpdateUserStatus = !desc.UserStatusAgentEnabled
+ resp.AllowUpdateUserRole = !desc.UserRoleAgentEnabled
// If original user system is enabled, admin can update user password and role in answer.
resp.AllowUpdateUserPassword = desc.EnabledOriginalUserSystem
- resp.AllowUpdateUserRole = desc.EnabledOriginalUserSystem
resp.AllowCreateUser = desc.EnabledOriginalUserSystem
return resp, nil
}
diff --git a/internal/service/user_external_login/user_external_login_service.go b/internal/service/user_external_login/user_external_login_service.go
index 4b10aef4..7e439ba3 100644
--- a/internal/service/user_external_login/user_external_login_service.go
+++ b/internal/service/user_external_login/user_external_login_service.go
@@ -41,7 +41,7 @@ type UserExternalLoginService struct {
userExternalLoginRepo UserExternalLoginRepo
userCommonService *usercommon.UserCommon
emailService *export.EmailService
- siteInfoCommonService *siteinfo_common.SiteInfoCommonService
+ siteInfoCommonService siteinfo_common.SiteInfoCommonService
userActivity activity.UserActiveActivityRepo
}
@@ -51,7 +51,7 @@ func NewUserExternalLoginService(
userCommonService *usercommon.UserCommon,
userExternalLoginRepo UserExternalLoginRepo,
emailService *export.EmailService,
- siteInfoCommonService *siteinfo_common.SiteInfoCommonService,
+ siteInfoCommonService siteinfo_common.SiteInfoCommonService,
userActivity activity.UserActiveActivityRepo,
) *UserExternalLoginService {
return &UserExternalLoginService{
diff --git a/internal/service/user_service.go b/internal/service/user_service.go
index 72b4440b..582a4cb1 100644
--- a/internal/service/user_service.go
+++ b/internal/service/user_service.go
@@ -38,7 +38,7 @@ type UserService struct {
activityRepo activity_common.ActivityRepo
emailService *export.EmailService
authService *auth.AuthService
- siteInfoService *siteinfo_common.SiteInfoCommonService
+ siteInfoService siteinfo_common.SiteInfoCommonService
userRoleService *role.UserRoleRelService
userExternalLoginService *user_external_login.UserExternalLoginService
}
@@ -48,7 +48,7 @@ func NewUserService(userRepo usercommon.UserRepo,
activityRepo activity_common.ActivityRepo,
emailService *export.EmailService,
authService *auth.AuthService,
- siteInfoService *siteinfo_common.SiteInfoCommonService,
+ siteInfoService siteinfo_common.SiteInfoCommonService,
userRoleService *role.UserRoleRelService,
userCommonService *usercommon.UserCommon,
userExternalLoginService *user_external_login.UserExternalLoginService,
@@ -606,7 +606,7 @@ func (us *UserService) UserChangeEmailSendCode(ctx context.Context, req *schema.
}
log.Infof("send email confirmation %s", verifyEmailURL)
- go us.emailService.SendAndSaveCode(context.Background(), req.Email, title, body, code, data.ToJSONString())
+ go us.emailService.SendAndSaveCode(ctx, req.Email, title, body, code, data.ToJSONString())
return nil, nil
}
diff --git a/internal/service/vote_service.go b/internal/service/vote_service.go
index 022f9dca..61f7e31d 100644
--- a/internal/service/vote_service.go
+++ b/internal/service/vote_service.go
@@ -2,6 +2,8 @@ package service
import (
"context"
+ "github.com/answerdev/answer/internal/service/activity_common"
+ "strings"
"github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/base/handler"
@@ -13,41 +15,37 @@ import (
"github.com/answerdev/answer/internal/service/config"
"github.com/answerdev/answer/internal/service/object_info"
"github.com/answerdev/answer/pkg/htmltext"
- "github.com/answerdev/answer/pkg/obj"
"github.com/segmentfault/pacman/log"
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/schema"
answercommon "github.com/answerdev/answer/internal/service/answer_common"
questioncommon "github.com/answerdev/answer/internal/service/question_common"
- "github.com/answerdev/answer/internal/service/unique"
"github.com/segmentfault/pacman/errors"
)
// VoteRepo activity repository
type VoteRepo interface {
- VoteUp(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error)
- VoteDown(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error)
- VoteUpCancel(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error)
- VoteDownCancel(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error)
- GetVoteResultByObjectId(ctx context.Context, objectID string) (resp *schema.VoteResp, err error)
- ListUserVotes(ctx context.Context, userID string, req schema.GetVoteWithPageReq, activityTypes []int) (voteList []entity.Activity, total int64, err error)
+ Vote(ctx context.Context, op *schema.VoteOperationInfo) (err error)
+ CancelVote(ctx context.Context, op *schema.VoteOperationInfo) (err error)
+ GetAndSaveVoteResult(ctx context.Context, objectID, objectType string) (up, down int64, err error)
+ ListUserVotes(ctx context.Context, userID string, page int, pageSize int, activityTypes []int) (
+ voteList []*entity.Activity, total int64, err error)
}
// VoteService user service
type VoteService struct {
voteRepo VoteRepo
- UniqueIDRepo unique.UniqueIDRepo
configService *config.ConfigService
questionRepo questioncommon.QuestionRepo
answerRepo answercommon.AnswerRepo
commentCommonRepo comment_common.CommentCommonRepo
objectService *object_info.ObjService
+ activityRepo activity_common.ActivityRepo
}
func NewVoteService(
- VoteRepo VoteRepo,
- uniqueIDRepo unique.UniqueIDRepo,
+ voteRepo VoteRepo,
configService *config.ConfigService,
questionRepo questioncommon.QuestionRepo,
answerRepo answercommon.AnswerRepo,
@@ -55,8 +53,7 @@ func NewVoteService(
objectService *object_info.ObjService,
) *VoteService {
return &VoteService{
- voteRepo: VoteRepo,
- UniqueIDRepo: uniqueIDRepo,
+ voteRepo: voteRepo,
configService: configService,
questionRepo: questionRepo,
answerRepo: answerRepo,
@@ -66,94 +63,87 @@ func NewVoteService(
}
// VoteUp vote up
-func (vs *VoteService) VoteUp(ctx context.Context, dto *schema.VoteDTO) (voteResp *schema.VoteResp, err error) {
- voteResp = &schema.VoteResp{}
-
- var objectUserID string
-
- objectUserID, err = vs.GetObjectUserID(ctx, dto.ObjectID)
+func (vs *VoteService) VoteUp(ctx context.Context, req *schema.VoteReq) (resp *schema.VoteResp, err error) {
+ objectInfo, err := vs.objectService.GetInfo(ctx, req.ObjectID)
if err != nil {
- return
+ return nil, err
}
+ // make object id must be decoded
+ objectInfo.ObjectID = req.ObjectID
// check user is voting self or not
- if objectUserID == dto.UserID {
- err = errors.BadRequest(reason.DisallowVoteYourSelf)
- return
+ if objectInfo.ObjectCreatorUserID == req.UserID {
+ return nil, errors.BadRequest(reason.DisallowVoteYourSelf)
}
- if dto.IsCancel {
- return vs.voteRepo.VoteUpCancel(ctx, dto.ObjectID, dto.UserID, objectUserID)
+ voteUpOperationInfo := vs.createVoteOperationInfo(ctx, req.UserID, true, objectInfo)
+
+ // vote operation
+ if req.IsCancel {
+ err = vs.voteRepo.CancelVote(ctx, voteUpOperationInfo)
} else {
- return vs.voteRepo.VoteUp(ctx, dto.ObjectID, dto.UserID, objectUserID)
+ // cancel vote down if exist
+ voteOperationInfo := vs.createVoteOperationInfo(ctx, req.UserID, false, objectInfo)
+ err = vs.voteRepo.CancelVote(ctx, voteOperationInfo)
+ if err != nil {
+ return nil, err
+ }
+ err = vs.voteRepo.Vote(ctx, voteUpOperationInfo)
}
+
+ resp = &schema.VoteResp{}
+ resp.UpVotes, resp.DownVotes, err = vs.voteRepo.GetAndSaveVoteResult(ctx, req.ObjectID, objectInfo.ObjectType)
+ if err != nil {
+ log.Error(err)
+ }
+ resp.Votes = resp.UpVotes - resp.DownVotes
+ if !req.IsCancel {
+ resp.VoteStatus = constant.ActVoteUp
+ }
+ return resp, nil
}
// VoteDown vote down
-func (vs *VoteService) VoteDown(ctx context.Context, dto *schema.VoteDTO) (voteResp *schema.VoteResp, err error) {
- voteResp = &schema.VoteResp{}
-
- var objectUserID string
-
- objectUserID, err = vs.GetObjectUserID(ctx, dto.ObjectID)
+func (vs *VoteService) VoteDown(ctx context.Context, req *schema.VoteReq) (resp *schema.VoteResp, err error) {
+ objectInfo, err := vs.objectService.GetInfo(ctx, req.ObjectID)
if err != nil {
- return
+ return nil, err
}
+ // make object id must be decoded
+ objectInfo.ObjectID = req.ObjectID
// check user is voting self or not
- if objectUserID == dto.UserID {
- err = errors.BadRequest(reason.DisallowVoteYourSelf)
- return
+ if objectInfo.ObjectCreatorUserID == req.UserID {
+ return nil, errors.BadRequest(reason.DisallowVoteYourSelf)
}
- if dto.IsCancel {
- return vs.voteRepo.VoteDownCancel(ctx, dto.ObjectID, dto.UserID, objectUserID)
+ // vote operation
+ voteDownOperationInfo := vs.createVoteOperationInfo(ctx, req.UserID, false, objectInfo)
+ if req.IsCancel {
+ err = vs.voteRepo.CancelVote(ctx, voteDownOperationInfo)
} else {
- return vs.voteRepo.VoteDown(ctx, dto.ObjectID, dto.UserID, objectUserID)
+ // cancel vote up if exist
+ err = vs.voteRepo.CancelVote(ctx, vs.createVoteOperationInfo(ctx, req.UserID, true, objectInfo))
+ if err != nil {
+ return nil, err
+ }
+ err = vs.voteRepo.Vote(ctx, voteDownOperationInfo)
}
-}
-
-func (vs *VoteService) GetObjectUserID(ctx context.Context, objectID string) (userID string, err error) {
- var objectKey string
- objectKey, err = obj.GetObjectTypeStrByObjectID(objectID)
+ resp = &schema.VoteResp{}
+ resp.UpVotes, resp.DownVotes, err = vs.voteRepo.GetAndSaveVoteResult(ctx, req.ObjectID, objectInfo.ObjectType)
if err != nil {
- err = nil
- return
+ log.Error(err)
}
-
- switch objectKey {
- case "question":
- object, has, e := vs.questionRepo.GetQuestion(ctx, objectID)
- if e != nil || !has {
- err = errors.BadRequest(reason.QuestionNotFound).WithError(e).WithStack()
- return
- }
- userID = object.UserID
- case "answer":
- object, has, e := vs.answerRepo.GetAnswer(ctx, objectID)
- if e != nil || !has {
- err = errors.BadRequest(reason.AnswerNotFound).WithError(e).WithStack()
- return
- }
- userID = object.UserID
- case "comment":
- object, has, e := vs.commentCommonRepo.GetComment(ctx, objectID)
- if e != nil || !has {
- err = errors.BadRequest(reason.CommentNotFound).WithError(e).WithStack()
- return
- }
- userID = object.UserID
- default:
- err = errors.BadRequest(reason.DisallowVote).WithError(err).WithStack()
- return
+ resp.Votes = resp.UpVotes - resp.DownVotes
+ if !req.IsCancel {
+ resp.VoteStatus = constant.ActVoteDown
}
-
- return
+ return resp, nil
}
// ListUserVotes list user's votes
-func (vs *VoteService) ListUserVotes(ctx context.Context, req schema.GetVoteWithPageReq) (model *pager.PageModel, err error) {
+func (vs *VoteService) ListUserVotes(ctx context.Context, req schema.GetVoteWithPageReq) (resp *pager.PageModel, err error) {
typeKeys := []string{
activity_type.QuestionVoteUp,
activity_type.QuestionVoteDown,
@@ -172,14 +162,14 @@ func (vs *VoteService) ListUserVotes(ctx context.Context, req schema.GetVoteWith
activityTypeMapping[cfg.ID] = typeKey
}
- voteList, total, err := vs.voteRepo.ListUserVotes(ctx, req.UserID, req, activityTypes)
+ voteList, total, err := vs.voteRepo.ListUserVotes(ctx, req.UserID, req.Page, req.PageSize, activityTypes)
if err != nil {
- return
+ return nil, err
}
lang := handler.GetLangByCtx(ctx)
- resp := make([]*schema.GetVoteWithPageResp, 0)
+ votes := make([]*schema.GetVoteWithPageResp, 0)
for _, voteInfo := range voteList {
objInfo, err := vs.objectService.GetInfo(ctx, voteInfo.ObjectID)
if err != nil {
@@ -202,7 +192,65 @@ func (vs *VoteService) ListUserVotes(ctx context.Context, req schema.GetVoteWith
if objInfo.QuestionStatus == entity.QuestionStatusDeleted {
item.Title = translator.Tr(lang, constant.DeletedQuestionTitleTrKey)
}
- resp = append(resp, item)
+ votes = append(votes, item)
}
- return pager.NewPageModel(total, resp), err
+ return pager.NewPageModel(total, votes), err
+}
+
+func (vs *VoteService) createVoteOperationInfo(ctx context.Context,
+ userID string, voteUp bool, objectInfo *schema.SimpleObjectInfo) *schema.VoteOperationInfo {
+ // warp vote operation
+ voteOperationInfo := &schema.VoteOperationInfo{
+ ObjectID: objectInfo.ObjectID,
+ ObjectType: objectInfo.ObjectType,
+ ObjectCreatorUserID: objectInfo.ObjectCreatorUserID,
+ OperatingUserID: userID,
+ VoteUp: voteUp,
+ VoteDown: !voteUp,
+ }
+ voteOperationInfo.Activities = vs.getActivities(ctx, voteOperationInfo)
+ return voteOperationInfo
+}
+
+func (vs *VoteService) getActivities(ctx context.Context, op *schema.VoteOperationInfo) (
+ activities []*schema.VoteActivity) {
+ activities = make([]*schema.VoteActivity, 0)
+
+ var actions []string
+ switch op.ObjectType {
+ case constant.QuestionObjectType:
+ if op.VoteUp {
+ actions = []string{activity_type.QuestionVoteUp, activity_type.QuestionVotedUp}
+ } else {
+ actions = []string{activity_type.QuestionVoteDown, activity_type.QuestionVotedDown}
+ }
+ case constant.AnswerObjectType:
+ if op.VoteUp {
+ actions = []string{activity_type.AnswerVoteUp, activity_type.AnswerVotedUp}
+ } else {
+ actions = []string{activity_type.AnswerVoteDown, activity_type.AnswerVotedDown}
+ }
+ case constant.CommentObjectType:
+ actions = []string{activity_type.CommentVoteUp}
+ }
+
+ for _, action := range actions {
+ t := &schema.VoteActivity{}
+ cfg, err := vs.configService.GetConfigByKey(ctx, action)
+ if err != nil {
+ log.Warnf("get config by key error: %v", err)
+ continue
+ }
+ t.ActivityType, t.Rank = cfg.ID, cfg.GetIntValue()
+
+ if strings.Contains(action, "voted") {
+ t.ActivityUserID = op.ObjectCreatorUserID
+ t.TriggerUserID = op.OperatingUserID
+ } else {
+ t.ActivityUserID = op.OperatingUserID
+ t.TriggerUserID = "0"
+ }
+ activities = append(activities, t)
+ }
+ return activities
}
diff --git a/pkg/uid/sid.go b/pkg/uid/sid.go
index 2e9f5117..27d162e5 100644
--- a/pkg/uid/sid.go
+++ b/pkg/uid/sid.go
@@ -8,8 +8,6 @@ import (
const salt = int64(100)
-var ShortIDSwitch = false
-
// NumToString num to string
func NumToShortID(id int64) string {
sid := strconv.FormatInt(id, 10)
@@ -45,14 +43,11 @@ func ShortIDToNum(code string) int64 {
}
func EnShortID(id string) string {
- if ShortIDSwitch {
- num, err := strconv.ParseInt(id, 10, 64)
- if err != nil {
- return id
- }
- return NumToShortID(num)
+ num, err := strconv.ParseInt(id, 10, 64)
+ if err != nil {
+ return id
}
- return id
+ return NumToShortID(num)
}
func DeShortID(sid string) string {
diff --git a/pkg/uid/sid_test.go b/pkg/uid/sid_test.go
index 96501a98..3c8ab899 100644
--- a/pkg/uid/sid_test.go
+++ b/pkg/uid/sid_test.go
@@ -31,7 +31,6 @@ func Test_ShortID(t *testing.T) {
func Test_EnDeShortID(t *testing.T) {
nums := []string{"0", "1", "10", "100", "1000", "10000", "100000", "1234567", "10000000000000000", "10010000000001316", "19930000000001316"}
- ShortIDSwitch = true
for _, num := range nums {
code := EnShortID(num)
denum := DeShortID(code)
diff --git a/plugin/user_center.go b/plugin/user_center.go
index 57061394..ee6d7431 100644
--- a/plugin/user_center.go
+++ b/plugin/user_center.go
@@ -33,6 +33,7 @@ type UserCenterDesc struct {
SignUpRedirectURL string `json:"sign_up_redirect_url"`
RankAgentEnabled bool `json:"rank_agent_enabled"`
UserStatusAgentEnabled bool `json:"user_status_agent_enabled"`
+ UserRoleAgentEnabled bool `json:"user_role_agent_enabled"`
MustAuthEmailEnabled bool `json:"must_auth_email_enabled"`
EnabledOriginalUserSystem bool `json:"enabled_original_user_system"`
}
diff --git a/ui/src/plugins/builtin/UcLogin/i18n/en_US.yaml b/ui/src/plugins/builtin/UcLogin/i18n/en_US.yaml
index b61e3d64..a141b60a 100644
--- a/ui/src/plugins/builtin/UcLogin/i18n/en_US.yaml
+++ b/ui/src/plugins/builtin/UcLogin/i18n/en_US.yaml
@@ -1,6 +1,7 @@
plugin:
uc_login:
ui:
+ connect: Connect with {{ auth_name }}
login: Login
qrcode_login_tip: Please use {{ agentName }} to scan the QR code and log in.
login_failed_email_tip: Login failed, please allow this app to access your email information before try again.
diff --git a/ui/src/plugins/builtin/UcLogin/i18n/zh_CN.yaml b/ui/src/plugins/builtin/UcLogin/i18n/zh_CN.yaml
index d8956ef3..492041a8 100644
--- a/ui/src/plugins/builtin/UcLogin/i18n/zh_CN.yaml
+++ b/ui/src/plugins/builtin/UcLogin/i18n/zh_CN.yaml
@@ -1,6 +1,7 @@
plugin:
uc_login:
ui:
+ connect: 连接到 {{ auth_name }}
login: 登录
qrcode_login_tip: 请使用 {{ agentName }} 扫描二维码登录
login_failed_email_tip: 登录失败, 请允许该应用程序访问您的电子邮件信息,然后再试一次。