mirror of https://gitee.com/answerdev/answer.git
Merge branch 'feat/1.1.1/state' into test
This commit is contained in:
commit
dabc5bfeb1
|
@ -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() {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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:
|
||||
|
|
1
go.mod
1
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
|
||||
)
|
||||
|
||||
|
|
5
go.sum
5
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=
|
||||
|
|
|
@ -1417,6 +1417,7 @@ ui:
|
|||
change: Change
|
||||
all: All
|
||||
staff: Staff
|
||||
more: More
|
||||
inactive: Inactive
|
||||
suspended: Suspended
|
||||
deleted: Deleted
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -2,4 +2,5 @@ package constant
|
|||
|
||||
const (
|
||||
AcceptLanguageFlag = "Accept-Language"
|
||||
ShortIDFlag = "Short-ID-Enabled"
|
||||
)
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -8,4 +8,5 @@ import (
|
|||
var ProviderSetMiddleware = wire.NewSet(
|
||||
NewAuthUserMiddleware,
|
||||
NewAvatarMiddleware,
|
||||
NewShortIDMiddleware,
|
||||
)
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -19,7 +19,7 @@ var ProviderSetController = wire.NewSet(
|
|||
NewRankController,
|
||||
NewReasonController,
|
||||
NewNotificationController,
|
||||
NewSiteinfoController,
|
||||
NewSiteInfoController,
|
||||
NewDashboardController,
|
||||
NewUploadController,
|
||||
NewActivityController,
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
package templaterender
|
|
@ -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(`<?xml version="1.0" encoding="UTF-8"?>`),
|
||||
"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(`<?xml version="1.0" encoding="UTF-8"?>`),
|
||||
"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(`<?xml version="1.0" encoding="UTF-8"?>`),
|
||||
"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(`<?xml version="1.0" encoding="UTF-8"?>`),
|
||||
"list": sitemapInfo.PageData,
|
||||
"list": questions,
|
||||
"general": general,
|
||||
"hastitle": hasTitle,
|
||||
"hastitle": siteInfo.PermaLink == constant.PermaLinkQuestionIDAndTitle ||
|
||||
siteInfo.PermaLink == constant.PermaLinkQuestionIDAndTitleByShortID,
|
||||
},
|
||||
)
|
||||
return nil
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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}}<br><br>\n\nClick the following link to confirm and activate your new account:<br>\n<a href='{{.RegisterUrl}}' target='_blank'>{{.RegisterUrl}}</a><br><br>\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}}].<br><br>\n\nIf it was not you, you can safely ignore this email.<br><br>\n\nClick the following link to choose a new password:<br>\n<a href='{{.PassResetUrl}}' target='_blank'>{{.PassResetUrl}}</a>\n","change_title":"[{{.SiteName}}] Confirm your new email address","change_body":"Confirm your new email address for {{.SiteName}} by clicking on the following link:<br><br>\n\n<a href='{{.ChangeEmailUrl}}' target='_blank'>{{.ChangeEmailUrl}}</a><br><br>\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":"<strong><a href='{{.AnswerUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>{{.AnswerSummary}}</blockquote><br>\n<a href='{{.AnswerUrl}}'>View it on {{.SiteName}}</a><br><br>\n\n<small>You are receiving this because you authored the thread. <a href='{{.UnsubscribeUrl}}'>Unsubscribe</a></small>","new_comment_title":"[{{.SiteName}}] {{.DisplayName}} commented on your post","new_comment_body":"<strong><a href='{{.CommentUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>{{.CommentSummary}}</blockquote><br>\n<a href='{{.CommentUrl}}'>View it on {{.SiteName}}</a><br><br>\n\n<small>You are receiving this because you authored the thread. <a href='{{.UnsubscribeUrl}}'>Unsubscribe</a></small>"}`},
|
||||
{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
|
||||
}
|
||||
|
|
|
@ -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}}<br><br>\n\nClick the following link to confirm and activate your new account:<br>\n<a href='{{.RegisterUrl}}' target='_blank'>{{.RegisterUrl}}</a><br><br>\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}}].<br><br>\n\nIf it was not you, you can safely ignore this email.<br><br>\n\nClick the following link to choose a new password:<br>\n<a href='{{.PassResetUrl}}' target='_blank'>{{.PassResetUrl}}</a>\n","change_title":"[{{.SiteName}}] Confirm your new email address","change_body":"Confirm your new email address for {{.SiteName}} by clicking on the following link:<br><br>\n\n<a href='{{.ChangeEmailUrl}}' target='_blank'>{{.ChangeEmailUrl}}</a><br><br>\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":"<strong><a href='{{.AnswerUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>{{.AnswerSummary}}</blockquote><br>\n<a href='{{.AnswerUrl}}'>View it on {{.SiteName}}</a><br><br>\n\n<small>You are receiving this because you authored the thread. <a href='{{.UnsubscribeUrl}}'>Unsubscribe</a></small>","new_comment_title":"[{{.SiteName}}] {{.DisplayName}} commented on your post","new_comment_body":"<strong><a href='{{.CommentUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>{{.CommentSummary}}</blockquote><br>\n<a href='{{.CommentUrl}}'>View it on {{.SiteName}}</a><br><br>\n\n<small>You are receiving this because you authored the thread. <a href='{{.UnsubscribeUrl}}'>Unsubscribe</a></small>"}`},
|
||||
{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`},
|
||||
}
|
||||
)
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -52,7 +52,6 @@ var ProviderSetRepo = wire.NewSet(
|
|||
activity.NewVoteRepo,
|
||||
activity.NewFollowRepo,
|
||||
activity.NewAnswerActivityRepo,
|
||||
activity.NewQuestionActivityRepo,
|
||||
activity.NewUserActiveActivityRepo,
|
||||
activity.NewActivityRepo,
|
||||
tag.NewTagRepo,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"`
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) (
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
plugin:
|
||||
uc_login:
|
||||
ui:
|
||||
connect: 连接到 {{ auth_name }}
|
||||
login: 登录
|
||||
qrcode_login_tip: 请使用 {{ agentName }} 扫描二维码登录
|
||||
login_failed_email_tip: 登录失败, 请允许该应用程序访问您的电子邮件信息,然后再试一次。
|
||||
|
|
Loading…
Reference in New Issue