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"
|
||||||
"github.com/answerdev/answer/internal/service/action"
|
"github.com/answerdev/answer/internal/service/action"
|
||||||
activity2 "github.com/answerdev/answer/internal/service/activity"
|
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"
|
"github.com/answerdev/answer/internal/service/answer_common"
|
||||||
auth2 "github.com/answerdev/answer/internal/service/auth"
|
auth2 "github.com/answerdev/answer/internal/service/auth"
|
||||||
"github.com/answerdev/answer/internal/service/collection_common"
|
"github.com/answerdev/answer/internal/service/collection_common"
|
||||||
|
@ -56,6 +56,7 @@ import (
|
||||||
export2 "github.com/answerdev/answer/internal/service/export"
|
export2 "github.com/answerdev/answer/internal/service/export"
|
||||||
"github.com/answerdev/answer/internal/service/follow"
|
"github.com/answerdev/answer/internal/service/follow"
|
||||||
meta2 "github.com/answerdev/answer/internal/service/meta"
|
meta2 "github.com/answerdev/answer/internal/service/meta"
|
||||||
|
"github.com/answerdev/answer/internal/service/notice_queue"
|
||||||
notification2 "github.com/answerdev/answer/internal/service/notification"
|
notification2 "github.com/answerdev/answer/internal/service/notification"
|
||||||
"github.com/answerdev/answer/internal/service/notification_common"
|
"github.com/answerdev/answer/internal/service/notification_common"
|
||||||
"github.com/answerdev/answer/internal/service/object_info"
|
"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)
|
userService := service.NewUserService(userRepo, userActiveActivityRepo, activityRepo, emailService, authService, siteInfoCommonService, userRoleRelService, userCommon, userExternalLoginService)
|
||||||
captchaRepo := captcha.NewCaptchaRepo(dataData)
|
captchaRepo := captcha.NewCaptchaRepo(dataData)
|
||||||
captchaService := action.NewCaptchaService(captchaRepo)
|
captchaService := action.NewCaptchaService(captchaRepo)
|
||||||
uploaderService := uploader.NewUploaderService(serviceConf, siteInfoCommonService)
|
userController := controller.NewUserController(authService, userService, captchaService, emailService, siteInfoCommonService)
|
||||||
userController := controller.NewUserController(authService, userService, captchaService, emailService, uploaderService, siteInfoCommonService)
|
|
||||||
commentRepo := comment.NewCommentRepo(dataData, uniqueIDRepo)
|
commentRepo := comment.NewCommentRepo(dataData, uniqueIDRepo)
|
||||||
commentCommonRepo := comment.NewCommentCommonRepo(dataData, uniqueIDRepo)
|
commentCommonRepo := comment.NewCommentCommonRepo(dataData, uniqueIDRepo)
|
||||||
answerRepo := answer.NewAnswerRepo(dataData, uniqueIDRepo, userRankRepo, activityRepo)
|
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)
|
tagRepo := tag.NewTagRepo(dataData, uniqueIDRepo)
|
||||||
revisionRepo := revision.NewRevisionRepo(dataData, uniqueIDRepo)
|
revisionRepo := revision.NewRevisionRepo(dataData, uniqueIDRepo)
|
||||||
revisionService := revision_common.NewRevisionService(revisionRepo, userRepo)
|
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)
|
objService := object_info.NewObjService(answerRepo, questionRepo, commentCommonRepo, tagCommonRepo, tagCommonService)
|
||||||
voteRepo := activity_common.NewVoteRepo(dataData, activityRepo)
|
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)
|
rolePowerRelRepo := role.NewRolePowerRelRepo(dataData)
|
||||||
rolePowerRelService := role2.NewRolePowerRelService(rolePowerRelRepo, userRoleRelService)
|
rolePowerRelService := role2.NewRolePowerRelService(rolePowerRelRepo, userRoleRelService)
|
||||||
rankService := rank2.NewRankService(userCommon, userRankRepo, objService, userRoleRelService, rolePowerRelService, configService)
|
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)
|
reportRepo := report.NewReportRepo(dataData, uniqueIDRepo)
|
||||||
reportService := report2.NewReportService(reportRepo, objService)
|
reportService := report2.NewReportService(reportRepo, objService)
|
||||||
reportController := controller.NewReportController(reportService, rankService)
|
reportController := controller.NewReportController(reportService, rankService)
|
||||||
serviceVoteRepo := activity.NewVoteRepo(dataData, uniqueIDRepo, configService, activityRepo, userRankRepo, voteRepo)
|
serviceVoteRepo := activity.NewVoteRepo(dataData, activityRepo, userRankRepo, notificationQueueService)
|
||||||
voteService := service.NewVoteService(serviceVoteRepo, uniqueIDRepo, configService, questionRepo, answerRepo, commentCommonRepo, objService)
|
voteService := service.NewVoteService(serviceVoteRepo, configService, questionRepo, answerRepo, commentCommonRepo, objService)
|
||||||
voteController := controller.NewVoteController(voteService, rankService)
|
voteController := controller.NewVoteController(voteService, rankService)
|
||||||
followRepo := activity_common.NewFollowRepo(dataData, uniqueIDRepo, activityRepo)
|
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)
|
tagController := controller.NewTagController(tagService, tagCommonService, rankService)
|
||||||
followFollowRepo := activity.NewFollowRepo(dataData, uniqueIDRepo, activityRepo)
|
followFollowRepo := activity.NewFollowRepo(dataData, uniqueIDRepo, activityRepo)
|
||||||
followService := follow.NewFollowService(followFollowRepo, followRepo, tagCommonRepo)
|
followService := follow.NewFollowService(followFollowRepo, followRepo, tagCommonRepo)
|
||||||
|
@ -165,25 +167,23 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||||
answerCommon := answercommon.NewAnswerCommon(answerRepo)
|
answerCommon := answercommon.NewAnswerCommon(answerRepo)
|
||||||
metaRepo := meta.NewMetaRepo(dataData)
|
metaRepo := meta.NewMetaRepo(dataData)
|
||||||
metaService := meta2.NewMetaService(metaRepo)
|
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)
|
collectionService := service.NewCollectionService(collectionRepo, collectionGroupRepo, questionCommon)
|
||||||
collectionController := controller.NewCollectionController(collectionService)
|
collectionController := controller.NewCollectionController(collectionService)
|
||||||
answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo)
|
answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo, notificationQueueService)
|
||||||
questionActivityRepo := activity.NewQuestionActivityRepo(dataData, activityRepo, userRankRepo)
|
answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, configService)
|
||||||
answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, questionActivityRepo)
|
questionService := service.NewQuestionService(questionRepo, tagCommonService, questionCommon, userCommon, userRepo, revisionService, metaService, collectionCommon, answerActivityService, emailService, notificationQueueService, activityQueueService, siteInfoCommonService)
|
||||||
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, notificationQueueService, activityQueueService)
|
||||||
answerService := service.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService)
|
questionController := controller.NewQuestionController(questionService, answerService, rankService, siteInfoCommonService)
|
||||||
questionController := controller.NewQuestionController(questionService, answerService, rankService)
|
answerController := controller.NewAnswerController(answerService, rankService)
|
||||||
dashboardService := dashboard.NewDashboardService(questionRepo, answerRepo, commentCommonRepo, voteRepo, userRepo, reportRepo, configService, siteInfoCommonService, serviceConf, dataData)
|
|
||||||
answerController := controller.NewAnswerController(answerService, rankService, dashboardService)
|
|
||||||
searchParser := search_parser.NewSearchParser(tagCommonService, userCommon)
|
searchParser := search_parser.NewSearchParser(tagCommonService, userCommon)
|
||||||
searchRepo := search_common.NewSearchRepo(dataData, uniqueIDRepo, userCommon)
|
searchRepo := search_common.NewSearchRepo(dataData, uniqueIDRepo, userCommon)
|
||||||
searchService := service.NewSearchService(searchParser, searchRepo)
|
searchService := service.NewSearchService(searchParser, searchRepo)
|
||||||
searchController := controller.NewSearchController(searchService)
|
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)
|
revisionController := controller.NewRevisionController(serviceRevisionService, rankService)
|
||||||
rankController := controller.NewRankController(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)
|
reportAdminService := report_admin.NewReportAdminService(reportRepo, userCommon, answerRepo, questionRepo, commentCommonRepo, reportHandle, configService, objService)
|
||||||
controller_adminReportController := controller_admin.NewReportController(reportAdminService)
|
controller_adminReportController := controller_admin.NewReportController(reportAdminService)
|
||||||
userAdminRepo := user.NewUserAdminRepo(dataData, authRepo)
|
userAdminRepo := user.NewUserAdminRepo(dataData, authRepo)
|
||||||
|
@ -195,36 +195,38 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||||
themeController := controller_admin.NewThemeController()
|
themeController := controller_admin.NewThemeController()
|
||||||
siteInfoService := siteinfo.NewSiteInfoService(siteInfoRepo, siteInfoCommonService, emailService, tagCommonService, configService, questionCommon)
|
siteInfoService := siteinfo.NewSiteInfoService(siteInfoRepo, siteInfoCommonService, emailService, tagCommonService, configService, questionCommon)
|
||||||
siteInfoController := controller_admin.NewSiteInfoController(siteInfoService)
|
siteInfoController := controller_admin.NewSiteInfoController(siteInfoService)
|
||||||
siteinfoController := controller.NewSiteinfoController(siteInfoCommonService)
|
controllerSiteInfoController := controller.NewSiteInfoController(siteInfoCommonService)
|
||||||
notificationRepo := notification.NewNotificationRepo(dataData)
|
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)
|
notificationService := notification2.NewNotificationService(dataData, notificationRepo, notificationCommon, revisionService)
|
||||||
notificationController := controller.NewNotificationController(notificationService, rankService)
|
notificationController := controller.NewNotificationController(notificationService, rankService)
|
||||||
|
dashboardService := dashboard.NewDashboardService(questionRepo, answerRepo, commentCommonRepo, voteRepo, userRepo, reportRepo, configService, siteInfoCommonService, serviceConf, dataData)
|
||||||
dashboardController := controller.NewDashboardController(dashboardService)
|
dashboardController := controller.NewDashboardController(dashboardService)
|
||||||
|
uploaderService := uploader.NewUploaderService(serviceConf, siteInfoCommonService)
|
||||||
uploadController := controller.NewUploadController(uploaderService)
|
uploadController := controller.NewUploadController(uploaderService)
|
||||||
activityCommon := activity_common2.NewActivityCommon(activityRepo)
|
|
||||||
activityActivityRepo := activity.NewActivityRepo(dataData, configService)
|
activityActivityRepo := activity.NewActivityRepo(dataData, configService)
|
||||||
commentCommonService := comment_common.NewCommentCommonService(commentCommonRepo)
|
commentCommonService := comment_common.NewCommentCommonService(commentCommonRepo)
|
||||||
activityService := activity2.NewActivityService(activityActivityRepo, userCommon, activityCommon, tagCommonService, objService, commentCommonService, revisionService, metaService, configService)
|
activityService := activity2.NewActivityService(activityActivityRepo, userCommon, tagCommonService, objService, commentCommonService, revisionService, metaService, configService)
|
||||||
activityController := controller.NewActivityController(activityCommon, activityService)
|
activityController := controller.NewActivityController(activityService)
|
||||||
roleController := controller_admin.NewRoleController(roleService)
|
roleController := controller_admin.NewRoleController(roleService)
|
||||||
pluginConfigRepo := plugin_config.NewPluginConfigRepo(dataData)
|
pluginConfigRepo := plugin_config.NewPluginConfigRepo(dataData)
|
||||||
pluginCommonService := plugin_common.NewPluginCommonService(pluginConfigRepo, configService)
|
pluginCommonService := plugin_common.NewPluginCommonService(pluginConfigRepo, configService)
|
||||||
pluginController := controller_admin.NewPluginController(pluginCommonService)
|
pluginController := controller_admin.NewPluginController(pluginCommonService)
|
||||||
permissionController := controller.NewPermissionController(rankService)
|
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)
|
swaggerRouter := router.NewSwaggerRouter(swaggerConf)
|
||||||
uiRouter := router.NewUIRouter(siteinfoController, siteInfoCommonService)
|
uiRouter := router.NewUIRouter(controllerSiteInfoController, siteInfoCommonService)
|
||||||
authUserMiddleware := middleware.NewAuthUserMiddleware(authService, siteInfoCommonService)
|
authUserMiddleware := middleware.NewAuthUserMiddleware(authService, siteInfoCommonService)
|
||||||
avatarMiddleware := middleware.NewAvatarMiddleware(serviceConf, uploaderService)
|
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)
|
templateController := controller.NewTemplateController(templateRenderController, siteInfoCommonService)
|
||||||
templateRouter := router.NewTemplateRouter(templateController, templateRenderController, siteInfoController)
|
templateRouter := router.NewTemplateRouter(templateController, templateRenderController, siteInfoController)
|
||||||
connectorController := controller.NewConnectorController(siteInfoCommonService, emailService, userExternalLoginService)
|
connectorController := controller.NewConnectorController(siteInfoCommonService, emailService, userExternalLoginService)
|
||||||
userCenterLoginService := user_external_login2.NewUserCenterLoginService(userRepo, userCommon, userExternalLoginRepo, userActiveActivityRepo, siteInfoCommonService)
|
userCenterLoginService := user_external_login2.NewUserCenterLoginService(userRepo, userCommon, userExternalLoginRepo, userActiveActivityRepo, siteInfoCommonService)
|
||||||
userCenterController := controller.NewUserCenterController(userCenterLoginService, siteInfoCommonService)
|
userCenterController := controller.NewUserCenterController(userCenterLoginService, siteInfoCommonService)
|
||||||
pluginAPIRouter := router.NewPluginAPIRouter(connectorController, userCenterController)
|
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)
|
scheduledTaskManager := cron.NewScheduledTaskManager(siteInfoCommonService, questionService)
|
||||||
application := newApplication(serverConf, ginEngine, scheduledTaskManager)
|
application := newApplication(serverConf, ginEngine, scheduledTaskManager)
|
||||||
return application, func() {
|
return application, func() {
|
||||||
|
|
|
@ -49,7 +49,7 @@ const docTemplate = `{
|
||||||
"tags": [
|
"tags": [
|
||||||
"admin"
|
"admin"
|
||||||
],
|
],
|
||||||
"summary": "AdminSearchAnswerList",
|
"summary": "AdminAnswerPage admin answer page",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
@ -379,7 +379,7 @@ const docTemplate = `{
|
||||||
"tags": [
|
"tags": [
|
||||||
"admin"
|
"admin"
|
||||||
],
|
],
|
||||||
"summary": "AdminSearchList",
|
"summary": "AdminQuestionPage admin question page",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
@ -8186,7 +8186,7 @@ const docTemplate = `{
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"site_seo": {
|
"site_seo": {
|
||||||
"$ref": "#/definitions/schema.SiteSeoReq"
|
"$ref": "#/definitions/schema.SiteSeoResp"
|
||||||
},
|
},
|
||||||
"site_users": {
|
"site_users": {
|
||||||
"$ref": "#/definitions/schema.SiteUsersResp"
|
"$ref": "#/definitions/schema.SiteUsersResp"
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
"tags": [
|
"tags": [
|
||||||
"admin"
|
"admin"
|
||||||
],
|
],
|
||||||
"summary": "AdminSearchAnswerList",
|
"summary": "AdminAnswerPage admin answer page",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
@ -367,7 +367,7 @@
|
||||||
"tags": [
|
"tags": [
|
||||||
"admin"
|
"admin"
|
||||||
],
|
],
|
||||||
"summary": "AdminSearchList",
|
"summary": "AdminQuestionPage admin question page",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
@ -8174,7 +8174,7 @@
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"site_seo": {
|
"site_seo": {
|
||||||
"$ref": "#/definitions/schema.SiteSeoReq"
|
"$ref": "#/definitions/schema.SiteSeoResp"
|
||||||
},
|
},
|
||||||
"site_users": {
|
"site_users": {
|
||||||
"$ref": "#/definitions/schema.SiteUsersResp"
|
"$ref": "#/definitions/schema.SiteUsersResp"
|
||||||
|
|
|
@ -1507,7 +1507,7 @@ definitions:
|
||||||
revision:
|
revision:
|
||||||
type: string
|
type: string
|
||||||
site_seo:
|
site_seo:
|
||||||
$ref: '#/definitions/schema.SiteSeoReq'
|
$ref: '#/definitions/schema.SiteSeoResp'
|
||||||
site_users:
|
site_users:
|
||||||
$ref: '#/definitions/schema.SiteUsersResp'
|
$ref: '#/definitions/schema.SiteUsersResp'
|
||||||
theme:
|
theme:
|
||||||
|
@ -2335,7 +2335,7 @@ paths:
|
||||||
$ref: '#/definitions/handler.RespBody'
|
$ref: '#/definitions/handler.RespBody'
|
||||||
security:
|
security:
|
||||||
- ApiKeyAuth: []
|
- ApiKeyAuth: []
|
||||||
summary: AdminSearchAnswerList
|
summary: AdminAnswerPage admin answer page
|
||||||
tags:
|
tags:
|
||||||
- admin
|
- admin
|
||||||
/answer/admin/api/answer/status:
|
/answer/admin/api/answer/status:
|
||||||
|
@ -2533,7 +2533,7 @@ paths:
|
||||||
$ref: '#/definitions/handler.RespBody'
|
$ref: '#/definitions/handler.RespBody'
|
||||||
security:
|
security:
|
||||||
- ApiKeyAuth: []
|
- ApiKeyAuth: []
|
||||||
summary: AdminSearchList
|
summary: AdminQuestionPage admin question page
|
||||||
tags:
|
tags:
|
||||||
- admin
|
- admin
|
||||||
/answer/admin/api/question/status:
|
/answer/admin/api/question/status:
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -48,7 +48,6 @@ require (
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
modernc.org/sqlite v1.14.2
|
modernc.org/sqlite v1.14.2
|
||||||
xorm.io/builder v0.3.12
|
xorm.io/builder v0.3.12
|
||||||
xorm.io/core v0.7.3
|
|
||||||
xorm.io/xorm v1.3.2
|
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 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
|
||||||
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
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.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 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
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=
|
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 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
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-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.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 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
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.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.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.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.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
google.golang.org/appengine v1.6.6/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.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||||
xorm.io/builder v0.3.12 h1:ASZYX7fQmy+o8UJdhlLHSW57JDOkM8DNhcAF5d0LiJM=
|
xorm.io/builder v0.3.12 h1:ASZYX7fQmy+o8UJdhlLHSW57JDOkM8DNhcAF5d0LiJM=
|
||||||
xorm.io/builder v0.3.12/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
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 h1:uTRRKF2jYzbZ5nsofXVUx6ncMaek+SHjWYtCXyZo1oM=
|
||||||
xorm.io/xorm v1.3.2/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw=
|
xorm.io/xorm v1.3.2/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw=
|
||||||
|
|
|
@ -1417,6 +1417,7 @@ ui:
|
||||||
change: Change
|
change: Change
|
||||||
all: All
|
all: All
|
||||||
staff: Staff
|
staff: Staff
|
||||||
|
more: More
|
||||||
inactive: Inactive
|
inactive: Inactive
|
||||||
suspended: Suspended
|
suspended: Suspended
|
||||||
deleted: Deleted
|
deleted: Deleted
|
||||||
|
|
|
@ -16,4 +16,7 @@ const (
|
||||||
ConfigKEY2ContentCacheKeyPrefix = "answer:config:key:"
|
ConfigKEY2ContentCacheKeyPrefix = "answer:config:key:"
|
||||||
ConnectorUserExternalInfoCacheKey = "answer:connector:"
|
ConnectorUserExternalInfoCacheKey = "answer:connector:"
|
||||||
ConnectorUserExternalInfoCacheTime = 10 * time.Minute
|
ConnectorUserExternalInfoCacheTime = 10 * time.Minute
|
||||||
|
SiteMapQuestionCacheKeyPrefix = "answer:sitemap:question:%d"
|
||||||
|
SiteMapQuestionCacheTime = time.Hour
|
||||||
|
SitemapMaxSize = 50000
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,4 +2,5 @@ package constant
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AcceptLanguageFlag = "Accept-Language"
|
AcceptLanguageFlag = "Accept-Language"
|
||||||
|
ShortIDFlag = "Short-ID-Enabled"
|
||||||
)
|
)
|
|
@ -7,3 +7,14 @@ const (
|
||||||
AvatarTypeGravatar = "gravatar"
|
AvatarTypeGravatar = "gravatar"
|
||||||
AvatarTypeCustom = "custom"
|
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
|
// ScheduledTaskManager scheduled task manager
|
||||||
type ScheduledTaskManager struct {
|
type ScheduledTaskManager struct {
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||||
questionService *service.QuestionService
|
questionService *service.QuestionService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewScheduledTaskManager new scheduled task manager
|
// NewScheduledTaskManager new scheduled task manager
|
||||||
func NewScheduledTaskManager(
|
func NewScheduledTaskManager(
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService,
|
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||||
questionService *service.QuestionService,
|
questionService *service.QuestionService,
|
||||||
) *ScheduledTaskManager {
|
) *ScheduledTaskManager {
|
||||||
manager := &ScheduledTaskManager{
|
manager := &ScheduledTaskManager{
|
||||||
|
|
|
@ -12,9 +12,9 @@ import (
|
||||||
"github.com/segmentfault/pacman/contrib/cache/memory"
|
"github.com/segmentfault/pacman/contrib/cache/memory"
|
||||||
"github.com/segmentfault/pacman/log"
|
"github.com/segmentfault/pacman/log"
|
||||||
_ "modernc.org/sqlite"
|
_ "modernc.org/sqlite"
|
||||||
"xorm.io/core"
|
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
ormlog "xorm.io/xorm/log"
|
ormlog "xorm.io/xorm/log"
|
||||||
|
"xorm.io/xorm/names"
|
||||||
"xorm.io/xorm/schemas"
|
"xorm.io/xorm/schemas"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ func NewDB(debug bool, dataConf *Database) (*xorm.Engine, error) {
|
||||||
if dataConf.ConnMaxLifeTime > 0 {
|
if dataConf.ConnMaxLifeTime > 0 {
|
||||||
engine.SetConnMaxLifetime(time.Duration(dataConf.ConnMaxLifeTime) * time.Second)
|
engine.SetConnMaxLifetime(time.Duration(dataConf.ConnMaxLifeTime) * time.Second)
|
||||||
}
|
}
|
||||||
engine.SetColumnMapper(core.GonicMapper{})
|
engine.SetColumnMapper(names.GonicMapper{})
|
||||||
return engine, nil
|
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/schema"
|
||||||
"github.com/answerdev/answer/internal/service/role"
|
"github.com/answerdev/answer/internal/service/role"
|
||||||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
"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/handler"
|
||||||
"github.com/answerdev/answer/internal/base/reason"
|
"github.com/answerdev/answer/internal/base/reason"
|
||||||
"github.com/answerdev/answer/internal/entity"
|
"github.com/answerdev/answer/internal/entity"
|
||||||
"github.com/answerdev/answer/internal/service/auth"
|
"github.com/answerdev/answer/internal/service/auth"
|
||||||
"github.com/answerdev/answer/pkg/converter"
|
"github.com/answerdev/answer/pkg/converter"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/segmentfault/pacman/errors"
|
"github.com/segmentfault/pacman/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,13 +21,13 @@ var ctxUUIDKey = "ctxUuidKey"
|
||||||
// AuthUserMiddleware auth user middleware
|
// AuthUserMiddleware auth user middleware
|
||||||
type AuthUserMiddleware struct {
|
type AuthUserMiddleware struct {
|
||||||
authService *auth.AuthService
|
authService *auth.AuthService
|
||||||
siteInfoCommonService *siteinfo_common.SiteInfoCommonService
|
siteInfoCommonService siteinfo_common.SiteInfoCommonService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAuthUserMiddleware new auth user middleware
|
// NewAuthUserMiddleware new auth user middleware
|
||||||
func NewAuthUserMiddleware(
|
func NewAuthUserMiddleware(
|
||||||
authService *auth.AuthService,
|
authService *auth.AuthService,
|
||||||
siteInfoCommonService *siteinfo_common.SiteInfoCommonService) *AuthUserMiddleware {
|
siteInfoCommonService siteinfo_common.SiteInfoCommonService) *AuthUserMiddleware {
|
||||||
return &AuthUserMiddleware{
|
return &AuthUserMiddleware{
|
||||||
authService: authService,
|
authService: authService,
|
||||||
siteInfoCommonService: siteInfoCommonService,
|
siteInfoCommonService: siteInfoCommonService,
|
||||||
|
|
|
@ -32,31 +32,28 @@ func NewAvatarMiddleware(serviceConfig *service_config.ServiceConfig,
|
||||||
|
|
||||||
func (am *AvatarMiddleware) AvatarThumb() gin.HandlerFunc {
|
func (am *AvatarMiddleware) AvatarThumb() gin.HandlerFunc {
|
||||||
return func(ctx *gin.Context) {
|
return func(ctx *gin.Context) {
|
||||||
u := ctx.Request.RequestURI
|
uri := ctx.Request.RequestURI
|
||||||
if strings.Contains(u, "/uploads/avatar/") {
|
if strings.Contains(uri, "/uploads/avatar/") {
|
||||||
sizeStr := ctx.Query("s")
|
size := converter.StringToInt(ctx.Query("s"))
|
||||||
size := converter.StringToInt(sizeStr)
|
uriWithoutQuery, _ := url.Parse(uri)
|
||||||
uUrl, err := url.Parse(u)
|
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 {
|
if err != nil {
|
||||||
ctx.Next()
|
log.Error(err)
|
||||||
|
ctx.Abort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
avatarFile, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
ctx.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, urlfileName := filepath.Split(uUrl.Path)
|
ctx.Header("content-type", fmt.Sprintf("image/%s", strings.TrimLeft(path.Ext(filePath), ".")))
|
||||||
uploadPath := am.serviceConfig.UploadPath
|
_, err = ctx.Writer.Write(avatarFile)
|
||||||
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))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -64,7 +61,7 @@ func (am *AvatarMiddleware) AvatarThumb() gin.HandlerFunc {
|
||||||
return
|
return
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
uUrl, err := url.Parse(u)
|
uUrl, err := url.Parse(uri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Next()
|
ctx.Next()
|
||||||
return
|
return
|
||||||
|
|
|
@ -8,4 +8,5 @@ import (
|
||||||
var ProviderSetMiddleware = wire.NewSet(
|
var ProviderSetMiddleware = wire.NewSet(
|
||||||
NewAuthUserMiddleware,
|
NewAuthUserMiddleware,
|
||||||
NewAvatarMiddleware,
|
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,
|
viewRouter *router.UIRouter,
|
||||||
authUserMiddleware *middleware.AuthUserMiddleware,
|
authUserMiddleware *middleware.AuthUserMiddleware,
|
||||||
avatarMiddleware *middleware.AvatarMiddleware,
|
avatarMiddleware *middleware.AvatarMiddleware,
|
||||||
|
shortIDMiddleware *middleware.ShortIDMiddleware,
|
||||||
templateRouter *router.TemplateRouter,
|
templateRouter *router.TemplateRouter,
|
||||||
pluginAPIRouter *router.PluginAPIRouter,
|
pluginAPIRouter *router.PluginAPIRouter,
|
||||||
) *gin.Engine {
|
) *gin.Engine {
|
||||||
|
@ -30,7 +31,7 @@ func NewHTTPServer(debug bool,
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
}
|
}
|
||||||
r := gin.New()
|
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") })
|
r.GET("/healthz", func(ctx *gin.Context) { ctx.String(200, "OK") })
|
||||||
|
|
||||||
html, _ := fs.Sub(ui.Template, "template")
|
html, _ := fs.Sub(ui.Template, "template")
|
||||||
|
|
|
@ -5,22 +5,19 @@ import (
|
||||||
"github.com/answerdev/answer/internal/base/middleware"
|
"github.com/answerdev/answer/internal/base/middleware"
|
||||||
"github.com/answerdev/answer/internal/schema"
|
"github.com/answerdev/answer/internal/schema"
|
||||||
"github.com/answerdev/answer/internal/service/activity"
|
"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/internal/service/role"
|
||||||
"github.com/answerdev/answer/pkg/uid"
|
"github.com/answerdev/answer/pkg/uid"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ActivityController struct {
|
type ActivityController struct {
|
||||||
activityCommonService *activity_common.ActivityCommon
|
|
||||||
activityService *activity.ActivityService
|
activityService *activity.ActivityService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewActivityController new activity controller.
|
// NewActivityController new activity controller.
|
||||||
func NewActivityController(
|
func NewActivityController(
|
||||||
activityCommonService *activity_common.ActivityCommon,
|
|
||||||
activityService *activity.ActivityService) *ActivityController {
|
activityService *activity.ActivityService) *ActivityController {
|
||||||
return &ActivityController{activityCommonService: activityCommonService, activityService: activityService}
|
return &ActivityController{activityService: activityService}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetObjectTimeline get object timeline
|
// GetObjectTimeline get object timeline
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"github.com/answerdev/answer/internal/base/reason"
|
"github.com/answerdev/answer/internal/base/reason"
|
||||||
"github.com/answerdev/answer/internal/schema"
|
"github.com/answerdev/answer/internal/schema"
|
||||||
"github.com/answerdev/answer/internal/service"
|
"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/permission"
|
||||||
"github.com/answerdev/answer/internal/service/rank"
|
"github.com/answerdev/answer/internal/service/rank"
|
||||||
"github.com/answerdev/answer/pkg/uid"
|
"github.com/answerdev/answer/pkg/uid"
|
||||||
|
@ -20,18 +19,15 @@ import (
|
||||||
type AnswerController struct {
|
type AnswerController struct {
|
||||||
answerService *service.AnswerService
|
answerService *service.AnswerService
|
||||||
rankService *rank.RankService
|
rankService *rank.RankService
|
||||||
dashboardService *dashboard.DashboardService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAnswerController new controller
|
// NewAnswerController new controller
|
||||||
func NewAnswerController(answerService *service.AnswerService,
|
func NewAnswerController(answerService *service.AnswerService,
|
||||||
rankService *rank.RankService,
|
rankService *rank.RankService,
|
||||||
dashboardService *dashboard.DashboardService,
|
|
||||||
) *AnswerController {
|
) *AnswerController {
|
||||||
return &AnswerController{
|
return &AnswerController{
|
||||||
answerService: answerService,
|
answerService: answerService,
|
||||||
rankService: rankService,
|
rankService: rankService,
|
||||||
dashboardService: dashboardService,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,14 +23,14 @@ const (
|
||||||
|
|
||||||
// ConnectorController comment controller
|
// ConnectorController comment controller
|
||||||
type ConnectorController struct {
|
type ConnectorController struct {
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||||
userExternalService *user_external_login.UserExternalLoginService
|
userExternalService *user_external_login.UserExternalLoginService
|
||||||
emailService *export.EmailService
|
emailService *export.EmailService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConnectorController new controller
|
// NewConnectorController new controller
|
||||||
func NewConnectorController(
|
func NewConnectorController(
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService,
|
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||||
emailService *export.EmailService,
|
emailService *export.EmailService,
|
||||||
userExternalService *user_external_login.UserExternalLoginService,
|
userExternalService *user_external_login.UserExternalLoginService,
|
||||||
) *ConnectorController {
|
) *ConnectorController {
|
||||||
|
|
|
@ -19,7 +19,7 @@ var ProviderSetController = wire.NewSet(
|
||||||
NewRankController,
|
NewRankController,
|
||||||
NewReasonController,
|
NewReasonController,
|
||||||
NewNotificationController,
|
NewNotificationController,
|
||||||
NewSiteinfoController,
|
NewSiteInfoController,
|
||||||
NewDashboardController,
|
NewDashboardController,
|
||||||
NewUploadController,
|
NewUploadController,
|
||||||
NewActivityController,
|
NewActivityController,
|
||||||
|
|
|
@ -7,12 +7,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type DashboardController struct {
|
type DashboardController struct {
|
||||||
dashboardService *dashboard.DashboardService
|
dashboardService dashboard.DashboardService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDashboardController new controller
|
// NewDashboardController new controller
|
||||||
func NewDashboardController(
|
func NewDashboardController(
|
||||||
dashboardService *dashboard.DashboardService,
|
dashboardService dashboard.DashboardService,
|
||||||
) *DashboardController {
|
) *DashboardController {
|
||||||
return &DashboardController{
|
return &DashboardController{
|
||||||
dashboardService: dashboardService,
|
dashboardService: dashboardService,
|
||||||
|
@ -29,7 +29,7 @@ func NewDashboardController(
|
||||||
// @Router /answer/admin/api/dashboard [get]
|
// @Router /answer/admin/api/dashboard [get]
|
||||||
// @Success 200 {object} handler.RespBody
|
// @Success 200 {object} handler.RespBody
|
||||||
func (ac *DashboardController) DashboardInfo(ctx *gin.Context) {
|
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{
|
handler.HandleResponse(ctx, err, gin.H{
|
||||||
"info": info,
|
"info": info,
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,11 +12,11 @@ import (
|
||||||
|
|
||||||
type LangController struct {
|
type LangController struct {
|
||||||
translator i18n.Translator
|
translator i18n.Translator
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLangController new language controller.
|
// 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}
|
return &LangController{translator: tr, siteInfoService: siteInfoService}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,13 +22,13 @@ const (
|
||||||
// UserCenterController comment controller
|
// UserCenterController comment controller
|
||||||
type UserCenterController struct {
|
type UserCenterController struct {
|
||||||
userCenterLoginService *user_external_login.UserCenterLoginService
|
userCenterLoginService *user_external_login.UserCenterLoginService
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUserCenterController new controller
|
// NewUserCenterController new controller
|
||||||
func NewUserCenterController(
|
func NewUserCenterController(
|
||||||
userCenterLoginService *user_external_login.UserCenterLoginService,
|
userCenterLoginService *user_external_login.UserCenterLoginService,
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService,
|
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||||
) *UserCenterController {
|
) *UserCenterController {
|
||||||
return &UserCenterController{
|
return &UserCenterController{
|
||||||
userCenterLoginService: userCenterLoginService,
|
userCenterLoginService: userCenterLoginService,
|
||||||
|
|
|
@ -7,11 +7,11 @@ import (
|
||||||
"github.com/answerdev/answer/internal/base/reason"
|
"github.com/answerdev/answer/internal/base/reason"
|
||||||
"github.com/answerdev/answer/internal/base/translator"
|
"github.com/answerdev/answer/internal/base/translator"
|
||||||
"github.com/answerdev/answer/internal/base/validator"
|
"github.com/answerdev/answer/internal/base/validator"
|
||||||
"github.com/answerdev/answer/internal/entity"
|
|
||||||
"github.com/answerdev/answer/internal/schema"
|
"github.com/answerdev/answer/internal/schema"
|
||||||
"github.com/answerdev/answer/internal/service"
|
"github.com/answerdev/answer/internal/service"
|
||||||
"github.com/answerdev/answer/internal/service/permission"
|
"github.com/answerdev/answer/internal/service/permission"
|
||||||
"github.com/answerdev/answer/internal/service/rank"
|
"github.com/answerdev/answer/internal/service/rank"
|
||||||
|
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||||
"github.com/answerdev/answer/pkg/uid"
|
"github.com/answerdev/answer/pkg/uid"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
|
@ -23,6 +23,7 @@ type QuestionController struct {
|
||||||
questionService *service.QuestionService
|
questionService *service.QuestionService
|
||||||
answerService *service.AnswerService
|
answerService *service.AnswerService
|
||||||
rankService *rank.RankService
|
rankService *rank.RankService
|
||||||
|
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewQuestionController new controller
|
// NewQuestionController new controller
|
||||||
|
@ -30,11 +31,13 @@ func NewQuestionController(
|
||||||
questionService *service.QuestionService,
|
questionService *service.QuestionService,
|
||||||
answerService *service.AnswerService,
|
answerService *service.AnswerService,
|
||||||
rankService *rank.RankService,
|
rankService *rank.RankService,
|
||||||
|
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||||
) *QuestionController {
|
) *QuestionController {
|
||||||
return &QuestionController{
|
return &QuestionController{
|
||||||
questionService: questionService,
|
questionService: questionService,
|
||||||
answerService: answerService,
|
answerService: answerService,
|
||||||
rankService: rankService,
|
rankService: rankService,
|
||||||
|
siteInfoService: siteInfoService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +223,9 @@ func (qc *QuestionController) GetQuestion(ctx *gin.Context) {
|
||||||
handler.HandleResponse(ctx, err, nil)
|
handler.HandleResponse(ctx, err, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
info.ID = uid.EnShortID(info.ID)
|
info.ID = uid.EnShortID(info.ID)
|
||||||
|
}
|
||||||
handler.HandleResponse(ctx, nil, info)
|
handler.HandleResponse(ctx, nil, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -703,8 +708,8 @@ func (qc *QuestionController) PersonalCollectionPage(ctx *gin.Context) {
|
||||||
handler.HandleResponse(ctx, err, resp)
|
handler.HandleResponse(ctx, err, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminSearchList godoc
|
// AdminQuestionPage admin question page
|
||||||
// @Summary AdminSearchList
|
// @Summary AdminQuestionPage admin question page
|
||||||
// @Description Status:[available,closed,deleted]
|
// @Description Status:[available,closed,deleted]
|
||||||
// @Tags admin
|
// @Tags admin
|
||||||
// @Accept json
|
// @Accept json
|
||||||
|
@ -716,21 +721,19 @@ func (qc *QuestionController) PersonalCollectionPage(ctx *gin.Context) {
|
||||||
// @Param query query string false "question id or title"
|
// @Param query query string false "question id or title"
|
||||||
// @Success 200 {object} handler.RespBody
|
// @Success 200 {object} handler.RespBody
|
||||||
// @Router /answer/admin/api/question/page [get]
|
// @Router /answer/admin/api/question/page [get]
|
||||||
func (qc *QuestionController) AdminSearchList(ctx *gin.Context) {
|
func (qc *QuestionController) AdminQuestionPage(ctx *gin.Context) {
|
||||||
req := &schema.AdminQuestionSearch{}
|
req := &schema.AdminQuestionPageReq{}
|
||||||
if handler.BindAndCheck(ctx, req) {
|
if handler.BindAndCheck(ctx, req) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userID := middleware.GetLoginUserIDFromContext(ctx)
|
|
||||||
questionList, count, err := qc.questionService.AdminSearchList(ctx, req, userID)
|
req.LoginUserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
handler.HandleResponse(ctx, err, gin.H{
|
resp, err := qc.questionService.AdminQuestionPage(ctx, req)
|
||||||
"list": questionList,
|
handler.HandleResponse(ctx, err, resp)
|
||||||
"count": count,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminSearchAnswerList godoc
|
// AdminAnswerPage admin answer page
|
||||||
// @Summary AdminSearchAnswerList
|
// @Summary AdminAnswerPage admin answer page
|
||||||
// @Description Status:[available,deleted]
|
// @Description Status:[available,deleted]
|
||||||
// @Tags admin
|
// @Tags admin
|
||||||
// @Accept json
|
// @Accept json
|
||||||
|
@ -743,21 +746,15 @@ func (qc *QuestionController) AdminSearchList(ctx *gin.Context) {
|
||||||
// @Param question_id query string false "question id"
|
// @Param question_id query string false "question id"
|
||||||
// @Success 200 {object} handler.RespBody
|
// @Success 200 {object} handler.RespBody
|
||||||
// @Router /answer/admin/api/answer/page [get]
|
// @Router /answer/admin/api/answer/page [get]
|
||||||
func (qc *QuestionController) AdminSearchAnswerList(ctx *gin.Context) {
|
func (qc *QuestionController) AdminAnswerPage(ctx *gin.Context) {
|
||||||
req := &entity.AdminAnswerSearch{}
|
req := &schema.AdminAnswerPageReq{}
|
||||||
if handler.BindAndCheck(ctx, req) {
|
if handler.BindAndCheck(ctx, req) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
req.QuestionID = uid.DeShortID(req.QuestionID)
|
|
||||||
if req.QuestionID == "0" {
|
req.LoginUserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
req.QuestionID = ""
|
resp, err := qc.questionService.AdminAnswerPage(ctx, req)
|
||||||
}
|
handler.HandleResponse(ctx, err, resp)
|
||||||
userID := middleware.GetLoginUserIDFromContext(ctx)
|
|
||||||
questionList, count, err := qc.questionService.AdminSearchAnswerList(ctx, req, userID)
|
|
||||||
handler.HandleResponse(ctx, err, gin.H{
|
|
||||||
"list": questionList,
|
|
||||||
"count": count,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminSetQuestionStatus godoc
|
// AdminSetQuestionStatus godoc
|
||||||
|
|
|
@ -11,13 +11,13 @@ import (
|
||||||
"github.com/segmentfault/pacman/log"
|
"github.com/segmentfault/pacman/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SiteinfoController struct {
|
type SiteInfoController struct {
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSiteinfoController new siteinfo controller.
|
// NewSiteInfoController new site info controller.
|
||||||
func NewSiteinfoController(siteInfoService *siteinfo_common.SiteInfoCommonService) *SiteinfoController {
|
func NewSiteInfoController(siteInfoService siteinfo_common.SiteInfoCommonService) *SiteInfoController {
|
||||||
return &SiteinfoController{
|
return &SiteInfoController{
|
||||||
siteInfoService: siteInfoService,
|
siteInfoService: siteInfoService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ func NewSiteinfoController(siteInfoService *siteinfo_common.SiteInfoCommonServic
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} handler.RespBody{data=schema.SiteInfoResp}
|
// @Success 200 {object} handler.RespBody{data=schema.SiteInfoResp}
|
||||||
// @Router /answer/api/v1/siteinfo [get]
|
// @Router /answer/api/v1/siteinfo [get]
|
||||||
func (sc *SiteinfoController) GetSiteInfo(ctx *gin.Context) {
|
func (sc *SiteInfoController) GetSiteInfo(ctx *gin.Context) {
|
||||||
var err error
|
var err error
|
||||||
resp := &schema.SiteInfoResp{Version: constant.Version, Revision: constant.Revision}
|
resp := &schema.SiteInfoResp{Version: constant.Version, Revision: constant.Revision}
|
||||||
resp.General, err = sc.siteInfoService.GetSiteGeneral(ctx)
|
resp.General, err = sc.siteInfoService.GetSiteGeneral(ctx)
|
||||||
|
@ -80,7 +80,7 @@ func (sc *SiteinfoController) GetSiteInfo(ctx *gin.Context) {
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} handler.RespBody{data=schema.GetSiteLegalInfoResp}
|
// @Success 200 {object} handler.RespBody{data=schema.GetSiteLegalInfoResp}
|
||||||
// @Router /answer/api/v1/siteinfo/legal [get]
|
// @Router /answer/api/v1/siteinfo/legal [get]
|
||||||
func (sc *SiteinfoController) GetSiteLegalInfo(ctx *gin.Context) {
|
func (sc *SiteInfoController) GetSiteLegalInfo(ctx *gin.Context) {
|
||||||
req := &schema.GetSiteLegalInfoReq{}
|
req := &schema.GetSiteLegalInfoReq{}
|
||||||
if handler.BindAndCheck(ctx, req) {
|
if handler.BindAndCheck(ctx, req) {
|
||||||
return
|
return
|
||||||
|
@ -102,7 +102,7 @@ func (sc *SiteinfoController) GetSiteLegalInfo(ctx *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetManifestJson get manifest.json
|
// GetManifestJson get manifest.json
|
||||||
func (sc *SiteinfoController) GetManifestJson(ctx *gin.Context) {
|
func (sc *SiteInfoController) GetManifestJson(ctx *gin.Context) {
|
||||||
favicon := "favicon.ico"
|
favicon := "favicon.ico"
|
||||||
resp := &schema.GetManifestJsonResp{
|
resp := &schema.GetManifestJsonResp{
|
||||||
ManifestVersion: 3,
|
ManifestVersion: 3,
|
||||||
|
|
|
@ -30,13 +30,13 @@ type TemplateController struct {
|
||||||
scriptPath string
|
scriptPath string
|
||||||
cssPath string
|
cssPath string
|
||||||
templateRenderController *templaterender.TemplateRenderController
|
templateRenderController *templaterender.TemplateRenderController
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTemplateController new controller
|
// NewTemplateController new controller
|
||||||
func NewTemplateController(
|
func NewTemplateController(
|
||||||
templateRenderController *templaterender.TemplateRenderController,
|
templateRenderController *templaterender.TemplateRenderController,
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService,
|
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||||
) *TemplateController {
|
) *TemplateController {
|
||||||
script, css := GetStyle()
|
script, css := GetStyle()
|
||||||
return &TemplateController{
|
return &TemplateController{
|
||||||
|
@ -116,7 +116,7 @@ func (tc *TemplateController) Index(ctx *gin.Context) {
|
||||||
siteInfo.Canonical = siteInfo.General.SiteUrl
|
siteInfo.Canonical = siteInfo.General.SiteUrl
|
||||||
|
|
||||||
UrlUseTitle := false
|
UrlUseTitle := false
|
||||||
if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionIDAndTitle {
|
if siteInfo.SiteSeo.PermaLink == constant.PermaLinkQuestionIDAndTitle {
|
||||||
UrlUseTitle = true
|
UrlUseTitle = true
|
||||||
}
|
}
|
||||||
siteInfo.Title = ""
|
siteInfo.Title = ""
|
||||||
|
@ -149,7 +149,7 @@ func (tc *TemplateController) QuestionList(ctx *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
UrlUseTitle := false
|
UrlUseTitle := false
|
||||||
if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionIDAndTitle {
|
if siteInfo.SiteSeo.PermaLink == constant.PermaLinkQuestionIDAndTitle {
|
||||||
UrlUseTitle = true
|
UrlUseTitle = true
|
||||||
}
|
}
|
||||||
siteInfo.Title = fmt.Sprintf("Questions - %s", siteInfo.General.Name)
|
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")
|
id := ctx.Param("id")
|
||||||
title := ctx.Param("title")
|
title := ctx.Param("title")
|
||||||
titleIsAnswerID := false
|
titleIsAnswerID := false
|
||||||
NeedChangeShortID := false
|
needChangeShortID := false
|
||||||
|
|
||||||
|
siteSeo, err := tc.siteInfoService.GetSiteSeo(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
isShortID := uid.IsShortID(id)
|
isShortID := uid.IsShortID(id)
|
||||||
if uid.ShortIDSwitch {
|
if siteSeo.IsShortLink() {
|
||||||
if !isShortID {
|
if !isShortID {
|
||||||
id = uid.EnShortID(id)
|
id = uid.EnShortID(id)
|
||||||
NeedChangeShortID = true
|
needChangeShortID = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if isShortID {
|
if isShortID {
|
||||||
NeedChangeShortID = true
|
needChangeShortID = true
|
||||||
id = uid.DeShortID(id)
|
id = uid.DeShortID(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -186,11 +191,11 @@ func (tc *TemplateController) QuestionInfoeRdirect(ctx *gin.Context, siteInfo *s
|
||||||
}
|
}
|
||||||
siteInfo = tc.SiteInfo(ctx)
|
siteInfo = tc.SiteInfo(ctx)
|
||||||
url = fmt.Sprintf("%s/questions/%s", siteInfo.General.SiteUrl, id)
|
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 {
|
if len(ctx.Request.URL.Query()) > 0 {
|
||||||
url = fmt.Sprintf("%s?%s", url, ctx.Request.URL.RawQuery)
|
url = fmt.Sprintf("%s?%s", url, ctx.Request.URL.RawQuery)
|
||||||
}
|
}
|
||||||
if NeedChangeShortID {
|
if needChangeShortID {
|
||||||
return true, url
|
return true, url
|
||||||
}
|
}
|
||||||
//not have title
|
//not have title
|
||||||
|
@ -216,7 +221,7 @@ func (tc *TemplateController) QuestionInfoeRdirect(ctx *gin.Context, siteInfo *s
|
||||||
}
|
}
|
||||||
//have title
|
//have title
|
||||||
if len(title) > 0 && !titleIsAnswerID && correctTitle {
|
if len(title) > 0 && !titleIsAnswerID && correctTitle {
|
||||||
if NeedChangeShortID {
|
if needChangeShortID {
|
||||||
return true, url
|
return true, url
|
||||||
}
|
}
|
||||||
return false, ""
|
return false, ""
|
||||||
|
@ -277,9 +282,11 @@ func (tc *TemplateController) QuestionInfo(ctx *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// comments
|
// comments
|
||||||
objectIDs := []string{id}
|
|
||||||
|
objectIDs := []string{uid.DeShortID(id)}
|
||||||
for _, answer := range answers {
|
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)
|
comments, err := tc.templateRenderController.CommentList(ctx, objectIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -287,7 +294,7 @@ func (tc *TemplateController) QuestionInfo(ctx *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
siteInfo.Canonical = fmt.Sprintf("%s/questions/%s/%s", siteInfo.General.SiteUrl, id, encodeTitle)
|
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)
|
siteInfo.Canonical = fmt.Sprintf("%s/questions/%s", siteInfo.General.SiteUrl, id)
|
||||||
}
|
}
|
||||||
jsonLD := &schema.QAPageJsonLD{}
|
jsonLD := &schema.QAPageJsonLD{}
|
||||||
|
@ -402,7 +409,7 @@ func (tc *TemplateController) TagInfo(ctx *gin.Context) {
|
||||||
siteInfo.Keywords = taginifo.DisplayName
|
siteInfo.Keywords = taginifo.DisplayName
|
||||||
|
|
||||||
UrlUseTitle := false
|
UrlUseTitle := false
|
||||||
if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionIDAndTitle {
|
if siteInfo.SiteSeo.PermaLink == constant.PermaLinkQuestionIDAndTitle {
|
||||||
UrlUseTitle = true
|
UrlUseTitle = true
|
||||||
}
|
}
|
||||||
siteInfo.Title = fmt.Sprintf("'%s' Questions - %s", taginifo.DisplayName, siteInfo.General.Name)
|
siteInfo.Title = fmt.Sprintf("'%s' Questions - %s", taginifo.DisplayName, siteInfo.General.Name)
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
package templaterender
|
package templaterender
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
questioncommon "github.com/answerdev/answer/internal/service/question_common"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/data"
|
|
||||||
"github.com/answerdev/answer/internal/service/comment"
|
"github.com/answerdev/answer/internal/service/comment"
|
||||||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||||
|
"github.com/google/wire"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/schema"
|
"github.com/answerdev/answer/internal/schema"
|
||||||
"github.com/answerdev/answer/internal/service"
|
"github.com/answerdev/answer/internal/service"
|
||||||
"github.com/answerdev/answer/internal/service/tag"
|
"github.com/answerdev/answer/internal/service/tag"
|
||||||
"github.com/google/wire"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProviderSetTemplateRenderController is template render controller providers.
|
// ProviderSetTemplateRenderController is template render controller providers.
|
||||||
|
@ -24,8 +24,8 @@ type TemplateRenderController struct {
|
||||||
tagService *tag.TagService
|
tagService *tag.TagService
|
||||||
answerService *service.AnswerService
|
answerService *service.AnswerService
|
||||||
commentService *comment.CommentService
|
commentService *comment.CommentService
|
||||||
data *data.Data
|
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
questionRepo questioncommon.QuestionRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTemplateRenderController(
|
func NewTemplateRenderController(
|
||||||
|
@ -34,9 +34,8 @@ func NewTemplateRenderController(
|
||||||
tagService *tag.TagService,
|
tagService *tag.TagService,
|
||||||
answerService *service.AnswerService,
|
answerService *service.AnswerService,
|
||||||
commentService *comment.CommentService,
|
commentService *comment.CommentService,
|
||||||
data *data.Data,
|
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService,
|
questionRepo questioncommon.QuestionRepo,
|
||||||
|
|
||||||
) *TemplateRenderController {
|
) *TemplateRenderController {
|
||||||
return &TemplateRenderController{
|
return &TemplateRenderController{
|
||||||
questionService: questionService,
|
questionService: questionService,
|
||||||
|
@ -44,7 +43,7 @@ func NewTemplateRenderController(
|
||||||
tagService: tagService,
|
tagService: tagService,
|
||||||
answerService: answerService,
|
answerService: answerService,
|
||||||
commentService: commentService,
|
commentService: commentService,
|
||||||
data: data,
|
questionRepo: questionRepo,
|
||||||
siteInfoService: siteInfoService,
|
siteInfoService: siteInfoService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
package templaterender
|
|
|
@ -1,11 +1,11 @@
|
||||||
package templaterender
|
package templaterender
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/answerdev/answer/internal/base/constant"
|
||||||
"github.com/answerdev/answer/internal/schema"
|
"github.com/answerdev/answer/internal/schema"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/segmentfault/pacman/log"
|
"github.com/segmentfault/pacman/log"
|
||||||
|
@ -31,48 +31,46 @@ func (t *TemplateRenderController) Sitemap(ctx *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sitemapInfo := &schema.SiteMapList{}
|
questions, err := t.questionRepo.SitemapQuestions(ctx, 1, constant.SitemapMaxSize)
|
||||||
infoStr, err := t.data.Cache.GetString(ctx, schema.SitemapCachekey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("get Cache failed: %s", err)
|
log.Errorf("get sitemap questions 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)
|
|
||||||
return
|
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(
|
ctx.HTML(
|
||||||
http.StatusOK, "sitemap.xml", gin.H{
|
http.StatusOK, "sitemap.xml", gin.H{
|
||||||
"xmlHeader": template.HTML(`<?xml version="1.0" encoding="UTF-8"?>`),
|
"xmlHeader": template.HTML(`<?xml version="1.0" encoding="UTF-8"?>`),
|
||||||
"list": sitemapInfo.QuestionIDs,
|
"list": questions,
|
||||||
"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,
|
|
||||||
"general": general,
|
"general": general,
|
||||||
|
"hastitle": siteInfo.PermaLink == constant.PermaLinkQuestionIDAndTitle ||
|
||||||
|
siteInfo.PermaLink == constant.PermaLinkQuestionIDAndTitleByShortID,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return
|
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 {
|
func (t *TemplateRenderController) SitemapPage(ctx *gin.Context, page int) error {
|
||||||
sitemapInfo := &schema.SiteMapPageList{}
|
|
||||||
general, err := t.siteInfoService.GetSiteGeneral(ctx)
|
general, err := t.siteInfoService.GetSiteGeneral(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("get site general failed:", err)
|
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)
|
log.Error("get site GetSiteSeo failed:", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
hasTitle := false
|
|
||||||
if siteInfo.PermaLink == schema.PermaLinkQuestionIDAndTitle || siteInfo.PermaLink == schema.PermaLinkQuestionIDAndTitleByShortID {
|
|
||||||
hasTitle = true
|
|
||||||
}
|
|
||||||
|
|
||||||
cachekey := fmt.Sprintf(schema.SitemapPageCachekey, page)
|
questions, err := t.questionRepo.SitemapQuestions(ctx, page, constant.SitemapMaxSize)
|
||||||
infoStr, err := t.data.Cache.GetString(ctx, cachekey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//If there is no cache, return directly.
|
log.Errorf("get sitemap questions failed: %s", err)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err = json.Unmarshal([]byte(infoStr), sitemapInfo); err != nil {
|
|
||||||
log.Errorf("get sitemap info failed: %s", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx.Header("Content-Type", "application/xml")
|
ctx.Header("Content-Type", "application/xml")
|
||||||
ctx.HTML(
|
ctx.HTML(
|
||||||
http.StatusOK, "sitemap.xml", gin.H{
|
http.StatusOK, "sitemap.xml", gin.H{
|
||||||
"xmlHeader": template.HTML(`<?xml version="1.0" encoding="UTF-8"?>`),
|
"xmlHeader": template.HTML(`<?xml version="1.0" encoding="UTF-8"?>`),
|
||||||
"list": sitemapInfo.PageData,
|
"list": questions,
|
||||||
"general": general,
|
"general": general,
|
||||||
"hastitle": hasTitle,
|
"hastitle": siteInfo.PermaLink == constant.PermaLinkQuestionIDAndTitle ||
|
||||||
|
siteInfo.PermaLink == constant.PermaLinkQuestionIDAndTitleByShortID,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/answerdev/answer/internal/service/auth"
|
"github.com/answerdev/answer/internal/service/auth"
|
||||||
"github.com/answerdev/answer/internal/service/export"
|
"github.com/answerdev/answer/internal/service/export"
|
||||||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||||
"github.com/answerdev/answer/internal/service/uploader"
|
|
||||||
"github.com/answerdev/answer/pkg/checker"
|
"github.com/answerdev/answer/pkg/checker"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/segmentfault/pacman/errors"
|
"github.com/segmentfault/pacman/errors"
|
||||||
|
@ -24,9 +23,8 @@ type UserController struct {
|
||||||
userService *service.UserService
|
userService *service.UserService
|
||||||
authService *auth.AuthService
|
authService *auth.AuthService
|
||||||
actionService *action.CaptchaService
|
actionService *action.CaptchaService
|
||||||
uploaderService uploader.UploaderService
|
|
||||||
emailService *export.EmailService
|
emailService *export.EmailService
|
||||||
siteInfoCommonService *siteinfo_common.SiteInfoCommonService
|
siteInfoCommonService siteinfo_common.SiteInfoCommonService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUserController new controller
|
// NewUserController new controller
|
||||||
|
@ -35,14 +33,12 @@ func NewUserController(
|
||||||
userService *service.UserService,
|
userService *service.UserService,
|
||||||
actionService *action.CaptchaService,
|
actionService *action.CaptchaService,
|
||||||
emailService *export.EmailService,
|
emailService *export.EmailService,
|
||||||
uploaderService uploader.UploaderService,
|
siteInfoCommonService siteinfo_common.SiteInfoCommonService,
|
||||||
siteInfoCommonService *siteinfo_common.SiteInfoCommonService,
|
|
||||||
) *UserController {
|
) *UserController {
|
||||||
return &UserController{
|
return &UserController{
|
||||||
authService: authService,
|
authService: authService,
|
||||||
userService: userService,
|
userService: userService,
|
||||||
actionService: actionService,
|
actionService: actionService,
|
||||||
uploaderService: uploaderService,
|
|
||||||
emailService: emailService,
|
emailService: emailService,
|
||||||
siteInfoCommonService: siteInfoCommonService,
|
siteInfoCommonService: siteInfoCommonService,
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"github.com/answerdev/answer/internal/service/rank"
|
"github.com/answerdev/answer/internal/service/rank"
|
||||||
"github.com/answerdev/answer/pkg/uid"
|
"github.com/answerdev/answer/pkg/uid"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/jinzhu/copier"
|
|
||||||
"github.com/segmentfault/pacman/errors"
|
"github.com/segmentfault/pacman/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,9 +53,7 @@ func (vc *VoteController) VoteUp(ctx *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dto := &schema.VoteDTO{}
|
resp, err := vc.VoteService.VoteUp(ctx, req)
|
||||||
_ = copier.Copy(dto, req)
|
|
||||||
resp, err := vc.VoteService.VoteUp(ctx, dto)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handler.HandleResponse(ctx, err, schema.ErrTypeToast)
|
handler.HandleResponse(ctx, err, schema.ErrTypeToast)
|
||||||
} else {
|
} else {
|
||||||
|
@ -93,9 +90,7 @@ func (vc *VoteController) VoteDown(ctx *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dto := &schema.VoteDTO{}
|
resp, err := vc.VoteService.VoteDown(ctx, req)
|
||||||
_ = copier.Copy(dto, req)
|
|
||||||
resp, err := vc.VoteService.VoteDown(ctx, dto)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handler.HandleResponse(ctx, err, schema.ErrTypeToast)
|
handler.HandleResponse(ctx, err, schema.ErrTypeToast)
|
||||||
} else {
|
} else {
|
||||||
|
@ -103,9 +98,9 @@ func (vc *VoteController) VoteDown(ctx *gin.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserVotes godoc
|
// UserVotes user votes
|
||||||
// @Summary user's votes
|
// @Summary get user personal votes
|
||||||
// @Description user's vote
|
// @Description get user personal votes
|
||||||
// @Tags Activity
|
// @Tags Activity
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
|
@ -116,21 +111,12 @@ func (vc *VoteController) VoteDown(ctx *gin.Context) {
|
||||||
// @Router /answer/api/v1/personal/vote/page [get]
|
// @Router /answer/api/v1/personal/vote/page [get]
|
||||||
func (vc *VoteController) UserVotes(ctx *gin.Context) {
|
func (vc *VoteController) UserVotes(ctx *gin.Context) {
|
||||||
req := schema.GetVoteWithPageReq{}
|
req := schema.GetVoteWithPageReq{}
|
||||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
|
||||||
if handler.BindAndCheck(ctx, &req) {
|
if handler.BindAndCheck(ctx, &req) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if req.Page == 0 {
|
|
||||||
req.Page = 1
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
}
|
|
||||||
if req.PageSize == 0 {
|
|
||||||
req.PageSize = 30
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := vc.VoteService.ListUserVotes(ctx, req)
|
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
|
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
|
// TableName answer table name
|
||||||
func (Answer) TableName() string {
|
func (Answer) TableName() string {
|
||||||
return "answer"
|
return "answer"
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/answerdev/answer/internal/migrations"
|
"github.com/answerdev/answer/internal/migrations"
|
||||||
"github.com/answerdev/answer/internal/schema"
|
"github.com/answerdev/answer/internal/schema"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
"github.com/segmentfault/pacman/errors"
|
"github.com/segmentfault/pacman/errors"
|
||||||
"github.com/segmentfault/pacman/log"
|
"github.com/segmentfault/pacman/log"
|
||||||
)
|
)
|
||||||
|
@ -184,17 +185,17 @@ func InitBaseInfo(ctx *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := migrations.InitDB(c.Data.Database); err != nil {
|
engine, err := data.NewDB(false, c.Data.Database)
|
||||||
log.Error("init database error: ", err.Error())
|
if err != nil {
|
||||||
handler.HandleResponse(ctx, errors.BadRequest(reason.InstallCreateTableFailed), schema.ErrTypeAlert)
|
log.Errorf("init database failed %s", err)
|
||||||
return
|
handler.HandleResponse(ctx, errors.BadRequest(reason.InstallCreateTableFailed), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = migrations.UpdateInstallInfo(c.Data.Database, req.Language, req.SiteName, req.SiteURL, req.ContactEmail,
|
inputData := &migrations.InitNeedUserInputData{}
|
||||||
req.AdminName, req.AdminPassword, req.AdminEmail)
|
_ = copier.Copy(inputData, req)
|
||||||
if err != nil {
|
if err := migrations.NewMentor(ctx, engine, inputData).InitDB(); err != nil {
|
||||||
log.Error(err)
|
log.Error("init database error: ", err.Error())
|
||||||
handler.HandleResponse(ctx, errors.BadRequest(reason.InstallConfigFailed), nil)
|
handler.HandleResponse(ctx, errors.BadRequest(reason.InstallConfigFailed), schema.ErrTypeAlert)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,188 +1,183 @@
|
||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/data"
|
|
||||||
"github.com/answerdev/answer/internal/entity"
|
"github.com/answerdev/answer/internal/entity"
|
||||||
"github.com/answerdev/answer/internal/service/permission"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
type Mentor struct {
|
||||||
defaultSEORobotTxt = `User-agent: *
|
ctx context.Context
|
||||||
Disallow: /admin
|
engine *xorm.Engine
|
||||||
Disallow: /search
|
userData *InitNeedUserInputData
|
||||||
Disallow: /install
|
err error
|
||||||
Disallow: /review
|
Done bool
|
||||||
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{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitDB init db
|
func NewMentor(ctx context.Context, engine *xorm.Engine, data *InitNeedUserInputData) *Mentor {
|
||||||
func InitDB(dataConf *data.Database) (err error) {
|
return &Mentor{ctx: ctx, engine: engine, userData: data}
|
||||||
engine, err := data.NewDB(false, dataConf)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("new database failed: ", err.Error())
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exist, err := engine.IsTableExist(&entity.Version{})
|
type InitNeedUserInputData struct {
|
||||||
if err != nil {
|
Language string
|
||||||
return fmt.Errorf("check table exists failed: %s", err)
|
SiteName string
|
||||||
|
SiteURL string
|
||||||
|
ContactEmail string
|
||||||
|
AdminName string
|
||||||
|
AdminPassword string
|
||||||
|
AdminEmail string
|
||||||
}
|
}
|
||||||
if exist {
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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")
|
fmt.Println("[database] already exists")
|
||||||
return nil
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = engine.Sync(tables...)
|
func (m *Mentor) syncTable() {
|
||||||
if err != nil {
|
m.err = m.engine.Context(m.ctx).Sync(tables...)
|
||||||
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)
|
func (m *Mentor) initVersionTable() {
|
||||||
if err != nil {
|
_, m.err = m.engine.Context(m.ctx).Insert(&entity.Version{ID: 1, VersionNumber: ExpectedVersion()})
|
||||||
return fmt.Errorf("init admin user failed: %s", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = initConfigTable(engine)
|
func (m *Mentor) initAdminUser() {
|
||||||
if err != nil {
|
generateFromPassword, _ := bcrypt.GenerateFromPassword([]byte(m.userData.AdminPassword), bcrypt.DefaultCost)
|
||||||
return fmt.Errorf("init config table: %s", err)
|
_, m.err = m.engine.Context(m.ctx).Insert(&entity.User{
|
||||||
}
|
|
||||||
|
|
||||||
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{
|
|
||||||
ID: "1",
|
ID: "1",
|
||||||
Username: "admin",
|
Username: m.userData.AdminName,
|
||||||
Pass: "$2a$10$.gnUnpW.8ssRNaEvx.XwvOR2NuPsGzFLWWX2rqSIVAdIvLNZZYs5y", // admin
|
Pass: string(generateFromPassword),
|
||||||
EMail: "admin@admin.com",
|
EMail: m.userData.AdminEmail,
|
||||||
MailStatus: 1,
|
MailStatus: 1,
|
||||||
NoticeStatus: 1,
|
NoticeStatus: 1,
|
||||||
Status: 1,
|
Status: 1,
|
||||||
Rank: 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{
|
interfaceData := map[string]string{
|
||||||
"language": language,
|
"language": m.userData.Language,
|
||||||
"time_zone": "UTC",
|
"time_zone": "UTC",
|
||||||
}
|
}
|
||||||
interfaceDataBytes, _ := json.Marshal(interfaceData)
|
interfaceDataBytes, _ := json.Marshal(interfaceData)
|
||||||
_, err := engine.InsertOne(&entity.SiteInfo{
|
_, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
|
||||||
Type: "interface",
|
Type: "interface",
|
||||||
Content: string(interfaceDataBytes),
|
Content: string(interfaceDataBytes),
|
||||||
Status: 1,
|
Status: 1,
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Mentor) initSiteInfoGeneralData() {
|
||||||
generalData := map[string]string{
|
generalData := map[string]string{
|
||||||
"name": siteName,
|
"name": m.userData.SiteName,
|
||||||
"site_url": siteURL,
|
"site_url": m.userData.SiteURL,
|
||||||
"contact_email": contactEmail,
|
"contact_email": m.userData.ContactEmail,
|
||||||
}
|
}
|
||||||
generalDataBytes, _ := json.Marshal(generalData)
|
generalDataBytes, _ := json.Marshal(generalData)
|
||||||
_, err = engine.InsertOne(&entity.SiteInfo{
|
_, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
|
||||||
Type: "general",
|
Type: "general",
|
||||||
Content: string(generalDataBytes),
|
Content: string(generalDataBytes),
|
||||||
Status: 1,
|
Status: 1,
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Mentor) initSiteInfoLoginConfig() {
|
||||||
loginConfig := map[string]bool{
|
loginConfig := map[string]bool{
|
||||||
"allow_new_registrations": true,
|
"allow_new_registrations": true,
|
||||||
"allow_email_registrations": true,
|
"allow_email_registrations": true,
|
||||||
"login_required": false,
|
"login_required": false,
|
||||||
}
|
}
|
||||||
loginConfigDataBytes, _ := json.Marshal(loginConfig)
|
loginConfigDataBytes, _ := json.Marshal(loginConfig)
|
||||||
_, err = engine.InsertOne(&entity.SiteInfo{
|
_, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
|
||||||
Type: "login",
|
Type: "login",
|
||||||
Content: string(loginConfigDataBytes),
|
Content: string(loginConfigDataBytes),
|
||||||
Status: 1,
|
Status: 1,
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Mentor) initSiteInfoThemeConfig() {
|
||||||
themeConfig := `{"theme":"default","theme_config":{"default":{"navbar_style":"colored","primary_color":"#0033ff"}}}`
|
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",
|
Type: "theme",
|
||||||
Content: themeConfig,
|
Content: themeConfig,
|
||||||
Status: 1,
|
Status: 1,
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
seoData := map[string]string{
|
func (m *Mentor) initSiteInfoSEOConfig() {
|
||||||
"robots": defaultSEORobotTxt + siteURL + "/sitemap.xml",
|
seoData := map[string]interface{}{
|
||||||
|
"permalink": 1,
|
||||||
|
"robots": defaultSEORobotTxt + m.userData.SiteURL + "/sitemap.xml",
|
||||||
}
|
}
|
||||||
seoDataBytes, _ := json.Marshal(seoData)
|
seoDataBytes, _ := json.Marshal(seoData)
|
||||||
_, err = engine.InsertOne(&entity.SiteInfo{
|
_, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
|
||||||
Type: "seo",
|
Type: "seo",
|
||||||
Content: string(seoDataBytes),
|
Content: string(seoDataBytes),
|
||||||
Status: 1,
|
Status: 1,
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Mentor) initSiteInfoUsersConfig() {
|
||||||
usersData := map[string]any{
|
usersData := map[string]any{
|
||||||
"default_avatar": "gravatar",
|
"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_display_name": true,
|
||||||
"allow_update_username": true,
|
"allow_update_username": true,
|
||||||
"allow_update_avatar": true,
|
"allow_update_avatar": true,
|
||||||
|
@ -191,344 +186,9 @@ func initSiteInfo(engine *xorm.Engine, language, siteName, siteURL, contactEmail
|
||||||
"allow_update_location": true,
|
"allow_update_location": true,
|
||||||
}
|
}
|
||||||
usersDataBytes, _ := json.Marshal(usersData)
|
usersDataBytes, _ := json.Marshal(usersData)
|
||||||
_, err = engine.InsertOne(&entity.SiteInfo{
|
_, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
|
||||||
Type: "users",
|
Type: "users",
|
||||||
Content: string(usersDataBytes),
|
Content: string(usersDataBytes),
|
||||||
Status: 1,
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/segmentfault/pacman/log"
|
||||||
"time"
|
"time"
|
||||||
|
"xorm.io/builder"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/constant"
|
"github.com/answerdev/answer/internal/base/constant"
|
||||||
"github.com/answerdev/answer/internal/base/data"
|
"github.com/answerdev/answer/internal/base/data"
|
||||||
|
@ -15,7 +18,6 @@ import (
|
||||||
"github.com/answerdev/answer/internal/service/rank"
|
"github.com/answerdev/answer/internal/service/rank"
|
||||||
"github.com/answerdev/answer/pkg/converter"
|
"github.com/answerdev/answer/pkg/converter"
|
||||||
"github.com/segmentfault/pacman/errors"
|
"github.com/segmentfault/pacman/errors"
|
||||||
"github.com/segmentfault/pacman/log"
|
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,334 +26,330 @@ type AnswerActivityRepo struct {
|
||||||
data *data.Data
|
data *data.Data
|
||||||
activityRepo activity_common.ActivityRepo
|
activityRepo activity_common.ActivityRepo
|
||||||
userRankRepo rank.UserRankRepo
|
userRankRepo rank.UserRankRepo
|
||||||
|
notificationQueueService notice_queue.NotificationQueueService
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
acceptAction = "accept"
|
|
||||||
acceptedAction = "accepted"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
acceptActionList = []string{acceptAction, acceptedAction}
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewAnswerActivityRepo new repository
|
// NewAnswerActivityRepo new repository
|
||||||
func NewAnswerActivityRepo(
|
func NewAnswerActivityRepo(
|
||||||
data *data.Data,
|
data *data.Data,
|
||||||
activityRepo activity_common.ActivityRepo,
|
activityRepo activity_common.ActivityRepo,
|
||||||
userRankRepo rank.UserRankRepo,
|
userRankRepo rank.UserRankRepo,
|
||||||
|
notificationQueueService notice_queue.NotificationQueueService,
|
||||||
) activity.AnswerActivityRepo {
|
) activity.AnswerActivityRepo {
|
||||||
return &AnswerActivityRepo{
|
return &AnswerActivityRepo{
|
||||||
data: data,
|
data: data,
|
||||||
activityRepo: activityRepo,
|
activityRepo: activityRepo,
|
||||||
userRankRepo: userRankRepo,
|
userRankRepo: userRankRepo,
|
||||||
|
notificationQueueService: notificationQueueService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewQuestionActivityRepo new repository
|
func (ar *AnswerActivityRepo) SaveAcceptAnswerActivity(ctx context.Context, op *schema.AcceptAnswerOperationInfo) (
|
||||||
func NewQuestionActivityRepo(
|
err error) {
|
||||||
data *data.Data,
|
// pre check
|
||||||
activityRepo activity_common.ActivityRepo,
|
noNeedToDo, err := ar.activityPreCheck(ctx, op)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
return err
|
||||||
}
|
}
|
||||||
if !exist {
|
if noNeedToDo {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all this object activity
|
// save 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))
|
|
||||||
|
|
||||||
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||||
session = session.Context(ctx)
|
session = session.Context(ctx)
|
||||||
for _, act := range activityList {
|
|
||||||
log.Infof("user %s rollback rank %d", act.UserID, -act.Rank)
|
userInfoMapping, err := ar.acquireUserInfo(session, op.GetUserIDs())
|
||||||
_, e := ar.userRankRepo.TriggerUserRank(
|
if err != nil {
|
||||||
ctx, session, act.UserID, -act.Rank, act.ActivityType)
|
return nil, err
|
||||||
if e != nil {
|
|
||||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, e := session.Where("id = ?", act.ID).Cols("cancelled", "cancelled_at").
|
err = ar.saveActivitiesAvailable(session, op)
|
||||||
Update(&entity.Activity{Cancelled: entity.ActivityCancelled, CancelledAt: time.Now()}); e != nil {
|
if err != nil {
|
||||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = ar.changeUserRank(ctx, session, op, userInfoMapping)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all answers
|
// notification
|
||||||
answerList := make([]*entity.Answer, 0)
|
ar.sendAcceptAnswerNotification(ctx, op)
|
||||||
err = ar.data.DB.Context(ctx).Find(&answerList, &entity.Answer{QuestionID: questionID})
|
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 {
|
if err != nil {
|
||||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
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 {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
return nil, err
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcceptAnswer accept other answer
|
users := make(map[string]*entity.User, 0)
|
||||||
func (ar *AnswerActivityRepo) AcceptAnswer(ctx context.Context,
|
for _, u := range us {
|
||||||
answerObjID, questionObjID, questionUserID, answerUserID string, isSelf bool,
|
users[u.ID] = u
|
||||||
) (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{
|
return users, nil
|
||||||
ObjectID: answerObjID,
|
|
||||||
OriginalObjectID: questionObjID,
|
|
||||||
ActivityType: activityType,
|
|
||||||
Rank: deltaRank,
|
|
||||||
HasRank: hasRank,
|
|
||||||
}
|
|
||||||
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 isSelf {
|
|
||||||
addActivity.Rank = 0
|
|
||||||
addActivity.HasRank = 0
|
|
||||||
}
|
|
||||||
addActivityList = append(addActivityList, addActivity)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
// saveActivitiesAvailable save activities
|
||||||
session = session.Context(ctx)
|
// If activity not exist it will be created or else will be updated
|
||||||
for _, addActivity := range addActivityList {
|
// If this activity is already exist, set activity rank to 0
|
||||||
existsActivity, exists, e := ar.activityRepo.GetActivity(
|
// So after this function, the activity rank will be correct for update user rank
|
||||||
ctx, session, answerObjID, addActivity.UserID, addActivity.ActivityType)
|
func (ar *AnswerActivityRepo) saveActivitiesAvailable(session *xorm.Session, op *schema.AcceptAnswerOperationInfo) (
|
||||||
if e != nil {
|
err error) {
|
||||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
for _, act := range op.Activities {
|
||||||
}
|
existsActivity := &entity.Activity{}
|
||||||
if exists && existsActivity.Cancelled == entity.ActivityAvailable {
|
exist, err := session.
|
||||||
continue
|
Where(builder.Eq{"object_id": op.AnswerObjectID}).
|
||||||
}
|
And(builder.Eq{"user_id": act.ActivityUserID}).
|
||||||
|
And(builder.Eq{"trigger_user_id": act.TriggerUserID}).
|
||||||
// trigger user rank and send notification
|
And(builder.Eq{"activity_type": act.ActivityType}).
|
||||||
if addActivity.Rank != 0 {
|
Get(existsActivity)
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, act := range addActivityList {
|
if exist && existsActivity.Cancelled == entity.ActivityAvailable {
|
||||||
msg := &schema.NotificationMsg{
|
act.Rank = 0
|
||||||
Type: schema.NotificationTypeAchievement,
|
continue
|
||||||
ObjectID: act.ObjectID,
|
|
||||||
ReceiverUserID: act.UserID,
|
|
||||||
}
|
|
||||||
if act.UserID == questionUserID {
|
|
||||||
msg.TriggerUserID = answerUserID
|
|
||||||
msg.ObjectType = constant.AnswerObjectType
|
|
||||||
} else {
|
|
||||||
msg.TriggerUserID = questionUserID
|
|
||||||
msg.ObjectType = constant.AnswerObjectType
|
|
||||||
}
|
|
||||||
if msg.TriggerUserID != msg.ReceiverUserID {
|
|
||||||
notice_queue.AddNotification(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, act := range addActivityList {
|
|
||||||
msg := &schema.NotificationMsg{
|
|
||||||
ReceiverUserID: act.UserID,
|
|
||||||
Type: schema.NotificationTypeInbox,
|
|
||||||
ObjectID: act.ObjectID,
|
|
||||||
}
|
|
||||||
if act.UserID != questionUserID {
|
|
||||||
msg.TriggerUserID = questionUserID
|
|
||||||
msg.ObjectType = constant.AnswerObjectType
|
|
||||||
msg.NotificationAction = constant.NotificationAcceptAnswer
|
|
||||||
notice_queue.AddNotification(msg)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if exist {
|
||||||
|
if _, err = session.Where("id = ?", existsActivity.ID).Cols("`cancelled`").
|
||||||
|
Update(&entity.Activity{Cancelled: entity.ActivityAvailable}); err != nil {
|
||||||
return err
|
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 {
|
} else {
|
||||||
addActivity.UserID = answerUserID
|
insertActivity := entity.Activity{
|
||||||
addActivity.OriginalObjectID = answerObjID
|
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,
|
||||||
}
|
}
|
||||||
addActivityList = append(addActivityList, addActivity)
|
_, err = session.Insert(&insertActivity)
|
||||||
}
|
|
||||||
|
|
||||||
_, 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, act := range addActivityList {
|
|
||||||
msg := &schema.NotificationMsg{
|
|
||||||
ReceiverUserID: act.UserID,
|
|
||||||
Type: schema.NotificationTypeAchievement,
|
|
||||||
ObjectID: act.ObjectID,
|
|
||||||
}
|
|
||||||
if act.UserID == questionUserID {
|
|
||||||
msg.TriggerUserID = answerUserID
|
|
||||||
msg.ObjectType = constant.QuestionObjectType
|
|
||||||
} else {
|
|
||||||
msg.TriggerUserID = questionUserID
|
|
||||||
msg.ObjectType = constant.AnswerObjectType
|
|
||||||
}
|
|
||||||
if msg.TriggerUserID != msg.ReceiverUserID {
|
|
||||||
notice_queue.AddNotification(msg)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ar *AnswerActivityRepo) DeleteAnswer(ctx context.Context, answerID string) (err error) {
|
// cancelActivities cancel activities
|
||||||
answerInfo := &entity.Answer{}
|
// If this activity is already cancelled, set activity rank to 0
|
||||||
exist, err := ar.data.DB.Context(ctx).Where("id = ?", answerID).Get(answerInfo)
|
// 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 {
|
if err != nil {
|
||||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
log.Error(err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
if !exist {
|
if !exist {
|
||||||
return nil
|
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
|
||||||
// get all this object activity
|
if t.Cancelled == entity.ActivityCancelled {
|
||||||
activityList := make([]*entity.Activity, 0)
|
act.Rank = 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 {
|
if _, err = session.ID(act.ID).Cols("cancelled", "cancelled_at").
|
||||||
return nil
|
Update(&entity.Activity{
|
||||||
}
|
Cancelled: entity.ActivityCancelled,
|
||||||
|
CancelledAt: time.Now(),
|
||||||
log.Infof("answerInfo %s deleted will rollback activity %d", answerID, len(activityList))
|
}); err != nil {
|
||||||
|
log.Error(err)
|
||||||
_, 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 err
|
||||||
}
|
}
|
||||||
return
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
user := userInfoMapping[act.ActivityUserID]
|
||||||
|
if user == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err = ar.userRankRepo.ChangeUserRank(ctx, session,
|
||||||
|
act.ActivityUserID, user.Rank, act.Rank); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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: op.AnswerObjectID,
|
||||||
|
ReceiverUserID: act.ActivityUserID,
|
||||||
|
}
|
||||||
|
if act.ActivityUserID == op.QuestionUserID {
|
||||||
|
msg.TriggerUserID = op.AnswerUserID
|
||||||
|
msg.ObjectType = constant.AnswerObjectType
|
||||||
|
} else {
|
||||||
|
msg.TriggerUserID = op.QuestionUserID
|
||||||
|
msg.ObjectType = constant.AnswerObjectType
|
||||||
|
}
|
||||||
|
if msg.TriggerUserID != msg.ReceiverUserID {
|
||||||
|
ar.notificationQueueService.Send(ctx, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, act := range op.Activities {
|
||||||
|
msg := &schema.NotificationMsg{
|
||||||
|
ReceiverUserID: act.ActivityUserID,
|
||||||
|
Type: schema.NotificationTypeInbox,
|
||||||
|
ObjectID: op.AnswerObjectID,
|
||||||
|
}
|
||||||
|
if act.ActivityUserID != op.QuestionUserID {
|
||||||
|
msg.TriggerUserID = op.QuestionUserID
|
||||||
|
msg.ObjectType = constant.AnswerObjectType
|
||||||
|
msg.NotificationAction = constant.NotificationAcceptAnswer
|
||||||
|
ar.notificationQueueService.Send(ctx, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ar *AnswerActivityRepo) sendCancelAcceptAnswerNotification(
|
||||||
|
ctx context.Context, op *schema.AcceptAnswerOperationInfo) {
|
||||||
|
for _, act := range op.Activities {
|
||||||
|
msg := &schema.NotificationMsg{
|
||||||
|
ReceiverUserID: act.ActivityUserID,
|
||||||
|
Type: schema.NotificationTypeAchievement,
|
||||||
|
ObjectID: op.AnswerObjectID,
|
||||||
|
}
|
||||||
|
if act.ActivityUserID == op.QuestionObjectID {
|
||||||
|
msg.TriggerUserID = op.AnswerObjectID
|
||||||
|
msg.ObjectType = constant.QuestionObjectType
|
||||||
|
} else {
|
||||||
|
msg.TriggerUserID = op.QuestionObjectID
|
||||||
|
msg.ObjectType = constant.AnswerObjectType
|
||||||
|
}
|
||||||
|
if msg.TriggerUserID != msg.ReceiverUserID {
|
||||||
|
ar.notificationQueueService.Send(ctx, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ func (ar *FollowRepo) Follow(ctx context.Context, objectID, userID string) error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ func (ar *FollowRepo) FollowCancel(ctx context.Context, objectID, userID string)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ package activity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"xorm.io/builder"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/data"
|
"github.com/answerdev/answer/internal/base/data"
|
||||||
"github.com/answerdev/answer/internal/base/reason"
|
"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) {
|
func (ar *UserActiveActivityRepo) UserActive(ctx context.Context, userID string) (err error) {
|
||||||
cfg, err := ar.configService.GetConfigByKey(ctx, UserActivated)
|
cfg, err := ar.configService.GetConfigByKey(ctx, UserActivated)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
activityType := cfg.ID
|
|
||||||
deltaRank := cfg.GetIntValue()
|
|
||||||
addActivity := &entity.Activity{
|
addActivity := &entity.Activity{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
ObjectID: "0",
|
ObjectID: "0",
|
||||||
OriginalObjectID: "0",
|
OriginalObjectID: "0",
|
||||||
ActivityType: activityType,
|
ActivityType: cfg.ID,
|
||||||
Rank: deltaRank,
|
Rank: cfg.GetIntValue(),
|
||||||
HasRank: 1,
|
HasRank: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||||
session = session.Context(ctx)
|
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 {
|
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
|
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 {
|
if err != nil {
|
||||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = session.Insert(addActivity)
|
_, err = session.Insert(addActivity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
})
|
})
|
||||||
return err
|
if err != nil {
|
||||||
|
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,28 +2,26 @@ package activity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strings"
|
"fmt"
|
||||||
|
"github.com/segmentfault/pacman/log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/constant"
|
"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/pkg/converter"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/pager"
|
"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/internal/service/rank"
|
||||||
"github.com/answerdev/answer/pkg/obj"
|
"github.com/answerdev/answer/pkg/obj"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"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/data"
|
||||||
"github.com/answerdev/answer/internal/base/reason"
|
"github.com/answerdev/answer/internal/base/reason"
|
||||||
"github.com/answerdev/answer/internal/entity"
|
"github.com/answerdev/answer/internal/entity"
|
||||||
"github.com/answerdev/answer/internal/schema"
|
"github.com/answerdev/answer/internal/schema"
|
||||||
"github.com/answerdev/answer/internal/service"
|
"github.com/answerdev/answer/internal/service"
|
||||||
|
"github.com/answerdev/answer/internal/service/activity_common"
|
||||||
"github.com/segmentfault/pacman/errors"
|
"github.com/segmentfault/pacman/errors"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
@ -31,366 +29,147 @@ import (
|
||||||
// VoteRepo activity repository
|
// VoteRepo activity repository
|
||||||
type VoteRepo struct {
|
type VoteRepo struct {
|
||||||
data *data.Data
|
data *data.Data
|
||||||
uniqueIDRepo unique.UniqueIDRepo
|
|
||||||
configService *config.ConfigService
|
|
||||||
activityRepo activity_common.ActivityRepo
|
activityRepo activity_common.ActivityRepo
|
||||||
userRankRepo rank.UserRankRepo
|
userRankRepo rank.UserRankRepo
|
||||||
voteCommon activity_common.VoteRepo
|
notificationQueueService notice_queue.NotificationQueueService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewVoteRepo new repository
|
// NewVoteRepo new repository
|
||||||
func NewVoteRepo(
|
func NewVoteRepo(
|
||||||
data *data.Data,
|
data *data.Data,
|
||||||
uniqueIDRepo unique.UniqueIDRepo,
|
|
||||||
configService *config.ConfigService,
|
|
||||||
activityRepo activity_common.ActivityRepo,
|
activityRepo activity_common.ActivityRepo,
|
||||||
userRankRepo rank.UserRankRepo,
|
userRankRepo rank.UserRankRepo,
|
||||||
voteCommon activity_common.VoteRepo,
|
notificationQueueService notice_queue.NotificationQueueService,
|
||||||
) service.VoteRepo {
|
) service.VoteRepo {
|
||||||
return &VoteRepo{
|
return &VoteRepo{
|
||||||
data: data,
|
data: data,
|
||||||
uniqueIDRepo: uniqueIDRepo,
|
|
||||||
configService: configService,
|
|
||||||
activityRepo: activityRepo,
|
activityRepo: activityRepo,
|
||||||
userRankRepo: userRankRepo,
|
userRankRepo: userRankRepo,
|
||||||
voteCommon: voteCommon,
|
notificationQueueService: notificationQueueService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var LimitUpActions = map[string][]string{
|
func (vr *VoteRepo) Vote(ctx context.Context, op *schema.VoteOperationInfo) (err error) {
|
||||||
"question": {"vote_up", "voted_up"},
|
noNeedToVote, err := vr.votePreCheck(ctx, op)
|
||||||
"answer": {"vote_up", "voted_up"},
|
if err != nil {
|
||||||
"comment": {"vote_up"},
|
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
|
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) {
|
_, err = vr.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||||
session = session.Context(ctx)
|
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)
|
userInfoMapping, err := vr.acquireUserInfo(session, userIDs)
|
||||||
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if isReachStandard {
|
|
||||||
insertActivity.Rank = 0
|
|
||||||
}
|
|
||||||
achievementNotificationUserIDs = append(achievementNotificationUserIDs, activityUserID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if has {
|
err = vr.setActivityRankToZeroIfUserReachLimit(ctx, session, op, maxDailyRank)
|
||||||
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sendInboxNotification = true
|
|
||||||
|
sendInboxNotification, err = vr.saveActivitiesAvailable(session, op)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// update votes
|
err = vr.changeUserRank(ctx, session, op, userInfoMapping)
|
||||||
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 {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
return nil, nil
|
||||||
}
|
|
||||||
return
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err = vr.GetVoteResultByObjectId(ctx, objectID)
|
for _, activity := range op.Activities {
|
||||||
resp.VoteStatus = vr.voteCommon.GetVoteStatus(ctx, objectID, userID)
|
if activity.Rank == 0 {
|
||||||
|
continue
|
||||||
for _, activityUserID := range achievementNotificationUserIDs {
|
}
|
||||||
vr.sendNotification(ctx, activityUserID, objectUserID, objectID)
|
vr.sendAchievementNotification(ctx, activity.ActivityUserID, op.ObjectCreatorUserID, op.ObjectID)
|
||||||
}
|
}
|
||||||
if sendInboxNotification {
|
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) 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
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
_, err = vr.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
_, err = vr.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||||
session = session.Context(ctx)
|
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)
|
userInfoMapping, err := vr.acquireUserInfo(session, userIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerUserID = userID
|
err = vr.cancelActivities(session, activities)
|
||||||
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 {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
|
||||||
notificationUserIDs = append(notificationUserIDs, activityUserID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update votes
|
err = vr.rollbackUserRank(ctx, session, activities, userInfoMapping)
|
||||||
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 {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
return nil, nil
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
})
|
})
|
||||||
if err != 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 {
|
for _, activity := range activities {
|
||||||
vr.sendNotification(ctx, activityUserID, objectUserID, objectID)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vr *VoteRepo) VoteUp(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) {
|
func (vr *VoteRepo) ListUserVotes(ctx context.Context, userID string,
|
||||||
resp = &schema.VoteResp{}
|
page int, pageSize int, activityTypes []int) (voteList []*entity.Activity, total int64, err error) {
|
||||||
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) {
|
|
||||||
session := vr.data.DB.Context(ctx)
|
session := vr.data.DB.Context(ctx)
|
||||||
cond := builder.
|
cond := builder.
|
||||||
And(
|
And(
|
||||||
|
@ -399,46 +178,243 @@ func (vr *VoteRepo) ListUserVotes(
|
||||||
builder.In("activity_type", activityTypes),
|
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 {
|
if err != nil {
|
||||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateVotes
|
func (vr *VoteRepo) votePreCheck(ctx context.Context, op *schema.VoteOperationInfo) (noNeedToVote bool, err error) {
|
||||||
// if votes < 0 Decr object vote_count,otherwise Incr object vote_count
|
activities, err := vr.getExistActivity(ctx, op)
|
||||||
func (vr *VoteRepo) updateVotes(ctx context.Context, session *xorm.Session, objectID string, votes int) (err error) {
|
if err != nil {
|
||||||
var (
|
return false, err
|
||||||
objectType string
|
}
|
||||||
e error
|
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 {
|
switch objectType {
|
||||||
case "question":
|
case constant.QuestionObjectType:
|
||||||
_, err = session.Where("id = ?", objectID).Incr("vote_count", votes).Update(&entity.Question{})
|
_, err = session.ID(objectID).Cols("vote_count").Update(&entity.Question{VoteCount: voteCount})
|
||||||
case "answer":
|
case constant.AnswerObjectType:
|
||||||
_, err = session.Where("id = ?", objectID).Incr("vote_count", votes).Update(&entity.Answer{})
|
_, err = session.ID(objectID).Cols("vote_count").Update(&entity.Answer{VoteCount: voteCount})
|
||||||
case "comment":
|
case constant.CommentObjectType:
|
||||||
_, err = session.Where("id = ?", objectID).Incr("vote_count", votes).Update(&entity.Comment{})
|
_, err = session.ID(objectID).Cols("vote_count").Update(&entity.Comment{VoteCount: voteCount})
|
||||||
default:
|
|
||||||
e = errors.BadRequest(reason.DisallowVote)
|
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
if e != nil {
|
log.Error(err)
|
||||||
err = e
|
|
||||||
} else if err != nil {
|
|
||||||
err = errors.BadRequest(reason.DatabaseError).WithError(err).WithStack()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendNotification send rank triggered notification
|
func (vr *VoteRepo) sendAchievementNotification(ctx context.Context, activityUserID, objectUserID, objectID string) {
|
||||||
func (vr *VoteRepo) sendNotification(ctx context.Context, activityUserID, objectUserID, objectID string) {
|
|
||||||
objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
|
objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -451,10 +427,10 @@ func (vr *VoteRepo) sendNotification(ctx context.Context, activityUserID, object
|
||||||
ObjectID: objectID,
|
ObjectID: objectID,
|
||||||
ObjectType: objectType,
|
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 {
|
if triggerUserID == receiverUserID {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -487,6 +463,6 @@ func (vr *VoteRepo) sendVoteInboxNotification(triggerUserID, receiverUserID, obj
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(msg.NotificationAction) > 0 {
|
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) (
|
func (ar *ActivityRepo) GetActivityTypeByObjID(ctx context.Context, objectID string, action string) (
|
||||||
activityType, rank, hasRank int, err error) {
|
activityType, rank, hasRank int, err error) {
|
||||||
objectKey, err := obj.GetObjectTypeStrByObjectID(objectID)
|
objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
confKey := fmt.Sprintf("%s.%s", objectKey, action)
|
confKey := fmt.Sprintf("%s.%s", objectType, action)
|
||||||
cfg, err := ar.configService.GetConfigByKey(ctx, confKey)
|
cfg, err := ar.configService.GetConfigByKey(ctx, confKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -59,11 +59,11 @@ func (ar *ActivityRepo) GetActivityTypeByObjID(ctx context.Context, objectID str
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ar *ActivityRepo) GetActivityTypeByObjKey(ctx context.Context, objectKey, action string) (activityType int, err error) {
|
func (ar *ActivityRepo) GetActivityTypeByObjectType(ctx context.Context, objectType, action string) (activityType int, err error) {
|
||||||
configKey := fmt.Sprintf("%s.%s", objectKey, action)
|
configKey := fmt.Sprintf("%s.%s", objectType, action)
|
||||||
cfg, err := ar.configService.GetConfigByKey(ctx, configKey)
|
cfg, err := ar.configService.GetConfigByKey(ctx, configKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
return 0, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
return cfg.ID, nil
|
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) {
|
func (ar *ActivityRepo) GetActivityTypeByConfigKey(ctx context.Context, configKey string) (activityType int, err error) {
|
||||||
cfg, err := ar.configService.GetConfigByKey(ctx, configKey)
|
cfg, err := ar.configService.GetConfigByKey(ctx, configKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
return 0, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
return cfg.ID, nil
|
return cfg.ID, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/answerdev/answer/internal/service/unique"
|
"github.com/answerdev/answer/internal/service/unique"
|
||||||
"github.com/answerdev/answer/pkg/obj"
|
"github.com/answerdev/answer/pkg/obj"
|
||||||
"github.com/segmentfault/pacman/errors"
|
"github.com/segmentfault/pacman/errors"
|
||||||
|
"github.com/segmentfault/pacman/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FollowRepo follow repository
|
// 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) {
|
func (ar *FollowRepo) GetFollowUserIDs(ctx context.Context, objectID string) (userIDs []string, err error) {
|
||||||
objectTypeStr, err := obj.GetObjectTypeStrByObjectID(objectID)
|
objectTypeStr, err := obj.GetObjectTypeStrByObjectID(objectID)
|
||||||
if err != nil {
|
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 {
|
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)
|
userIDs = make([]string, 0)
|
||||||
|
@ -94,7 +96,7 @@ func (ar *FollowRepo) GetFollowUserIDs(ctx context.Context, objectID string) (us
|
||||||
// GetFollowIDs get all follow id list
|
// GetFollowIDs get all follow id list
|
||||||
func (ar *FollowRepo) GetFollowIDs(ctx context.Context, userID, objectKey string) (followIDs []string, err error) {
|
func (ar *FollowRepo) GetFollowIDs(ctx context.Context, userID, objectKey string) (followIDs []string, err error) {
|
||||||
followIDs = make([]string, 0)
|
followIDs = make([]string, 0)
|
||||||
activityType, err := ar.activityRepo.GetActivityTypeByObjKey(ctx, objectKey, "follow")
|
activityType, err := ar.activityRepo.GetActivityTypeByObjectType(ctx, objectKey, "follow")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
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
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
activityType, err := ar.activityRepo.GetActivityTypeByObjKey(ctx, objectKey, "follow")
|
activityType, err := ar.activityRepo.GetActivityTypeByObjectType(ctx, objectKey, "follow")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,11 @@ package answer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
|
||||||
|
|
||||||
"xorm.io/builder"
|
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/constant"
|
"github.com/answerdev/answer/internal/base/constant"
|
||||||
"github.com/answerdev/answer/internal/base/data"
|
"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/pager"
|
||||||
"github.com/answerdev/answer/internal/base/reason"
|
"github.com/answerdev/answer/internal/base/reason"
|
||||||
"github.com/answerdev/answer/internal/entity"
|
"github.com/answerdev/answer/internal/entity"
|
||||||
|
@ -58,8 +55,10 @@ func (ar *answerRepo) AddAnswer(ctx context.Context, answer *entity.Answer) (err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
answer.ID = uid.EnShortID(answer.ID)
|
answer.ID = uid.EnShortID(answer.ID)
|
||||||
answer.QuestionID = uid.EnShortID(answer.QuestionID)
|
answer.QuestionID = uid.EnShortID(answer.QuestionID)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,9 +108,10 @@ func (ar *answerRepo) GetAnswer(ctx context.Context, id string) (
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
answer.ID = uid.EnShortID(answer.ID)
|
answer.ID = uid.EnShortID(answer.ID)
|
||||||
answer.QuestionID = uid.EnShortID(answer.QuestionID)
|
answer.QuestionID = uid.EnShortID(answer.QuestionID)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,10 +134,12 @@ func (ar *answerRepo) GetAnswerList(ctx context.Context, answer *entity.Answer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
for _, item := range answerList {
|
for _, item := range answerList {
|
||||||
item.ID = uid.EnShortID(item.ID)
|
item.ID = uid.EnShortID(item.ID)
|
||||||
item.QuestionID = uid.EnShortID(item.QuestionID)
|
item.QuestionID = uid.EnShortID(item.QuestionID)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,10 +152,12 @@ func (ar *answerRepo) GetAnswerPage(ctx context.Context, page, pageSize int, ans
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
for _, item := range answerList {
|
for _, item := range answerList {
|
||||||
item.ID = uid.EnShortID(item.ID)
|
item.ID = uid.EnShortID(item.ID)
|
||||||
item.QuestionID = uid.EnShortID(item.QuestionID)
|
item.QuestionID = uid.EnShortID(item.QuestionID)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,8 +195,10 @@ func (ar *answerRepo) GetByID(ctx context.Context, id string) (*entity.Answer, b
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &resp, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
return &resp, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
resp.ID = uid.EnShortID(resp.ID)
|
resp.ID = uid.EnShortID(resp.ID)
|
||||||
resp.QuestionID = uid.EnShortID(resp.QuestionID)
|
resp.QuestionID = uid.EnShortID(resp.QuestionID)
|
||||||
|
}
|
||||||
return &resp, has, nil
|
return &resp, has, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,8 +228,10 @@ func (ar *answerRepo) GetByUserIDQuestionID(ctx context.Context, userID string,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &resp, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
return &resp, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
resp.ID = uid.EnShortID(resp.ID)
|
resp.ID = uid.EnShortID(resp.ID)
|
||||||
resp.QuestionID = uid.EnShortID(resp.QuestionID)
|
resp.QuestionID = uid.EnShortID(resp.QuestionID)
|
||||||
|
}
|
||||||
return &resp, has, nil
|
return &resp, has, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,87 +282,40 @@ func (ar *answerRepo) SearchList(ctx context.Context, search *entity.AnswerSearc
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return rows, count, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
return rows, count, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
for _, item := range rows {
|
for _, item := range rows {
|
||||||
item.ID = uid.EnShortID(item.ID)
|
item.ID = uid.EnShortID(item.ID)
|
||||||
item.QuestionID = uid.EnShortID(item.QuestionID)
|
item.QuestionID = uid.EnShortID(item.QuestionID)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return rows, count, nil
|
return rows, count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ar *answerRepo) AdminSearchList(ctx context.Context, search *entity.AdminAnswerSearch) ([]*entity.Answer, int64, error) {
|
func (ar *answerRepo) AdminSearchList(ctx context.Context, req *schema.AdminAnswerPageReq) (
|
||||||
var (
|
resp []*entity.Answer, total int64, err error) {
|
||||||
count int64
|
cond := &entity.Answer{}
|
||||||
err error
|
session := ar.data.DB.Context(ctx)
|
||||||
session = ar.data.DB.Context(ctx).Table([]string{entity.Answer{}.TableName(), "a"}).Select("a.*")
|
if len(req.QuestionID) == 0 && len(req.AnswerID) == 0 {
|
||||||
)
|
session.Join("INNER", "question", "answer.question_id = question.id")
|
||||||
if search.QuestionID != "" {
|
if len(req.QuestionTitle) > 0 {
|
||||||
search.QuestionID = uid.DeShortID(search.QuestionID)
|
session.Where("question.title like ?", "%"+req.QuestionTitle+"%")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
|
||||||
session.Where(builder.Eq{
|
resp = make([]*entity.Answer, 0)
|
||||||
"a.status": search.Status,
|
total, err = pager.Help(req.Page, req.PageSize, &resp, cond, session)
|
||||||
})
|
|
||||||
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check search by question id
|
|
||||||
if len(search.QuestionID) > 0 {
|
|
||||||
session.And(builder.Eq{
|
|
||||||
"question_id": search.QuestionID,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
offset := search.Page * search.PageSize
|
|
||||||
session.
|
|
||||||
OrderBy("a.created_at desc").
|
|
||||||
Limit(search.PageSize, offset)
|
|
||||||
count, err = session.FindAndCount(&rows)
|
|
||||||
if err != nil {
|
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 {
|
return resp, total, nil
|
||||||
item.ID = uid.EnShortID(item.ID)
|
|
||||||
item.QuestionID = uid.EnShortID(item.QuestionID)
|
|
||||||
}
|
|
||||||
return rows, count, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ func (cr configRepo) GetConfigByID(ctx context.Context, id int) (c *entity.Confi
|
||||||
}
|
}
|
||||||
|
|
||||||
c = &entity.Config{}
|
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 {
|
if err != nil {
|
||||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
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) {
|
func (cr configRepo) UpdateConfig(ctx context.Context, key string, value string) (err error) {
|
||||||
// check if key exists
|
// check if key exists
|
||||||
oldConfig := &entity.Config{}
|
oldConfig := &entity.Config{Key: key}
|
||||||
exist, err := cr.data.DB.Context(ctx).Get(oldConfig)
|
exist, err := cr.data.DB.Context(ctx).Get(oldConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
|
|
|
@ -52,7 +52,6 @@ var ProviderSetRepo = wire.NewSet(
|
||||||
activity.NewVoteRepo,
|
activity.NewVoteRepo,
|
||||||
activity.NewFollowRepo,
|
activity.NewFollowRepo,
|
||||||
activity.NewAnswerActivityRepo,
|
activity.NewAnswerActivityRepo,
|
||||||
activity.NewQuestionActivityRepo,
|
|
||||||
activity.NewUserActiveActivityRepo,
|
activity.NewUserActiveActivityRepo,
|
||||||
activity.NewActivityRepo,
|
activity.NewActivityRepo,
|
||||||
tag.NewTagRepo,
|
tag.NewTagRepo,
|
||||||
|
|
|
@ -2,11 +2,14 @@ package question
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/segmentfault/pacman/log"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/answerdev/answer/internal/base/handler"
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/constant"
|
"github.com/answerdev/answer/internal/base/constant"
|
||||||
|
@ -50,7 +53,9 @@ func (qr *questionRepo) AddQuestion(ctx context.Context, question *entity.Questi
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
question.ID = uid.EnShortID(question.ID)
|
question.ID = uid.EnShortID(question.ID)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +76,9 @@ func (qr *questionRepo) UpdateQuestion(ctx context.Context, question *entity.Que
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
question.ID = uid.EnShortID(question.ID)
|
question.ID = uid.EnShortID(question.ID)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,7 +171,9 @@ func (qr *questionRepo) GetQuestion(ctx context.Context, id string) (
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
question.ID = uid.EnShortID(question.ID)
|
question.ID = uid.EnShortID(question.ID)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,9 +184,11 @@ func (qr *questionRepo) SearchByTitleLike(ctx context.Context, title string) (qu
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
for _, item := range questionList {
|
for _, item := range questionList {
|
||||||
item.ID = uid.EnShortID(item.ID)
|
item.ID = uid.EnShortID(item.ID)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,9 +201,11 @@ func (qr *questionRepo) FindByID(ctx context.Context, id []string) (questionList
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
for _, item := range questionList {
|
for _, item := range questionList {
|
||||||
item.ID = uid.EnShortID(item.ID)
|
item.ID = uid.EnShortID(item.ID)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return
|
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) {
|
func (qr *questionRepo) GetQuestionCount(ctx context.Context) (count int64, err error) {
|
||||||
questionList := make([]*entity.Question, 0)
|
session := qr.data.DB.Context(ctx)
|
||||||
|
session.Where("status = ? OR status = ?", entity.QuestionStatusAvailable, entity.QuestionStatusClosed)
|
||||||
count, err = qr.data.DB.Context(ctx).In("question.status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed}).FindAndCount(&questionList)
|
count, err = session.Count(&entity.Question{Show: entity.QuestionShow})
|
||||||
if err != nil {
|
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) {
|
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
|
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 = make([]*schema.SiteMapQuestionInfo, 0)
|
questionIDList []*schema.SiteMapQuestionInfo, err error) {
|
||||||
rows := make([]*entity.Question, 0)
|
|
||||||
if page > 0 {
|
|
||||||
page = page - 1
|
page = page - 1
|
||||||
} else {
|
questionIDList = make([]*schema.SiteMapQuestionInfo, 0)
|
||||||
page = 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
|
||||||
}
|
}
|
||||||
if pageSize == 0 {
|
|
||||||
pageSize = constant.DefaultPageSize
|
// get sitemap data from db
|
||||||
}
|
rows := make([]*entity.Question, 0)
|
||||||
offset := page * pageSize
|
session := qr.data.DB.Context(ctx)
|
||||||
session := qr.data.DB.Context(ctx).Table("question")
|
session.Select("id,title,created_at,post_update_time")
|
||||||
session = session.In("question.status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed})
|
session.Where("`show` = ?", entity.QuestionShow)
|
||||||
session.And("question.show = ?", entity.QuestionShow)
|
session.Where("status = ? OR status = ?", entity.QuestionStatusAvailable, entity.QuestionStatusClosed)
|
||||||
session = session.Limit(pageSize, offset)
|
session.Limit(pageSize, page*pageSize)
|
||||||
session = session.OrderBy("question.created_at asc")
|
session.Asc("created_at")
|
||||||
err = session.Select("id,title,created_at,post_update_time").Find(&rows)
|
err = session.Find(&rows)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return questionIDList, err
|
return questionIDList, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// warp data
|
||||||
for _, question := range rows {
|
for _, question := range rows {
|
||||||
item := &schema.SiteMapQuestionInfo{}
|
item := &schema.SiteMapQuestionInfo{ID: question.ID}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
item.ID = uid.EnShortID(question.ID)
|
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.UpdateTime = updateTime
|
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)
|
||||||
|
}
|
||||||
questionIDList = append(questionIDList, item)
|
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
|
return questionIDList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,13 +339,15 @@ func (qr *questionRepo) GetQuestionPage(ctx context.Context, page, pageSize int,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
for _, item := range questionList {
|
for _, item := range questionList {
|
||||||
item.ID = uid.EnShortID(item.ID)
|
item.ID = uid.EnShortID(item.ID)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return questionList, total, err
|
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 (
|
var (
|
||||||
count int64
|
count int64
|
||||||
err error
|
err error
|
||||||
|
@ -379,8 +408,10 @@ func (qr *questionRepo) AdminSearchList(ctx context.Context, search *schema.Admi
|
||||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
return rows, count, err
|
return rows, count, err
|
||||||
}
|
}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
for _, item := range rows {
|
for _, item := range rows {
|
||||||
item.ID = uid.EnShortID(item.ID)
|
item.ID = uid.EnShortID(item.ID)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return rows, count, nil
|
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
|
// TriggerUserRank trigger user rank change
|
||||||
// session is need provider, it means this action must be success or failure
|
// session is need provider, it means this action must be success or failure
|
||||||
// if outer action is failed then this action is need rollback
|
// 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,
|
session *xorm.Session, userID string, deltaRank int, activityType int,
|
||||||
) (isReachStandard bool, err error) {
|
) (isReachStandard bool, err error) {
|
||||||
// IMPORTANT: If user center enabled the rank agent, then we should not change user rank.
|
// IMPORTANT: If user center enabled the rank agent, then we should not change user rank.
|
||||||
if plugin.RankAgentEnabled() {
|
if plugin.RankAgentEnabled() || deltaRank == 0 {
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
if deltaRank == 0 {
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +161,7 @@ func (ur *UserRankRepo) checkUserTodayRank(ctx context.Context,
|
||||||
LessVal: start,
|
LessVal: start,
|
||||||
MoreVal: end,
|
MoreVal: end,
|
||||||
})
|
})
|
||||||
earned, err := session.Sum(&entity.Activity{}, "rank")
|
earned, err := session.Sum(&entity.Activity{}, "`rank`")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -137,7 +184,7 @@ func (ur *UserRankRepo) UserRankPage(ctx context.Context, userID string, page, p
|
||||||
) {
|
) {
|
||||||
rankPage = make([]*entity.Activity, 0)
|
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")
|
session.Desc("created_at")
|
||||||
|
|
||||||
cond := &entity.Activity{UserID: userID}
|
cond := &entity.Activity{UserID: userID}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/data"
|
"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/base/reason"
|
||||||
"github.com/answerdev/answer/internal/entity"
|
"github.com/answerdev/answer/internal/entity"
|
||||||
tagcommon "github.com/answerdev/answer/internal/service/tag_common"
|
tagcommon "github.com/answerdev/answer/internal/service/tag_common"
|
||||||
|
@ -36,9 +37,11 @@ func (tr *tagRelRepo) AddTagRelList(ctx context.Context, tagList []*entity.TagRe
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
for _, item := range tagList {
|
for _, item := range tagList {
|
||||||
item.ObjectID = uid.EnShortID(item.ObjectID)
|
item.ObjectID = uid.EnShortID(item.ObjectID)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return
|
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) {
|
func (tr *tagRelRepo) HideTagRelListByObjectID(ctx context.Context, objectID string) (err error) {
|
||||||
objectID = uid.DeShortID(objectID)
|
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 {
|
if err != nil {
|
||||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
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) {
|
func (tr *tagRelRepo) ShowTagRelListByObjectID(ctx context.Context, objectID string) (err error) {
|
||||||
objectID = uid.DeShortID(objectID)
|
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 {
|
if err != nil {
|
||||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
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)
|
exist, err = session.Get(tagRel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,10 +118,13 @@ func (tr *tagRelRepo) GetObjectTagRelList(ctx context.Context, objectID string)
|
||||||
err = session.Find(&tagListList)
|
err = session.Find(&tagListList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
for _, item := range tagListList {
|
for _, item := range tagListList {
|
||||||
item.ObjectID = uid.EnShortID(item.ObjectID)
|
item.ObjectID = uid.EnShortID(item.ObjectID)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,10 +139,13 @@ func (tr *tagRelRepo) BatchGetObjectTagRelList(ctx context.Context, objectIds []
|
||||||
err = session.Find(&tagListList)
|
err = session.Find(&tagListList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
for _, item := range tagListList {
|
for _, item := range tagListList {
|
||||||
item.ObjectID = uid.EnShortID(item.ObjectID)
|
item.ObjectID = uid.EnShortID(item.ObjectID)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return
|
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) {
|
func (ur *userRepo) UpdateQuestionCount(ctx context.Context, userID string, count int64) (err error) {
|
||||||
user := &entity.User{}
|
user := &entity.User{}
|
||||||
user.QuestionCount = int(count)
|
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 {
|
if err != nil {
|
||||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
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) {
|
func (ur *userRepo) UpdateAnswerCount(ctx context.Context, userID string, count int) (err error) {
|
||||||
user := &entity.User{}
|
user := &entity.User{}
|
||||||
user.AnswerCount = count
|
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 {
|
if err != nil {
|
||||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
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) {
|
func (ur *userRepo) GetByUsernames(ctx context.Context, usernames []string) ([]*entity.User, error) {
|
||||||
list := make([]*entity.User, 0)
|
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 {
|
if err != nil {
|
||||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
return list, err
|
return list, err
|
||||||
|
|
|
@ -26,7 +26,7 @@ type AnswerAPIRouter struct {
|
||||||
reasonController *controller.ReasonController
|
reasonController *controller.ReasonController
|
||||||
themeController *controller_admin.ThemeController
|
themeController *controller_admin.ThemeController
|
||||||
siteInfoController *controller_admin.SiteInfoController
|
siteInfoController *controller_admin.SiteInfoController
|
||||||
siteinfoController *controller.SiteinfoController
|
siteinfoController *controller.SiteInfoController
|
||||||
notificationController *controller.NotificationController
|
notificationController *controller.NotificationController
|
||||||
dashboardController *controller.DashboardController
|
dashboardController *controller.DashboardController
|
||||||
uploadController *controller.UploadController
|
uploadController *controller.UploadController
|
||||||
|
@ -55,7 +55,7 @@ func NewAnswerAPIRouter(
|
||||||
reasonController *controller.ReasonController,
|
reasonController *controller.ReasonController,
|
||||||
themeController *controller_admin.ThemeController,
|
themeController *controller_admin.ThemeController,
|
||||||
siteInfoController *controller_admin.SiteInfoController,
|
siteInfoController *controller_admin.SiteInfoController,
|
||||||
siteinfoController *controller.SiteinfoController,
|
siteinfoController *controller.SiteInfoController,
|
||||||
notificationController *controller.NotificationController,
|
notificationController *controller.NotificationController,
|
||||||
dashboardController *controller.DashboardController,
|
dashboardController *controller.DashboardController,
|
||||||
uploadController *controller.UploadController,
|
uploadController *controller.UploadController,
|
||||||
|
@ -244,9 +244,9 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(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.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)
|
r.PUT("/answer/status", a.answerController.AdminSetAnswerStatus)
|
||||||
|
|
||||||
// report
|
// report
|
||||||
|
|
|
@ -17,7 +17,6 @@ func NewTemplateRouter(
|
||||||
templateController *controller.TemplateController,
|
templateController *controller.TemplateController,
|
||||||
templateRenderController *templaterender.TemplateRenderController,
|
templateRenderController *templaterender.TemplateRenderController,
|
||||||
siteInfoController *controller_admin.SiteInfoController,
|
siteInfoController *controller_admin.SiteInfoController,
|
||||||
|
|
||||||
) *TemplateRouter {
|
) *TemplateRouter {
|
||||||
return &TemplateRouter{
|
return &TemplateRouter{
|
||||||
templateController: templateController,
|
templateController: templateController,
|
||||||
|
@ -26,7 +25,7 @@ func NewTemplateRouter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TemplateRouter template router
|
// RegisterTemplateRouter template router
|
||||||
func (a *TemplateRouter) RegisterTemplateRouter(r *gin.RouterGroup) {
|
func (a *TemplateRouter) RegisterTemplateRouter(r *gin.RouterGroup) {
|
||||||
r.GET("/sitemap.xml", a.templateController.Sitemap)
|
r.GET("/sitemap.xml", a.templateController.Sitemap)
|
||||||
r.GET("/sitemap/:page", a.templateController.SitemapPage)
|
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("/custom.css", a.siteInfoController.GetCss)
|
||||||
|
|
||||||
r.GET("/", a.templateController.Index)
|
r.GET("/", a.templateController.Index)
|
||||||
r.GET("/index", a.templateController.Index)
|
|
||||||
|
|
||||||
r.GET("/questions", a.templateController.QuestionList)
|
r.GET("/questions", a.templateController.QuestionList)
|
||||||
r.GET("/questions/:id", a.templateController.QuestionInfo)
|
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
|
// UIRouter is an interface that provides ui static file routers
|
||||||
type UIRouter struct {
|
type UIRouter struct {
|
||||||
siteInfoController *controller.SiteinfoController
|
siteInfoController *controller.SiteInfoController
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUIRouter creates a new UIRouter instance with the embed resources
|
// NewUIRouter creates a new UIRouter instance with the embed resources
|
||||||
func NewUIRouter(
|
func NewUIRouter(
|
||||||
siteInfoController *controller.SiteinfoController,
|
siteInfoController *controller.SiteInfoController,
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService,
|
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||||
) *UIRouter {
|
) *UIRouter {
|
||||||
return &UIRouter{
|
return &UIRouter{
|
||||||
siteInfoController: siteInfoController,
|
siteInfoController: siteInfoController,
|
||||||
|
|
|
@ -4,12 +4,13 @@ import "github.com/answerdev/answer/internal/base/constant"
|
||||||
|
|
||||||
// ActivityMsg activity message
|
// ActivityMsg activity message
|
||||||
type ActivityMsg struct {
|
type ActivityMsg struct {
|
||||||
UserID string `json:"user_id"`
|
UserID string
|
||||||
TriggerUserID int64 `json:"trigger_user_id"`
|
TriggerUserID int64
|
||||||
ObjectID string `json:"object_id"`
|
ObjectID string
|
||||||
OriginalObjectID string `json:"original_object_id"`
|
OriginalObjectID string
|
||||||
ActivityTypeKey constant.ActivityTypeKey `json:"activity_type_key"`
|
ActivityTypeKey constant.ActivityTypeKey
|
||||||
RevisionID string `json:"revision_id"`
|
RevisionID string
|
||||||
|
ExtraInfo map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetObjectTimelineReq get object timeline request
|
// 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
|
var AppStartTime time.Time
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DashBoardCachekey = "answer@dashboard"
|
DashboardCacheKey = "answer:dashboard"
|
||||||
DashBoardCacheTime = 60 * time.Minute
|
DashboardCacheTime = 60 * time.Minute
|
||||||
)
|
)
|
||||||
|
|
||||||
type DashboardInfo struct {
|
type DashboardInfo struct {
|
||||||
|
|
|
@ -63,6 +63,8 @@ type NotificationMsg struct {
|
||||||
NotificationAction string
|
NotificationAction string
|
||||||
// if true no need to send notification to all followers
|
// if true no need to send notification to all followers
|
||||||
NoNeedPushAllFollow bool
|
NoNeedPushAllFollow bool
|
||||||
|
// extra info
|
||||||
|
ExtraInfo map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ObjectInfo struct {
|
type ObjectInfo struct {
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
package schema
|
package schema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/validator"
|
"github.com/answerdev/answer/internal/base/validator"
|
||||||
|
"github.com/answerdev/answer/internal/entity"
|
||||||
"github.com/answerdev/answer/pkg/converter"
|
"github.com/answerdev/answer/pkg/converter"
|
||||||
|
"github.com/answerdev/answer/pkg/uid"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SitemapMaxSize = 50000
|
|
||||||
SitemapCachekey = "answer@sitemap"
|
|
||||||
SitemapPageCachekey = "answer@sitemap@page%d"
|
|
||||||
QuestionOperationPin = "pin"
|
QuestionOperationPin = "pin"
|
||||||
QuestionOperationUnPin = "unpin"
|
QuestionOperationUnPin = "unpin"
|
||||||
QuestionOperationHide = "hide"
|
QuestionOperationHide = "hide"
|
||||||
|
@ -361,12 +361,62 @@ type QuestionPageRespOperator struct {
|
||||||
DisplayName string `json:"display_name"`
|
DisplayName string `json:"display_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdminQuestionSearch struct {
|
type AdminQuestionPageReq struct {
|
||||||
Page int `json:"page" form:"page"` // Query number of pages
|
Page int `validate:"omitempty,min=1" form:"page"`
|
||||||
PageSize int `json:"page_size" form:"page_size"` // Search page size
|
PageSize int `validate:"omitempty,min=1" form:"page_size"`
|
||||||
Status int `json:"-" form:"-"`
|
StatusCond string `validate:"omitempty,oneof=normal closed deleted" form:"status"`
|
||||||
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 `validate:"omitempty,gt=0,lte=100" json:"query" form:"query" ` //Query string
|
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 {
|
type AdminSetQuestionStatusRequest struct {
|
||||||
|
@ -374,21 +424,6 @@ type AdminSetQuestionStatusRequest struct {
|
||||||
QuestionID string `json:"question_id" form:"question_id"`
|
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 {
|
type PersonalQuestionPageReq struct {
|
||||||
Page int `validate:"omitempty,min=1" form:"page"`
|
Page int `validate:"omitempty,min=1" form:"page"`
|
||||||
PageSize int `validate:"omitempty,min=1" form:"page_size"`
|
PageSize int `validate:"omitempty,min=1" form:"page_size"`
|
||||||
|
|
|
@ -14,11 +14,6 @@ import (
|
||||||
"github.com/segmentfault/pacman/errors"
|
"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
|
// SiteGeneralReq site general request
|
||||||
type SiteGeneralReq struct {
|
type SiteGeneralReq struct {
|
||||||
Name string `validate:"required,sanitizer,gt=1,lte=128" form:"name" json:"name"`
|
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"`
|
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() {
|
func (r *SiteGeneralReq) FormatSiteUrl() {
|
||||||
parsedUrl, err := url.Parse(r.SiteUrl)
|
parsedUrl, err := url.Parse(r.SiteUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -127,6 +117,16 @@ type SiteThemeReq struct {
|
||||||
ThemeConfig map[string]interface{} `validate:"omitempty" json:"theme_config"`
|
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
|
// SiteGeneralResp site general response
|
||||||
type SiteGeneralResp SiteGeneralReq
|
type SiteGeneralResp SiteGeneralReq
|
||||||
|
|
||||||
|
@ -186,7 +186,7 @@ type SiteInfoResp struct {
|
||||||
Login *SiteLoginResp `json:"login"`
|
Login *SiteLoginResp `json:"login"`
|
||||||
Theme *SiteThemeResp `json:"theme"`
|
Theme *SiteThemeResp `json:"theme"`
|
||||||
CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"`
|
CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"`
|
||||||
SiteSeo *SiteSeoReq `json:"site_seo"`
|
SiteSeo *SiteSeoResp `json:"site_seo"`
|
||||||
SiteUsers *SiteUsersResp `json:"site_users"`
|
SiteUsers *SiteUsersResp `json:"site_users"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Revision string `json:"revision"`
|
Revision string `json:"revision"`
|
||||||
|
@ -195,7 +195,7 @@ type TemplateSiteInfoResp struct {
|
||||||
General *SiteGeneralResp `json:"general"`
|
General *SiteGeneralResp `json:"general"`
|
||||||
Interface *SiteInterfaceResp `json:"interface"`
|
Interface *SiteInterfaceResp `json:"interface"`
|
||||||
Branding *SiteBrandingResp `json:"branding"`
|
Branding *SiteBrandingResp `json:"branding"`
|
||||||
SiteSeo *SiteSeoReq `json:"site_seo"`
|
SiteSeo *SiteSeoResp `json:"site_seo"`
|
||||||
CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"`
|
CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"`
|
||||||
Title string
|
Title string
|
||||||
Year 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:"-"`
|
UserID string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type VoteDTO struct {
|
type VoteResp struct {
|
||||||
// object TagID
|
UpVotes int64 `json:"up_votes"`
|
||||||
ObjectID string
|
DownVotes int64 `json:"down_votes"`
|
||||||
// is cancel
|
Votes int64 `json:"votes"`
|
||||||
IsCancel bool
|
VoteStatus string `json:"vote_status"`
|
||||||
// user TagID
|
|
||||||
UserID string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type VoteResp struct {
|
// VoteOperationInfo vote operation info
|
||||||
UpVotes int `json:"up_votes"`
|
type VoteOperationInfo struct {
|
||||||
DownVotes int `json:"down_votes"`
|
// operation object id
|
||||||
Votes int `json:"votes"`
|
ObjectID string
|
||||||
VoteStatus string `json:"vote_status"`
|
// 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 {
|
type GetVoteWithPageReq struct {
|
||||||
|
@ -28,23 +52,7 @@ type GetVoteWithPageReq struct {
|
||||||
// page size
|
// page size
|
||||||
PageSize int `validate:"omitempty,min=1" form:"page_size"`
|
PageSize int `validate:"omitempty,min=1" form:"page_size"`
|
||||||
// user id
|
// user id
|
||||||
UserID string `validate:"required" form:"user_id"`
|
UserID string `json:"-"`
|
||||||
}
|
|
||||||
|
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetVoteWithPageResp struct {
|
type GetVoteWithPageResp struct {
|
||||||
|
|
|
@ -7,9 +7,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/constant"
|
"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/entity"
|
||||||
"github.com/answerdev/answer/internal/schema"
|
"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/comment_common"
|
||||||
"github.com/answerdev/answer/internal/service/config"
|
"github.com/answerdev/answer/internal/service/config"
|
||||||
"github.com/answerdev/answer/internal/service/meta"
|
"github.com/answerdev/answer/internal/service/meta"
|
||||||
|
@ -32,7 +32,6 @@ type ActivityRepo interface {
|
||||||
type ActivityService struct {
|
type ActivityService struct {
|
||||||
activityRepo ActivityRepo
|
activityRepo ActivityRepo
|
||||||
userCommon *usercommon.UserCommon
|
userCommon *usercommon.UserCommon
|
||||||
activityCommonService *activity_common.ActivityCommon
|
|
||||||
tagCommonService *tag_common.TagCommonService
|
tagCommonService *tag_common.TagCommonService
|
||||||
objectInfoService *object_info.ObjService
|
objectInfoService *object_info.ObjService
|
||||||
commentCommonService *comment_common.CommentCommonService
|
commentCommonService *comment_common.CommentCommonService
|
||||||
|
@ -45,7 +44,6 @@ type ActivityService struct {
|
||||||
func NewActivityService(
|
func NewActivityService(
|
||||||
activityRepo ActivityRepo,
|
activityRepo ActivityRepo,
|
||||||
userCommon *usercommon.UserCommon,
|
userCommon *usercommon.UserCommon,
|
||||||
activityCommonService *activity_common.ActivityCommon,
|
|
||||||
tagCommonService *tag_common.TagCommonService,
|
tagCommonService *tag_common.TagCommonService,
|
||||||
objectInfoService *object_info.ObjService,
|
objectInfoService *object_info.ObjService,
|
||||||
commentCommonService *comment_common.CommentCommonService,
|
commentCommonService *comment_common.CommentCommonService,
|
||||||
|
@ -57,7 +55,6 @@ func NewActivityService(
|
||||||
objectInfoService: objectInfoService,
|
objectInfoService: objectInfoService,
|
||||||
activityRepo: activityRepo,
|
activityRepo: activityRepo,
|
||||||
userCommon: userCommon,
|
userCommon: userCommon,
|
||||||
activityCommonService: activityCommonService,
|
|
||||||
tagCommonService: tagCommonService,
|
tagCommonService: tagCommonService,
|
||||||
commentCommonService: commentCommonService,
|
commentCommonService: commentCommonService,
|
||||||
revisionService: revisionService,
|
revisionService: revisionService,
|
||||||
|
@ -97,8 +94,10 @@ func (as *ActivityService) GetObjectTimeline(ctx context.Context, req *schema.Ge
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.ObjectType == constant.QuestionObjectType || item.ObjectType == constant.AnswerObjectType {
|
if item.ObjectType == constant.QuestionObjectType || item.ObjectType == constant.AnswerObjectType {
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
item.ObjectID = uid.EnShortID(act.ObjectID)
|
item.ObjectID = uid.EnShortID(act.ObjectID)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cfg, err := as.configService.GetConfigByID(ctx, act.ActivityType)
|
cfg, err := as.configService.GetConfigByID(ctx, act.ActivityType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -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"
|
"time"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/entity"
|
"github.com/answerdev/answer/internal/entity"
|
||||||
|
"github.com/answerdev/answer/internal/schema"
|
||||||
"github.com/answerdev/answer/internal/service/activity_queue"
|
"github.com/answerdev/answer/internal/service/activity_queue"
|
||||||
"github.com/answerdev/answer/pkg/converter"
|
"github.com/answerdev/answer/pkg/converter"
|
||||||
"github.com/answerdev/answer/pkg/uid"
|
"github.com/answerdev/answer/pkg/uid"
|
||||||
|
@ -14,7 +15,7 @@ import (
|
||||||
|
|
||||||
type ActivityRepo interface {
|
type ActivityRepo interface {
|
||||||
GetActivityTypeByObjID(ctx context.Context, objectId string, action string) (activityType, rank int, hasRank int, err error)
|
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) (
|
GetActivity(ctx context.Context, session *xorm.Session, objectID, userID string, activityType int) (
|
||||||
existsActivity *entity.Activity, exist bool, err error)
|
existsActivity *entity.Activity, exist bool, err error)
|
||||||
GetUserIDObjectIDActivitySum(ctx context.Context, userID, objectID string) (int, error)
|
GetUserIDObjectIDActivitySum(ctx context.Context, userID, objectID string) (int, error)
|
||||||
|
@ -28,34 +29,28 @@ type ActivityRepo interface {
|
||||||
|
|
||||||
type ActivityCommon struct {
|
type ActivityCommon struct {
|
||||||
activityRepo ActivityRepo
|
activityRepo ActivityRepo
|
||||||
|
activityQueueService activity_queue.ActivityQueueService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewActivityCommon new activity common
|
// NewActivityCommon new activity common
|
||||||
func NewActivityCommon(
|
func NewActivityCommon(
|
||||||
activityRepo ActivityRepo,
|
activityRepo ActivityRepo,
|
||||||
|
activityQueueService activity_queue.ActivityQueueService,
|
||||||
) *ActivityCommon {
|
) *ActivityCommon {
|
||||||
activity := &ActivityCommon{
|
activity := &ActivityCommon{
|
||||||
activityRepo: activityRepo,
|
activityRepo: activityRepo,
|
||||||
|
activityQueueService: activityQueueService,
|
||||||
}
|
}
|
||||||
activity.HandleActivity()
|
activity.activityQueueService.RegisterHandler(activity.HandleActivity)
|
||||||
return activity
|
return activity
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleActivity handle activity message
|
// HandleActivity handle activity message
|
||||||
func (ac *ActivityCommon) HandleActivity() {
|
func (ac *ActivityCommon) HandleActivity(ctx context.Context, msg *schema.ActivityMsg) error {
|
||||||
go func() {
|
activityType, err := ac.activityRepo.GetActivityTypeByConfigKey(ctx, string(msg.ActivityTypeKey))
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
log.Error(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 {
|
if err != nil {
|
||||||
log.Errorf("error getting activity type %s, activity type is %d", err, activityType)
|
log.Errorf("error getting activity type %s, activity type is %d", err, activityType)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
act := &entity.Activity{
|
act := &entity.Activity{
|
||||||
|
@ -69,9 +64,8 @@ func (ac *ActivityCommon) HandleActivity() {
|
||||||
if len(msg.RevisionID) > 0 {
|
if len(msg.RevisionID) > 0 {
|
||||||
act.RevisionID = converter.StringToInt64(msg.RevisionID)
|
act.RevisionID = converter.StringToInt64(msg.RevisionID)
|
||||||
}
|
}
|
||||||
if err := ac.activityRepo.AddActivity(context.TODO(), act); err != nil {
|
if err := ac.activityRepo.AddActivity(ctx, act); err != nil {
|
||||||
log.Error(err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
return nil
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,50 @@
|
||||||
package activity_queue
|
package activity_queue
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/schema"
|
"github.com/answerdev/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/pacman/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type ActivityQueueService interface {
|
||||||
ActivityQueue = make(chan *schema.ActivityMsg, 128)
|
Send(ctx context.Context, msg *schema.ActivityMsg)
|
||||||
)
|
RegisterHandler(handler func(ctx context.Context, msg *schema.ActivityMsg) error)
|
||||||
|
}
|
||||||
// AddActivity add new activity
|
|
||||||
func AddActivity(msg *schema.ActivityMsg) {
|
type activityQueueService struct {
|
||||||
ActivityQueue <- msg
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/answerdev/answer/internal/base/handler"
|
||||||
"github.com/answerdev/answer/internal/entity"
|
"github.com/answerdev/answer/internal/entity"
|
||||||
"github.com/answerdev/answer/internal/schema"
|
"github.com/answerdev/answer/internal/schema"
|
||||||
"github.com/answerdev/answer/pkg/htmltext"
|
"github.com/answerdev/answer/pkg/htmltext"
|
||||||
|
"github.com/answerdev/answer/pkg/uid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AnswerRepo interface {
|
type AnswerRepo interface {
|
||||||
|
@ -21,7 +23,7 @@ type AnswerRepo interface {
|
||||||
GetCountByUserID(ctx context.Context, userID string) (int64, error)
|
GetCountByUserID(ctx context.Context, userID string) (int64, error)
|
||||||
GetByUserIDQuestionID(ctx context.Context, userID string, questionID string) (*entity.Answer, bool, error)
|
GetByUserIDQuestionID(ctx context.Context, userID string, questionID string) (*entity.Answer, bool, error)
|
||||||
SearchList(ctx context.Context, search *entity.AnswerSearch) ([]*entity.Answer, int64, error)
|
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)
|
UpdateAnswerStatus(ctx context.Context, answer *entity.Answer) (err error)
|
||||||
GetAnswerCount(ctx context.Context) (count int64, 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
|
return has, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *AnswerCommon) AdminSearchList(ctx context.Context, search *entity.AdminAnswerSearch) ([]*entity.Answer, int64, error) {
|
func (as *AnswerCommon) AdminSearchList(ctx context.Context, req *schema.AdminAnswerPageReq) (
|
||||||
if search.Status == 0 {
|
resp []*entity.Answer, count int64, err error) {
|
||||||
search.Status = 1
|
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) {
|
func (as *AnswerCommon) Search(ctx context.Context, search *entity.AnswerSearch) ([]*entity.Answer, int64, error) {
|
||||||
|
|
|
@ -43,6 +43,8 @@ type AnswerService struct {
|
||||||
voteRepo activity_common.VoteRepo
|
voteRepo activity_common.VoteRepo
|
||||||
emailService *export.EmailService
|
emailService *export.EmailService
|
||||||
roleService *role.UserRoleRelService
|
roleService *role.UserRoleRelService
|
||||||
|
notificationQueueService notice_queue.NotificationQueueService
|
||||||
|
activityQueueService activity_queue.ActivityQueueService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAnswerService(
|
func NewAnswerService(
|
||||||
|
@ -58,6 +60,8 @@ func NewAnswerService(
|
||||||
voteRepo activity_common.VoteRepo,
|
voteRepo activity_common.VoteRepo,
|
||||||
emailService *export.EmailService,
|
emailService *export.EmailService,
|
||||||
roleService *role.UserRoleRelService,
|
roleService *role.UserRoleRelService,
|
||||||
|
notificationQueueService notice_queue.NotificationQueueService,
|
||||||
|
activityQueueService activity_queue.ActivityQueueService,
|
||||||
) *AnswerService {
|
) *AnswerService {
|
||||||
return &AnswerService{
|
return &AnswerService{
|
||||||
answerRepo: answerRepo,
|
answerRepo: answerRepo,
|
||||||
|
@ -72,6 +76,8 @@ func NewAnswerService(
|
||||||
voteRepo: voteRepo,
|
voteRepo: voteRepo,
|
||||||
emailService: emailService,
|
emailService: emailService,
|
||||||
roleService: roleService,
|
roleService: roleService,
|
||||||
|
notificationQueueService: notificationQueueService,
|
||||||
|
activityQueueService: activityQueueService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +142,7 @@ func (as *AnswerService) RemoveAnswer(ctx context.Context, req *schema.RemoveAns
|
||||||
//if err != nil {
|
//if err != nil {
|
||||||
// log.Errorf("delete answer activity change failed: %s", err.Error())
|
// log.Errorf("delete answer activity change failed: %s", err.Error())
|
||||||
//}
|
//}
|
||||||
activity_queue.AddActivity(&schema.ActivityMsg{
|
as.activityQueueService.Send(ctx, &schema.ActivityMsg{
|
||||||
UserID: req.UserID,
|
UserID: req.UserID,
|
||||||
ObjectID: answerInfo.ID,
|
ObjectID: answerInfo.ID,
|
||||||
OriginalObjectID: 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,
|
as.notificationAnswerTheQuestion(ctx, questionInfo.UserID, questionInfo.ID, insertData.ID, req.UserID, questionInfo.Title,
|
||||||
insertData.OriginalText)
|
insertData.OriginalText)
|
||||||
|
|
||||||
activity_queue.AddActivity(&schema.ActivityMsg{
|
as.activityQueueService.Send(ctx, &schema.ActivityMsg{
|
||||||
UserID: insertData.UserID,
|
UserID: insertData.UserID,
|
||||||
ObjectID: insertData.ID,
|
ObjectID: insertData.ID,
|
||||||
OriginalObjectID: insertData.ID,
|
OriginalObjectID: insertData.ID,
|
||||||
ActivityTypeKey: constant.ActAnswerAnswered,
|
ActivityTypeKey: constant.ActAnswerAnswered,
|
||||||
RevisionID: revisionID,
|
RevisionID: revisionID,
|
||||||
})
|
})
|
||||||
activity_queue.AddActivity(&schema.ActivityMsg{
|
as.activityQueueService.Send(ctx, &schema.ActivityMsg{
|
||||||
UserID: insertData.UserID,
|
UserID: insertData.UserID,
|
||||||
ObjectID: insertData.ID,
|
ObjectID: insertData.ID,
|
||||||
OriginalObjectID: questionInfo.ID,
|
OriginalObjectID: questionInfo.ID,
|
||||||
|
@ -305,7 +311,7 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq
|
||||||
return insertData.ID, err
|
return insertData.ID, err
|
||||||
}
|
}
|
||||||
if canUpdate {
|
if canUpdate {
|
||||||
activity_queue.AddActivity(&schema.ActivityMsg{
|
as.activityQueueService.Send(ctx, &schema.ActivityMsg{
|
||||||
UserID: insertData.UserID,
|
UserID: insertData.UserID,
|
||||||
ObjectID: insertData.ID,
|
ObjectID: insertData.ID,
|
||||||
OriginalObjectID: insertData.ID,
|
OriginalObjectID: insertData.ID,
|
||||||
|
@ -472,7 +478,7 @@ func (as *AnswerService) AdminSetAnswerStatus(ctx context.Context, req *schema.A
|
||||||
//if err != nil {
|
//if err != nil {
|
||||||
// log.Errorf("admin delete question then rank rollback error %s", err.Error())
|
// 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,
|
UserID: req.UserID,
|
||||||
ObjectID: answerInfo.ID,
|
ObjectID: answerInfo.ID,
|
||||||
OriginalObjectID: answerInfo.ID,
|
OriginalObjectID: answerInfo.ID,
|
||||||
|
@ -487,7 +493,7 @@ func (as *AnswerService) AdminSetAnswerStatus(ctx context.Context, req *schema.A
|
||||||
msg.TriggerUserID = answerInfo.UserID
|
msg.TriggerUserID = answerInfo.UserID
|
||||||
msg.ObjectType = constant.AnswerObjectType
|
msg.ObjectType = constant.AnswerObjectType
|
||||||
msg.NotificationAction = constant.NotificationYourAnswerWasDeleted
|
msg.NotificationAction = constant.NotificationYourAnswerWasDeleted
|
||||||
notice_queue.AddNotification(msg)
|
as.notificationQueueService.Send(ctx, msg)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -579,7 +585,7 @@ func (as *AnswerService) notificationUpdateAnswer(ctx context.Context, questionU
|
||||||
}
|
}
|
||||||
msg.ObjectType = constant.AnswerObjectType
|
msg.ObjectType = constant.AnswerObjectType
|
||||||
msg.NotificationAction = constant.NotificationUpdateAnswer
|
msg.NotificationAction = constant.NotificationUpdateAnswer
|
||||||
notice_queue.AddNotification(msg)
|
as.notificationQueueService.Send(ctx, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *AnswerService) notificationAnswerTheQuestion(ctx context.Context,
|
func (as *AnswerService) notificationAnswerTheQuestion(ctx context.Context,
|
||||||
|
@ -596,7 +602,7 @@ func (as *AnswerService) notificationAnswerTheQuestion(ctx context.Context,
|
||||||
}
|
}
|
||||||
msg.ObjectType = constant.AnswerObjectType
|
msg.ObjectType = constant.AnswerObjectType
|
||||||
msg.NotificationAction = constant.NotificationAnswerTheQuestion
|
msg.NotificationAction = constant.NotificationAnswerTheQuestion
|
||||||
notice_queue.AddNotification(msg)
|
as.notificationQueueService.Send(ctx, msg)
|
||||||
|
|
||||||
userInfo, exist, err := as.userRepo.GetByUserID(ctx, questionUserID)
|
userInfo, exist, err := as.userRepo.GetByUserID(ctx, questionUserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -65,6 +65,8 @@ type CommentService struct {
|
||||||
objectInfoService *object_info.ObjService
|
objectInfoService *object_info.ObjService
|
||||||
emailService *export.EmailService
|
emailService *export.EmailService
|
||||||
userRepo usercommon.UserRepo
|
userRepo usercommon.UserRepo
|
||||||
|
notificationQueueService notice_queue.NotificationQueueService
|
||||||
|
activityQueueService activity_queue.ActivityQueueService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCommentService new comment service
|
// NewCommentService new comment service
|
||||||
|
@ -76,6 +78,8 @@ func NewCommentService(
|
||||||
voteCommon activity_common.VoteRepo,
|
voteCommon activity_common.VoteRepo,
|
||||||
emailService *export.EmailService,
|
emailService *export.EmailService,
|
||||||
userRepo usercommon.UserRepo,
|
userRepo usercommon.UserRepo,
|
||||||
|
notificationQueueService notice_queue.NotificationQueueService,
|
||||||
|
activityQueueService activity_queue.ActivityQueueService,
|
||||||
) *CommentService {
|
) *CommentService {
|
||||||
return &CommentService{
|
return &CommentService{
|
||||||
commentRepo: commentRepo,
|
commentRepo: commentRepo,
|
||||||
|
@ -85,6 +89,8 @@ func NewCommentService(
|
||||||
objectInfoService: objectInfoService,
|
objectInfoService: objectInfoService,
|
||||||
emailService: emailService,
|
emailService: emailService,
|
||||||
userRepo: userRepo,
|
userRepo: userRepo,
|
||||||
|
notificationQueueService: notificationQueueService,
|
||||||
|
activityQueueService: activityQueueService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +167,7 @@ func (cs *CommentService) AddComment(ctx context.Context, req *schema.AddComment
|
||||||
case constant.AnswerObjectType:
|
case constant.AnswerObjectType:
|
||||||
activityMsg.ActivityTypeKey = constant.ActAnswerCommented
|
activityMsg.ActivityTypeKey = constant.ActAnswerCommented
|
||||||
}
|
}
|
||||||
activity_queue.AddActivity(activityMsg)
|
cs.activityQueueService.Send(ctx, activityMsg)
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -476,7 +482,7 @@ func (cs *CommentService) notificationQuestionComment(ctx context.Context, quest
|
||||||
}
|
}
|
||||||
msg.ObjectType = constant.CommentObjectType
|
msg.ObjectType = constant.CommentObjectType
|
||||||
msg.NotificationAction = constant.NotificationCommentQuestion
|
msg.NotificationAction = constant.NotificationCommentQuestion
|
||||||
notice_queue.AddNotification(msg)
|
cs.notificationQueueService.Send(ctx, msg)
|
||||||
|
|
||||||
receiverUserInfo, exist, err := cs.userRepo.GetByUserID(ctx, questionUserID)
|
receiverUserInfo, exist, err := cs.userRepo.GetByUserID(ctx, questionUserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -535,7 +541,7 @@ func (cs *CommentService) notificationAnswerComment(ctx context.Context,
|
||||||
}
|
}
|
||||||
msg.ObjectType = constant.CommentObjectType
|
msg.ObjectType = constant.CommentObjectType
|
||||||
msg.NotificationAction = constant.NotificationCommentAnswer
|
msg.NotificationAction = constant.NotificationCommentAnswer
|
||||||
notice_queue.AddNotification(msg)
|
cs.notificationQueueService.Send(ctx, msg)
|
||||||
|
|
||||||
receiverUserInfo, exist, err := cs.userRepo.GetByUserID(ctx, answerUserID)
|
receiverUserInfo, exist, err := cs.userRepo.GetByUserID(ctx, answerUserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -591,7 +597,7 @@ func (cs *CommentService) notificationCommentReply(ctx context.Context, replyUse
|
||||||
}
|
}
|
||||||
msg.ObjectType = constant.CommentObjectType
|
msg.ObjectType = constant.CommentObjectType
|
||||||
msg.NotificationAction = constant.NotificationReplyToYou
|
msg.NotificationAction = constant.NotificationReplyToYou
|
||||||
notice_queue.AddNotification(msg)
|
cs.notificationQueueService.Send(ctx, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *CommentService) notificationMention(
|
func (cs *CommentService) notificationMention(
|
||||||
|
@ -612,7 +618,7 @@ func (cs *CommentService) notificationMention(
|
||||||
}
|
}
|
||||||
msg.ObjectType = constant.CommentObjectType
|
msg.ObjectType = constant.CommentObjectType
|
||||||
msg.NotificationAction = constant.NotificationMentionYou
|
msg.NotificationAction = constant.NotificationMentionYou
|
||||||
notice_queue.AddNotification(msg)
|
cs.notificationQueueService.Send(ctx, msg)
|
||||||
alreadyNotifiedUserIDs = append(alreadyNotifiedUserIDs, userInfo.ID)
|
alreadyNotifiedUserIDs = append(alreadyNotifiedUserIDs, userInfo.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/constant"
|
"github.com/answerdev/answer/internal/base/constant"
|
||||||
"github.com/answerdev/answer/internal/base/data"
|
"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/schema"
|
||||||
"github.com/answerdev/answer/internal/service/activity_common"
|
"github.com/answerdev/answer/internal/service/activity_common"
|
||||||
answercommon "github.com/answerdev/answer/internal/service/answer_common"
|
answercommon "github.com/answerdev/answer/internal/service/answer_common"
|
||||||
|
@ -24,11 +23,10 @@ import (
|
||||||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||||
usercommon "github.com/answerdev/answer/internal/service/user_common"
|
usercommon "github.com/answerdev/answer/internal/service/user_common"
|
||||||
"github.com/answerdev/answer/pkg/dir"
|
"github.com/answerdev/answer/pkg/dir"
|
||||||
"github.com/segmentfault/pacman/errors"
|
|
||||||
"github.com/segmentfault/pacman/log"
|
"github.com/segmentfault/pacman/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DashboardService struct {
|
type dashboardService struct {
|
||||||
questionRepo questioncommon.QuestionRepo
|
questionRepo questioncommon.QuestionRepo
|
||||||
answerRepo answercommon.AnswerRepo
|
answerRepo answercommon.AnswerRepo
|
||||||
commentRepo comment_common.CommentCommonRepo
|
commentRepo comment_common.CommentCommonRepo
|
||||||
|
@ -36,9 +34,8 @@ type DashboardService struct {
|
||||||
userRepo usercommon.UserRepo
|
userRepo usercommon.UserRepo
|
||||||
reportRepo report_common.ReportRepo
|
reportRepo report_common.ReportRepo
|
||||||
configService *config.ConfigService
|
configService *config.ConfigService
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||||
serviceConfig *service_config.ServiceConfig
|
serviceConfig *service_config.ServiceConfig
|
||||||
|
|
||||||
data *data.Data
|
data *data.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,12 +47,11 @@ func NewDashboardService(
|
||||||
userRepo usercommon.UserRepo,
|
userRepo usercommon.UserRepo,
|
||||||
reportRepo report_common.ReportRepo,
|
reportRepo report_common.ReportRepo,
|
||||||
configService *config.ConfigService,
|
configService *config.ConfigService,
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService,
|
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||||
serviceConfig *service_config.ServiceConfig,
|
serviceConfig *service_config.ServiceConfig,
|
||||||
|
|
||||||
data *data.Data,
|
data *data.Data,
|
||||||
) *DashboardService {
|
) DashboardService {
|
||||||
return &DashboardService{
|
return &dashboardService{
|
||||||
questionRepo: questionRepo,
|
questionRepo: questionRepo,
|
||||||
answerRepo: answerRepo,
|
answerRepo: answerRepo,
|
||||||
commentRepo: commentRepo,
|
commentRepo: commentRepo,
|
||||||
|
@ -65,63 +61,102 @@ func NewDashboardService(
|
||||||
configService: configService,
|
configService: configService,
|
||||||
siteInfoService: siteInfoService,
|
siteInfoService: siteInfoService,
|
||||||
serviceConfig: serviceConfig,
|
serviceConfig: serviceConfig,
|
||||||
|
|
||||||
data: data,
|
data: data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ds *DashboardService) StatisticalByCache(ctx context.Context) (*schema.DashboardInfo, error) {
|
type DashboardService interface {
|
||||||
dashboardInfo := &schema.DashboardInfo{}
|
Statistical(ctx context.Context) (resp *schema.DashboardInfo, err error)
|
||||||
infoStr, err := ds.data.Cache.GetString(ctx, schema.DashBoardCachekey)
|
}
|
||||||
|
|
||||||
|
func (ds *dashboardService) Statistical(ctx context.Context) (*schema.DashboardInfo, error) {
|
||||||
|
dashboardInfo, err := ds.getFromCache(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
info, statisticalErr := ds.Statistical(ctx)
|
dashboardInfo = &schema.DashboardInfo{}
|
||||||
if statisticalErr != nil {
|
dashboardInfo.QuestionCount = ds.questionCount(ctx)
|
||||||
return nil, statisticalErr
|
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 setCacheErr := ds.SetCache(ctx, info); setCacheErr != nil {
|
|
||||||
log.Errorf("set dashboard statistical failed: %s", setCacheErr)
|
dashboardInfo.SMTP = ds.smtpStatus(ctx)
|
||||||
}
|
dashboardInfo.HTTPS = ds.httpsStatus(ctx)
|
||||||
return info, nil
|
dashboardInfo.TimeZone = ds.getTimezone(ctx)
|
||||||
}
|
dashboardInfo.UploadingFiles = true
|
||||||
if err = json.Unmarshal([]byte(infoStr), dashboardInfo); err != nil {
|
dashboardInfo.AppStartTime = fmt.Sprintf("%d", time.Now().Unix()-schema.AppStartTime.Unix())
|
||||||
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.VersionInfo.Version = constant.Version
|
dashboardInfo.VersionInfo.Version = constant.Version
|
||||||
dashboardInfo.VersionInfo.Revision = constant.Revision
|
dashboardInfo.VersionInfo.Revision = constant.Revision
|
||||||
|
|
||||||
|
ds.setCache(ctx, dashboardInfo)
|
||||||
return dashboardInfo, nil
|
return dashboardInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ds *DashboardService) SetCache(ctx context.Context, info *schema.DashboardInfo) error {
|
func (ds *dashboardService) getFromCache(ctx context.Context) (*schema.DashboardInfo, error) {
|
||||||
infoStr, err := json.Marshal(info)
|
infoStr, err := ds.data.Cache.GetString(ctx, schema.DashboardCacheKey)
|
||||||
if err != nil {
|
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)
|
dashboardInfo := &schema.DashboardInfo{}
|
||||||
if err != nil {
|
if err = json.Unmarshal([]byte(infoStr), dashboardInfo); err != nil {
|
||||||
return errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil
|
return dashboardInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Statistical
|
func (ds *dashboardService) setCache(ctx context.Context, info *schema.DashboardInfo) {
|
||||||
func (ds *DashboardService) Statistical(ctx context.Context) (*schema.DashboardInfo, error) {
|
infoStr, _ := json.Marshal(info)
|
||||||
dashboardInfo := &schema.DashboardInfo{}
|
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)
|
questionCount, err := ds.questionRepo.GetQuestionCount(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dashboardInfo, err
|
log.Errorf("get question count failed: %s", err)
|
||||||
}
|
}
|
||||||
answerCount, err := ds.answerRepo.GetAnswerCount(ctx)
|
return questionCount
|
||||||
if err != nil {
|
|
||||||
return dashboardInfo, err
|
|
||||||
}
|
|
||||||
commentCount, err := ds.commentRepo.GetCommentCount(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return dashboardInfo, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ds *dashboardService) answerCount(ctx context.Context) int64 {
|
||||||
|
answerCount, err := ds.answerRepo.GetAnswerCount(ctx)
|
||||||
|
if err != nil {
|
||||||
|
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 {
|
||||||
|
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{
|
typeKeys := []string{
|
||||||
"question.vote_up",
|
"question.vote_up",
|
||||||
"question.vote_down",
|
"question.vote_down",
|
||||||
|
@ -129,7 +164,6 @@ func (ds *DashboardService) Statistical(ctx context.Context) (*schema.DashboardI
|
||||||
"answer.vote_down",
|
"answer.vote_down",
|
||||||
}
|
}
|
||||||
var activityTypes []int
|
var activityTypes []int
|
||||||
|
|
||||||
for _, typeKey := range typeKeys {
|
for _, typeKey := range typeKeys {
|
||||||
cfg, err := ds.configService.GetConfigByKey(ctx, typeKey)
|
cfg, err := ds.configService.GetConfigByKey(ctx, typeKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -137,69 +171,14 @@ func (ds *DashboardService) Statistical(ctx context.Context) (*schema.DashboardI
|
||||||
}
|
}
|
||||||
activityTypes = append(activityTypes, cfg.ID)
|
activityTypes = append(activityTypes, cfg.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
voteCount, err := ds.voteRepo.GetVoteCount(ctx, activityTypes)
|
voteCount, err := ds.voteRepo.GetVoteCount(ctx, activityTypes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dashboardInfo, err
|
log.Errorf("get vote count failed: %s", err)
|
||||||
}
|
}
|
||||||
userCount, err := ds.userRepo.GetUserCount(ctx)
|
return voteCount
|
||||||
if err != nil {
|
|
||||||
return dashboardInfo, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reportCount, err := ds.reportRepo.GetReportCount(ctx)
|
func (ds *dashboardService) remoteVersion(ctx context.Context) string {
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ds *DashboardService) RemoteVersion(ctx context.Context) string {
|
|
||||||
url := "https://answer.dev/getlatest"
|
url := "https://answer.dev/getlatest"
|
||||||
req, _ := http.NewRequest("GET", url, nil)
|
req, _ := http.NewRequest("GET", url, nil)
|
||||||
req.Header.Set("User-Agent", "Answer/"+constant.Version)
|
req.Header.Set("User-Agent", "Answer/"+constant.Version)
|
||||||
|
@ -224,15 +203,48 @@ func (ds *DashboardService) RemoteVersion(ctx context.Context) string {
|
||||||
return remoteVersion.Release.Version
|
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")
|
emailConf, err := ds.configService.GetStringValue(ctx, "email.config")
|
||||||
if err != nil {
|
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)
|
err = json.Unmarshal([]byte(emailConf), ec)
|
||||||
if err != nil {
|
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)
|
log.Errorf("get email config failed: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if len(ec.SMTPHost) == 0 {
|
||||||
|
log.Warnf("smtp host is empty, skip send email")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
m := gomail.NewMessage()
|
m := gomail.NewMessage()
|
||||||
fromName := mime.QEncoding.Encode("utf-8", ec.FromName)
|
fromName := mime.QEncoding.Encode("utf-8", ec.FromName)
|
||||||
|
|
|
@ -1,13 +1,50 @@
|
||||||
package notice_queue
|
package notice_queue
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/schema"
|
"github.com/answerdev/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/pacman/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type NotificationQueueService interface {
|
||||||
NotificationQueue = make(chan *schema.NotificationMsg, 128)
|
Send(ctx context.Context, msg *schema.NotificationMsg)
|
||||||
)
|
RegisterHandler(handler func(ctx context.Context, msg *schema.NotificationMsg) error)
|
||||||
|
}
|
||||||
func AddNotification(msg *schema.NotificationMsg) {
|
|
||||||
NotificationQueue <- msg
|
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) (
|
func (ns *NotificationService) formatNotificationPage(ctx context.Context, notifications []*entity.Notification) (
|
||||||
resp []*schema.NotificationContent, err error) {
|
resp []*schema.NotificationContent, err error) {
|
||||||
lang := handler.GetLangByCtx(ctx)
|
lang := handler.GetLangByCtx(ctx)
|
||||||
|
enableShortID := handler.GetEnableShortID(ctx)
|
||||||
for _, notificationInfo := range notifications {
|
for _, notificationInfo := range notifications {
|
||||||
item := &schema.NotificationContent{}
|
item := &schema.NotificationContent{}
|
||||||
if err := json.Unmarshal([]byte(notificationInfo.Content), item); err != nil {
|
if err := json.Unmarshal([]byte(notificationInfo.Content), item); err != nil {
|
||||||
|
@ -163,6 +164,7 @@ func (ns *NotificationService) formatNotificationPage(ctx context.Context, notif
|
||||||
item.UpdateTime = notificationInfo.UpdatedAt.Unix()
|
item.UpdateTime = notificationInfo.UpdatedAt.Unix()
|
||||||
item.IsRead = notificationInfo.IsRead == schema.NotificationRead
|
item.IsRead = notificationInfo.IsRead == schema.NotificationRead
|
||||||
|
|
||||||
|
if enableShortID {
|
||||||
if answerID, ok := item.ObjectInfo.ObjectMap["answer"]; ok {
|
if answerID, ok := item.ObjectInfo.ObjectMap["answer"]; ok {
|
||||||
if item.ObjectInfo.ObjectID == answerID {
|
if item.ObjectInfo.ObjectID == answerID {
|
||||||
item.ObjectInfo.ObjectID = uid.EnShortID(item.ObjectInfo.ObjectMap["answer"])
|
item.ObjectInfo.ObjectID = uid.EnShortID(item.ObjectInfo.ObjectMap["answer"])
|
||||||
|
@ -175,6 +177,7 @@ func (ns *NotificationService) formatNotificationPage(ctx context.Context, notif
|
||||||
}
|
}
|
||||||
item.ObjectInfo.ObjectMap["question"] = uid.EnShortID(item.ObjectInfo.ObjectMap["question"])
|
item.ObjectInfo.ObjectMap["question"] = uid.EnShortID(item.ObjectInfo.ObjectMap["question"])
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resp = append(resp, item)
|
resp = append(resp, item)
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ type NotificationCommon struct {
|
||||||
followRepo activity_common.FollowRepo
|
followRepo activity_common.FollowRepo
|
||||||
userCommon *usercommon.UserCommon
|
userCommon *usercommon.UserCommon
|
||||||
objectInfoService *object_info.ObjService
|
objectInfoService *object_info.ObjService
|
||||||
|
notificationQueueService notice_queue.NotificationQueueService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNotificationCommon(
|
func NewNotificationCommon(
|
||||||
|
@ -48,6 +49,7 @@ func NewNotificationCommon(
|
||||||
activityRepo activity_common.ActivityRepo,
|
activityRepo activity_common.ActivityRepo,
|
||||||
followRepo activity_common.FollowRepo,
|
followRepo activity_common.FollowRepo,
|
||||||
objectInfoService *object_info.ObjService,
|
objectInfoService *object_info.ObjService,
|
||||||
|
notificationQueueService notice_queue.NotificationQueueService,
|
||||||
) *NotificationCommon {
|
) *NotificationCommon {
|
||||||
notification := &NotificationCommon{
|
notification := &NotificationCommon{
|
||||||
data: data,
|
data: data,
|
||||||
|
@ -56,23 +58,12 @@ func NewNotificationCommon(
|
||||||
followRepo: followRepo,
|
followRepo: followRepo,
|
||||||
userCommon: userCommon,
|
userCommon: userCommon,
|
||||||
objectInfoService: objectInfoService,
|
objectInfoService: objectInfoService,
|
||||||
|
notificationQueueService: notificationQueueService,
|
||||||
}
|
}
|
||||||
notification.HandleNotification()
|
notificationQueueService.RegisterHandler(notification.AddNotification)
|
||||||
return notification
|
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
|
// AddNotification
|
||||||
// need set
|
// need set
|
||||||
// LoginUserID
|
// LoginUserID
|
||||||
|
@ -172,7 +163,7 @@ func (ns *NotificationCommon) AddNotification(ctx context.Context, msg *schema.N
|
||||||
log.Error("addRedDot Error", err.Error())
|
log.Error("addRedDot Error", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
go ns.SendNotificationToAllFollower(context.Background(), msg, questionID)
|
go ns.SendNotificationToAllFollower(ctx, msg, questionID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,6 +204,6 @@ func (ns *NotificationCommon) SendNotificationToAllFollower(ctx context.Context,
|
||||||
t.ReceiverUserID = userID
|
t.ReceiverUserID = userID
|
||||||
t.TriggerUserID = msg.TriggerUserID
|
t.TriggerUserID = msg.TriggerUserID
|
||||||
t.NoNeedPushAllFollow = true
|
t.NoNeedPushAllFollow = true
|
||||||
notice_queue.AddNotification(t)
|
ns.notificationQueueService.Send(ctx, t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/constant"
|
"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/base/reason"
|
||||||
"github.com/answerdev/answer/internal/schema"
|
"github.com/answerdev/answer/internal/schema"
|
||||||
answercommon "github.com/answerdev/answer/internal/service/answer_common"
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
questionInfo.ID = uid.EnShortID(questionInfo.ID)
|
questionInfo.ID = uid.EnShortID(questionInfo.ID)
|
||||||
|
}
|
||||||
if !exist {
|
if !exist {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -87,7 +90,9 @@ func (os *ObjService) GetUnreviewedRevisionInfo(ctx context.Context, objectID st
|
||||||
if !exist {
|
if !exist {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if handler.GetEnableShortID(ctx) {
|
||||||
questionInfo.ID = uid.EnShortID(questionInfo.ID)
|
questionInfo.ID = uid.EnShortID(questionInfo.ID)
|
||||||
|
}
|
||||||
objInfo = &schema.UnreviewedRevisionInfoInfo{
|
objInfo = &schema.UnreviewedRevisionInfoInfo{
|
||||||
ObjectID: answerInfo.ID,
|
ObjectID: answerInfo.ID,
|
||||||
Title: questionInfo.Title,
|
Title: questionInfo.Title,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"github.com/answerdev/answer/internal/service/action"
|
"github.com/answerdev/answer/internal/service/action"
|
||||||
"github.com/answerdev/answer/internal/service/activity"
|
"github.com/answerdev/answer/internal/service/activity"
|
||||||
"github.com/answerdev/answer/internal/service/activity_common"
|
"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"
|
answercommon "github.com/answerdev/answer/internal/service/answer_common"
|
||||||
"github.com/answerdev/answer/internal/service/auth"
|
"github.com/answerdev/answer/internal/service/auth"
|
||||||
collectioncommon "github.com/answerdev/answer/internal/service/collection_common"
|
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/export"
|
||||||
"github.com/answerdev/answer/internal/service/follow"
|
"github.com/answerdev/answer/internal/service/follow"
|
||||||
"github.com/answerdev/answer/internal/service/meta"
|
"github.com/answerdev/answer/internal/service/meta"
|
||||||
|
"github.com/answerdev/answer/internal/service/notice_queue"
|
||||||
"github.com/answerdev/answer/internal/service/notification"
|
"github.com/answerdev/answer/internal/service/notification"
|
||||||
notficationcommon "github.com/answerdev/answer/internal/service/notification_common"
|
notficationcommon "github.com/answerdev/answer/internal/service/notification_common"
|
||||||
"github.com/answerdev/answer/internal/service/object_info"
|
"github.com/answerdev/answer/internal/service/object_info"
|
||||||
|
@ -86,4 +88,6 @@ var ProviderSetService = wire.NewSet(
|
||||||
user_external_login.NewUserCenterLoginService,
|
user_external_login.NewUserCenterLoginService,
|
||||||
plugin_common.NewPluginCommonService,
|
plugin_common.NewPluginCommonService,
|
||||||
config.NewConfigService,
|
config.NewConfigService,
|
||||||
|
notice_queue.NewNotificationQueueService,
|
||||||
|
activity_queue.NewActivityQueueService,
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,6 @@ package questioncommon
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"math"
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -48,11 +47,11 @@ type QuestionRepo interface {
|
||||||
UpdateAccepted(ctx context.Context, question *entity.Question) (err error)
|
UpdateAccepted(ctx context.Context, question *entity.Question) (err error)
|
||||||
UpdateLastAnswer(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)
|
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)
|
GetQuestionCount(ctx context.Context) (count int64, err error)
|
||||||
GetUserQuestionCount(ctx context.Context, userID string) (count int64, err error)
|
GetUserQuestionCount(ctx context.Context, userID string) (count int64, err error)
|
||||||
GetQuestionCountByIDs(ctx context.Context, ids []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
|
// QuestionCommon user service
|
||||||
|
@ -67,6 +66,7 @@ type QuestionCommon struct {
|
||||||
AnswerCommon *answercommon.AnswerCommon
|
AnswerCommon *answercommon.AnswerCommon
|
||||||
metaService *meta.MetaService
|
metaService *meta.MetaService
|
||||||
configService *config.ConfigService
|
configService *config.ConfigService
|
||||||
|
activityQueueService activity_queue.ActivityQueueService
|
||||||
data *data.Data
|
data *data.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,8 +80,8 @@ func NewQuestionCommon(questionRepo QuestionRepo,
|
||||||
answerCommon *answercommon.AnswerCommon,
|
answerCommon *answercommon.AnswerCommon,
|
||||||
metaService *meta.MetaService,
|
metaService *meta.MetaService,
|
||||||
configService *config.ConfigService,
|
configService *config.ConfigService,
|
||||||
|
activityQueueService activity_queue.ActivityQueueService,
|
||||||
data *data.Data,
|
data *data.Data,
|
||||||
|
|
||||||
) *QuestionCommon {
|
) *QuestionCommon {
|
||||||
return &QuestionCommon{
|
return &QuestionCommon{
|
||||||
questionRepo: questionRepo,
|
questionRepo: questionRepo,
|
||||||
|
@ -94,6 +94,7 @@ func NewQuestionCommon(questionRepo QuestionRepo,
|
||||||
AnswerCommon: answerCommon,
|
AnswerCommon: answerCommon,
|
||||||
metaService: metaService,
|
metaService: metaService,
|
||||||
configService: configService,
|
configService: configService,
|
||||||
|
activityQueueService: activityQueueService,
|
||||||
data: data,
|
data: data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -513,7 +514,7 @@ func (qs *QuestionCommon) CloseQuestion(ctx context.Context, req *schema.CloseQu
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
activity_queue.AddActivity(&schema.ActivityMsg{
|
qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
|
||||||
UserID: questionInfo.UserID,
|
UserID: questionInfo.UserID,
|
||||||
ObjectID: questionInfo.ID,
|
ObjectID: questionInfo.ID,
|
||||||
OriginalObjectID: 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) {
|
func (qs *QuestionCommon) SitemapCron(ctx context.Context) {
|
||||||
data := &schema.SiteMapList{}
|
|
||||||
questionNum, err := qs.questionRepo.GetQuestionCount(ctx)
|
questionNum, err := qs.questionRepo.GetQuestionCount(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetQuestionCount error", err)
|
log.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if questionNum <= schema.SitemapMaxSize {
|
if questionNum <= constant.SitemapMaxSize {
|
||||||
questionIDList, err := qs.questionRepo.GetQuestionIDsPage(ctx, 0, int(questionNum))
|
_, err = qs.questionRepo.SitemapQuestions(ctx, 1, int(questionNum))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetQuestionIDsPage error", err)
|
log.Errorf("get site map question error: %v", err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data.QuestionIDs = questionIDList
|
|
||||||
|
|
||||||
} else {
|
totalPages := int(math.Ceil(float64(questionNum) / float64(constant.SitemapMaxSize)))
|
||||||
nums := make([]int, 0)
|
for i := 1; i <= totalPages; i++ {
|
||||||
totalpages := int(math.Ceil(float64(questionNum) / float64(schema.SitemapMaxSize)))
|
_, err = qs.questionRepo.SitemapQuestions(ctx, i, constant.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 {
|
if err != nil {
|
||||||
log.Error("GetQuestionIDsPage error", err)
|
log.Errorf("get site map question error: %v", err)
|
||||||
return
|
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()
|
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 {
|
if err != nil {
|
||||||
return errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
return errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,11 @@ package service
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/constant"
|
"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/handler"
|
||||||
"github.com/answerdev/answer/internal/base/pager"
|
"github.com/answerdev/answer/internal/base/pager"
|
||||||
"github.com/answerdev/answer/internal/base/reason"
|
"github.com/answerdev/answer/internal/base/reason"
|
||||||
|
@ -50,8 +50,10 @@ type QuestionService struct {
|
||||||
metaService *meta.MetaService
|
metaService *meta.MetaService
|
||||||
collectionCommon *collectioncommon.CollectionCommon
|
collectionCommon *collectioncommon.CollectionCommon
|
||||||
answerActivityService *activity.AnswerActivityService
|
answerActivityService *activity.AnswerActivityService
|
||||||
data *data.Data
|
|
||||||
emailService *export.EmailService
|
emailService *export.EmailService
|
||||||
|
notificationQueueService notice_queue.NotificationQueueService
|
||||||
|
activityQueueService activity_queue.ActivityQueueService
|
||||||
|
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewQuestionService(
|
func NewQuestionService(
|
||||||
|
@ -64,8 +66,10 @@ func NewQuestionService(
|
||||||
metaService *meta.MetaService,
|
metaService *meta.MetaService,
|
||||||
collectionCommon *collectioncommon.CollectionCommon,
|
collectionCommon *collectioncommon.CollectionCommon,
|
||||||
answerActivityService *activity.AnswerActivityService,
|
answerActivityService *activity.AnswerActivityService,
|
||||||
data *data.Data,
|
|
||||||
emailService *export.EmailService,
|
emailService *export.EmailService,
|
||||||
|
notificationQueueService notice_queue.NotificationQueueService,
|
||||||
|
activityQueueService activity_queue.ActivityQueueService,
|
||||||
|
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||||
) *QuestionService {
|
) *QuestionService {
|
||||||
return &QuestionService{
|
return &QuestionService{
|
||||||
questionRepo: questionRepo,
|
questionRepo: questionRepo,
|
||||||
|
@ -77,8 +81,10 @@ func NewQuestionService(
|
||||||
metaService: metaService,
|
metaService: metaService,
|
||||||
collectionCommon: collectionCommon,
|
collectionCommon: collectionCommon,
|
||||||
answerActivityService: answerActivityService,
|
answerActivityService: answerActivityService,
|
||||||
data: data,
|
|
||||||
emailService: emailService,
|
emailService: emailService,
|
||||||
|
notificationQueueService: notificationQueueService,
|
||||||
|
activityQueueService: activityQueueService,
|
||||||
|
siteInfoService: siteInfoService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +112,7 @@ func (qs *QuestionService) CloseQuestion(ctx context.Context, req *schema.CloseQ
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
activity_queue.AddActivity(&schema.ActivityMsg{
|
qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
|
||||||
UserID: req.UserID,
|
UserID: req.UserID,
|
||||||
ObjectID: questionInfo.ID,
|
ObjectID: questionInfo.ID,
|
||||||
OriginalObjectID: questionInfo.ID,
|
OriginalObjectID: questionInfo.ID,
|
||||||
|
@ -130,7 +136,7 @@ func (qs *QuestionService) ReopenQuestion(ctx context.Context, req *schema.Reope
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
activity_queue.AddActivity(&schema.ActivityMsg{
|
qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
|
||||||
UserID: req.UserID,
|
UserID: req.UserID,
|
||||||
ObjectID: questionInfo.ID,
|
ObjectID: questionInfo.ID,
|
||||||
OriginalObjectID: 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,
|
UserID: question.UserID,
|
||||||
ObjectID: question.ID,
|
ObjectID: question.ID,
|
||||||
OriginalObjectID: question.ID,
|
OriginalObjectID: question.ID,
|
||||||
|
@ -381,7 +387,7 @@ func (qs *QuestionService) OperationQuestion(ctx context.Context, req *schema.Op
|
||||||
actMap[schema.QuestionOperationShow] = constant.ActQuestionShow
|
actMap[schema.QuestionOperationShow] = constant.ActQuestionShow
|
||||||
_, ok := actMap[req.Operation]
|
_, ok := actMap[req.Operation]
|
||||||
if ok {
|
if ok {
|
||||||
activity_queue.AddActivity(&schema.ActivityMsg{
|
qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
|
||||||
UserID: req.UserID,
|
UserID: req.UserID,
|
||||||
ObjectID: questionInfo.ID,
|
ObjectID: questionInfo.ID,
|
||||||
OriginalObjectID: questionInfo.ID,
|
OriginalObjectID: questionInfo.ID,
|
||||||
|
@ -473,7 +479,7 @@ func (qs *QuestionService) RemoveQuestion(ctx context.Context, req *schema.Remov
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// log.Errorf("user DeleteQuestion rank rollback error %s", err.Error())
|
// log.Errorf("user DeleteQuestion rank rollback error %s", err.Error())
|
||||||
// }
|
// }
|
||||||
activity_queue.AddActivity(&schema.ActivityMsg{
|
qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
|
||||||
UserID: req.UserID,
|
UserID: req.UserID,
|
||||||
ObjectID: questionInfo.ID,
|
ObjectID: questionInfo.ID,
|
||||||
OriginalObjectID: questionInfo.ID,
|
OriginalObjectID: questionInfo.ID,
|
||||||
|
@ -632,7 +638,7 @@ func (qs *QuestionService) notificationInviteUser(
|
||||||
}
|
}
|
||||||
msg.ObjectType = constant.QuestionObjectType
|
msg.ObjectType = constant.QuestionObjectType
|
||||||
msg.NotificationAction = constant.NotificationInvitedYouToAnswer
|
msg.NotificationAction = constant.NotificationInvitedYouToAnswer
|
||||||
notice_queue.AddNotification(msg)
|
qs.notificationQueueService.Send(ctx, msg)
|
||||||
|
|
||||||
userInfo, ok := invitee[userID]
|
userInfo, ok := invitee[userID]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -822,7 +828,7 @@ func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.Quest
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if canUpdate {
|
if canUpdate {
|
||||||
activity_queue.AddActivity(&schema.ActivityMsg{
|
qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
|
||||||
UserID: req.UserID,
|
UserID: req.UserID,
|
||||||
ObjectID: question.ID,
|
ObjectID: question.ID,
|
||||||
ActivityTypeKey: constant.ActQuestionEdited,
|
ActivityTypeKey: constant.ActQuestionEdited,
|
||||||
|
@ -1017,16 +1023,19 @@ func (qs *QuestionService) PersonalCollectionPage(ctx context.Context, req *sche
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, id := range questionIDs {
|
for _, id := range questionIDs {
|
||||||
_, ok := questionMaps[uid.EnShortID(id)]
|
if handler.GetEnableShortID(ctx) {
|
||||||
if ok {
|
id = uid.EnShortID(id)
|
||||||
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"
|
|
||||||
}
|
}
|
||||||
list = append(list, questionMaps[uid.EnShortID(id)])
|
_, ok := questionMaps[id]
|
||||||
|
if ok {
|
||||||
|
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[id])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1213,7 +1222,7 @@ func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, questionI
|
||||||
//if err != nil {
|
//if err != nil {
|
||||||
// log.Errorf("admin delete question then rank rollback error %s", err.Error())
|
// 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,
|
UserID: questionInfo.UserID,
|
||||||
ObjectID: questionInfo.ID,
|
ObjectID: questionInfo.ID,
|
||||||
OriginalObjectID: 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 {
|
if setStatus == entity.QuestionStatusAvailable && questionInfo.Status == entity.QuestionStatusClosed {
|
||||||
activity_queue.AddActivity(&schema.ActivityMsg{
|
qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
|
||||||
UserID: questionInfo.UserID,
|
UserID: questionInfo.UserID,
|
||||||
ObjectID: questionInfo.ID,
|
ObjectID: questionInfo.ID,
|
||||||
OriginalObjectID: 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 {
|
if setStatus == entity.QuestionStatusClosed && questionInfo.Status != entity.QuestionStatusClosed {
|
||||||
activity_queue.AddActivity(&schema.ActivityMsg{
|
qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
|
||||||
UserID: questionInfo.UserID,
|
UserID: questionInfo.UserID,
|
||||||
ObjectID: questionInfo.ID,
|
ObjectID: questionInfo.ID,
|
||||||
OriginalObjectID: questionInfo.ID,
|
OriginalObjectID: questionInfo.ID,
|
||||||
|
@ -1243,93 +1252,77 @@ func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, questionI
|
||||||
msg.TriggerUserID = questionInfo.UserID
|
msg.TriggerUserID = questionInfo.UserID
|
||||||
msg.ObjectType = constant.QuestionObjectType
|
msg.ObjectType = constant.QuestionObjectType
|
||||||
msg.NotificationAction = constant.NotificationYourQuestionWasDeleted
|
msg.NotificationAction = constant.NotificationYourQuestionWasDeleted
|
||||||
notice_queue.AddNotification(msg)
|
qs.notificationQueueService.Send(ctx, msg)
|
||||||
return nil
|
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)
|
list := make([]*schema.AdminQuestionInfo, 0)
|
||||||
|
questionList, count, err := qs.questionRepo.AdminQuestionPage(ctx, req)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return list, count, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
userIds := make([]string, 0)
|
userIds := make([]string, 0)
|
||||||
for _, dbitem := range dblist {
|
for _, info := range questionList {
|
||||||
item := &schema.AdminQuestionInfo{}
|
item := &schema.AdminQuestionInfo{}
|
||||||
_ = copier.Copy(item, dbitem)
|
_ = copier.Copy(item, info)
|
||||||
item.CreateTime = dbitem.CreatedAt.Unix()
|
item.CreateTime = info.CreatedAt.Unix()
|
||||||
item.UpdateTime = dbitem.PostUpdateTime.Unix()
|
item.UpdateTime = info.PostUpdateTime.Unix()
|
||||||
item.EditTime = dbitem.UpdatedAt.Unix()
|
item.EditTime = info.UpdatedAt.Unix()
|
||||||
list = append(list, item)
|
list = append(list, item)
|
||||||
userIds = append(userIds, dbitem.UserID)
|
userIds = append(userIds, info.UserID)
|
||||||
}
|
}
|
||||||
userInfoMap, err := qs.userCommon.BatchUserBasicInfoByID(ctx, userIds)
|
userInfoMap, err := qs.userCommon.BatchUserBasicInfoByID(ctx, userIds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return list, count, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, item := range list {
|
for _, item := range list {
|
||||||
_, ok = userInfoMap[item.UserID]
|
if u, ok := userInfoMap[item.UserID]; ok {
|
||||||
if ok {
|
item.UserInfo = u
|
||||||
item.UserInfo = userInfoMap[item.UserID]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return pager.NewPageModel(count, list), nil
|
||||||
|
}
|
||||||
|
|
||||||
return list, count, nil
|
// AdminAnswerPage search answer list
|
||||||
}
|
func (qs *QuestionService) AdminAnswerPage(ctx context.Context, req *schema.AdminAnswerPageReq) (
|
||||||
|
resp *pager.PageModel, err error) {
|
||||||
// AdminSearchList
|
answerList, count, err := qs.questioncommon.AnswerCommon.AdminSearchList(ctx, req)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return answerlist, count, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
questionIDs := make([]string, 0)
|
questionIDs := make([]string, 0)
|
||||||
userIds := make([]string, 0)
|
userIds := make([]string, 0)
|
||||||
for _, item := range dblist {
|
answerResp := make([]*schema.AdminAnswerInfo, 0)
|
||||||
answerinfo := qs.questioncommon.AnswerCommon.AdminShowFormat(ctx, item)
|
for _, item := range answerList {
|
||||||
answerlist = append(answerlist, answerinfo)
|
answerInfo := qs.questioncommon.AnswerCommon.AdminShowFormat(ctx, item)
|
||||||
|
answerResp = append(answerResp, answerInfo)
|
||||||
questionIDs = append(questionIDs, item.QuestionID)
|
questionIDs = append(questionIDs, item.QuestionID)
|
||||||
userIds = append(userIds, item.UserID)
|
userIds = append(userIds, item.UserID)
|
||||||
}
|
}
|
||||||
userInfoMap, err := qs.userCommon.BatchUserBasicInfoByID(ctx, userIds)
|
userInfoMap, err := qs.userCommon.BatchUserBasicInfoByID(ctx, userIds)
|
||||||
if err != nil {
|
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)
|
for _, item := range answerResp {
|
||||||
if err != nil {
|
if q, ok := questionMaps[item.QuestionID]; ok {
|
||||||
return answerlist, count, err
|
item.QuestionInfo.Title = q.Title
|
||||||
}
|
}
|
||||||
for _, item := range answerlist {
|
if u, ok := userInfoMap[item.UserID]; ok {
|
||||||
_, ok := questionMaps[item.QuestionID]
|
item.UserInfo = u
|
||||||
if ok {
|
|
||||||
item.QuestionInfo.Title = questionMaps[item.QuestionID].Title
|
|
||||||
}
|
|
||||||
_, ok = userInfoMap[item.UserID]
|
|
||||||
if ok {
|
|
||||||
item.UserInfo = userInfoMap[item.UserID]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return answerlist, count, nil
|
return pager.NewPageModel(count, answerResp), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qs *QuestionService) changeQuestionToRevision(ctx context.Context, questionInfo *entity.Question, tags []*entity.Tag) (
|
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) {
|
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)
|
qs.questioncommon.SitemapCron(ctx)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,10 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserRankRepo interface {
|
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)
|
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)
|
UserRankPage(ctx context.Context, userId string, page, pageSize int) (rankPage []*entity.Activity, total int64, err error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/service/config"
|
"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/base/constant"
|
||||||
"github.com/answerdev/answer/internal/entity"
|
"github.com/answerdev/answer/internal/entity"
|
||||||
"github.com/answerdev/answer/internal/schema"
|
"github.com/answerdev/answer/internal/schema"
|
||||||
"github.com/answerdev/answer/internal/service/comment"
|
"github.com/answerdev/answer/internal/service/comment"
|
||||||
"github.com/answerdev/answer/internal/service/notice_queue"
|
|
||||||
questioncommon "github.com/answerdev/answer/internal/service/question_common"
|
questioncommon "github.com/answerdev/answer/internal/service/question_common"
|
||||||
"github.com/answerdev/answer/pkg/obj"
|
"github.com/answerdev/answer/pkg/obj"
|
||||||
)
|
)
|
||||||
|
@ -18,16 +18,20 @@ type ReportHandle struct {
|
||||||
questionCommon *questioncommon.QuestionCommon
|
questionCommon *questioncommon.QuestionCommon
|
||||||
commentRepo comment.CommentRepo
|
commentRepo comment.CommentRepo
|
||||||
configService *config.ConfigService
|
configService *config.ConfigService
|
||||||
|
notificationQueueService notice_queue.NotificationQueueService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReportHandle(
|
func NewReportHandle(
|
||||||
questionCommon *questioncommon.QuestionCommon,
|
questionCommon *questioncommon.QuestionCommon,
|
||||||
commentRepo comment.CommentRepo,
|
commentRepo comment.CommentRepo,
|
||||||
configService *config.ConfigService) *ReportHandle {
|
configService *config.ConfigService,
|
||||||
|
notificationQueueService notice_queue.NotificationQueueService,
|
||||||
|
) *ReportHandle {
|
||||||
return &ReportHandle{
|
return &ReportHandle{
|
||||||
questionCommon: questionCommon,
|
questionCommon: questionCommon,
|
||||||
commentRepo: commentRepo,
|
commentRepo: commentRepo,
|
||||||
configService: configService,
|
configService: configService,
|
||||||
|
notificationQueueService: notificationQueueService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,5 +92,5 @@ func (rh *ReportHandle) sendNotification(ctx context.Context, reportedUserID, ob
|
||||||
ObjectType: constant.ReportObjectType,
|
ObjectType: constant.ReportObjectType,
|
||||||
NotificationAction: notificationAction,
|
NotificationAction: notificationAction,
|
||||||
}
|
}
|
||||||
notice_queue.AddNotification(msg)
|
rh.notificationQueueService.Send(ctx, msg)
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,8 @@ type RevisionService struct {
|
||||||
answerRepo answercommon.AnswerRepo
|
answerRepo answercommon.AnswerRepo
|
||||||
tagRepo tag_common.TagRepo
|
tagRepo tag_common.TagRepo
|
||||||
tagCommon *tagcommon.TagCommonService
|
tagCommon *tagcommon.TagCommonService
|
||||||
|
notificationQueueService notice_queue.NotificationQueueService
|
||||||
|
activityQueueService activity_queue.ActivityQueueService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRevisionService(
|
func NewRevisionService(
|
||||||
|
@ -49,6 +51,8 @@ func NewRevisionService(
|
||||||
answerRepo answercommon.AnswerRepo,
|
answerRepo answercommon.AnswerRepo,
|
||||||
tagRepo tag_common.TagRepo,
|
tagRepo tag_common.TagRepo,
|
||||||
tagCommon *tagcommon.TagCommonService,
|
tagCommon *tagcommon.TagCommonService,
|
||||||
|
notificationQueueService notice_queue.NotificationQueueService,
|
||||||
|
activityQueueService activity_queue.ActivityQueueService,
|
||||||
) *RevisionService {
|
) *RevisionService {
|
||||||
return &RevisionService{
|
return &RevisionService{
|
||||||
revisionRepo: revisionRepo,
|
revisionRepo: revisionRepo,
|
||||||
|
@ -60,6 +64,8 @@ func NewRevisionService(
|
||||||
answerRepo: answerRepo,
|
answerRepo: answerRepo,
|
||||||
tagRepo: tagRepo,
|
tagRepo: tagRepo,
|
||||||
tagCommon: tagCommon,
|
tagCommon: tagCommon,
|
||||||
|
notificationQueueService: notificationQueueService,
|
||||||
|
activityQueueService: activityQueueService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +161,7 @@ func (rs *RevisionService) revisionAuditQuestion(ctx context.Context, revisionit
|
||||||
if saveerr != nil {
|
if saveerr != nil {
|
||||||
return saveerr
|
return saveerr
|
||||||
}
|
}
|
||||||
activity_queue.AddActivity(&schema.ActivityMsg{
|
rs.activityQueueService.Send(ctx, &schema.ActivityMsg{
|
||||||
UserID: revisionitem.UserID,
|
UserID: revisionitem.UserID,
|
||||||
ObjectID: revisionitem.ObjectID,
|
ObjectID: revisionitem.ObjectID,
|
||||||
ActivityTypeKey: constant.ActQuestionEdited,
|
ActivityTypeKey: constant.ActQuestionEdited,
|
||||||
|
@ -210,9 +216,9 @@ func (rs *RevisionService) revisionAuditAnswer(ctx context.Context, revisionitem
|
||||||
}
|
}
|
||||||
msg.ObjectType = constant.AnswerObjectType
|
msg.ObjectType = constant.AnswerObjectType
|
||||||
msg.NotificationAction = constant.NotificationUpdateAnswer
|
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,
|
UserID: revisionitem.UserID,
|
||||||
ObjectID: insertData.ID,
|
ObjectID: insertData.ID,
|
||||||
OriginalObjectID: 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,
|
UserID: revisionitem.UserID,
|
||||||
ObjectID: taginfo.TagID,
|
ObjectID: taginfo.TagID,
|
||||||
OriginalObjectID: taginfo.TagID,
|
OriginalObjectID: taginfo.TagID,
|
||||||
|
|
|
@ -16,7 +16,6 @@ import (
|
||||||
questioncommon "github.com/answerdev/answer/internal/service/question_common"
|
questioncommon "github.com/answerdev/answer/internal/service/question_common"
|
||||||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||||
tagcommon "github.com/answerdev/answer/internal/service/tag_common"
|
tagcommon "github.com/answerdev/answer/internal/service/tag_common"
|
||||||
"github.com/answerdev/answer/pkg/uid"
|
|
||||||
"github.com/answerdev/answer/plugin"
|
"github.com/answerdev/answer/plugin"
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
"github.com/segmentfault/pacman/errors"
|
"github.com/segmentfault/pacman/errors"
|
||||||
|
@ -25,7 +24,7 @@ import (
|
||||||
|
|
||||||
type SiteInfoService struct {
|
type SiteInfoService struct {
|
||||||
siteInfoRepo siteinfo_common.SiteInfoRepo
|
siteInfoRepo siteinfo_common.SiteInfoRepo
|
||||||
siteInfoCommonService *siteinfo_common.SiteInfoCommonService
|
siteInfoCommonService siteinfo_common.SiteInfoCommonService
|
||||||
emailService *export.EmailService
|
emailService *export.EmailService
|
||||||
tagCommonService *tagcommon.TagCommonService
|
tagCommonService *tagcommon.TagCommonService
|
||||||
configService *config.ConfigService
|
configService *config.ConfigService
|
||||||
|
@ -34,7 +33,7 @@ type SiteInfoService struct {
|
||||||
|
|
||||||
func NewSiteInfoService(
|
func NewSiteInfoService(
|
||||||
siteInfoRepo siteinfo_common.SiteInfoRepo,
|
siteInfoRepo siteinfo_common.SiteInfoRepo,
|
||||||
siteInfoCommonService *siteinfo_common.SiteInfoCommonService,
|
siteInfoCommonService siteinfo_common.SiteInfoCommonService,
|
||||||
emailService *export.EmailService,
|
emailService *export.EmailService,
|
||||||
tagCommonService *tagcommon.TagCommonService,
|
tagCommonService *tagcommon.TagCommonService,
|
||||||
configService *config.ConfigService,
|
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) {
|
func (s *SiteInfoService) SaveSeo(ctx context.Context, req schema.SiteSeoReq) (err error) {
|
||||||
var (
|
content, _ := json.Marshal(req)
|
||||||
siteType = constant.SiteTypeSeo
|
|
||||||
content []byte
|
|
||||||
)
|
|
||||||
content, _ = json.Marshal(req)
|
|
||||||
|
|
||||||
data := entity.SiteInfo{
|
data := entity.SiteInfo{
|
||||||
Type: siteType,
|
Type: constant.SiteTypeSeo,
|
||||||
Content: string(content),
|
Content: string(content),
|
||||||
}
|
}
|
||||||
|
return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeSeo, &data)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SiteInfoService) GetPrivilegesConfig(ctx context.Context) (resp *schema.GetPrivilegesConfigResp, err error) {
|
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/entity"
|
||||||
"github.com/answerdev/answer/internal/schema"
|
"github.com/answerdev/answer/internal/schema"
|
||||||
"github.com/answerdev/answer/pkg/gravatar"
|
"github.com/answerdev/answer/pkg/gravatar"
|
||||||
"github.com/answerdev/answer/pkg/uid"
|
|
||||||
"github.com/segmentfault/pacman/log"
|
"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)
|
GetByType(ctx context.Context, siteType string) (siteInfo *entity.SiteInfo, exist bool, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SiteInfoCommonService site info common service
|
// siteInfoCommonService site info common service
|
||||||
type SiteInfoCommonService struct {
|
type siteInfoCommonService struct {
|
||||||
siteInfoRepo SiteInfoRepo
|
siteInfoRepo SiteInfoRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSiteInfoCommonService new site info common service
|
type SiteInfoCommonService interface {
|
||||||
func NewSiteInfoCommonService(siteInfoRepo SiteInfoRepo) *SiteInfoCommonService {
|
GetSiteGeneral(ctx context.Context) (resp *schema.SiteGeneralResp, err error)
|
||||||
siteInfo := &SiteInfoCommonService{
|
GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error)
|
||||||
siteInfoRepo: siteInfoRepo,
|
GetSiteBranding(ctx context.Context) (resp *schema.SiteBrandingResp, err error)
|
||||||
}
|
GetSiteUsers(ctx context.Context) (resp *schema.SiteUsersResp, err error)
|
||||||
seoinfo, err := siteInfo.GetSiteSeo(context.Background())
|
FormatAvatar(ctx context.Context, originalAvatarData, email string) *schema.AvatarInfo
|
||||||
if err != nil {
|
FormatListAvatar(ctx context.Context, userList []*entity.User) (userID2AvatarMapping map[string]*schema.AvatarInfo)
|
||||||
log.Error("seoinfo error", err)
|
GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error)
|
||||||
}
|
GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error)
|
||||||
if seoinfo.PermaLink == schema.PermaLinkQuestionIDAndTitleByShortID || seoinfo.PermaLink == schema.PermaLinkQuestionIDByShortID {
|
GetSiteLogin(ctx context.Context) (resp *schema.SiteLoginResp, err error)
|
||||||
uid.ShortIDSwitch = true
|
GetSiteCustomCssHTML(ctx context.Context) (resp *schema.SiteCustomCssHTMLResp, err error)
|
||||||
} else {
|
GetSiteTheme(ctx context.Context) (resp *schema.SiteThemeResp, err error)
|
||||||
uid.ShortIDSwitch = false
|
GetSiteSeo(ctx context.Context) (resp *schema.SiteSeoResp, err error)
|
||||||
|
GetSiteInfoByType(ctx context.Context, siteType string, resp interface{}) (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
return siteInfo
|
// NewSiteInfoCommonService new site info common service
|
||||||
|
func NewSiteInfoCommonService(siteInfoRepo SiteInfoRepo) SiteInfoCommonService {
|
||||||
|
return &siteInfoCommonService{
|
||||||
|
siteInfoRepo: siteInfoRepo,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSiteGeneral get site info general
|
// 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{}
|
resp = &schema.SiteGeneralResp{}
|
||||||
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeGeneral, resp); err != nil {
|
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeGeneral, resp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -51,7 +55,7 @@ func (s *SiteInfoCommonService) GetSiteGeneral(ctx context.Context) (resp *schem
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSiteInterface get site info interface
|
// 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{}
|
resp = &schema.SiteInterfaceResp{}
|
||||||
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeInterface, resp); err != nil {
|
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeInterface, resp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -60,7 +64,7 @@ func (s *SiteInfoCommonService) GetSiteInterface(ctx context.Context) (resp *sch
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSiteBranding get site info branding
|
// 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{}
|
resp = &schema.SiteBrandingResp{}
|
||||||
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeBranding, resp); err != nil {
|
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeBranding, resp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -69,7 +73,7 @@ func (s *SiteInfoCommonService) GetSiteBranding(ctx context.Context) (resp *sche
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSiteUsers get site info about users
|
// 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{}
|
resp = &schema.SiteUsersResp{}
|
||||||
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeUsers, resp); err != nil {
|
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeUsers, resp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -78,13 +82,13 @@ func (s *SiteInfoCommonService) GetSiteUsers(ctx context.Context) (resp *schema.
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatAvatar format avatar
|
// 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)
|
gravatarBaseURL, defaultAvatar := s.getAvatarDefaultConfig(ctx)
|
||||||
return s.selectedAvatar(originalAvatarData, defaultAvatar, gravatarBaseURL, email)
|
return s.selectedAvatar(originalAvatarData, defaultAvatar, gravatarBaseURL, email)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatListAvatar format avatar
|
// 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) {
|
avatarMapping map[string]*schema.AvatarInfo) {
|
||||||
gravatarBaseURL, defaultAvatar := s.getAvatarDefaultConfig(ctx)
|
gravatarBaseURL, defaultAvatar := s.getAvatarDefaultConfig(ctx)
|
||||||
avatarMapping = make(map[string]*schema.AvatarInfo)
|
avatarMapping = make(map[string]*schema.AvatarInfo)
|
||||||
|
@ -94,19 +98,22 @@ func (s *SiteInfoCommonService) FormatListAvatar(ctx context.Context, userList [
|
||||||
return avatarMapping
|
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
|
gravatarBaseURL, defaultAvatar := constant.DefaultGravatarBaseURL, constant.DefaultAvatar
|
||||||
usersConfig, err := s.GetSiteUsers(ctx)
|
usersConfig, err := s.GetSiteUsers(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
} else {
|
}
|
||||||
|
if len(usersConfig.GravatarBaseURL) > 0 {
|
||||||
gravatarBaseURL = usersConfig.GravatarBaseURL
|
gravatarBaseURL = usersConfig.GravatarBaseURL
|
||||||
|
}
|
||||||
|
if len(usersConfig.DefaultAvatar) > 0 {
|
||||||
defaultAvatar = usersConfig.DefaultAvatar
|
defaultAvatar = usersConfig.DefaultAvatar
|
||||||
}
|
}
|
||||||
return gravatarBaseURL, defaultAvatar
|
return gravatarBaseURL, defaultAvatar
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SiteInfoCommonService) selectedAvatar(
|
func (s *siteInfoCommonService) selectedAvatar(
|
||||||
originalAvatarData string, defaultAvatar string, gravatarBaseURL string, email string) *schema.AvatarInfo {
|
originalAvatarData string, defaultAvatar string, gravatarBaseURL string, email string) *schema.AvatarInfo {
|
||||||
avatarInfo := &schema.AvatarInfo{}
|
avatarInfo := &schema.AvatarInfo{}
|
||||||
_ = json.Unmarshal([]byte(originalAvatarData), avatarInfo)
|
_ = json.Unmarshal([]byte(originalAvatarData), avatarInfo)
|
||||||
|
@ -121,7 +128,7 @@ func (s *SiteInfoCommonService) selectedAvatar(
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSiteWrite get site info write
|
// 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{}
|
resp = &schema.SiteWriteResp{}
|
||||||
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeWrite, resp); err != nil {
|
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeWrite, resp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -130,7 +137,7 @@ func (s *SiteInfoCommonService) GetSiteWrite(ctx context.Context) (resp *schema.
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSiteLegal get site info write
|
// 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{}
|
resp = &schema.SiteLegalResp{}
|
||||||
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeLegal, resp); err != nil {
|
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeLegal, resp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -139,7 +146,7 @@ func (s *SiteInfoCommonService) GetSiteLegal(ctx context.Context) (resp *schema.
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSiteLogin get site login config
|
// 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{}
|
resp = &schema.SiteLoginResp{}
|
||||||
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeLogin, resp); err != nil {
|
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeLogin, resp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -148,7 +155,7 @@ func (s *SiteInfoCommonService) GetSiteLogin(ctx context.Context) (resp *schema.
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSiteCustomCssHTML get site custom css html config
|
// 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{}
|
resp = &schema.SiteCustomCssHTMLResp{}
|
||||||
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeCustomCssHTML, resp); err != nil {
|
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeCustomCssHTML, resp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -157,7 +164,7 @@ func (s *SiteInfoCommonService) GetSiteCustomCssHTML(ctx context.Context) (resp
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSiteTheme get site theme
|
// 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{
|
resp = &schema.SiteThemeResp{
|
||||||
ThemeOptions: schema.GetThemeOptions,
|
ThemeOptions: schema.GetThemeOptions,
|
||||||
}
|
}
|
||||||
|
@ -169,15 +176,24 @@ func (s *SiteInfoCommonService) GetSiteTheme(ctx context.Context) (resp *schema.
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSiteSeo get site seo
|
// GetSiteSeo get site seo
|
||||||
func (s *SiteInfoCommonService) GetSiteSeo(ctx context.Context) (resp *schema.SiteSeoReq, err error) {
|
func (s *siteInfoCommonService) GetSiteSeo(ctx context.Context) (resp *schema.SiteSeoResp, err error) {
|
||||||
resp = &schema.SiteSeoReq{}
|
resp = &schema.SiteSeoResp{}
|
||||||
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeSeo, resp); err != nil {
|
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeSeo, resp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return resp, nil
|
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)
|
siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, siteType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||||
tagcommonser "github.com/answerdev/answer/internal/service/tag_common"
|
tagcommonser "github.com/answerdev/answer/internal/service/tag_common"
|
||||||
"github.com/answerdev/answer/pkg/htmltext"
|
"github.com/answerdev/answer/pkg/htmltext"
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/pager"
|
"github.com/answerdev/answer/internal/base/pager"
|
||||||
"github.com/answerdev/answer/internal/base/reason"
|
"github.com/answerdev/answer/internal/base/reason"
|
||||||
|
@ -18,7 +19,6 @@ import (
|
||||||
"github.com/answerdev/answer/internal/service/activity_common"
|
"github.com/answerdev/answer/internal/service/activity_common"
|
||||||
"github.com/answerdev/answer/internal/service/permission"
|
"github.com/answerdev/answer/internal/service/permission"
|
||||||
"github.com/answerdev/answer/pkg/converter"
|
"github.com/answerdev/answer/pkg/converter"
|
||||||
"github.com/jinzhu/copier"
|
|
||||||
"github.com/segmentfault/pacman/errors"
|
"github.com/segmentfault/pacman/errors"
|
||||||
"github.com/segmentfault/pacman/log"
|
"github.com/segmentfault/pacman/log"
|
||||||
)
|
)
|
||||||
|
@ -29,7 +29,8 @@ type TagService struct {
|
||||||
tagCommonService *tagcommonser.TagCommonService
|
tagCommonService *tagcommonser.TagCommonService
|
||||||
revisionService *revision_common.RevisionService
|
revisionService *revision_common.RevisionService
|
||||||
followCommon activity_common.FollowRepo
|
followCommon activity_common.FollowRepo
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||||
|
activityQueueService activity_queue.ActivityQueueService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTagService new tag service
|
// NewTagService new tag service
|
||||||
|
@ -38,13 +39,16 @@ func NewTagService(
|
||||||
tagCommonService *tagcommonser.TagCommonService,
|
tagCommonService *tagcommonser.TagCommonService,
|
||||||
revisionService *revision_common.RevisionService,
|
revisionService *revision_common.RevisionService,
|
||||||
followCommon activity_common.FollowRepo,
|
followCommon activity_common.FollowRepo,
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService) *TagService {
|
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||||
|
activityQueueService activity_queue.ActivityQueueService,
|
||||||
|
) *TagService {
|
||||||
return &TagService{
|
return &TagService{
|
||||||
tagRepo: tagRepo,
|
tagRepo: tagRepo,
|
||||||
tagCommonService: tagCommonService,
|
tagCommonService: tagCommonService,
|
||||||
revisionService: revisionService,
|
revisionService: revisionService,
|
||||||
followCommon: followCommon,
|
followCommon: followCommon,
|
||||||
siteInfoService: siteInfoService,
|
siteInfoService: siteInfoService,
|
||||||
|
activityQueueService: activityQueueService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +77,7 @@ func (ts *TagService) RemoveTag(ctx context.Context, req *schema.RemoveTagReq) (
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
activity_queue.AddActivity(&schema.ActivityMsg{
|
ts.activityQueueService.Send(ctx, &schema.ActivityMsg{
|
||||||
UserID: req.UserID,
|
UserID: req.UserID,
|
||||||
ObjectID: req.TagID,
|
ObjectID: req.TagID,
|
||||||
OriginalObjectID: req.TagID,
|
OriginalObjectID: req.TagID,
|
||||||
|
@ -298,7 +302,7 @@ func (ts *TagService) UpdateTagSynonym(ctx context.Context, req *schema.UpdateTa
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
activity_queue.AddActivity(&schema.ActivityMsg{
|
ts.activityQueueService.Send(ctx, &schema.ActivityMsg{
|
||||||
UserID: req.UserID,
|
UserID: req.UserID,
|
||||||
ObjectID: tag.ID,
|
ObjectID: tag.ID,
|
||||||
OriginalObjectID: tag.ID,
|
OriginalObjectID: tag.ID,
|
||||||
|
|
|
@ -61,7 +61,8 @@ type TagCommonService struct {
|
||||||
tagCommonRepo TagCommonRepo
|
tagCommonRepo TagCommonRepo
|
||||||
tagRelRepo TagRelRepo
|
tagRelRepo TagRelRepo
|
||||||
tagRepo TagRepo
|
tagRepo TagRepo
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||||
|
activityQueueService activity_queue.ActivityQueueService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTagCommonService new tag service
|
// NewTagCommonService new tag service
|
||||||
|
@ -70,7 +71,8 @@ func NewTagCommonService(
|
||||||
tagRelRepo TagRelRepo,
|
tagRelRepo TagRelRepo,
|
||||||
tagRepo TagRepo,
|
tagRepo TagRepo,
|
||||||
revisionService *revision_common.RevisionService,
|
revisionService *revision_common.RevisionService,
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService,
|
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||||
|
activityQueueService activity_queue.ActivityQueueService,
|
||||||
) *TagCommonService {
|
) *TagCommonService {
|
||||||
return &TagCommonService{
|
return &TagCommonService{
|
||||||
tagCommonRepo: tagCommonRepo,
|
tagCommonRepo: tagCommonRepo,
|
||||||
|
@ -78,6 +80,7 @@ func NewTagCommonService(
|
||||||
tagRepo: tagRepo,
|
tagRepo: tagRepo,
|
||||||
revisionService: revisionService,
|
revisionService: revisionService,
|
||||||
siteInfoService: siteInfoService,
|
siteInfoService: siteInfoService,
|
||||||
|
activityQueueService: activityQueueService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -645,7 +648,7 @@ func (ts *TagCommonService) ObjectChangeTag(ctx context.Context, objectTagData *
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
activity_queue.AddActivity(&schema.ActivityMsg{
|
ts.activityQueueService.Send(ctx, &schema.ActivityMsg{
|
||||||
UserID: objectTagData.UserID,
|
UserID: objectTagData.UserID,
|
||||||
ObjectID: tag.ID,
|
ObjectID: tag.ID,
|
||||||
OriginalObjectID: tag.ID,
|
OriginalObjectID: tag.ID,
|
||||||
|
@ -845,7 +848,7 @@ func (ts *TagCommonService) UpdateTag(ctx context.Context, req *schema.UpdateTag
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if canUpdate {
|
if canUpdate {
|
||||||
activity_queue.AddActivity(&schema.ActivityMsg{
|
ts.activityQueueService.Send(ctx, &schema.ActivityMsg{
|
||||||
UserID: req.UserID,
|
UserID: req.UserID,
|
||||||
ObjectID: tagInfo.ID,
|
ObjectID: tagInfo.ID,
|
||||||
OriginalObjectID: tagInfo.ID,
|
OriginalObjectID: tagInfo.ID,
|
||||||
|
|
|
@ -53,20 +53,20 @@ var (
|
||||||
|
|
||||||
type UploaderService interface {
|
type UploaderService interface {
|
||||||
UploadAvatarFile(ctx *gin.Context) (url string, err error)
|
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)
|
UploadPostFile(ctx *gin.Context) (url string, err error)
|
||||||
UploadBrandingFile(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
|
// uploaderService uploader service
|
||||||
type uploaderService struct {
|
type uploaderService struct {
|
||||||
serviceConfig *service_config.ServiceConfig
|
serviceConfig *service_config.ServiceConfig
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUploaderService new upload service
|
// NewUploaderService new upload service
|
||||||
func NewUploaderService(serviceConfig *service_config.ServiceConfig,
|
func NewUploaderService(serviceConfig *service_config.ServiceConfig,
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService) UploaderService {
|
siteInfoService siteinfo_common.SiteInfoCommonService) UploaderService {
|
||||||
for _, subPath := range subPathList {
|
for _, subPath := range subPathList {
|
||||||
err := dir.CreateDirIfNotExist(filepath.Join(serviceConfig.UploadPath, subPath))
|
err := dir.CreateDirIfNotExist(filepath.Join(serviceConfig.UploadPath, subPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -105,26 +105,26 @@ func (us *uploaderService) UploadAvatarFile(ctx *gin.Context) (url string, err e
|
||||||
return us.uploadFile(ctx, file, avatarFilePath)
|
return us.uploadFile(ctx, file, avatarFilePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (us *uploaderService) AvatarThumbFile(ctx *gin.Context, uploadPath, fileName string, size int) (
|
func (us *uploaderService) AvatarThumbFile(ctx *gin.Context, fileName string, size int) (url string, err error) {
|
||||||
avatarfile []byte, err error) {
|
|
||||||
if size > 1024 {
|
if size > 1024 {
|
||||||
size = 1024
|
size = 1024
|
||||||
}
|
}
|
||||||
|
|
||||||
thumbFileName := fmt.Sprintf("%d_%d@%s", size, size, fileName)
|
thumbFileName := fmt.Sprintf("%d_%d@%s", size, size, fileName)
|
||||||
thumbfilePath := fmt.Sprintf("%s/%s/%s", uploadPath, avatarThumbSubPath, thumbFileName)
|
thumbFilePath := fmt.Sprintf("%s/%s/%s", us.serviceConfig.UploadPath, avatarThumbSubPath, thumbFileName)
|
||||||
avatarfile, err = os.ReadFile(thumbfilePath)
|
avatarfile, err := os.ReadFile(thumbFilePath)
|
||||||
if err == nil {
|
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)
|
avatarfile, err = os.ReadFile(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return avatarfile, errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
reader := bytes.NewReader(avatarfile)
|
reader := bytes.NewReader(avatarfile)
|
||||||
img, err := imaging.Decode(reader)
|
img, err := imaging.Decode(reader)
|
||||||
if err != nil {
|
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)
|
new_image := imaging.Fill(img, size, size, imaging.Center, imaging.Linear)
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
@ -133,29 +133,29 @@ func (us *uploaderService) AvatarThumbFile(ctx *gin.Context, uploadPath, fileNam
|
||||||
_, ok := FormatExts[fileSuffix]
|
_, ok := FormatExts[fileSuffix]
|
||||||
|
|
||||||
if !ok {
|
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])
|
err = imaging.Encode(&buf, new_image, FormatExts[fileSuffix])
|
||||||
if err != nil {
|
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())
|
thumbReader := bytes.NewReader(buf.Bytes())
|
||||||
err = dir.CreateDirIfNotExist(path.Join(us.serviceConfig.UploadPath, avatarThumbSubPath))
|
err = dir.CreateDirIfNotExist(path.Join(us.serviceConfig.UploadPath, avatarThumbSubPath))
|
||||||
if err != nil {
|
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)
|
avatarFilePath := path.Join(avatarThumbSubPath, thumbFileName)
|
||||||
savefilePath := path.Join(us.serviceConfig.UploadPath, avatarFilePath)
|
savefilePath := path.Join(us.serviceConfig.UploadPath, avatarFilePath)
|
||||||
out, err := os.Create(savefilePath)
|
out, err := os.Create(savefilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return avatarfile, errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
defer out.Close()
|
defer out.Close()
|
||||||
_, err = io.Copy(out, thumbReader)
|
_, err = io.Copy(out, thumbReader)
|
||||||
if err != nil {
|
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) (
|
func (us *uploaderService) UploadPostFile(ctx *gin.Context) (
|
||||||
|
|
|
@ -41,7 +41,7 @@ type UserAdminService struct {
|
||||||
authService *auth.AuthService
|
authService *auth.AuthService
|
||||||
userCommonService *usercommon.UserCommon
|
userCommonService *usercommon.UserCommon
|
||||||
userActivity activity.UserActiveActivityRepo
|
userActivity activity.UserActiveActivityRepo
|
||||||
siteInfoCommonService *siteinfo_common.SiteInfoCommonService
|
siteInfoCommonService siteinfo_common.SiteInfoCommonService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUserAdminService new user admin service
|
// NewUserAdminService new user admin service
|
||||||
|
@ -51,7 +51,7 @@ func NewUserAdminService(
|
||||||
authService *auth.AuthService,
|
authService *auth.AuthService,
|
||||||
userCommonService *usercommon.UserCommon,
|
userCommonService *usercommon.UserCommon,
|
||||||
userActivity activity.UserActiveActivityRepo,
|
userActivity activity.UserActiveActivityRepo,
|
||||||
siteInfoCommonService *siteinfo_common.SiteInfoCommonService,
|
siteInfoCommonService siteinfo_common.SiteInfoCommonService,
|
||||||
) *UserAdminService {
|
) *UserAdminService {
|
||||||
return &UserAdminService{
|
return &UserAdminService{
|
||||||
userRepo: userRepo,
|
userRepo: userRepo,
|
||||||
|
|
|
@ -44,14 +44,14 @@ type UserCommon struct {
|
||||||
userRepo UserRepo
|
userRepo UserRepo
|
||||||
userRoleService *role.UserRoleRelService
|
userRoleService *role.UserRoleRelService
|
||||||
authService *auth.AuthService
|
authService *auth.AuthService
|
||||||
siteInfoCommonService *siteinfo_common.SiteInfoCommonService
|
siteInfoCommonService siteinfo_common.SiteInfoCommonService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserCommon(
|
func NewUserCommon(
|
||||||
userRepo UserRepo,
|
userRepo UserRepo,
|
||||||
userRoleService *role.UserRoleRelService,
|
userRoleService *role.UserRoleRelService,
|
||||||
authService *auth.AuthService,
|
authService *auth.AuthService,
|
||||||
siteInfoCommonService *siteinfo_common.SiteInfoCommonService,
|
siteInfoCommonService siteinfo_common.SiteInfoCommonService,
|
||||||
) *UserCommon {
|
) *UserCommon {
|
||||||
return &UserCommon{
|
return &UserCommon{
|
||||||
userRepo: userRepo,
|
userRepo: userRepo,
|
||||||
|
|
|
@ -27,7 +27,7 @@ type UserCenterLoginService struct {
|
||||||
userExternalLoginRepo UserExternalLoginRepo
|
userExternalLoginRepo UserExternalLoginRepo
|
||||||
userCommonService *usercommon.UserCommon
|
userCommonService *usercommon.UserCommon
|
||||||
userActivity activity.UserActiveActivityRepo
|
userActivity activity.UserActiveActivityRepo
|
||||||
siteInfoCommonService *siteinfo_common.SiteInfoCommonService
|
siteInfoCommonService siteinfo_common.SiteInfoCommonService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUserCenterLoginService new user external login service
|
// NewUserCenterLoginService new user external login service
|
||||||
|
@ -36,7 +36,7 @@ func NewUserCenterLoginService(
|
||||||
userCommonService *usercommon.UserCommon,
|
userCommonService *usercommon.UserCommon,
|
||||||
userExternalLoginRepo UserExternalLoginRepo,
|
userExternalLoginRepo UserExternalLoginRepo,
|
||||||
userActivity activity.UserActiveActivityRepo,
|
userActivity activity.UserActiveActivityRepo,
|
||||||
siteInfoCommonService *siteinfo_common.SiteInfoCommonService,
|
siteInfoCommonService siteinfo_common.SiteInfoCommonService,
|
||||||
) *UserCenterLoginService {
|
) *UserCenterLoginService {
|
||||||
return &UserCenterLoginService{
|
return &UserCenterLoginService{
|
||||||
userRepo: userRepo,
|
userRepo: userRepo,
|
||||||
|
@ -228,10 +228,10 @@ func (us *UserCenterLoginService) UserCenterAdminFunctionAgent(ctx context.Conte
|
||||||
desc := userCenter.Description()
|
desc := userCenter.Description()
|
||||||
// If user status agent is enabled, admin can not update user status in answer.
|
// If user status agent is enabled, admin can not update user status in answer.
|
||||||
resp.AllowUpdateUserStatus = !desc.UserStatusAgentEnabled
|
resp.AllowUpdateUserStatus = !desc.UserStatusAgentEnabled
|
||||||
|
resp.AllowUpdateUserRole = !desc.UserRoleAgentEnabled
|
||||||
|
|
||||||
// If original user system is enabled, admin can update user password and role in answer.
|
// If original user system is enabled, admin can update user password and role in answer.
|
||||||
resp.AllowUpdateUserPassword = desc.EnabledOriginalUserSystem
|
resp.AllowUpdateUserPassword = desc.EnabledOriginalUserSystem
|
||||||
resp.AllowUpdateUserRole = desc.EnabledOriginalUserSystem
|
|
||||||
resp.AllowCreateUser = desc.EnabledOriginalUserSystem
|
resp.AllowCreateUser = desc.EnabledOriginalUserSystem
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ type UserExternalLoginService struct {
|
||||||
userExternalLoginRepo UserExternalLoginRepo
|
userExternalLoginRepo UserExternalLoginRepo
|
||||||
userCommonService *usercommon.UserCommon
|
userCommonService *usercommon.UserCommon
|
||||||
emailService *export.EmailService
|
emailService *export.EmailService
|
||||||
siteInfoCommonService *siteinfo_common.SiteInfoCommonService
|
siteInfoCommonService siteinfo_common.SiteInfoCommonService
|
||||||
userActivity activity.UserActiveActivityRepo
|
userActivity activity.UserActiveActivityRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ func NewUserExternalLoginService(
|
||||||
userCommonService *usercommon.UserCommon,
|
userCommonService *usercommon.UserCommon,
|
||||||
userExternalLoginRepo UserExternalLoginRepo,
|
userExternalLoginRepo UserExternalLoginRepo,
|
||||||
emailService *export.EmailService,
|
emailService *export.EmailService,
|
||||||
siteInfoCommonService *siteinfo_common.SiteInfoCommonService,
|
siteInfoCommonService siteinfo_common.SiteInfoCommonService,
|
||||||
userActivity activity.UserActiveActivityRepo,
|
userActivity activity.UserActiveActivityRepo,
|
||||||
) *UserExternalLoginService {
|
) *UserExternalLoginService {
|
||||||
return &UserExternalLoginService{
|
return &UserExternalLoginService{
|
||||||
|
|
|
@ -38,7 +38,7 @@ type UserService struct {
|
||||||
activityRepo activity_common.ActivityRepo
|
activityRepo activity_common.ActivityRepo
|
||||||
emailService *export.EmailService
|
emailService *export.EmailService
|
||||||
authService *auth.AuthService
|
authService *auth.AuthService
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||||
userRoleService *role.UserRoleRelService
|
userRoleService *role.UserRoleRelService
|
||||||
userExternalLoginService *user_external_login.UserExternalLoginService
|
userExternalLoginService *user_external_login.UserExternalLoginService
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ func NewUserService(userRepo usercommon.UserRepo,
|
||||||
activityRepo activity_common.ActivityRepo,
|
activityRepo activity_common.ActivityRepo,
|
||||||
emailService *export.EmailService,
|
emailService *export.EmailService,
|
||||||
authService *auth.AuthService,
|
authService *auth.AuthService,
|
||||||
siteInfoService *siteinfo_common.SiteInfoCommonService,
|
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||||
userRoleService *role.UserRoleRelService,
|
userRoleService *role.UserRoleRelService,
|
||||||
userCommonService *usercommon.UserCommon,
|
userCommonService *usercommon.UserCommon,
|
||||||
userExternalLoginService *user_external_login.UserExternalLoginService,
|
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)
|
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
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@ package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/answerdev/answer/internal/service/activity_common"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/constant"
|
"github.com/answerdev/answer/internal/base/constant"
|
||||||
"github.com/answerdev/answer/internal/base/handler"
|
"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/config"
|
||||||
"github.com/answerdev/answer/internal/service/object_info"
|
"github.com/answerdev/answer/internal/service/object_info"
|
||||||
"github.com/answerdev/answer/pkg/htmltext"
|
"github.com/answerdev/answer/pkg/htmltext"
|
||||||
"github.com/answerdev/answer/pkg/obj"
|
|
||||||
"github.com/segmentfault/pacman/log"
|
"github.com/segmentfault/pacman/log"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/reason"
|
"github.com/answerdev/answer/internal/base/reason"
|
||||||
"github.com/answerdev/answer/internal/schema"
|
"github.com/answerdev/answer/internal/schema"
|
||||||
answercommon "github.com/answerdev/answer/internal/service/answer_common"
|
answercommon "github.com/answerdev/answer/internal/service/answer_common"
|
||||||
questioncommon "github.com/answerdev/answer/internal/service/question_common"
|
questioncommon "github.com/answerdev/answer/internal/service/question_common"
|
||||||
"github.com/answerdev/answer/internal/service/unique"
|
|
||||||
"github.com/segmentfault/pacman/errors"
|
"github.com/segmentfault/pacman/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VoteRepo activity repository
|
// VoteRepo activity repository
|
||||||
type VoteRepo interface {
|
type VoteRepo interface {
|
||||||
VoteUp(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error)
|
Vote(ctx context.Context, op *schema.VoteOperationInfo) (err error)
|
||||||
VoteDown(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error)
|
CancelVote(ctx context.Context, op *schema.VoteOperationInfo) (err error)
|
||||||
VoteUpCancel(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error)
|
GetAndSaveVoteResult(ctx context.Context, objectID, objectType string) (up, down int64, err error)
|
||||||
VoteDownCancel(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error)
|
ListUserVotes(ctx context.Context, userID string, page int, pageSize int, activityTypes []int) (
|
||||||
GetVoteResultByObjectId(ctx context.Context, objectID string) (resp *schema.VoteResp, err error)
|
voteList []*entity.Activity, total int64, err error)
|
||||||
ListUserVotes(ctx context.Context, userID string, req schema.GetVoteWithPageReq, activityTypes []int) (voteList []entity.Activity, total int64, err error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// VoteService user service
|
// VoteService user service
|
||||||
type VoteService struct {
|
type VoteService struct {
|
||||||
voteRepo VoteRepo
|
voteRepo VoteRepo
|
||||||
UniqueIDRepo unique.UniqueIDRepo
|
|
||||||
configService *config.ConfigService
|
configService *config.ConfigService
|
||||||
questionRepo questioncommon.QuestionRepo
|
questionRepo questioncommon.QuestionRepo
|
||||||
answerRepo answercommon.AnswerRepo
|
answerRepo answercommon.AnswerRepo
|
||||||
commentCommonRepo comment_common.CommentCommonRepo
|
commentCommonRepo comment_common.CommentCommonRepo
|
||||||
objectService *object_info.ObjService
|
objectService *object_info.ObjService
|
||||||
|
activityRepo activity_common.ActivityRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVoteService(
|
func NewVoteService(
|
||||||
VoteRepo VoteRepo,
|
voteRepo VoteRepo,
|
||||||
uniqueIDRepo unique.UniqueIDRepo,
|
|
||||||
configService *config.ConfigService,
|
configService *config.ConfigService,
|
||||||
questionRepo questioncommon.QuestionRepo,
|
questionRepo questioncommon.QuestionRepo,
|
||||||
answerRepo answercommon.AnswerRepo,
|
answerRepo answercommon.AnswerRepo,
|
||||||
|
@ -55,8 +53,7 @@ func NewVoteService(
|
||||||
objectService *object_info.ObjService,
|
objectService *object_info.ObjService,
|
||||||
) *VoteService {
|
) *VoteService {
|
||||||
return &VoteService{
|
return &VoteService{
|
||||||
voteRepo: VoteRepo,
|
voteRepo: voteRepo,
|
||||||
UniqueIDRepo: uniqueIDRepo,
|
|
||||||
configService: configService,
|
configService: configService,
|
||||||
questionRepo: questionRepo,
|
questionRepo: questionRepo,
|
||||||
answerRepo: answerRepo,
|
answerRepo: answerRepo,
|
||||||
|
@ -66,94 +63,87 @@ func NewVoteService(
|
||||||
}
|
}
|
||||||
|
|
||||||
// VoteUp vote up
|
// VoteUp vote up
|
||||||
func (vs *VoteService) VoteUp(ctx context.Context, dto *schema.VoteDTO) (voteResp *schema.VoteResp, err error) {
|
func (vs *VoteService) VoteUp(ctx context.Context, req *schema.VoteReq) (resp *schema.VoteResp, err error) {
|
||||||
voteResp = &schema.VoteResp{}
|
objectInfo, err := vs.objectService.GetInfo(ctx, req.ObjectID)
|
||||||
|
|
||||||
var objectUserID string
|
|
||||||
|
|
||||||
objectUserID, err = vs.GetObjectUserID(ctx, dto.ObjectID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// make object id must be decoded
|
||||||
|
objectInfo.ObjectID = req.ObjectID
|
||||||
|
|
||||||
// check user is voting self or not
|
// check user is voting self or not
|
||||||
if objectUserID == dto.UserID {
|
if objectInfo.ObjectCreatorUserID == req.UserID {
|
||||||
err = errors.BadRequest(reason.DisallowVoteYourSelf)
|
return nil, errors.BadRequest(reason.DisallowVoteYourSelf)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if dto.IsCancel {
|
voteUpOperationInfo := vs.createVoteOperationInfo(ctx, req.UserID, true, objectInfo)
|
||||||
return vs.voteRepo.VoteUpCancel(ctx, dto.ObjectID, dto.UserID, objectUserID)
|
|
||||||
|
// vote operation
|
||||||
|
if req.IsCancel {
|
||||||
|
err = vs.voteRepo.CancelVote(ctx, voteUpOperationInfo)
|
||||||
} else {
|
} 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
|
// VoteDown vote down
|
||||||
func (vs *VoteService) VoteDown(ctx context.Context, dto *schema.VoteDTO) (voteResp *schema.VoteResp, err error) {
|
func (vs *VoteService) VoteDown(ctx context.Context, req *schema.VoteReq) (resp *schema.VoteResp, err error) {
|
||||||
voteResp = &schema.VoteResp{}
|
objectInfo, err := vs.objectService.GetInfo(ctx, req.ObjectID)
|
||||||
|
|
||||||
var objectUserID string
|
|
||||||
|
|
||||||
objectUserID, err = vs.GetObjectUserID(ctx, dto.ObjectID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// make object id must be decoded
|
||||||
|
objectInfo.ObjectID = req.ObjectID
|
||||||
|
|
||||||
// check user is voting self or not
|
// check user is voting self or not
|
||||||
if objectUserID == dto.UserID {
|
if objectInfo.ObjectCreatorUserID == req.UserID {
|
||||||
err = errors.BadRequest(reason.DisallowVoteYourSelf)
|
return nil, errors.BadRequest(reason.DisallowVoteYourSelf)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if dto.IsCancel {
|
// vote operation
|
||||||
return vs.voteRepo.VoteDownCancel(ctx, dto.ObjectID, dto.UserID, objectUserID)
|
voteDownOperationInfo := vs.createVoteOperationInfo(ctx, req.UserID, false, objectInfo)
|
||||||
|
if req.IsCancel {
|
||||||
|
err = vs.voteRepo.CancelVote(ctx, voteDownOperationInfo)
|
||||||
} else {
|
} 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))
|
||||||
}
|
|
||||||
|
|
||||||
func (vs *VoteService) GetObjectUserID(ctx context.Context, objectID string) (userID string, err error) {
|
|
||||||
var objectKey string
|
|
||||||
objectKey, err = obj.GetObjectTypeStrByObjectID(objectID)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = nil
|
return nil, err
|
||||||
return
|
}
|
||||||
|
err = vs.voteRepo.Vote(ctx, voteDownOperationInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch objectKey {
|
resp = &schema.VoteResp{}
|
||||||
case "question":
|
resp.UpVotes, resp.DownVotes, err = vs.voteRepo.GetAndSaveVoteResult(ctx, req.ObjectID, objectInfo.ObjectType)
|
||||||
object, has, e := vs.questionRepo.GetQuestion(ctx, objectID)
|
if err != nil {
|
||||||
if e != nil || !has {
|
log.Error(err)
|
||||||
err = errors.BadRequest(reason.QuestionNotFound).WithError(e).WithStack()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
userID = object.UserID
|
resp.Votes = resp.UpVotes - resp.DownVotes
|
||||||
case "answer":
|
if !req.IsCancel {
|
||||||
object, has, e := vs.answerRepo.GetAnswer(ctx, objectID)
|
resp.VoteStatus = constant.ActVoteDown
|
||||||
if e != nil || !has {
|
|
||||||
err = errors.BadRequest(reason.AnswerNotFound).WithError(e).WithStack()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
userID = object.UserID
|
return resp, nil
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListUserVotes list user's votes
|
// 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{
|
typeKeys := []string{
|
||||||
activity_type.QuestionVoteUp,
|
activity_type.QuestionVoteUp,
|
||||||
activity_type.QuestionVoteDown,
|
activity_type.QuestionVoteDown,
|
||||||
|
@ -172,14 +162,14 @@ func (vs *VoteService) ListUserVotes(ctx context.Context, req schema.GetVoteWith
|
||||||
activityTypeMapping[cfg.ID] = typeKey
|
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 {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
lang := handler.GetLangByCtx(ctx)
|
lang := handler.GetLangByCtx(ctx)
|
||||||
|
|
||||||
resp := make([]*schema.GetVoteWithPageResp, 0)
|
votes := make([]*schema.GetVoteWithPageResp, 0)
|
||||||
for _, voteInfo := range voteList {
|
for _, voteInfo := range voteList {
|
||||||
objInfo, err := vs.objectService.GetInfo(ctx, voteInfo.ObjectID)
|
objInfo, err := vs.objectService.GetInfo(ctx, voteInfo.ObjectID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -202,7 +192,65 @@ func (vs *VoteService) ListUserVotes(ctx context.Context, req schema.GetVoteWith
|
||||||
if objInfo.QuestionStatus == entity.QuestionStatusDeleted {
|
if objInfo.QuestionStatus == entity.QuestionStatusDeleted {
|
||||||
item.Title = translator.Tr(lang, constant.DeletedQuestionTitleTrKey)
|
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)
|
const salt = int64(100)
|
||||||
|
|
||||||
var ShortIDSwitch = false
|
|
||||||
|
|
||||||
// NumToString num to string
|
// NumToString num to string
|
||||||
func NumToShortID(id int64) string {
|
func NumToShortID(id int64) string {
|
||||||
sid := strconv.FormatInt(id, 10)
|
sid := strconv.FormatInt(id, 10)
|
||||||
|
@ -45,15 +43,12 @@ func ShortIDToNum(code string) int64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
func EnShortID(id string) string {
|
func EnShortID(id string) string {
|
||||||
if ShortIDSwitch {
|
|
||||||
num, err := strconv.ParseInt(id, 10, 64)
|
num, err := strconv.ParseInt(id, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
return NumToShortID(num)
|
return NumToShortID(num)
|
||||||
}
|
}
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeShortID(sid string) string {
|
func DeShortID(sid string) string {
|
||||||
num, err := strconv.ParseInt(sid, 10, 64)
|
num, err := strconv.ParseInt(sid, 10, 64)
|
||||||
|
|
|
@ -31,7 +31,6 @@ func Test_ShortID(t *testing.T) {
|
||||||
|
|
||||||
func Test_EnDeShortID(t *testing.T) {
|
func Test_EnDeShortID(t *testing.T) {
|
||||||
nums := []string{"0", "1", "10", "100", "1000", "10000", "100000", "1234567", "10000000000000000", "10010000000001316", "19930000000001316"}
|
nums := []string{"0", "1", "10", "100", "1000", "10000", "100000", "1234567", "10000000000000000", "10010000000001316", "19930000000001316"}
|
||||||
ShortIDSwitch = true
|
|
||||||
for _, num := range nums {
|
for _, num := range nums {
|
||||||
code := EnShortID(num)
|
code := EnShortID(num)
|
||||||
denum := DeShortID(code)
|
denum := DeShortID(code)
|
||||||
|
|
|
@ -33,6 +33,7 @@ type UserCenterDesc struct {
|
||||||
SignUpRedirectURL string `json:"sign_up_redirect_url"`
|
SignUpRedirectURL string `json:"sign_up_redirect_url"`
|
||||||
RankAgentEnabled bool `json:"rank_agent_enabled"`
|
RankAgentEnabled bool `json:"rank_agent_enabled"`
|
||||||
UserStatusAgentEnabled bool `json:"user_status_agent_enabled"`
|
UserStatusAgentEnabled bool `json:"user_status_agent_enabled"`
|
||||||
|
UserRoleAgentEnabled bool `json:"user_role_agent_enabled"`
|
||||||
MustAuthEmailEnabled bool `json:"must_auth_email_enabled"`
|
MustAuthEmailEnabled bool `json:"must_auth_email_enabled"`
|
||||||
EnabledOriginalUserSystem bool `json:"enabled_original_user_system"`
|
EnabledOriginalUserSystem bool `json:"enabled_original_user_system"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
plugin:
|
plugin:
|
||||||
uc_login:
|
uc_login:
|
||||||
ui:
|
ui:
|
||||||
|
connect: Connect with {{ auth_name }}
|
||||||
login: Login
|
login: Login
|
||||||
qrcode_login_tip: Please use {{ agentName }} to scan the QR code and log in.
|
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.
|
login_failed_email_tip: Login failed, please allow this app to access your email information before try again.
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
plugin:
|
plugin:
|
||||||
uc_login:
|
uc_login:
|
||||||
ui:
|
ui:
|
||||||
|
connect: 连接到 {{ auth_name }}
|
||||||
login: 登录
|
login: 登录
|
||||||
qrcode_login_tip: 请使用 {{ agentName }} 扫描二维码登录
|
qrcode_login_tip: 请使用 {{ agentName }} 扫描二维码登录
|
||||||
login_failed_email_tip: 登录失败, 请允许该应用程序访问您的电子邮件信息,然后再试一次。
|
login_failed_email_tip: 登录失败, 请允许该应用程序访问您的电子邮件信息,然后再试一次。
|
||||||
|
|
Loading…
Reference in New Issue