Merge branch 'release/1.1.1' into github-main
# Conflicts: # i18n/en_US.yaml
This commit is contained in:
Vendored
+2
-1
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"eslint.workingDirectories": [
|
||||
"ui"
|
||||
]
|
||||
],
|
||||
"explorer.autoReveal": "focusNoScroll"
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ LABEL maintainer="aichy@sf.com"
|
||||
|
||||
ARG GOPROXY
|
||||
# ENV GOPROXY ${GOPROXY:-direct}
|
||||
ENV GOPROXY=https://goproxy.io,direct
|
||||
ENV GOPROXY=https://proxy.golang.com.cn,direct
|
||||
|
||||
ENV GOPATH /go
|
||||
ENV GOROOT /usr/local/go
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.PHONY: build clean ui
|
||||
|
||||
VERSION=1.1.0
|
||||
VERSION=1.1.1
|
||||
BIN=answer
|
||||
DIR_SRC=./cmd/answer
|
||||
DOCKER_CMD=docker
|
||||
|
||||
+1
-1
@@ -114,7 +114,7 @@ To run answer, use:
|
||||
fmt.Println("read config failed: ", err.Error())
|
||||
return
|
||||
}
|
||||
if err = migrations.Migrate(c.Data.Database, c.Data.Cache, upgradeVersion); err != nil {
|
||||
if err = migrations.Migrate(c.Debug, c.Data.Database, c.Data.Cache, upgradeVersion); err != nil {
|
||||
fmt.Println("migrate failed: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
+36
-32
@@ -46,6 +46,7 @@ import (
|
||||
"github.com/answerdev/answer/internal/service/action"
|
||||
activity2 "github.com/answerdev/answer/internal/service/activity"
|
||||
activity_common2 "github.com/answerdev/answer/internal/service/activity_common"
|
||||
"github.com/answerdev/answer/internal/service/activity_queue"
|
||||
"github.com/answerdev/answer/internal/service/answer_common"
|
||||
auth2 "github.com/answerdev/answer/internal/service/auth"
|
||||
"github.com/answerdev/answer/internal/service/collection_common"
|
||||
@@ -56,6 +57,7 @@ import (
|
||||
export2 "github.com/answerdev/answer/internal/service/export"
|
||||
"github.com/answerdev/answer/internal/service/follow"
|
||||
meta2 "github.com/answerdev/answer/internal/service/meta"
|
||||
"github.com/answerdev/answer/internal/service/notice_queue"
|
||||
notification2 "github.com/answerdev/answer/internal/service/notification"
|
||||
"github.com/answerdev/answer/internal/service/notification_common"
|
||||
"github.com/answerdev/answer/internal/service/object_info"
|
||||
@@ -128,8 +130,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||
userService := service.NewUserService(userRepo, userActiveActivityRepo, activityRepo, emailService, authService, siteInfoCommonService, userRoleRelService, userCommon, userExternalLoginService)
|
||||
captchaRepo := captcha.NewCaptchaRepo(dataData)
|
||||
captchaService := action.NewCaptchaService(captchaRepo)
|
||||
uploaderService := uploader.NewUploaderService(serviceConf, siteInfoCommonService)
|
||||
userController := controller.NewUserController(authService, userService, captchaService, emailService, uploaderService, siteInfoCommonService)
|
||||
userController := controller.NewUserController(authService, userService, captchaService, emailService, siteInfoCommonService)
|
||||
commentRepo := comment.NewCommentRepo(dataData, uniqueIDRepo)
|
||||
commentCommonRepo := comment.NewCommentCommonRepo(dataData, uniqueIDRepo)
|
||||
answerRepo := answer.NewAnswerRepo(dataData, uniqueIDRepo, userRankRepo, activityRepo)
|
||||
@@ -139,22 +140,24 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||
tagRepo := tag.NewTagRepo(dataData, uniqueIDRepo)
|
||||
revisionRepo := revision.NewRevisionRepo(dataData, uniqueIDRepo)
|
||||
revisionService := revision_common.NewRevisionService(revisionRepo, userRepo)
|
||||
tagCommonService := tag_common2.NewTagCommonService(tagCommonRepo, tagRelRepo, tagRepo, revisionService, siteInfoCommonService)
|
||||
activityQueueService := activity_queue.NewActivityQueueService()
|
||||
tagCommonService := tag_common2.NewTagCommonService(tagCommonRepo, tagRelRepo, tagRepo, revisionService, siteInfoCommonService, activityQueueService)
|
||||
objService := object_info.NewObjService(answerRepo, questionRepo, commentCommonRepo, tagCommonRepo, tagCommonService)
|
||||
voteRepo := activity_common.NewVoteRepo(dataData, activityRepo)
|
||||
commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userCommon, objService, voteRepo, emailService, userRepo)
|
||||
notificationQueueService := notice_queue.NewNotificationQueueService()
|
||||
commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userCommon, objService, voteRepo, emailService, userRepo, notificationQueueService, activityQueueService)
|
||||
rolePowerRelRepo := role.NewRolePowerRelRepo(dataData)
|
||||
rolePowerRelService := role2.NewRolePowerRelService(rolePowerRelRepo, userRoleRelService)
|
||||
rankService := rank2.NewRankService(userCommon, userRankRepo, objService, userRoleRelService, rolePowerRelService, configService)
|
||||
commentController := controller.NewCommentController(commentService, rankService)
|
||||
commentController := controller.NewCommentController(commentService, rankService, captchaService)
|
||||
reportRepo := report.NewReportRepo(dataData, uniqueIDRepo)
|
||||
reportService := report2.NewReportService(reportRepo, objService)
|
||||
reportController := controller.NewReportController(reportService, rankService)
|
||||
serviceVoteRepo := activity.NewVoteRepo(dataData, uniqueIDRepo, configService, activityRepo, userRankRepo, voteRepo)
|
||||
voteService := service.NewVoteService(serviceVoteRepo, uniqueIDRepo, configService, questionRepo, answerRepo, commentCommonRepo, objService)
|
||||
voteController := controller.NewVoteController(voteService, rankService)
|
||||
reportController := controller.NewReportController(reportService, rankService, captchaService)
|
||||
serviceVoteRepo := activity.NewVoteRepo(dataData, activityRepo, userRankRepo, notificationQueueService)
|
||||
voteService := service.NewVoteService(serviceVoteRepo, configService, questionRepo, answerRepo, commentCommonRepo, objService)
|
||||
voteController := controller.NewVoteController(voteService, rankService, captchaService)
|
||||
followRepo := activity_common.NewFollowRepo(dataData, uniqueIDRepo, activityRepo)
|
||||
tagService := tag2.NewTagService(tagRepo, tagCommonService, revisionService, followRepo, siteInfoCommonService)
|
||||
tagService := tag2.NewTagService(tagRepo, tagCommonService, revisionService, followRepo, siteInfoCommonService, activityQueueService)
|
||||
tagController := controller.NewTagController(tagService, tagCommonService, rankService)
|
||||
followFollowRepo := activity.NewFollowRepo(dataData, uniqueIDRepo, activityRepo)
|
||||
followService := follow.NewFollowService(followFollowRepo, followRepo, tagCommonRepo)
|
||||
@@ -165,29 +168,27 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||
answerCommon := answercommon.NewAnswerCommon(answerRepo)
|
||||
metaRepo := meta.NewMetaRepo(dataData)
|
||||
metaService := meta2.NewMetaService(metaRepo)
|
||||
questionCommon := questioncommon.NewQuestionCommon(questionRepo, answerRepo, voteRepo, followRepo, tagCommonService, userCommon, collectionCommon, answerCommon, metaService, configService, dataData)
|
||||
questionCommon := questioncommon.NewQuestionCommon(questionRepo, answerRepo, voteRepo, followRepo, tagCommonService, userCommon, collectionCommon, answerCommon, metaService, configService, activityQueueService, dataData)
|
||||
collectionService := service.NewCollectionService(collectionRepo, collectionGroupRepo, questionCommon)
|
||||
collectionController := controller.NewCollectionController(collectionService)
|
||||
answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo)
|
||||
questionActivityRepo := activity.NewQuestionActivityRepo(dataData, activityRepo, userRankRepo)
|
||||
answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, questionActivityRepo)
|
||||
questionService := service.NewQuestionService(questionRepo, tagCommonService, questionCommon, userCommon, userRepo, revisionService, metaService, collectionCommon, answerActivityService, dataData, emailService)
|
||||
answerService := service.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService)
|
||||
questionController := controller.NewQuestionController(questionService, answerService, rankService)
|
||||
dashboardService := dashboard.NewDashboardService(questionRepo, answerRepo, commentCommonRepo, voteRepo, userRepo, reportRepo, configService, siteInfoCommonService, serviceConf, dataData)
|
||||
answerController := controller.NewAnswerController(answerService, rankService, dashboardService)
|
||||
answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo, notificationQueueService)
|
||||
answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, configService)
|
||||
questionService := service.NewQuestionService(questionRepo, tagCommonService, questionCommon, userCommon, userRepo, revisionService, metaService, collectionCommon, answerActivityService, emailService, notificationQueueService, activityQueueService, siteInfoCommonService)
|
||||
answerService := service.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService, notificationQueueService, activityQueueService)
|
||||
questionController := controller.NewQuestionController(questionService, answerService, rankService, siteInfoCommonService, captchaService)
|
||||
answerController := controller.NewAnswerController(answerService, rankService, captchaService)
|
||||
searchParser := search_parser.NewSearchParser(tagCommonService, userCommon)
|
||||
searchRepo := search_common.NewSearchRepo(dataData, uniqueIDRepo, userCommon)
|
||||
searchService := service.NewSearchService(searchParser, searchRepo)
|
||||
searchController := controller.NewSearchController(searchService)
|
||||
serviceRevisionService := service.NewRevisionService(revisionRepo, userCommon, questionCommon, answerService, objService, questionRepo, answerRepo, tagRepo, tagCommonService)
|
||||
searchController := controller.NewSearchController(searchService, captchaService)
|
||||
serviceRevisionService := service.NewRevisionService(revisionRepo, userCommon, questionCommon, answerService, objService, questionRepo, answerRepo, tagRepo, tagCommonService, notificationQueueService, activityQueueService)
|
||||
revisionController := controller.NewRevisionController(serviceRevisionService, rankService)
|
||||
rankController := controller.NewRankController(rankService)
|
||||
reportHandle := report_handle_admin.NewReportHandle(questionCommon, commentRepo, configService)
|
||||
reportHandle := report_handle_admin.NewReportHandle(questionCommon, commentRepo, configService, notificationQueueService)
|
||||
reportAdminService := report_admin.NewReportAdminService(reportRepo, userCommon, answerRepo, questionRepo, commentCommonRepo, reportHandle, configService, objService)
|
||||
controller_adminReportController := controller_admin.NewReportController(reportAdminService)
|
||||
userAdminRepo := user.NewUserAdminRepo(dataData, authRepo)
|
||||
userAdminService := user_admin.NewUserAdminService(userAdminRepo, userRoleRelService, authService, userCommon, userActiveActivityRepo, siteInfoCommonService)
|
||||
userAdminService := user_admin.NewUserAdminService(userAdminRepo, userRoleRelService, authService, userCommon, userActiveActivityRepo, siteInfoCommonService, emailService)
|
||||
userAdminController := controller_admin.NewUserAdminController(userAdminService)
|
||||
reasonRepo := reason.NewReasonRepo(configService)
|
||||
reasonService := reason2.NewReasonService(reasonRepo)
|
||||
@@ -195,36 +196,39 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||
themeController := controller_admin.NewThemeController()
|
||||
siteInfoService := siteinfo.NewSiteInfoService(siteInfoRepo, siteInfoCommonService, emailService, tagCommonService, configService, questionCommon)
|
||||
siteInfoController := controller_admin.NewSiteInfoController(siteInfoService)
|
||||
siteinfoController := controller.NewSiteinfoController(siteInfoCommonService)
|
||||
controllerSiteInfoController := controller.NewSiteInfoController(siteInfoCommonService)
|
||||
notificationRepo := notification.NewNotificationRepo(dataData)
|
||||
notificationCommon := notificationcommon.NewNotificationCommon(dataData, notificationRepo, userCommon, activityRepo, followRepo, objService)
|
||||
notificationCommon := notificationcommon.NewNotificationCommon(dataData, notificationRepo, userCommon, activityRepo, followRepo, objService, notificationQueueService)
|
||||
notificationService := notification2.NewNotificationService(dataData, notificationRepo, notificationCommon, revisionService)
|
||||
notificationController := controller.NewNotificationController(notificationService, rankService)
|
||||
dashboardService := dashboard.NewDashboardService(questionRepo, answerRepo, commentCommonRepo, voteRepo, userRepo, reportRepo, configService, siteInfoCommonService, serviceConf, dataData)
|
||||
dashboardController := controller.NewDashboardController(dashboardService)
|
||||
uploaderService := uploader.NewUploaderService(serviceConf, siteInfoCommonService)
|
||||
uploadController := controller.NewUploadController(uploaderService)
|
||||
activityCommon := activity_common2.NewActivityCommon(activityRepo)
|
||||
activityActivityRepo := activity.NewActivityRepo(dataData, configService)
|
||||
activityCommon := activity_common2.NewActivityCommon(activityRepo, activityQueueService)
|
||||
commentCommonService := comment_common.NewCommentCommonService(commentCommonRepo)
|
||||
activityService := activity2.NewActivityService(activityActivityRepo, userCommon, activityCommon, tagCommonService, objService, commentCommonService, revisionService, metaService, configService)
|
||||
activityController := controller.NewActivityController(activityCommon, activityService)
|
||||
activityController := controller.NewActivityController(activityService)
|
||||
roleController := controller_admin.NewRoleController(roleService)
|
||||
pluginConfigRepo := plugin_config.NewPluginConfigRepo(dataData)
|
||||
pluginCommonService := plugin_common.NewPluginCommonService(pluginConfigRepo, configService)
|
||||
pluginController := controller_admin.NewPluginController(pluginCommonService)
|
||||
permissionController := controller.NewPermissionController(rankService)
|
||||
answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, controller_adminReportController, userAdminController, reasonController, themeController, siteInfoController, siteinfoController, notificationController, dashboardController, uploadController, activityController, roleController, pluginController, permissionController)
|
||||
answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, controller_adminReportController, userAdminController, reasonController, themeController, siteInfoController, controllerSiteInfoController, notificationController, dashboardController, uploadController, activityController, roleController, pluginController, permissionController)
|
||||
swaggerRouter := router.NewSwaggerRouter(swaggerConf)
|
||||
uiRouter := router.NewUIRouter(siteinfoController, siteInfoCommonService)
|
||||
uiRouter := router.NewUIRouter(controllerSiteInfoController, siteInfoCommonService)
|
||||
authUserMiddleware := middleware.NewAuthUserMiddleware(authService, siteInfoCommonService)
|
||||
avatarMiddleware := middleware.NewAvatarMiddleware(serviceConf, uploaderService)
|
||||
templateRenderController := templaterender.NewTemplateRenderController(questionService, userService, tagService, answerService, commentService, dataData, siteInfoCommonService)
|
||||
shortIDMiddleware := middleware.NewShortIDMiddleware(siteInfoCommonService)
|
||||
templateRenderController := templaterender.NewTemplateRenderController(questionService, userService, tagService, answerService, commentService, siteInfoCommonService, questionRepo)
|
||||
templateController := controller.NewTemplateController(templateRenderController, siteInfoCommonService)
|
||||
templateRouter := router.NewTemplateRouter(templateController, templateRenderController, siteInfoController)
|
||||
templateRouter := router.NewTemplateRouter(templateController, templateRenderController, siteInfoController, authUserMiddleware)
|
||||
connectorController := controller.NewConnectorController(siteInfoCommonService, emailService, userExternalLoginService)
|
||||
userCenterLoginService := user_external_login2.NewUserCenterLoginService(userRepo, userCommon, userExternalLoginRepo, userActiveActivityRepo, siteInfoCommonService)
|
||||
userCenterController := controller.NewUserCenterController(userCenterLoginService, siteInfoCommonService)
|
||||
pluginAPIRouter := router.NewPluginAPIRouter(connectorController, userCenterController)
|
||||
ginEngine := server.NewHTTPServer(debug, staticRouter, answerAPIRouter, swaggerRouter, uiRouter, authUserMiddleware, avatarMiddleware, templateRouter, pluginAPIRouter)
|
||||
ginEngine := server.NewHTTPServer(debug, staticRouter, answerAPIRouter, swaggerRouter, uiRouter, authUserMiddleware, avatarMiddleware, shortIDMiddleware, templateRouter, pluginAPIRouter)
|
||||
scheduledTaskManager := cron.NewScheduledTaskManager(siteInfoCommonService, questionService)
|
||||
application := newApplication(serverConf, ginEngine, scheduledTaskManager)
|
||||
return application, func() {
|
||||
|
||||
+151
-18
@@ -1,5 +1,5 @@
|
||||
// Package docs GENERATED BY SWAG; DO NOT EDIT
|
||||
// This file was generated by swaggo/swag
|
||||
// Code generated by swaggo/swag. DO NOT EDIT.
|
||||
|
||||
package docs
|
||||
|
||||
import "github.com/swaggo/swag"
|
||||
@@ -49,7 +49,7 @@ const docTemplate = `{
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "AdminSearchAnswerList",
|
||||
"summary": "AdminAnswerPage admin answer page",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
@@ -379,7 +379,7 @@ const docTemplate = `{
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "AdminSearchList",
|
||||
"summary": "AdminQuestionPage admin question page",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
@@ -1578,6 +1578,52 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/user/activation": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "get user activation",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "get user activation",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "user id",
|
||||
"name": "user_id",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/schema.GetUserActivationResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/user/password": {
|
||||
"put": {
|
||||
"security": [
|
||||
@@ -1695,6 +1741,42 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/users/activation": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "send user activation",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "send user activation",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "SendUserActivationReq",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.SendUserActivationReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/users/page": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -3444,7 +3526,7 @@ const docTemplate = `{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "user's vote",
|
||||
"description": "get user personal votes",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -3454,7 +3536,7 @@ const docTemplate = `{
|
||||
"tags": [
|
||||
"Activity"
|
||||
],
|
||||
"summary": "user's votes",
|
||||
"summary": "get user personal votes",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
@@ -6345,7 +6427,8 @@ const docTemplate = `{
|
||||
"properties": {
|
||||
"display_name": {
|
||||
"type": "string",
|
||||
"maxLength": 30
|
||||
"maxLength": 30,
|
||||
"minLength": 4
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
@@ -7082,7 +7165,7 @@ const docTemplate = `{
|
||||
}
|
||||
},
|
||||
"selected_level": {
|
||||
"type": "integer"
|
||||
"$ref": "#/definitions/schema.PrivilegeLevel"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -7397,6 +7480,14 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.GetUserActivationResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"activation_url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.GetUserPageResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -7573,11 +7664,24 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.PrivilegeLevel": {
|
||||
"type": "integer",
|
||||
"enum": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"PrivilegeLevel1",
|
||||
"PrivilegeLevel2",
|
||||
"PrivilegeLevel3"
|
||||
]
|
||||
},
|
||||
"schema.PrivilegeOption": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"level": {
|
||||
"type": "integer"
|
||||
"$ref": "#/definitions/schema.PrivilegeLevel"
|
||||
},
|
||||
"level_desc": {
|
||||
"type": "string"
|
||||
@@ -7663,11 +7767,11 @@ const docTemplate = `{
|
||||
"schema.QuestionPageReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"inDays": {
|
||||
"in_days": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"orderCond": {
|
||||
"order": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"newest",
|
||||
@@ -7681,7 +7785,7 @@ const docTemplate = `{
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"pageSize": {
|
||||
"page_size": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
@@ -7992,7 +8096,11 @@ const docTemplate = `{
|
||||
},
|
||||
"user_info": {
|
||||
"description": "user info",
|
||||
"$ref": "#/definitions/schema.UserBasicInfo"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/schema.UserBasicInfo"
|
||||
}
|
||||
]
|
||||
},
|
||||
"vote_count": {
|
||||
"type": "integer"
|
||||
@@ -8004,7 +8112,11 @@ const docTemplate = `{
|
||||
"properties": {
|
||||
"object": {
|
||||
"description": "this object",
|
||||
"$ref": "#/definitions/schema.SearchObject"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/schema.SearchObject"
|
||||
}
|
||||
]
|
||||
},
|
||||
"object_type": {
|
||||
"description": "object_type",
|
||||
@@ -8012,6 +8124,17 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.SendUserActivationReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"user_id"
|
||||
],
|
||||
"properties": {
|
||||
"user_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.SiteBrandingReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -8186,7 +8309,7 @@ const docTemplate = `{
|
||||
"type": "string"
|
||||
},
|
||||
"site_seo": {
|
||||
"$ref": "#/definitions/schema.SiteSeoReq"
|
||||
"$ref": "#/definitions/schema.SiteSeoResp"
|
||||
},
|
||||
"site_users": {
|
||||
"$ref": "#/definitions/schema.SiteUsersResp"
|
||||
@@ -8635,7 +8758,11 @@ const docTemplate = `{
|
||||
"properties": {
|
||||
"avatar": {
|
||||
"description": "avatar",
|
||||
"$ref": "#/definitions/schema.AvatarInfo"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/schema.AvatarInfo"
|
||||
}
|
||||
]
|
||||
},
|
||||
"bio": {
|
||||
"description": "bio",
|
||||
@@ -8702,9 +8829,13 @@ const docTemplate = `{
|
||||
],
|
||||
"properties": {
|
||||
"level": {
|
||||
"type": "integer",
|
||||
"maximum": 3,
|
||||
"minimum": 1
|
||||
"minimum": 1,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/schema.PrivilegeLevel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -9312,6 +9443,8 @@ var SwaggerInfo = &swag.Spec{
|
||||
Description: "",
|
||||
InfoInstanceName: "swagger",
|
||||
SwaggerTemplate: docTemplate,
|
||||
LeftDelim: "{{",
|
||||
RightDelim: "}}",
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
+147
-16
@@ -37,7 +37,7 @@
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "AdminSearchAnswerList",
|
||||
"summary": "AdminAnswerPage admin answer page",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
@@ -367,7 +367,7 @@
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "AdminSearchList",
|
||||
"summary": "AdminQuestionPage admin question page",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
@@ -1566,6 +1566,52 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/user/activation": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "get user activation",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "get user activation",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "user id",
|
||||
"name": "user_id",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/schema.GetUserActivationResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/user/password": {
|
||||
"put": {
|
||||
"security": [
|
||||
@@ -1683,6 +1729,42 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/users/activation": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "send user activation",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "send user activation",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "SendUserActivationReq",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.SendUserActivationReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/admin/api/users/page": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -3432,7 +3514,7 @@
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "user's vote",
|
||||
"description": "get user personal votes",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -3442,7 +3524,7 @@
|
||||
"tags": [
|
||||
"Activity"
|
||||
],
|
||||
"summary": "user's votes",
|
||||
"summary": "get user personal votes",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
@@ -6333,7 +6415,8 @@
|
||||
"properties": {
|
||||
"display_name": {
|
||||
"type": "string",
|
||||
"maxLength": 30
|
||||
"maxLength": 30,
|
||||
"minLength": 4
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
@@ -7070,7 +7153,7 @@
|
||||
}
|
||||
},
|
||||
"selected_level": {
|
||||
"type": "integer"
|
||||
"$ref": "#/definitions/schema.PrivilegeLevel"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -7385,6 +7468,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.GetUserActivationResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"activation_url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.GetUserPageResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -7561,11 +7652,24 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.PrivilegeLevel": {
|
||||
"type": "integer",
|
||||
"enum": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"PrivilegeLevel1",
|
||||
"PrivilegeLevel2",
|
||||
"PrivilegeLevel3"
|
||||
]
|
||||
},
|
||||
"schema.PrivilegeOption": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"level": {
|
||||
"type": "integer"
|
||||
"$ref": "#/definitions/schema.PrivilegeLevel"
|
||||
},
|
||||
"level_desc": {
|
||||
"type": "string"
|
||||
@@ -7651,11 +7755,11 @@
|
||||
"schema.QuestionPageReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"inDays": {
|
||||
"in_days": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"orderCond": {
|
||||
"order": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"newest",
|
||||
@@ -7669,7 +7773,7 @@
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"pageSize": {
|
||||
"page_size": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
@@ -7980,7 +8084,11 @@
|
||||
},
|
||||
"user_info": {
|
||||
"description": "user info",
|
||||
"$ref": "#/definitions/schema.UserBasicInfo"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/schema.UserBasicInfo"
|
||||
}
|
||||
]
|
||||
},
|
||||
"vote_count": {
|
||||
"type": "integer"
|
||||
@@ -7992,7 +8100,11 @@
|
||||
"properties": {
|
||||
"object": {
|
||||
"description": "this object",
|
||||
"$ref": "#/definitions/schema.SearchObject"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/schema.SearchObject"
|
||||
}
|
||||
]
|
||||
},
|
||||
"object_type": {
|
||||
"description": "object_type",
|
||||
@@ -8000,6 +8112,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.SendUserActivationReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"user_id"
|
||||
],
|
||||
"properties": {
|
||||
"user_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.SiteBrandingReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -8174,7 +8297,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"site_seo": {
|
||||
"$ref": "#/definitions/schema.SiteSeoReq"
|
||||
"$ref": "#/definitions/schema.SiteSeoResp"
|
||||
},
|
||||
"site_users": {
|
||||
"$ref": "#/definitions/schema.SiteUsersResp"
|
||||
@@ -8623,7 +8746,11 @@
|
||||
"properties": {
|
||||
"avatar": {
|
||||
"description": "avatar",
|
||||
"$ref": "#/definitions/schema.AvatarInfo"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/schema.AvatarInfo"
|
||||
}
|
||||
]
|
||||
},
|
||||
"bio": {
|
||||
"description": "bio",
|
||||
@@ -8690,9 +8817,13 @@
|
||||
],
|
||||
"properties": {
|
||||
"level": {
|
||||
"type": "integer",
|
||||
"maximum": 3,
|
||||
"minimum": 1
|
||||
"minimum": 1,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/schema.PrivilegeLevel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+89
-14
@@ -206,6 +206,7 @@ definitions:
|
||||
properties:
|
||||
display_name:
|
||||
maxLength: 30
|
||||
minLength: 4
|
||||
type: string
|
||||
email:
|
||||
maxLength: 500
|
||||
@@ -729,7 +730,7 @@ definitions:
|
||||
$ref: '#/definitions/schema.PrivilegeOption'
|
||||
type: array
|
||||
selected_level:
|
||||
type: integer
|
||||
$ref: '#/definitions/schema.PrivilegeLevel'
|
||||
type: object
|
||||
schema.GetRankPersonalPageResp:
|
||||
properties:
|
||||
@@ -952,6 +953,11 @@ definitions:
|
||||
unreviewed_info:
|
||||
$ref: '#/definitions/schema.GetRevisionResp'
|
||||
type: object
|
||||
schema.GetUserActivationResp:
|
||||
properties:
|
||||
activation_url:
|
||||
type: string
|
||||
type: object
|
||||
schema.GetUserPageResp:
|
||||
properties:
|
||||
avatar:
|
||||
@@ -1075,10 +1081,20 @@ definitions:
|
||||
content:
|
||||
type: string
|
||||
type: object
|
||||
schema.PrivilegeLevel:
|
||||
enum:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
type: integer
|
||||
x-enum-varnames:
|
||||
- PrivilegeLevel1
|
||||
- PrivilegeLevel2
|
||||
- PrivilegeLevel3
|
||||
schema.PrivilegeOption:
|
||||
properties:
|
||||
level:
|
||||
type: integer
|
||||
$ref: '#/definitions/schema.PrivilegeLevel'
|
||||
level_desc:
|
||||
type: string
|
||||
privileges:
|
||||
@@ -1141,10 +1157,10 @@ definitions:
|
||||
type: object
|
||||
schema.QuestionPageReq:
|
||||
properties:
|
||||
inDays:
|
||||
in_days:
|
||||
minimum: 1
|
||||
type: integer
|
||||
orderCond:
|
||||
order:
|
||||
enum:
|
||||
- newest
|
||||
- active
|
||||
@@ -1155,7 +1171,7 @@ definitions:
|
||||
page:
|
||||
minimum: 1
|
||||
type: integer
|
||||
pageSize:
|
||||
page_size:
|
||||
minimum: 1
|
||||
type: integer
|
||||
tag:
|
||||
@@ -1368,7 +1384,8 @@ definitions:
|
||||
title:
|
||||
type: string
|
||||
user_info:
|
||||
$ref: '#/definitions/schema.UserBasicInfo'
|
||||
allOf:
|
||||
- $ref: '#/definitions/schema.UserBasicInfo'
|
||||
description: user info
|
||||
vote_count:
|
||||
type: integer
|
||||
@@ -1376,12 +1393,20 @@ definitions:
|
||||
schema.SearchResp:
|
||||
properties:
|
||||
object:
|
||||
$ref: '#/definitions/schema.SearchObject'
|
||||
allOf:
|
||||
- $ref: '#/definitions/schema.SearchObject'
|
||||
description: this object
|
||||
object_type:
|
||||
description: object_type
|
||||
type: string
|
||||
type: object
|
||||
schema.SendUserActivationReq:
|
||||
properties:
|
||||
user_id:
|
||||
type: string
|
||||
required:
|
||||
- user_id
|
||||
type: object
|
||||
schema.SiteBrandingReq:
|
||||
properties:
|
||||
favicon:
|
||||
@@ -1507,7 +1532,7 @@ definitions:
|
||||
revision:
|
||||
type: string
|
||||
site_seo:
|
||||
$ref: '#/definitions/schema.SiteSeoReq'
|
||||
$ref: '#/definitions/schema.SiteSeoResp'
|
||||
site_users:
|
||||
$ref: '#/definitions/schema.SiteUsersResp'
|
||||
theme:
|
||||
@@ -1811,7 +1836,8 @@ definitions:
|
||||
schema.UpdateInfoRequest:
|
||||
properties:
|
||||
avatar:
|
||||
$ref: '#/definitions/schema.AvatarInfo'
|
||||
allOf:
|
||||
- $ref: '#/definitions/schema.AvatarInfo'
|
||||
description: avatar
|
||||
bio:
|
||||
description: bio
|
||||
@@ -1858,9 +1884,10 @@ definitions:
|
||||
schema.UpdatePrivilegesConfigReq:
|
||||
properties:
|
||||
level:
|
||||
allOf:
|
||||
- $ref: '#/definitions/schema.PrivilegeLevel'
|
||||
maximum: 3
|
||||
minimum: 1
|
||||
type: integer
|
||||
required:
|
||||
- level
|
||||
type: object
|
||||
@@ -2335,7 +2362,7 @@ paths:
|
||||
$ref: '#/definitions/handler.RespBody'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: AdminSearchAnswerList
|
||||
summary: AdminAnswerPage admin answer page
|
||||
tags:
|
||||
- admin
|
||||
/answer/admin/api/answer/status:
|
||||
@@ -2533,7 +2560,7 @@ paths:
|
||||
$ref: '#/definitions/handler.RespBody'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: AdminSearchList
|
||||
summary: AdminQuestionPage admin question page
|
||||
tags:
|
||||
- admin
|
||||
/answer/admin/api/question/status:
|
||||
@@ -3219,6 +3246,32 @@ paths:
|
||||
summary: add user
|
||||
tags:
|
||||
- admin
|
||||
/answer/admin/api/user/activation:
|
||||
get:
|
||||
description: get user activation
|
||||
parameters:
|
||||
- description: user id
|
||||
in: query
|
||||
name: user_id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/handler.RespBody'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/schema.GetUserActivationResp'
|
||||
type: object
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: get user activation
|
||||
tags:
|
||||
- admin
|
||||
/answer/admin/api/user/password:
|
||||
put:
|
||||
consumes:
|
||||
@@ -3291,6 +3344,28 @@ paths:
|
||||
summary: update user
|
||||
tags:
|
||||
- admin
|
||||
/answer/admin/api/users/activation:
|
||||
post:
|
||||
description: send user activation
|
||||
parameters:
|
||||
- description: SendUserActivationReq
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/schema.SendUserActivationReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.RespBody'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: send user activation
|
||||
tags:
|
||||
- admin
|
||||
/answer/admin/api/users/page:
|
||||
get:
|
||||
description: get user page
|
||||
@@ -4364,7 +4439,7 @@ paths:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: user's vote
|
||||
description: get user personal votes
|
||||
parameters:
|
||||
- description: page size
|
||||
in: query
|
||||
@@ -4395,7 +4470,7 @@ paths:
|
||||
type: object
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: user's votes
|
||||
summary: get user personal votes
|
||||
tags:
|
||||
- Activity
|
||||
/answer/api/v1/post/render:
|
||||
|
||||
@@ -10,12 +10,12 @@ require (
|
||||
github.com/bwmarrin/snowflake v0.3.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/gin-gonic/gin v1.8.1
|
||||
github.com/go-playground/locales v0.14.0
|
||||
github.com/go-playground/universal-translator v0.18.0
|
||||
github.com/go-playground/validator/v10 v10.11.1
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/go-playground/locales v0.14.1
|
||||
github.com/go-playground/universal-translator v0.18.1
|
||||
github.com/go-playground/validator/v10 v10.14.0
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/goccy/go-json v0.9.11
|
||||
github.com/goccy/go-json v0.10.2
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/google/wire v0.5.0
|
||||
@@ -36,19 +36,18 @@ require (
|
||||
github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20221018072427-a15dd1434e05
|
||||
github.com/segmentfault/pacman/contrib/server/http v0.0.0-20221018072427-a15dd1434e05
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/swaggo/files v1.0.0
|
||||
github.com/swaggo/gin-swagger v1.5.3
|
||||
github.com/swaggo/swag v1.8.10
|
||||
github.com/swaggo/swag v1.16.1
|
||||
github.com/tidwall/gjson v1.14.4
|
||||
github.com/yuin/goldmark v1.4.13
|
||||
golang.org/x/crypto v0.1.0
|
||||
golang.org/x/net v0.7.0
|
||||
golang.org/x/crypto v0.11.0
|
||||
golang.org/x/net v0.12.0
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.14.2
|
||||
modernc.org/sqlite v1.24.0
|
||||
xorm.io/builder v0.3.12
|
||||
xorm.io/core v0.7.3
|
||||
xorm.io/xorm v1.3.2
|
||||
)
|
||||
|
||||
@@ -60,7 +59,9 @@ require (
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/containerd/continuity v0.3.0 // indirect
|
||||
github.com/docker/cli v20.10.14+incompatible // indirect
|
||||
github.com/docker/docker v20.10.7+incompatible // indirect
|
||||
@@ -70,18 +71,19 @@ require (
|
||||
github.com/dsoprea/go-jpeg-image-structure v0.0.0-20190422055009-d6f9ba25cf48 // indirect
|
||||
github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696 // indirect
|
||||
github.com/dsoprea/go-png-image-structure v0.0.0-20190624104353-c9b28dcdc5c8 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/spec v0.20.7 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/spec v0.20.9 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/geo v0.0.0-20190812012225-f41920e961ce // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/gosimple/unidecode v1.0.1 // indirect
|
||||
@@ -91,13 +93,13 @@ require (
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible // indirect
|
||||
github.com/lestrrat-go/strftime v1.0.6 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.16 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
@@ -107,10 +109,10 @@ require (
|
||||
github.com/opencontainers/runc v1.1.2 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/spf13/afero v1.9.2 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
@@ -121,32 +123,36 @@ require (
|
||||
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
go.uber.org/zap v1.23.0 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/image v0.1.0 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
golang.org/x/tools v0.11.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
lukechampine.com/uint128 v1.1.1 // indirect
|
||||
modernc.org/cc/v3 v3.35.18 // indirect
|
||||
modernc.org/ccgo/v3 v3.12.82 // indirect
|
||||
modernc.org/libc v1.11.87 // indirect
|
||||
modernc.org/mathutil v1.4.1 // indirect
|
||||
modernc.org/memory v1.0.5 // indirect
|
||||
modernc.org/opt v0.1.1 // indirect
|
||||
modernc.org/strutil v1.1.1 // indirect
|
||||
modernc.org/token v1.0.0 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/strutil v1.1.3 // indirect
|
||||
modernc.org/token v1.0.1 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace lukechampine.com/uint128 v1.1.1 => github.com/aichy126/uint128 v1.1.1
|
||||
|
||||
replace modernc.org/cc/v3 v3.40.0 => gitlab.com/cznic/cc/v3 v3.40.0
|
||||
|
||||
@@ -65,7 +65,6 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMx
|
||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
||||
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
|
||||
github.com/aichy126/uint128 v1.1.1 h1:xH1bCWDzq7Ebm4lpXCeIiWco0VWi7UmiKkvTQSWBmb0=
|
||||
github.com/aichy126/uint128 v1.1.1/go.mod h1:Hke/MPGXUxOl0OXHoNcVesBL4N+XalHEJ9e1jaIbl8o=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
@@ -96,6 +95,9 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
|
||||
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
|
||||
@@ -103,6 +105,9 @@ github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInq
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
@@ -155,8 +160,9 @@ github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2
|
||||
github.com/dsoprea/go-png-image-structure v0.0.0-20190624104353-c9b28dcdc5c8 h1:SVQfy5rBFZXzvGkU2MZ0RzpS912/1sJrEJ+FMmeaC9U=
|
||||
github.com/dsoprea/go-png-image-structure v0.0.0-20190624104353-c9b28dcdc5c8/go.mod h1:Bf0nmcDFFRQBjZwr9qY6c0zTxKQa+Q8YWZmlYxXGxY0=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
@@ -176,14 +182,17 @@ github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
|
||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
@@ -196,39 +205,44 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ=
|
||||
github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA=
|
||||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
|
||||
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
|
||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
||||
github.com/go-openapi/spec v0.20.7 h1:1Rlu/ZrOCCob0n+JKKJAWhNWMPW8bOZRg8FJaY+0SKI=
|
||||
github.com/go-openapi/spec v0.20.7/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
|
||||
github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8=
|
||||
github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
|
||||
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
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.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
||||
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
@@ -291,7 +305,6 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
@@ -306,6 +319,7 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
@@ -439,22 +453,26 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:C
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
|
||||
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
|
||||
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4=
|
||||
@@ -490,13 +508,11 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
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.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg=
|
||||
github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM=
|
||||
@@ -572,8 +588,8 @@ github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtP
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
@@ -604,8 +620,9 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
@@ -682,8 +699,11 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
|
||||
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
|
||||
@@ -692,8 +712,8 @@ github.com/swaggo/files v1.0.0/go.mod h1:N59U6URJLyU1PQgFqPM7wXLMhJx7QAolnvfQkqO
|
||||
github.com/swaggo/gin-swagger v1.5.3 h1:8mWmHLolIbrhJJTflsaFoZzRBYVmEE7JZGIq08EiC0Q=
|
||||
github.com/swaggo/gin-swagger v1.5.3/go.mod h1:3XJKSfHjDMB5dBo/0rrTXidPmgLeqsX89Yp4uA50HpI=
|
||||
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
|
||||
github.com/swaggo/swag v1.8.10 h1:eExW4bFa52WOjqRzRD58bgWsWfdFJso50lpbeTcmTfo=
|
||||
github.com/swaggo/swag v1.8.10/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
|
||||
github.com/swaggo/swag v1.16.1 h1:fTNRhKstPKxcnoKsytm4sahr8FaYzUcT7i1/3nd/fBg=
|
||||
github.com/swaggo/swag v1.16.1/go.mod h1:9/LMvHycG3NFHfR6LwvikHv5iFvmPADQ359cKikGxto=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
@@ -704,11 +724,14 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
@@ -731,6 +754,8 @@ github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
gitlab.com/cznic/cc/v3 v3.40.0 h1:3yn6qVCeHjTSJio35QIhNsD5POKm7ETHXS54yKqzDEo=
|
||||
gitlab.com/cznic/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
@@ -760,6 +785,9 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
|
||||
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -780,9 +808,8 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -822,8 +849,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -866,12 +893,12 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -893,7 +920,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -962,12 +989,14 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -983,8 +1012,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -1052,8 +1081,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
|
||||
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -1084,7 +1113,6 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
@@ -1160,8 +1188,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
@@ -1208,6 +1236,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
@@ -1221,7 +1251,6 @@ modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g
|
||||
modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.18 h1:rMZhRcWrba0y3nVmdiQ7kxAgOOSq2m2f2VzjHLgEs6U=
|
||||
modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
|
||||
modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw=
|
||||
@@ -1257,10 +1286,11 @@ modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/E
|
||||
modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84=
|
||||
modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ=
|
||||
modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
|
||||
modernc.org/ccgo/v3 v3.12.82 h1:wudcnJyjLj1aQQCXF3IM9Gz2X6UNjw+afIghzdtn0v8=
|
||||
modernc.org/ccgo/v3 v3.12.82/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
|
||||
modernc.org/ccorpus v1.11.1 h1:K0qPfpVG1MJh5BYazccnmhywH4zHuOgJXgbjzyp6dWA=
|
||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
||||
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
||||
@@ -1297,29 +1327,37 @@ modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
|
||||
modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0=
|
||||
modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
|
||||
modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
|
||||
modernc.org/libc v1.11.87 h1:PzIzOqtlzMDDcCzJ5cUP6h/Ku6Fa9iyflP2ccTY64aE=
|
||||
modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
|
||||
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
|
||||
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
|
||||
modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
|
||||
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
|
||||
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
|
||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.14.2 h1:ohsW2+e+Qe2To1W6GNezzKGwjXwSax6R+CrhRxVaFbE=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.14.2/go.mod h1:yqfn85u8wVOE6ub5UT8VI9JjhrwBUUCNyTACN0h6Sx8=
|
||||
modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
|
||||
modernc.org/sqlite v1.24.0 h1:EsClRIWHGhLTCX44p+Ri/JLD+vFGo0QGjasg2/F9TlI=
|
||||
modernc.org/sqlite v1.24.0/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
||||
modernc.org/tcl v1.8.13 h1:V0sTNBw0Re86PvXZxuCub3oO9WrSTqALgrwNZNvLFGw=
|
||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||
modernc.org/tcl v1.8.13/go.mod h1:V+q/Ef0IJaNUSECieLU4o+8IScapxnMyFV6i/7uQlAY=
|
||||
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
|
||||
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
|
||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.2.19 h1:BGyRFWhDVn5LFS5OcX4Yd/MlpRTOc7hOPTdcIpCiUao=
|
||||
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
||||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY=
|
||||
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
@@ -1329,7 +1367,5 @@ sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1
|
||||
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||
xorm.io/builder v0.3.12 h1:ASZYX7fQmy+o8UJdhlLHSW57JDOkM8DNhcAF5d0LiJM=
|
||||
xorm.io/builder v0.3.12/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||
xorm.io/core v0.7.3 h1:W8ws1PlrnkS1CZU1YWaYLMQcQilwAmQXU0BJDJon+H0=
|
||||
xorm.io/core v0.7.3/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
|
||||
xorm.io/xorm v1.3.2 h1:uTRRKF2jYzbZ5nsofXVUx6ncMaek+SHjWYtCXyZo1oM=
|
||||
xorm.io/xorm v1.3.2/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw=
|
||||
|
||||
+39
-31
@@ -108,8 +108,12 @@ backend:
|
||||
other: Manage tag synonyms
|
||||
email:
|
||||
other: Email
|
||||
e_mail:
|
||||
other: Email
|
||||
password:
|
||||
other: Password
|
||||
pass:
|
||||
other: Password
|
||||
email_or_password_wrong_error:
|
||||
other: Email and password do not match.
|
||||
error:
|
||||
@@ -504,6 +508,7 @@ ui:
|
||||
title: Notifications
|
||||
inbox: Inbox
|
||||
achievement: Achievements
|
||||
new_alerts: New alerts
|
||||
all_read: Mark all as read
|
||||
show_more: Show more
|
||||
someone: Someone
|
||||
@@ -729,7 +734,7 @@ ui:
|
||||
tip_answer: >-
|
||||
Use comments to reply to other users or notify them of changes. If you are
|
||||
adding new information, edit your post instead of commenting.
|
||||
tip_vote: It adds something useful to the post
|
||||
tip_vote: It is useful
|
||||
edit_answer:
|
||||
title: Edit Answer
|
||||
default_reason: Edit answer
|
||||
@@ -841,6 +846,9 @@ ui:
|
||||
change_btn_name: Change email
|
||||
msg:
|
||||
empty: Cannot be empty.
|
||||
resend_email:
|
||||
url_label: Are you sure you want to resend the activation email?
|
||||
url_text: You can also give the activation link above to the user.
|
||||
login:
|
||||
login_to_continue: Log in to continue
|
||||
info_sign: Don't have an account? <1>Sign up</1>
|
||||
@@ -940,7 +948,6 @@ ui:
|
||||
gravatar: Gravatar
|
||||
gravatar_text: You can change image on
|
||||
custom: Custom
|
||||
btn_refresh: Refresh
|
||||
custom_text: You can upload your image.
|
||||
default: System
|
||||
msg: Please upload an avatar
|
||||
@@ -1000,9 +1007,9 @@ ui:
|
||||
flag_success: Thanks for flagging.
|
||||
forbidden_operate_self: Forbidden to operate on yourself
|
||||
review: Your revision will show after review.
|
||||
sent_success: Sent successfully
|
||||
related_question:
|
||||
title: Related Questions
|
||||
btn: Add question
|
||||
answers: answers
|
||||
invite_to_answer:
|
||||
title: People asked
|
||||
@@ -1026,6 +1033,7 @@ ui:
|
||||
useful: Useful
|
||||
question_useful: It is useful and clear
|
||||
question_un_useful: It is unclear or not useful
|
||||
question_bookmark: Bookmark this question
|
||||
answer_useful: It is useful
|
||||
answer_un_useful: It is not useful
|
||||
answers:
|
||||
@@ -1093,6 +1101,8 @@ ui:
|
||||
question: Question
|
||||
answer: Answer
|
||||
comment: Comment
|
||||
refresh: Refresh
|
||||
resend: Resend
|
||||
search:
|
||||
title: Search Results
|
||||
keywords: Keywords
|
||||
@@ -1404,6 +1414,31 @@ ui:
|
||||
title: Change user role to...
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
new_password_modal:
|
||||
title: Set new password
|
||||
form:
|
||||
fields:
|
||||
password:
|
||||
label: Password
|
||||
text: The user will be logged out and need to login again.
|
||||
msg: Password must be at 8-32 characters in length.
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
user_modal:
|
||||
title: Add new user
|
||||
form:
|
||||
fields:
|
||||
display_name:
|
||||
label: Display Name
|
||||
msg: Display Name must be at 4-30 characters in length.
|
||||
email:
|
||||
label: Email
|
||||
msg: Email is not valid.
|
||||
password:
|
||||
label: Password
|
||||
msg: Password must be at 8-32 characters in length.
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
users:
|
||||
title: Users
|
||||
name: Name
|
||||
@@ -1433,33 +1468,6 @@ ui:
|
||||
change_role: Change role
|
||||
show_logs: Show logs
|
||||
add_user: Add user
|
||||
new_password_modal:
|
||||
title: Set new password
|
||||
form:
|
||||
fields:
|
||||
password:
|
||||
label: Password
|
||||
text: The user will be logged out and need to login again.
|
||||
msg: Password must be at 8-32 characters in length.
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
user_modal:
|
||||
title: Add new user
|
||||
form:
|
||||
fields:
|
||||
display_name:
|
||||
label: Display Name
|
||||
msg: Display Name must be at 4-30 characters in length.
|
||||
email:
|
||||
label: Email
|
||||
msg: Email is not valid.
|
||||
password:
|
||||
label: Password
|
||||
msg: Password must be at 8-32 characters in length.
|
||||
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
|
||||
questions:
|
||||
page_title: Questions
|
||||
normal: Normal
|
||||
@@ -1628,7 +1636,7 @@ ui:
|
||||
text: This will insert after <body>
|
||||
footer:
|
||||
label: Footer
|
||||
text: This will insert before </html>.
|
||||
text: This will insert before </body>.
|
||||
sidebar:
|
||||
label: Sidebar
|
||||
text: This will insert in sidebar.
|
||||
|
||||
@@ -107,8 +107,12 @@ backend:
|
||||
other: 管理标签同义词
|
||||
email:
|
||||
other: 邮箱
|
||||
e_mail:
|
||||
other: 邮箱
|
||||
password:
|
||||
other: 密码
|
||||
pass:
|
||||
other: 密码
|
||||
email_or_password_wrong_error:
|
||||
other: 邮箱和密码不匹配。
|
||||
error:
|
||||
|
||||
@@ -16,4 +16,7 @@ const (
|
||||
ConfigKEY2ContentCacheKeyPrefix = "answer:config:key:"
|
||||
ConnectorUserExternalInfoCacheKey = "answer:connector:"
|
||||
ConnectorUserExternalInfoCacheTime = 10 * time.Minute
|
||||
SiteMapQuestionCacheKeyPrefix = "answer:sitemap:question:%d"
|
||||
SiteMapQuestionCacheTime = time.Hour
|
||||
SitemapMaxSize = 50000
|
||||
)
|
||||
|
||||
@@ -2,4 +2,5 @@ package constant
|
||||
|
||||
const (
|
||||
AcceptLanguageFlag = "Accept-Language"
|
||||
ShortIDFlag = "Short-ID-Enabled"
|
||||
)
|
||||
@@ -7,3 +7,14 @@ const (
|
||||
AvatarTypeGravatar = "gravatar"
|
||||
AvatarTypeCustom = "custom"
|
||||
)
|
||||
|
||||
const (
|
||||
// PermaLinkQuestionIDAndTitle /questions/10010000000000001/post-title
|
||||
PermaLinkQuestionIDAndTitle = iota + 1
|
||||
// PermaLinkQuestionID /questions/10010000000000001
|
||||
PermaLinkQuestionID
|
||||
// PermaLinkQuestionIDAndTitleByShortID /questions/11/post-title
|
||||
PermaLinkQuestionIDAndTitleByShortID
|
||||
// PermaLinkQuestionIDByShortID /questions/11
|
||||
PermaLinkQuestionIDByShortID
|
||||
)
|
||||
|
||||
@@ -12,13 +12,13 @@ import (
|
||||
|
||||
// ScheduledTaskManager scheduled task manager
|
||||
type ScheduledTaskManager struct {
|
||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||
questionService *service.QuestionService
|
||||
}
|
||||
|
||||
// NewScheduledTaskManager new scheduled task manager
|
||||
func NewScheduledTaskManager(
|
||||
siteInfoService *siteinfo_common.SiteInfoCommonService,
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||
questionService *service.QuestionService,
|
||||
) *ScheduledTaskManager {
|
||||
manager := &ScheduledTaskManager{
|
||||
|
||||
@@ -12,9 +12,9 @@ import (
|
||||
"github.com/segmentfault/pacman/contrib/cache/memory"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
_ "modernc.org/sqlite"
|
||||
"xorm.io/core"
|
||||
"xorm.io/xorm"
|
||||
ormlog "xorm.io/xorm/log"
|
||||
"xorm.io/xorm/names"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
@@ -71,7 +71,7 @@ func NewDB(debug bool, dataConf *Database) (*xorm.Engine, error) {
|
||||
if dataConf.ConnMaxLifeTime > 0 {
|
||||
engine.SetConnMaxLifetime(time.Duration(dataConf.ConnMaxLifeTime) * time.Second)
|
||||
}
|
||||
engine.SetColumnMapper(core.GonicMapper{})
|
||||
engine.SetColumnMapper(names.GonicMapper{})
|
||||
return engine, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/constant"
|
||||
)
|
||||
|
||||
// GetEnableShortID get language from header
|
||||
func GetEnableShortID(ctx context.Context) bool {
|
||||
flag, ok := ctx.Value(constant.ShortIDFlag).(bool)
|
||||
if ok {
|
||||
return flag
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,19 +1,22 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/role"
|
||||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||
"github.com/answerdev/answer/ui"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/service/auth"
|
||||
"github.com/answerdev/answer/pkg/converter"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
var ctxUUIDKey = "ctxUuidKey"
|
||||
@@ -21,13 +24,13 @@ var ctxUUIDKey = "ctxUuidKey"
|
||||
// AuthUserMiddleware auth user middleware
|
||||
type AuthUserMiddleware struct {
|
||||
authService *auth.AuthService
|
||||
siteInfoCommonService *siteinfo_common.SiteInfoCommonService
|
||||
siteInfoCommonService siteinfo_common.SiteInfoCommonService
|
||||
}
|
||||
|
||||
// NewAuthUserMiddleware new auth user middleware
|
||||
func NewAuthUserMiddleware(
|
||||
authService *auth.AuthService,
|
||||
siteInfoCommonService *siteinfo_common.SiteInfoCommonService) *AuthUserMiddleware {
|
||||
siteInfoCommonService siteinfo_common.SiteInfoCommonService) *AuthUserMiddleware {
|
||||
return &AuthUserMiddleware{
|
||||
authService: authService,
|
||||
siteInfoCommonService: siteInfoCommonService,
|
||||
@@ -140,6 +143,34 @@ func (am *AuthUserMiddleware) AdminAuth() gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (am *AuthUserMiddleware) CheckPrivateMode() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
resp, err := am.siteInfoCommonService.GetSiteLogin(ctx)
|
||||
if err != nil {
|
||||
ShowIndexPage(ctx)
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
if resp.LoginRequired {
|
||||
ShowIndexPage(ctx)
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
func ShowIndexPage(ctx *gin.Context) {
|
||||
ctx.Header("content-type", "text/html;charset=utf-8")
|
||||
ctx.Header("X-Frame-Options", "DENY")
|
||||
file, err := ui.Build.ReadFile("build/index.html")
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
ctx.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
ctx.String(http.StatusOK, string(file))
|
||||
}
|
||||
|
||||
// GetLoginUserIDFromContext get user id from context
|
||||
func GetLoginUserIDFromContext(ctx *gin.Context) (userID string) {
|
||||
userInfo := GetUserInfoFromContext(ctx)
|
||||
@@ -171,6 +202,21 @@ func GetUserInfoFromContext(ctx *gin.Context) (u *entity.UserCacheInfo) {
|
||||
return u
|
||||
}
|
||||
|
||||
func GetUserIsAdminModerator(ctx *gin.Context) (isAdminModerator bool) {
|
||||
userInfo, exist := ctx.Get(ctxUUIDKey)
|
||||
if !exist {
|
||||
return false
|
||||
}
|
||||
u, ok := userInfo.(*entity.UserCacheInfo)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if u.RoleID == role.RoleAdminID || u.RoleID == role.RoleModeratorID {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetLoginUserIDInt64FromContext(ctx *gin.Context) (userID int64) {
|
||||
userIDStr := GetLoginUserIDFromContext(ctx)
|
||||
return converter.StringToInt64(userIDStr)
|
||||
|
||||
@@ -32,31 +32,28 @@ func NewAvatarMiddleware(serviceConfig *service_config.ServiceConfig,
|
||||
|
||||
func (am *AvatarMiddleware) AvatarThumb() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
u := ctx.Request.RequestURI
|
||||
if strings.Contains(u, "/uploads/avatar/") {
|
||||
sizeStr := ctx.Query("s")
|
||||
size := converter.StringToInt(sizeStr)
|
||||
uUrl, err := url.Parse(u)
|
||||
uri := ctx.Request.RequestURI
|
||||
if strings.Contains(uri, "/uploads/avatar/") {
|
||||
size := converter.StringToInt(ctx.Query("s"))
|
||||
uriWithoutQuery, _ := url.Parse(uri)
|
||||
filename := filepath.Base(uriWithoutQuery.Path)
|
||||
filePath := fmt.Sprintf("%s/avatar/%s", am.serviceConfig.UploadPath, filename)
|
||||
var err error
|
||||
if size != 0 {
|
||||
filePath, err = am.uploaderService.AvatarThumbFile(ctx, filename, size)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
ctx.Abort()
|
||||
}
|
||||
}
|
||||
avatarFile, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
ctx.Next()
|
||||
log.Error(err)
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
_, urlfileName := filepath.Split(uUrl.Path)
|
||||
uploadPath := am.serviceConfig.UploadPath
|
||||
filePath := fmt.Sprintf("%s/avatar/%s", uploadPath, urlfileName)
|
||||
var avatarfile []byte
|
||||
if size == 0 {
|
||||
avatarfile, err = os.ReadFile(filePath)
|
||||
} else {
|
||||
avatarfile, err = am.uploaderService.AvatarThumbFile(ctx, uploadPath, urlfileName, size)
|
||||
}
|
||||
if err != nil {
|
||||
ctx.Next()
|
||||
return
|
||||
}
|
||||
ext := strings.ToLower(path.Ext(filePath)[1:])
|
||||
ctx.Header("content-type", fmt.Sprintf("image/%s", ext))
|
||||
_, err = ctx.Writer.WriteString(string(avatarfile))
|
||||
ctx.Header("content-type", fmt.Sprintf("image/%s", strings.TrimLeft(path.Ext(filePath), ".")))
|
||||
_, err = ctx.Writer.Write(avatarFile)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
@@ -64,7 +61,7 @@ func (am *AvatarMiddleware) AvatarThumb() gin.HandlerFunc {
|
||||
return
|
||||
|
||||
} else {
|
||||
uUrl, err := url.Parse(u)
|
||||
uUrl, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
ctx.Next()
|
||||
return
|
||||
|
||||
@@ -8,4 +8,5 @@ import (
|
||||
var ProviderSetMiddleware = wire.NewSet(
|
||||
NewAuthUserMiddleware,
|
||||
NewAvatarMiddleware,
|
||||
NewShortIDMiddleware,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/answerdev/answer/internal/base/constant"
|
||||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
type ShortIDMiddleware struct {
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||
}
|
||||
|
||||
func NewShortIDMiddleware(siteInfoService siteinfo_common.SiteInfoCommonService) *ShortIDMiddleware {
|
||||
return &ShortIDMiddleware{
|
||||
siteInfoService: siteInfoService,
|
||||
}
|
||||
}
|
||||
|
||||
func (sm *ShortIDMiddleware) SetShortIDFlag() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
siteSeo, err := sm.siteInfoService.GetSiteSeo(ctx)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
ctx.Set(constant.ShortIDFlag, siteSeo.IsShortLink())
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ func NewHTTPServer(debug bool,
|
||||
viewRouter *router.UIRouter,
|
||||
authUserMiddleware *middleware.AuthUserMiddleware,
|
||||
avatarMiddleware *middleware.AvatarMiddleware,
|
||||
shortIDMiddleware *middleware.ShortIDMiddleware,
|
||||
templateRouter *router.TemplateRouter,
|
||||
pluginAPIRouter *router.PluginAPIRouter,
|
||||
) *gin.Engine {
|
||||
@@ -30,7 +31,7 @@ func NewHTTPServer(debug bool,
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
r := gin.New()
|
||||
r.Use(brotli.Brotli(brotli.DefaultCompression), middleware.ExtractAndSetAcceptLanguage)
|
||||
r.Use(brotli.Brotli(brotli.DefaultCompression), middleware.ExtractAndSetAcceptLanguage, shortIDMiddleware.SetShortIDFlag())
|
||||
r.GET("/healthz", func(ctx *gin.Context) { ctx.String(200, "OK") })
|
||||
|
||||
html, _ := fs.Sub(ui.Template, "template")
|
||||
|
||||
+15
-2
@@ -207,7 +207,8 @@ func copyUIFiles(b *buildingMaterial) (err error) {
|
||||
|
||||
goModUIDir := filepath.Join(strings.TrimSpace(buf.String()), "ui")
|
||||
localUIBuildDir := filepath.Join(b.tmpDir, "vendor/github.com/answerdev/answer/ui/")
|
||||
if err = copyDirEntries(os.DirFS(goModUIDir), ".", localUIBuildDir); err != nil {
|
||||
// The node_modules folder generated during development will interfere packaging, so it needs to be ignored.
|
||||
if err = copyDirEntries(os.DirFS(goModUIDir), ".", localUIBuildDir, "node_modules"); err != nil {
|
||||
return fmt.Errorf("failed to copy ui files: %w", err)
|
||||
}
|
||||
return nil
|
||||
@@ -366,15 +367,27 @@ func mergeI18nFiles(b *buildingMaterial) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
func copyDirEntries(sourceFs fs.FS, sourceDir string, targetDir string) (err error) {
|
||||
func copyDirEntries(sourceFs fs.FS, sourceDir, targetDir string, ignoreDir ...string) (err error) {
|
||||
err = dir.CreateDirIfNotExist(targetDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ignoreThisDir := func(path string) bool {
|
||||
for _, s := range ignoreDir {
|
||||
if strings.HasPrefix(path, s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
err = fs.WalkDir(sourceFs, sourceDir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ignoreThisDir(path) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert the path to use forward slashes, important because we use embedded FS which always uses forward slashes
|
||||
path = filepath.ToSlash(path)
|
||||
|
||||
@@ -5,22 +5,19 @@ import (
|
||||
"github.com/answerdev/answer/internal/base/middleware"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/activity"
|
||||
"github.com/answerdev/answer/internal/service/activity_common"
|
||||
"github.com/answerdev/answer/internal/service/role"
|
||||
"github.com/answerdev/answer/pkg/uid"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ActivityController struct {
|
||||
activityCommonService *activity_common.ActivityCommon
|
||||
activityService *activity.ActivityService
|
||||
activityService *activity.ActivityService
|
||||
}
|
||||
|
||||
// NewActivityController new activity controller.
|
||||
func NewActivityController(
|
||||
activityCommonService *activity_common.ActivityCommon,
|
||||
activityService *activity.ActivityService) *ActivityController {
|
||||
return &ActivityController{activityCommonService: activityCommonService, activityService: activityService}
|
||||
return &ActivityController{activityService: activityService}
|
||||
}
|
||||
|
||||
// GetObjectTimeline get object timeline
|
||||
|
||||
@@ -6,9 +6,12 @@ import (
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
"github.com/answerdev/answer/internal/base/middleware"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/base/translator"
|
||||
"github.com/answerdev/answer/internal/base/validator"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service"
|
||||
"github.com/answerdev/answer/internal/service/dashboard"
|
||||
"github.com/answerdev/answer/internal/service/action"
|
||||
"github.com/answerdev/answer/internal/service/permission"
|
||||
"github.com/answerdev/answer/internal/service/rank"
|
||||
"github.com/answerdev/answer/pkg/uid"
|
||||
@@ -18,20 +21,21 @@ import (
|
||||
|
||||
// AnswerController answer controller
|
||||
type AnswerController struct {
|
||||
answerService *service.AnswerService
|
||||
rankService *rank.RankService
|
||||
dashboardService *dashboard.DashboardService
|
||||
answerService *service.AnswerService
|
||||
rankService *rank.RankService
|
||||
actionService *action.CaptchaService
|
||||
}
|
||||
|
||||
// NewAnswerController new controller
|
||||
func NewAnswerController(answerService *service.AnswerService,
|
||||
func NewAnswerController(
|
||||
answerService *service.AnswerService,
|
||||
rankService *rank.RankService,
|
||||
dashboardService *dashboard.DashboardService,
|
||||
actionService *action.CaptchaService,
|
||||
) *AnswerController {
|
||||
return &AnswerController{
|
||||
answerService: answerService,
|
||||
rankService: rankService,
|
||||
dashboardService: dashboardService,
|
||||
answerService: answerService,
|
||||
rankService: rankService,
|
||||
actionService: actionService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +56,19 @@ func (ac *AnswerController) RemoveAnswer(ctx *gin.Context) {
|
||||
}
|
||||
req.ID = uid.DeShortID(req.ID)
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin {
|
||||
captchaPass := ac.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionDelete, req.UserID, req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
objectOwner := ac.rankService.CheckOperationObjectOwner(ctx, req.UserID, req.ID)
|
||||
canList, err := ac.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
|
||||
permission.AnswerDelete,
|
||||
@@ -67,6 +84,9 @@ func (ac *AnswerController) RemoveAnswer(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
err = ac.answerService.RemoveAnswer(ctx, req)
|
||||
if !isAdmin {
|
||||
ac.actionService.ActionRecordAdd(ctx, entity.CaptchaActionDelete, req.UserID)
|
||||
}
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
@@ -117,6 +137,30 @@ func (ac *AnswerController) Add(ctx *gin.Context) {
|
||||
req.QuestionID = uid.DeShortID(req.QuestionID)
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
|
||||
canList, err := ac.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
|
||||
permission.AnswerEdit,
|
||||
permission.AnswerDelete,
|
||||
permission.LinkUrlLimit,
|
||||
})
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
|
||||
linkUrlLimitUser := canList[2]
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin || !linkUrlLimitUser {
|
||||
captchaPass := ac.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionAnswer, req.UserID, req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
can, err := ac.rankService.CheckOperationPermission(ctx, req.UserID, permission.AnswerAdd, "")
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
@@ -132,6 +176,9 @@ func (ac *AnswerController) Add(ctx *gin.Context) {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
if !isAdmin || !linkUrlLimitUser {
|
||||
ac.actionService.ActionRecordAdd(ctx, entity.CaptchaActionAnswer, req.UserID)
|
||||
}
|
||||
info, questionInfo, has, err := ac.answerService.Get(ctx, answerID, req.UserID)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
@@ -142,15 +189,6 @@ func (ac *AnswerController) Add(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
canList, err := ac.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
|
||||
permission.AnswerEdit,
|
||||
permission.AnswerDelete,
|
||||
})
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
|
||||
objectOwner := ac.rankService.CheckOperationObjectOwner(ctx, req.UserID, info.ID)
|
||||
req.CanEdit = canList[0] || objectOwner
|
||||
req.CanDelete = canList[1] || objectOwner
|
||||
@@ -181,16 +219,30 @@ func (ac *AnswerController) Update(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
req.QuestionID = uid.DeShortID(req.QuestionID)
|
||||
|
||||
canList, err := ac.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
|
||||
permission.AnswerEdit,
|
||||
permission.AnswerEditWithoutReview,
|
||||
permission.LinkUrlLimit,
|
||||
})
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
req.QuestionID = uid.DeShortID(req.QuestionID)
|
||||
linkUrlLimitUser := canList[2]
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin || !linkUrlLimitUser {
|
||||
captchaPass := ac.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionEdit, req.UserID, req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
objectOwner := ac.rankService.CheckOperationObjectOwner(ctx, req.UserID, req.ID)
|
||||
req.CanEdit = canList[0] || objectOwner
|
||||
@@ -205,6 +257,9 @@ func (ac *AnswerController) Update(ctx *gin.Context) {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
if !isAdmin || !linkUrlLimitUser {
|
||||
ac.actionService.ActionRecordAdd(ctx, entity.CaptchaActionEdit, req.UserID)
|
||||
}
|
||||
_, _, _, err = ac.answerService.Get(ctx, req.ID, req.UserID)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
|
||||
@@ -4,7 +4,11 @@ import (
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
"github.com/answerdev/answer/internal/base/middleware"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/base/translator"
|
||||
"github.com/answerdev/answer/internal/base/validator"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/action"
|
||||
"github.com/answerdev/answer/internal/service/comment"
|
||||
"github.com/answerdev/answer/internal/service/permission"
|
||||
"github.com/answerdev/answer/internal/service/rank"
|
||||
@@ -17,13 +21,20 @@ import (
|
||||
type CommentController struct {
|
||||
commentService *comment.CommentService
|
||||
rankService *rank.RankService
|
||||
actionService *action.CaptchaService
|
||||
}
|
||||
|
||||
// NewCommentController new controller
|
||||
func NewCommentController(
|
||||
commentService *comment.CommentService,
|
||||
rankService *rank.RankService) *CommentController {
|
||||
return &CommentController{commentService: commentService, rankService: rankService}
|
||||
rankService *rank.RankService,
|
||||
actionService *action.CaptchaService,
|
||||
) *CommentController {
|
||||
return &CommentController{
|
||||
commentService: commentService,
|
||||
rankService: rankService,
|
||||
actionService: actionService,
|
||||
}
|
||||
}
|
||||
|
||||
// AddComment add comment
|
||||
@@ -43,15 +54,31 @@ func (cc *CommentController) AddComment(ctx *gin.Context) {
|
||||
}
|
||||
req.ObjectID = uid.DeShortID(req.ObjectID)
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
|
||||
canList, err := cc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
|
||||
permission.CommentAdd,
|
||||
permission.CommentEdit,
|
||||
permission.CommentDelete,
|
||||
permission.LinkUrlLimit,
|
||||
})
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
linkUrlLimitUser := canList[3]
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin || !linkUrlLimitUser {
|
||||
captchaPass := cc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionComment, req.UserID, req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
req.CanAdd = canList[0]
|
||||
req.CanEdit = canList[1]
|
||||
req.CanDelete = canList[2]
|
||||
@@ -61,6 +88,9 @@ func (cc *CommentController) AddComment(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
resp, err := cc.commentService.AddComment(ctx, req)
|
||||
if !isAdmin || !linkUrlLimitUser {
|
||||
cc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionComment, req.UserID)
|
||||
}
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
@@ -81,6 +111,18 @@ func (cc *CommentController) RemoveComment(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin {
|
||||
captchaPass := cc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionDelete, req.UserID, req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
}
|
||||
can, err := cc.rankService.CheckOperationPermission(ctx, req.UserID, permission.CommentDelete, req.CommentID)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
@@ -92,6 +134,9 @@ func (cc *CommentController) RemoveComment(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
err = cc.commentService.RemoveComment(ctx, req)
|
||||
if !isAdmin {
|
||||
cc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionDelete, req.UserID)
|
||||
}
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
@@ -112,16 +157,31 @@ func (cc *CommentController) UpdateComment(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
req.IsAdmin = middleware.GetIsAdminFromContext(ctx)
|
||||
canList, err := cc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
|
||||
permission.CommentAdd,
|
||||
permission.CommentEdit,
|
||||
permission.CommentDelete,
|
||||
permission.LinkUrlLimit,
|
||||
})
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
linkUrlLimitUser := canList[3]
|
||||
req.IsAdmin = middleware.GetIsAdminFromContext(ctx)
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin || !linkUrlLimitUser {
|
||||
captchaPass := cc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionEdit, req.UserID, req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
req.CanAdd = canList[0]
|
||||
req.CanEdit = canList[1]
|
||||
req.CanDelete = canList[2]
|
||||
@@ -136,6 +196,9 @@ func (cc *CommentController) UpdateComment(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
resp, err := cc.commentService.UpdateComment(ctx, req)
|
||||
if !isAdmin || !linkUrlLimitUser {
|
||||
cc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionEdit, req.UserID)
|
||||
}
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,14 +23,14 @@ const (
|
||||
|
||||
// ConnectorController comment controller
|
||||
type ConnectorController struct {
|
||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||
userExternalService *user_external_login.UserExternalLoginService
|
||||
emailService *export.EmailService
|
||||
}
|
||||
|
||||
// NewConnectorController new controller
|
||||
func NewConnectorController(
|
||||
siteInfoService *siteinfo_common.SiteInfoCommonService,
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||
emailService *export.EmailService,
|
||||
userExternalService *user_external_login.UserExternalLoginService,
|
||||
) *ConnectorController {
|
||||
@@ -93,7 +93,6 @@ func (cc *ConnectorController) ConnectorLogin(connector plugin.Connector) (fn fu
|
||||
if len(redirectURL) > 0 {
|
||||
ctx.Redirect(http.StatusFound, redirectURL)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ var ProviderSetController = wire.NewSet(
|
||||
NewRankController,
|
||||
NewReasonController,
|
||||
NewNotificationController,
|
||||
NewSiteinfoController,
|
||||
NewSiteInfoController,
|
||||
NewDashboardController,
|
||||
NewUploadController,
|
||||
NewActivityController,
|
||||
|
||||
@@ -7,12 +7,12 @@ import (
|
||||
)
|
||||
|
||||
type DashboardController struct {
|
||||
dashboardService *dashboard.DashboardService
|
||||
dashboardService dashboard.DashboardService
|
||||
}
|
||||
|
||||
// NewDashboardController new controller
|
||||
func NewDashboardController(
|
||||
dashboardService *dashboard.DashboardService,
|
||||
dashboardService dashboard.DashboardService,
|
||||
) *DashboardController {
|
||||
return &DashboardController{
|
||||
dashboardService: dashboardService,
|
||||
@@ -29,7 +29,7 @@ func NewDashboardController(
|
||||
// @Router /answer/admin/api/dashboard [get]
|
||||
// @Success 200 {object} handler.RespBody
|
||||
func (ac *DashboardController) DashboardInfo(ctx *gin.Context) {
|
||||
info, err := ac.dashboardService.StatisticalByCache(ctx)
|
||||
info, err := ac.dashboardService.Statistical(ctx)
|
||||
handler.HandleResponse(ctx, err, gin.H{
|
||||
"info": info,
|
||||
})
|
||||
|
||||
@@ -12,11 +12,11 @@ import (
|
||||
|
||||
type LangController struct {
|
||||
translator i18n.Translator
|
||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||
}
|
||||
|
||||
// NewLangController new language controller.
|
||||
func NewLangController(tr i18n.Translator, siteInfoService *siteinfo_common.SiteInfoCommonService) *LangController {
|
||||
func NewLangController(tr i18n.Translator, siteInfoService siteinfo_common.SiteInfoCommonService) *LangController {
|
||||
return &LangController{translator: tr, siteInfoService: siteInfoService}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,13 +22,13 @@ const (
|
||||
// UserCenterController comment controller
|
||||
type UserCenterController struct {
|
||||
userCenterLoginService *user_external_login.UserCenterLoginService
|
||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||
}
|
||||
|
||||
// NewUserCenterController new controller
|
||||
func NewUserCenterController(
|
||||
userCenterLoginService *user_external_login.UserCenterLoginService,
|
||||
siteInfoService *siteinfo_common.SiteInfoCommonService,
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||
) *UserCenterController {
|
||||
return &UserCenterController{
|
||||
userCenterLoginService: userCenterLoginService,
|
||||
|
||||
@@ -10,8 +10,10 @@ import (
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service"
|
||||
"github.com/answerdev/answer/internal/service/action"
|
||||
"github.com/answerdev/answer/internal/service/permission"
|
||||
"github.com/answerdev/answer/internal/service/rank"
|
||||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||
"github.com/answerdev/answer/pkg/uid"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jinzhu/copier"
|
||||
@@ -23,6 +25,8 @@ type QuestionController struct {
|
||||
questionService *service.QuestionService
|
||||
answerService *service.AnswerService
|
||||
rankService *rank.RankService
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||
actionService *action.CaptchaService
|
||||
}
|
||||
|
||||
// NewQuestionController new controller
|
||||
@@ -30,11 +34,15 @@ func NewQuestionController(
|
||||
questionService *service.QuestionService,
|
||||
answerService *service.AnswerService,
|
||||
rankService *rank.RankService,
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||
actionService *action.CaptchaService,
|
||||
) *QuestionController {
|
||||
return &QuestionController{
|
||||
questionService: questionService,
|
||||
answerService: answerService,
|
||||
rankService: rankService,
|
||||
siteInfoService: siteInfoService,
|
||||
actionService: actionService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +64,19 @@ func (qc *QuestionController) RemoveQuestion(ctx *gin.Context) {
|
||||
req.ID = uid.DeShortID(req.ID)
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
req.IsAdmin = middleware.GetIsAdminFromContext(ctx)
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin {
|
||||
captchaPass := qc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionDelete, req.UserID, req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
can, err := qc.rankService.CheckOperationPermission(ctx, req.UserID, permission.QuestionDelete, req.ID)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
@@ -65,8 +86,10 @@ func (qc *QuestionController) RemoveQuestion(ctx *gin.Context) {
|
||||
handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil)
|
||||
return
|
||||
}
|
||||
|
||||
err = qc.questionService.RemoveQuestion(ctx, req)
|
||||
if !isAdmin {
|
||||
qc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionDelete, req.UserID)
|
||||
}
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
@@ -220,7 +243,9 @@ func (qc *QuestionController) GetQuestion(ctx *gin.Context) {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
info.ID = uid.EnShortID(info.ID)
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
info.ID = uid.EnShortID(info.ID)
|
||||
}
|
||||
handler.HandleResponse(ctx, nil, info)
|
||||
}
|
||||
|
||||
@@ -305,8 +330,8 @@ func (qc *QuestionController) AddQuestion(ctx *gin.Context) {
|
||||
if ctx.IsAborted() {
|
||||
return
|
||||
}
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
canList, requireRanks, err := qc.rankService.CheckOperationPermissionsForRanks(ctx, req.UserID, []string{
|
||||
permission.QuestionAdd,
|
||||
permission.QuestionEdit,
|
||||
@@ -315,11 +340,26 @@ func (qc *QuestionController) AddQuestion(ctx *gin.Context) {
|
||||
permission.QuestionReopen,
|
||||
permission.TagUseReservedTag,
|
||||
permission.TagAdd,
|
||||
permission.LinkUrlLimit,
|
||||
})
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
linkUrlLimitUser := canList[7]
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin || !linkUrlLimitUser {
|
||||
captchaPass := qc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionQuestion, req.UserID, req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
req.CanAdd = canList[0]
|
||||
req.CanEdit = canList[1]
|
||||
req.CanDelete = canList[2]
|
||||
@@ -370,7 +410,9 @@ func (qc *QuestionController) AddQuestion(ctx *gin.Context) {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), errFields)
|
||||
return
|
||||
}
|
||||
|
||||
if !isAdmin || !linkUrlLimitUser {
|
||||
qc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionQuestion, req.UserID)
|
||||
}
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
@@ -399,11 +441,26 @@ func (qc *QuestionController) AddQuestionByAnswer(ctx *gin.Context) {
|
||||
permission.QuestionClose,
|
||||
permission.QuestionReopen,
|
||||
permission.TagUseReservedTag,
|
||||
permission.LinkUrlLimit,
|
||||
})
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
|
||||
linkUrlLimitUser := canList[6]
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin || !linkUrlLimitUser {
|
||||
captchaPass := qc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionQuestion, req.UserID, req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
}
|
||||
req.CanAdd = canList[0]
|
||||
req.CanEdit = canList[1]
|
||||
req.CanDelete = canList[2]
|
||||
@@ -441,6 +498,10 @@ func (qc *QuestionController) AddQuestionByAnswer(ctx *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if !isAdmin || !linkUrlLimitUser {
|
||||
qc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionQuestion, req.UserID)
|
||||
}
|
||||
|
||||
if len(errFields) > 0 {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), errFields)
|
||||
return
|
||||
@@ -495,18 +556,31 @@ func (qc *QuestionController) UpdateQuestion(ctx *gin.Context) {
|
||||
}
|
||||
req.ID = uid.DeShortID(req.ID)
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
|
||||
canList, requireRanks, err := qc.rankService.CheckOperationPermissionsForRanks(ctx, req.UserID, []string{
|
||||
permission.QuestionEdit,
|
||||
permission.QuestionDelete,
|
||||
permission.QuestionEditWithoutReview,
|
||||
permission.TagUseReservedTag,
|
||||
permission.TagAdd,
|
||||
permission.LinkUrlLimit,
|
||||
})
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
linkUrlLimitUser := canList[5]
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin || !linkUrlLimitUser {
|
||||
captchaPass := qc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionEdit, req.UserID, req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
objectOwner := qc.rankService.CheckOperationObjectOwner(ctx, req.UserID, req.ID)
|
||||
req.CanEdit = canList[0] || objectOwner
|
||||
@@ -547,6 +621,9 @@ func (qc *QuestionController) UpdateQuestion(ctx *gin.Context) {
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
return
|
||||
}
|
||||
if !isAdmin || !linkUrlLimitUser {
|
||||
qc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionEdit, req.UserID)
|
||||
}
|
||||
handler.HandleResponse(ctx, nil, &schema.UpdateQuestionResp{WaitForReview: !req.NoNeedReview})
|
||||
}
|
||||
|
||||
@@ -566,8 +643,24 @@ func (qc *QuestionController) UpdateQuestionInviteUser(ctx *gin.Context) {
|
||||
if ctx.IsAborted() {
|
||||
return
|
||||
}
|
||||
if len(errFields) > 0 {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), errFields)
|
||||
return
|
||||
}
|
||||
req.ID = uid.DeShortID(req.ID)
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin {
|
||||
captchaPass := qc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionInvitationAnswer, req.UserID, req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
canList, err := qc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
|
||||
permission.AnswerInviteSomeoneToAnswer,
|
||||
@@ -577,21 +670,19 @@ func (qc *QuestionController) UpdateQuestionInviteUser(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
objectOwner := qc.rankService.CheckOperationObjectOwner(ctx, req.UserID, req.ID)
|
||||
req.CanEdit = canList[0] || objectOwner
|
||||
if !req.CanEdit {
|
||||
req.CanInviteOtherToAnswer = canList[0]
|
||||
if !req.CanInviteOtherToAnswer {
|
||||
handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil)
|
||||
return
|
||||
}
|
||||
if len(errFields) > 0 {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), errFields)
|
||||
return
|
||||
}
|
||||
err = qc.questionService.UpdateQuestionInviteUser(ctx, req)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
if !isAdmin {
|
||||
qc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionInvitationAnswer, req.UserID)
|
||||
}
|
||||
handler.HandleResponse(ctx, nil, nil)
|
||||
}
|
||||
|
||||
@@ -703,8 +794,8 @@ func (qc *QuestionController) PersonalCollectionPage(ctx *gin.Context) {
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
// AdminSearchList godoc
|
||||
// @Summary AdminSearchList
|
||||
// AdminQuestionPage admin question page
|
||||
// @Summary AdminQuestionPage admin question page
|
||||
// @Description Status:[available,closed,deleted]
|
||||
// @Tags admin
|
||||
// @Accept json
|
||||
@@ -716,21 +807,19 @@ func (qc *QuestionController) PersonalCollectionPage(ctx *gin.Context) {
|
||||
// @Param query query string false "question id or title"
|
||||
// @Success 200 {object} handler.RespBody
|
||||
// @Router /answer/admin/api/question/page [get]
|
||||
func (qc *QuestionController) AdminSearchList(ctx *gin.Context) {
|
||||
req := &schema.AdminQuestionSearch{}
|
||||
func (qc *QuestionController) AdminQuestionPage(ctx *gin.Context) {
|
||||
req := &schema.AdminQuestionPageReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
userID := middleware.GetLoginUserIDFromContext(ctx)
|
||||
questionList, count, err := qc.questionService.AdminSearchList(ctx, req, userID)
|
||||
handler.HandleResponse(ctx, err, gin.H{
|
||||
"list": questionList,
|
||||
"count": count,
|
||||
})
|
||||
|
||||
req.LoginUserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
resp, err := qc.questionService.AdminQuestionPage(ctx, req)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
// AdminSearchAnswerList godoc
|
||||
// @Summary AdminSearchAnswerList
|
||||
// AdminAnswerPage admin answer page
|
||||
// @Summary AdminAnswerPage admin answer page
|
||||
// @Description Status:[available,deleted]
|
||||
// @Tags admin
|
||||
// @Accept json
|
||||
@@ -743,21 +832,15 @@ func (qc *QuestionController) AdminSearchList(ctx *gin.Context) {
|
||||
// @Param question_id query string false "question id"
|
||||
// @Success 200 {object} handler.RespBody
|
||||
// @Router /answer/admin/api/answer/page [get]
|
||||
func (qc *QuestionController) AdminSearchAnswerList(ctx *gin.Context) {
|
||||
req := &entity.AdminAnswerSearch{}
|
||||
func (qc *QuestionController) AdminAnswerPage(ctx *gin.Context) {
|
||||
req := &schema.AdminAnswerPageReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
req.QuestionID = uid.DeShortID(req.QuestionID)
|
||||
if req.QuestionID == "0" {
|
||||
req.QuestionID = ""
|
||||
}
|
||||
userID := middleware.GetLoginUserIDFromContext(ctx)
|
||||
questionList, count, err := qc.questionService.AdminSearchAnswerList(ctx, req, userID)
|
||||
handler.HandleResponse(ctx, err, gin.H{
|
||||
"list": questionList,
|
||||
"count": count,
|
||||
})
|
||||
|
||||
req.LoginUserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
resp, err := qc.questionService.AdminAnswerPage(ctx, req)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
// AdminSetQuestionStatus godoc
|
||||
|
||||
@@ -4,7 +4,11 @@ import (
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
"github.com/answerdev/answer/internal/base/middleware"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/base/translator"
|
||||
"github.com/answerdev/answer/internal/base/validator"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/action"
|
||||
"github.com/answerdev/answer/internal/service/permission"
|
||||
"github.com/answerdev/answer/internal/service/rank"
|
||||
"github.com/answerdev/answer/internal/service/report"
|
||||
@@ -17,11 +21,20 @@ import (
|
||||
type ReportController struct {
|
||||
reportService *report.ReportService
|
||||
rankService *rank.RankService
|
||||
actionService *action.CaptchaService
|
||||
}
|
||||
|
||||
// NewReportController new controller
|
||||
func NewReportController(reportService *report.ReportService, rankService *rank.RankService) *ReportController {
|
||||
return &ReportController{reportService: reportService, rankService: rankService}
|
||||
func NewReportController(
|
||||
reportService *report.ReportService,
|
||||
rankService *rank.RankService,
|
||||
actionService *action.CaptchaService,
|
||||
) *ReportController {
|
||||
return &ReportController{
|
||||
reportService: reportService,
|
||||
rankService: rankService,
|
||||
actionService: actionService,
|
||||
}
|
||||
}
|
||||
|
||||
// AddReport add report
|
||||
@@ -42,6 +55,19 @@ func (rc *ReportController) AddReport(ctx *gin.Context) {
|
||||
}
|
||||
req.ObjectID = uid.DeShortID(req.ObjectID)
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin {
|
||||
captchaPass := rc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionReport, req.UserID, req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
can, err := rc.rankService.CheckOperationPermission(ctx, req.UserID, permission.ReportAdd, "")
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
@@ -53,5 +79,8 @@ func (rc *ReportController) AddReport(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
err = rc.reportService.AddReport(ctx, req)
|
||||
if !isAdmin {
|
||||
rc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionReport, req.UserID)
|
||||
}
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
@@ -3,19 +3,32 @@ package controller
|
||||
import (
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
"github.com/answerdev/answer/internal/base/middleware"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/base/translator"
|
||||
"github.com/answerdev/answer/internal/base/validator"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service"
|
||||
"github.com/answerdev/answer/internal/service/action"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
)
|
||||
|
||||
// SearchController tag controller
|
||||
type SearchController struct {
|
||||
searchService *service.SearchService
|
||||
actionService *action.CaptchaService
|
||||
}
|
||||
|
||||
// NewSearchController new controller
|
||||
func NewSearchController(searchService *service.SearchService) *SearchController {
|
||||
return &SearchController{searchService: searchService}
|
||||
func NewSearchController(
|
||||
searchService *service.SearchService,
|
||||
actionService *action.CaptchaService,
|
||||
) *SearchController {
|
||||
return &SearchController{
|
||||
searchService: searchService,
|
||||
actionService: actionService,
|
||||
}
|
||||
}
|
||||
|
||||
// Search godoc
|
||||
@@ -35,12 +48,29 @@ func (sc *SearchController) Search(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
dto.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
unit := ctx.ClientIP()
|
||||
if dto.UserID != "" {
|
||||
unit = dto.UserID
|
||||
}
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin {
|
||||
captchaPass := sc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionSearch, unit, dto.CaptchaID, dto.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
resp, total, extra, err := sc.searchService.Search(ctx, &dto)
|
||||
|
||||
resp, total, err := sc.searchService.Search(ctx, &dto)
|
||||
if !isAdmin {
|
||||
sc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionSearch, unit)
|
||||
}
|
||||
handler.HandleResponse(ctx, err, schema.SearchListResp{
|
||||
Total: total,
|
||||
SearchResp: resp,
|
||||
Extra: extra,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@ import (
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
type SiteinfoController struct {
|
||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
||||
type SiteInfoController struct {
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||
}
|
||||
|
||||
// NewSiteinfoController new siteinfo controller.
|
||||
func NewSiteinfoController(siteInfoService *siteinfo_common.SiteInfoCommonService) *SiteinfoController {
|
||||
return &SiteinfoController{
|
||||
// NewSiteInfoController new site info controller.
|
||||
func NewSiteInfoController(siteInfoService siteinfo_common.SiteInfoCommonService) *SiteInfoController {
|
||||
return &SiteInfoController{
|
||||
siteInfoService: siteInfoService,
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ func NewSiteinfoController(siteInfoService *siteinfo_common.SiteInfoCommonServic
|
||||
// @Produce json
|
||||
// @Success 200 {object} handler.RespBody{data=schema.SiteInfoResp}
|
||||
// @Router /answer/api/v1/siteinfo [get]
|
||||
func (sc *SiteinfoController) GetSiteInfo(ctx *gin.Context) {
|
||||
func (sc *SiteInfoController) GetSiteInfo(ctx *gin.Context) {
|
||||
var err error
|
||||
resp := &schema.SiteInfoResp{Version: constant.Version, Revision: constant.Revision}
|
||||
resp.General, err = sc.siteInfoService.GetSiteGeneral(ctx)
|
||||
@@ -80,7 +80,7 @@ func (sc *SiteinfoController) GetSiteInfo(ctx *gin.Context) {
|
||||
// @Produce json
|
||||
// @Success 200 {object} handler.RespBody{data=schema.GetSiteLegalInfoResp}
|
||||
// @Router /answer/api/v1/siteinfo/legal [get]
|
||||
func (sc *SiteinfoController) GetSiteLegalInfo(ctx *gin.Context) {
|
||||
func (sc *SiteInfoController) GetSiteLegalInfo(ctx *gin.Context) {
|
||||
req := &schema.GetSiteLegalInfoReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
@@ -102,7 +102,7 @@ func (sc *SiteinfoController) GetSiteLegalInfo(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
// GetManifestJson get manifest.json
|
||||
func (sc *SiteinfoController) GetManifestJson(ctx *gin.Context) {
|
||||
func (sc *SiteInfoController) GetManifestJson(ctx *gin.Context) {
|
||||
favicon := "favicon.ico"
|
||||
resp := &schema.GetManifestJsonResp{
|
||||
ManifestVersion: 3,
|
||||
|
||||
@@ -30,13 +30,13 @@ type TemplateController struct {
|
||||
scriptPath string
|
||||
cssPath string
|
||||
templateRenderController *templaterender.TemplateRenderController
|
||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||
}
|
||||
|
||||
// NewTemplateController new controller
|
||||
func NewTemplateController(
|
||||
templateRenderController *templaterender.TemplateRenderController,
|
||||
siteInfoService *siteinfo_common.SiteInfoCommonService,
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||
) *TemplateController {
|
||||
script, css := GetStyle()
|
||||
return &TemplateController{
|
||||
@@ -116,7 +116,7 @@ func (tc *TemplateController) Index(ctx *gin.Context) {
|
||||
siteInfo.Canonical = siteInfo.General.SiteUrl
|
||||
|
||||
UrlUseTitle := false
|
||||
if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionIDAndTitle {
|
||||
if siteInfo.SiteSeo.PermaLink == constant.PermaLinkQuestionIDAndTitle {
|
||||
UrlUseTitle = true
|
||||
}
|
||||
siteInfo.Title = ""
|
||||
@@ -149,7 +149,7 @@ func (tc *TemplateController) QuestionList(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
UrlUseTitle := false
|
||||
if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionIDAndTitle {
|
||||
if siteInfo.SiteSeo.PermaLink == constant.PermaLinkQuestionIDAndTitle {
|
||||
UrlUseTitle = true
|
||||
}
|
||||
siteInfo.Title = fmt.Sprintf("Questions - %s", siteInfo.General.Name)
|
||||
@@ -164,16 +164,21 @@ func (tc *TemplateController) QuestionInfoeRdirect(ctx *gin.Context, siteInfo *s
|
||||
id := ctx.Param("id")
|
||||
title := ctx.Param("title")
|
||||
titleIsAnswerID := false
|
||||
NeedChangeShortID := false
|
||||
needChangeShortID := false
|
||||
|
||||
siteSeo, err := tc.siteInfoService.GetSiteSeo(ctx)
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
isShortID := uid.IsShortID(id)
|
||||
if uid.ShortIDSwitch {
|
||||
if siteSeo.IsShortLink() {
|
||||
if !isShortID {
|
||||
id = uid.EnShortID(id)
|
||||
NeedChangeShortID = true
|
||||
needChangeShortID = true
|
||||
}
|
||||
} else {
|
||||
if isShortID {
|
||||
NeedChangeShortID = true
|
||||
needChangeShortID = true
|
||||
id = uid.DeShortID(id)
|
||||
}
|
||||
}
|
||||
@@ -184,13 +189,12 @@ func (tc *TemplateController) QuestionInfoeRdirect(ctx *gin.Context, siteInfo *s
|
||||
titleIsAnswerID = true
|
||||
}
|
||||
}
|
||||
siteInfo = tc.SiteInfo(ctx)
|
||||
url = fmt.Sprintf("%s/questions/%s", siteInfo.General.SiteUrl, id)
|
||||
if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionID || siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionIDByShortID {
|
||||
if siteInfo.SiteSeo.PermaLink == constant.PermaLinkQuestionID || siteInfo.SiteSeo.PermaLink == constant.PermaLinkQuestionIDByShortID {
|
||||
if len(ctx.Request.URL.Query()) > 0 {
|
||||
url = fmt.Sprintf("%s?%s", url, ctx.Request.URL.RawQuery)
|
||||
}
|
||||
if NeedChangeShortID {
|
||||
if needChangeShortID {
|
||||
return true, url
|
||||
}
|
||||
//not have title
|
||||
@@ -216,7 +220,7 @@ func (tc *TemplateController) QuestionInfoeRdirect(ctx *gin.Context, siteInfo *s
|
||||
}
|
||||
//have title
|
||||
if len(title) > 0 && !titleIsAnswerID && correctTitle {
|
||||
if NeedChangeShortID {
|
||||
if needChangeShortID {
|
||||
return true, url
|
||||
}
|
||||
return false, ""
|
||||
@@ -289,7 +293,7 @@ func (tc *TemplateController) QuestionInfo(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
siteInfo.Canonical = fmt.Sprintf("%s/questions/%s/%s", siteInfo.General.SiteUrl, id, encodeTitle)
|
||||
if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionID {
|
||||
if siteInfo.SiteSeo.PermaLink == constant.PermaLinkQuestionID {
|
||||
siteInfo.Canonical = fmt.Sprintf("%s/questions/%s", siteInfo.General.SiteUrl, id)
|
||||
}
|
||||
jsonLD := &schema.QAPageJsonLD{}
|
||||
@@ -404,7 +408,7 @@ func (tc *TemplateController) TagInfo(ctx *gin.Context) {
|
||||
siteInfo.Keywords = taginifo.DisplayName
|
||||
|
||||
UrlUseTitle := false
|
||||
if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionIDAndTitle {
|
||||
if siteInfo.SiteSeo.PermaLink == constant.PermaLinkQuestionIDAndTitle {
|
||||
UrlUseTitle = true
|
||||
}
|
||||
siteInfo.Title = fmt.Sprintf("'%s' Questions - %s", taginifo.DisplayName, siteInfo.General.Name)
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package templaterender
|
||||
|
||||
import (
|
||||
questioncommon "github.com/answerdev/answer/internal/service/question_common"
|
||||
"math"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/data"
|
||||
"github.com/answerdev/answer/internal/service/comment"
|
||||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service"
|
||||
"github.com/answerdev/answer/internal/service/tag"
|
||||
"github.com/google/wire"
|
||||
)
|
||||
|
||||
// ProviderSetTemplateRenderController is template render controller providers.
|
||||
@@ -24,8 +24,8 @@ type TemplateRenderController struct {
|
||||
tagService *tag.TagService
|
||||
answerService *service.AnswerService
|
||||
commentService *comment.CommentService
|
||||
data *data.Data
|
||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||
questionRepo questioncommon.QuestionRepo
|
||||
}
|
||||
|
||||
func NewTemplateRenderController(
|
||||
@@ -34,9 +34,8 @@ func NewTemplateRenderController(
|
||||
tagService *tag.TagService,
|
||||
answerService *service.AnswerService,
|
||||
commentService *comment.CommentService,
|
||||
data *data.Data,
|
||||
siteInfoService *siteinfo_common.SiteInfoCommonService,
|
||||
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||
questionRepo questioncommon.QuestionRepo,
|
||||
) *TemplateRenderController {
|
||||
return &TemplateRenderController{
|
||||
questionService: questionService,
|
||||
@@ -44,7 +43,7 @@ func NewTemplateRenderController(
|
||||
tagService: tagService,
|
||||
answerService: answerService,
|
||||
commentService: commentService,
|
||||
data: data,
|
||||
questionRepo: questionRepo,
|
||||
siteInfoService: siteInfoService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
package templaterender
|
||||
@@ -1,11 +1,11 @@
|
||||
package templaterender
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"math"
|
||||
"net/http"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/constant"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
@@ -31,48 +31,46 @@ func (t *TemplateRenderController) Sitemap(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
sitemapInfo := &schema.SiteMapList{}
|
||||
infoStr, err := t.data.Cache.GetString(ctx, schema.SitemapCachekey)
|
||||
questions, err := t.questionRepo.SitemapQuestions(ctx, 1, constant.SitemapMaxSize)
|
||||
if err != nil {
|
||||
log.Errorf("get Cache failed: %s", err)
|
||||
return
|
||||
}
|
||||
hasTitle := false
|
||||
if siteInfo.PermaLink == schema.PermaLinkQuestionIDAndTitle || siteInfo.PermaLink == schema.PermaLinkQuestionIDAndTitleByShortID {
|
||||
hasTitle = true
|
||||
}
|
||||
if err = json.Unmarshal([]byte(infoStr), sitemapInfo); err != nil {
|
||||
log.Errorf("get sitemap info failed: %s", err)
|
||||
log.Errorf("get sitemap questions failed: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(sitemapInfo.QuestionIDs) > 0 {
|
||||
//question url list
|
||||
ctx.Header("Content-Type", "application/xml")
|
||||
ctx.Header("Content-Type", "application/xml")
|
||||
if len(questions) < constant.SitemapMaxSize {
|
||||
ctx.HTML(
|
||||
http.StatusOK, "sitemap.xml", gin.H{
|
||||
"xmlHeader": template.HTML(`<?xml version="1.0" encoding="UTF-8"?>`),
|
||||
"list": sitemapInfo.QuestionIDs,
|
||||
"general": general,
|
||||
"hastitle": hasTitle,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
//question list page
|
||||
ctx.Header("Content-Type", "application/xml")
|
||||
ctx.HTML(
|
||||
http.StatusOK, "sitemap-list.xml", gin.H{
|
||||
"xmlHeader": template.HTML(`<?xml version="1.0" encoding="UTF-8"?>`),
|
||||
"page": sitemapInfo.MaxPageNum,
|
||||
"list": questions,
|
||||
"general": general,
|
||||
"hastitle": siteInfo.PermaLink == constant.PermaLinkQuestionIDAndTitle ||
|
||||
siteInfo.PermaLink == constant.PermaLinkQuestionIDAndTitleByShortID,
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
questionNum, err := t.questionRepo.GetQuestionCount(ctx)
|
||||
if err != nil {
|
||||
log.Error("GetQuestionCount error", err)
|
||||
return
|
||||
}
|
||||
var pageList []int
|
||||
totalPages := int(math.Ceil(float64(questionNum) / float64(constant.SitemapMaxSize)))
|
||||
for i := 1; i <= totalPages; i++ {
|
||||
pageList = append(pageList, i)
|
||||
}
|
||||
ctx.HTML(
|
||||
http.StatusOK, "sitemap-list.xml", gin.H{
|
||||
"xmlHeader": template.HTML(`<?xml version="1.0" encoding="UTF-8"?>`),
|
||||
"page": pageList,
|
||||
"general": general,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (t *TemplateRenderController) SitemapPage(ctx *gin.Context, page int) error {
|
||||
sitemapInfo := &schema.SiteMapPageList{}
|
||||
general, err := t.siteInfoService.GetSiteGeneral(ctx)
|
||||
if err != nil {
|
||||
log.Error("get site general failed:", err)
|
||||
@@ -83,28 +81,20 @@ func (t *TemplateRenderController) SitemapPage(ctx *gin.Context, page int) error
|
||||
log.Error("get site GetSiteSeo failed:", err)
|
||||
return err
|
||||
}
|
||||
hasTitle := false
|
||||
if siteInfo.PermaLink == schema.PermaLinkQuestionIDAndTitle || siteInfo.PermaLink == schema.PermaLinkQuestionIDAndTitleByShortID {
|
||||
hasTitle = true
|
||||
}
|
||||
|
||||
cachekey := fmt.Sprintf(schema.SitemapPageCachekey, page)
|
||||
infoStr, err := t.data.Cache.GetString(ctx, cachekey)
|
||||
questions, err := t.questionRepo.SitemapQuestions(ctx, page, constant.SitemapMaxSize)
|
||||
if err != nil {
|
||||
//If there is no cache, return directly.
|
||||
return nil
|
||||
}
|
||||
if err = json.Unmarshal([]byte(infoStr), sitemapInfo); err != nil {
|
||||
log.Errorf("get sitemap info failed: %s", err)
|
||||
log.Errorf("get sitemap questions failed: %s", err)
|
||||
return err
|
||||
}
|
||||
ctx.Header("Content-Type", "application/xml")
|
||||
ctx.HTML(
|
||||
http.StatusOK, "sitemap.xml", gin.H{
|
||||
"xmlHeader": template.HTML(`<?xml version="1.0" encoding="UTF-8"?>`),
|
||||
"list": sitemapInfo.PageData,
|
||||
"list": questions,
|
||||
"general": general,
|
||||
"hastitle": hasTitle,
|
||||
"hastitle": siteInfo.PermaLink == constant.PermaLinkQuestionIDAndTitle ||
|
||||
siteInfo.PermaLink == constant.PermaLinkQuestionIDAndTitleByShortID,
|
||||
},
|
||||
)
|
||||
return nil
|
||||
|
||||
@@ -6,13 +6,13 @@ import (
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/base/translator"
|
||||
"github.com/answerdev/answer/internal/base/validator"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service"
|
||||
"github.com/answerdev/answer/internal/service/action"
|
||||
"github.com/answerdev/answer/internal/service/auth"
|
||||
"github.com/answerdev/answer/internal/service/export"
|
||||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||
"github.com/answerdev/answer/internal/service/uploader"
|
||||
"github.com/answerdev/answer/pkg/checker"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
@@ -24,9 +24,8 @@ type UserController struct {
|
||||
userService *service.UserService
|
||||
authService *auth.AuthService
|
||||
actionService *action.CaptchaService
|
||||
uploaderService uploader.UploaderService
|
||||
emailService *export.EmailService
|
||||
siteInfoCommonService *siteinfo_common.SiteInfoCommonService
|
||||
siteInfoCommonService siteinfo_common.SiteInfoCommonService
|
||||
}
|
||||
|
||||
// NewUserController new controller
|
||||
@@ -35,14 +34,12 @@ func NewUserController(
|
||||
userService *service.UserService,
|
||||
actionService *action.CaptchaService,
|
||||
emailService *export.EmailService,
|
||||
uploaderService uploader.UploaderService,
|
||||
siteInfoCommonService *siteinfo_common.SiteInfoCommonService,
|
||||
siteInfoCommonService siteinfo_common.SiteInfoCommonService,
|
||||
) *UserController {
|
||||
return &UserController{
|
||||
authService: authService,
|
||||
userService: userService,
|
||||
actionService: actionService,
|
||||
uploaderService: uploaderService,
|
||||
emailService: emailService,
|
||||
siteInfoCommonService: siteInfoCommonService,
|
||||
}
|
||||
@@ -109,20 +106,22 @@ func (uc *UserController) UserEmailLogin(ctx *gin.Context) {
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
|
||||
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecordTypeLogin, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin {
|
||||
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionPassword, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := uc.userService.EmailLogin(ctx, req)
|
||||
if err != nil {
|
||||
_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeLogin, ctx.ClientIP())
|
||||
_, _ = uc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionEmail, ctx.ClientIP())
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "e_mail",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.EmailOrPasswordWrong),
|
||||
@@ -130,7 +129,9 @@ func (uc *UserController) UserEmailLogin(ctx *gin.Context) {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.EmailOrPasswordWrong), errFields)
|
||||
return
|
||||
}
|
||||
uc.actionService.ActionRecordDel(ctx, schema.ActionRecordTypeLogin, ctx.ClientIP())
|
||||
if !isAdmin {
|
||||
uc.actionService.ActionRecordDel(ctx, entity.CaptchaActionPassword, ctx.ClientIP())
|
||||
}
|
||||
handler.HandleResponse(ctx, nil, resp)
|
||||
}
|
||||
|
||||
@@ -148,16 +149,18 @@ func (uc *UserController) RetrievePassWord(ctx *gin.Context) {
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecordTypeFindPass, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin {
|
||||
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionEmail, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
}
|
||||
_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeFindPass, ctx.ClientIP())
|
||||
err := uc.userService.RetrievePassWord(ctx, req)
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
@@ -185,7 +188,7 @@ func (uc *UserController) UseRePassWord(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
err := uc.userService.UpdatePasswordWhenForgot(ctx, req)
|
||||
uc.actionService.ActionRecordDel(ctx, schema.ActionRecordTypeFindPass, ctx.ClientIP())
|
||||
uc.actionService.ActionRecordDel(ctx, entity.CaptchaActionPassword, ctx.ClientIP())
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
@@ -238,14 +241,17 @@ func (uc *UserController) UserRegisterByEmail(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
req.IP = ctx.ClientIP()
|
||||
captchaPass := uc.actionService.UserRegisterVerifyCaptcha(ctx, req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin {
|
||||
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionEmail, req.IP, req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
resp, errFields, err := uc.userService.UserRegisterByEmail(ctx, req)
|
||||
@@ -288,7 +294,7 @@ func (uc *UserController) UserVerifyEmail(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
uc.actionService.ActionRecordDel(ctx, schema.ActionRecordTypeEmail, ctx.ClientIP())
|
||||
uc.actionService.ActionRecordDel(ctx, entity.CaptchaActionEmail, ctx.ClientIP())
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
@@ -313,22 +319,20 @@ func (uc *UserController) UserVerifyEmailSend(ctx *gin.Context) {
|
||||
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
|
||||
return
|
||||
}
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin {
|
||||
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionEmail, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecordTypeEmail, ctx.ClientIP(),
|
||||
req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
_, err := uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeEmail, ctx.ClientIP())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
err = uc.userService.UserVerifyEmailSend(ctx, userInfo.UserID)
|
||||
err := uc.userService.UserVerifyEmailSend(ctx, userInfo.UserID)
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
@@ -349,20 +353,22 @@ func (uc *UserController) UserModifyPassWord(ctx *gin.Context) {
|
||||
}
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
req.AccessToken = middleware.ExtractToken(ctx)
|
||||
|
||||
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecordTypeModifyPass, ctx.ClientIP(),
|
||||
req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
_, err := uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeModifyPass, ctx.ClientIP())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin {
|
||||
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionPassword, req.UserID,
|
||||
req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
_, err := uc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionPassword, req.UserID)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
oldPassVerification, err := uc.userService.UserModifyPassWordVerification(ctx, req)
|
||||
@@ -389,7 +395,7 @@ func (uc *UserController) UserModifyPassWord(ctx *gin.Context) {
|
||||
}
|
||||
err = uc.userService.UserModifyPassword(ctx, req)
|
||||
if err == nil {
|
||||
uc.actionService.ActionRecordDel(ctx, schema.ActionRecordTypeLogin, ctx.ClientIP())
|
||||
uc.actionService.ActionRecordDel(ctx, entity.CaptchaActionPassword, req.UserID)
|
||||
}
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
@@ -452,10 +458,21 @@ func (uc *UserController) ActionRecord(ctx *gin.Context) {
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
userinfo := middleware.GetUserInfoFromContext(ctx)
|
||||
if userinfo != nil {
|
||||
req.UserID = userinfo.UserID
|
||||
}
|
||||
req.IP = ctx.ClientIP()
|
||||
resp := &schema.ActionRecordResp{}
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if isAdmin {
|
||||
resp.Verify = false
|
||||
handler.HandleResponse(ctx, nil, resp)
|
||||
} else {
|
||||
resp, err := uc.actionService.ActionRecord(ctx, req)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
resp, err := uc.actionService.ActionRecord(ctx, req)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
// UserRegisterCaptcha godoc
|
||||
@@ -523,22 +540,26 @@ func (uc *UserController) UserChangeEmailSendCode(ctx *gin.Context) {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.EmailIllegalDomainError), nil)
|
||||
return
|
||||
}
|
||||
|
||||
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecordTypeEmail, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin {
|
||||
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionPassword, req.UserID, req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
}
|
||||
_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeEmail, ctx.ClientIP())
|
||||
resp, err := uc.userService.UserChangeEmailSendCode(ctx, req)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
return
|
||||
}
|
||||
if !isAdmin {
|
||||
uc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionPassword, req.UserID)
|
||||
}
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
@@ -565,7 +586,7 @@ func (uc *UserController) UserChangeEmailVerify(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
resp, err := uc.userService.UserChangeEmailVerify(ctx, req.Content)
|
||||
uc.actionService.ActionRecordDel(ctx, schema.ActionRecordTypeEmail, ctx.ClientIP())
|
||||
uc.actionService.ActionRecordDel(ctx, entity.CaptchaActionEmail, ctx.ClientIP())
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,24 +5,35 @@ import (
|
||||
"github.com/answerdev/answer/internal/base/middleware"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/base/translator"
|
||||
"github.com/answerdev/answer/internal/base/validator"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service"
|
||||
"github.com/answerdev/answer/internal/service/action"
|
||||
"github.com/answerdev/answer/internal/service/rank"
|
||||
"github.com/answerdev/answer/pkg/uid"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
)
|
||||
|
||||
// VoteController activity controller
|
||||
type VoteController struct {
|
||||
VoteService *service.VoteService
|
||||
rankService *rank.RankService
|
||||
VoteService *service.VoteService
|
||||
rankService *rank.RankService
|
||||
actionService *action.CaptchaService
|
||||
}
|
||||
|
||||
// NewVoteController new controller
|
||||
func NewVoteController(voteService *service.VoteService, rankService *rank.RankService) *VoteController {
|
||||
return &VoteController{VoteService: voteService, rankService: rankService}
|
||||
func NewVoteController(
|
||||
voteService *service.VoteService,
|
||||
rankService *rank.RankService,
|
||||
actionService *action.CaptchaService,
|
||||
) *VoteController {
|
||||
return &VoteController{
|
||||
VoteService: voteService,
|
||||
rankService: rankService,
|
||||
actionService: actionService,
|
||||
}
|
||||
}
|
||||
|
||||
// VoteUp godoc
|
||||
@@ -42,6 +53,7 @@ func (vc *VoteController) VoteUp(ctx *gin.Context) {
|
||||
}
|
||||
req.ObjectID = uid.DeShortID(req.ObjectID)
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
|
||||
can, needRank, err := vc.rankService.CheckVotePermission(ctx, req.UserID, req.ObjectID, true)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
@@ -54,9 +66,23 @@ func (vc *VoteController) VoteUp(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
dto := &schema.VoteDTO{}
|
||||
_ = copier.Copy(dto, req)
|
||||
resp, err := vc.VoteService.VoteUp(ctx, dto)
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin {
|
||||
captchaPass := vc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionVote, req.UserID, req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !isAdmin {
|
||||
vc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionVote, req.UserID)
|
||||
}
|
||||
resp, err := vc.VoteService.VoteUp(ctx, req)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, schema.ErrTypeToast)
|
||||
} else {
|
||||
@@ -81,6 +107,8 @@ func (vc *VoteController) VoteDown(ctx *gin.Context) {
|
||||
}
|
||||
req.ObjectID = uid.DeShortID(req.ObjectID)
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
|
||||
can, needRank, err := vc.rankService.CheckVotePermission(ctx, req.UserID, req.ObjectID, false)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
@@ -93,9 +121,21 @@ func (vc *VoteController) VoteDown(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
dto := &schema.VoteDTO{}
|
||||
_ = copier.Copy(dto, req)
|
||||
resp, err := vc.VoteService.VoteDown(ctx, dto)
|
||||
if !isAdmin {
|
||||
captchaPass := vc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionVote, req.UserID, req.CaptchaID, req.CaptchaCode)
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
}
|
||||
}
|
||||
if !isAdmin {
|
||||
vc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionVote, req.UserID)
|
||||
}
|
||||
resp, err := vc.VoteService.VoteDown(ctx, req)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, schema.ErrTypeToast)
|
||||
} else {
|
||||
@@ -103,9 +143,9 @@ func (vc *VoteController) VoteDown(ctx *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// UserVotes godoc
|
||||
// @Summary user's votes
|
||||
// @Description user's vote
|
||||
// UserVotes user votes
|
||||
// @Summary get user personal votes
|
||||
// @Description get user personal votes
|
||||
// @Tags Activity
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
@@ -116,21 +156,12 @@ func (vc *VoteController) VoteDown(ctx *gin.Context) {
|
||||
// @Router /answer/api/v1/personal/vote/page [get]
|
||||
func (vc *VoteController) UserVotes(ctx *gin.Context) {
|
||||
req := schema.GetVoteWithPageReq{}
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
if handler.BindAndCheck(ctx, &req) {
|
||||
return
|
||||
}
|
||||
if req.Page == 0 {
|
||||
req.Page = 1
|
||||
}
|
||||
if req.PageSize == 0 {
|
||||
req.PageSize = 30
|
||||
}
|
||||
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
|
||||
resp, err := vc.VoteService.ListUserVotes(ctx, req)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, schema.ErrTypeModal)
|
||||
} else {
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
@@ -135,3 +135,41 @@ func (uc *UserAdminController) GetUserPage(ctx *gin.Context) {
|
||||
resp, err := uc.userService.GetUserPage(ctx, req)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
// GetUserActivation get user activation
|
||||
// @Summary get user activation
|
||||
// @Description get user activation
|
||||
// @Security ApiKeyAuth
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Param user_id query string true "user id"
|
||||
// @Success 200 {object} handler.RespBody{data=schema.GetUserActivationResp}
|
||||
// @Router /answer/admin/api/user/activation [get]
|
||||
func (uc *UserAdminController) GetUserActivation(ctx *gin.Context) {
|
||||
req := &schema.GetUserActivationReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := uc.userService.GetUserActivation(ctx, req)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
// SendUserActivation send user activation
|
||||
// @Summary send user activation
|
||||
// @Description send user activation
|
||||
// @Security ApiKeyAuth
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Param data body schema.SendUserActivationReq true "SendUserActivationReq"
|
||||
// @Success 200 {object} handler.RespBody
|
||||
// @Router /answer/admin/api/users/activation [post]
|
||||
func (uc *UserAdminController) SendUserActivation(ctx *gin.Context) {
|
||||
req := &schema.SendUserActivationReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
|
||||
err := uc.userService.SendUserActivation(ctx, req)
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
@@ -42,15 +42,6 @@ type AnswerSearch struct {
|
||||
PageSize int `json:"page_size" form:"page_size"` // Search page size
|
||||
}
|
||||
|
||||
type AdminAnswerSearch struct {
|
||||
Page int `json:"page" form:"page"` // Query number of pages
|
||||
PageSize int `json:"page_size" form:"page_size"` // Search page size
|
||||
Status int `json:"-" form:"-"`
|
||||
StatusStr string `json:"status" form:"status"` // Status 1 Available 2 closed 10 Deleted
|
||||
Query string `validate:"omitempty,gt=0,lte=100" json:"query" form:"query" ` //Query string
|
||||
QuestionID string `validate:"omitempty,gt=0,lte=24" json:"question_id" form:"question_id" ` //Query string
|
||||
}
|
||||
|
||||
// TableName answer table name
|
||||
func (Answer) TableName() string {
|
||||
return "answer"
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package entity
|
||||
|
||||
const (
|
||||
CaptchaActionEmail = "email"
|
||||
CaptchaActionPassword = "password"
|
||||
CaptchaActionEditUserinfo = "edit_userinfo"
|
||||
CaptchaActionQuestion = "question"
|
||||
CaptchaActionAnswer = "answer"
|
||||
CaptchaActionComment = "comment"
|
||||
CaptchaActionEdit = "edit"
|
||||
CaptchaActionInvitationAnswer = "invitation_answer"
|
||||
CaptchaActionSearch = "search"
|
||||
CaptchaActionReport = "report"
|
||||
CaptchaActionDelete = "delete"
|
||||
CaptchaActionVote = "vote"
|
||||
)
|
||||
|
||||
type ActionRecordInfo struct {
|
||||
LastTime int64 `json:"last_time"`
|
||||
Num int `json:"num"`
|
||||
Config string `json:"config"`
|
||||
}
|
||||
@@ -24,7 +24,7 @@ type Revision struct {
|
||||
ObjectType int `xorm:"not null default 0 INT(11) object_type"`
|
||||
ObjectID string `xorm:"not null default 0 BIGINT(20) INDEX object_id"`
|
||||
Title string `xorm:"not null default '' VARCHAR(255) title"`
|
||||
Content string `xorm:"not null TEXT content"`
|
||||
Content string `xorm:"not null MEDIUMTEXT content"`
|
||||
Log string `xorm:"VARCHAR(255) log"`
|
||||
Status int `xorm:"not null default 1 INT(11) status"`
|
||||
ReviewUserID int64 `xorm:"not null default 0 BIGINT(20) review_user_id"`
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/answerdev/answer/internal/migrations"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
@@ -184,17 +185,17 @@ func InitBaseInfo(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := migrations.InitDB(c.Data.Database); err != nil {
|
||||
log.Error("init database error: ", err.Error())
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.InstallCreateTableFailed), schema.ErrTypeAlert)
|
||||
return
|
||||
engine, err := data.NewDB(false, c.Data.Database)
|
||||
if err != nil {
|
||||
log.Errorf("init database failed %s", err)
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.InstallCreateTableFailed), nil)
|
||||
}
|
||||
|
||||
err = migrations.UpdateInstallInfo(c.Data.Database, req.Language, req.SiteName, req.SiteURL, req.ContactEmail,
|
||||
req.AdminName, req.AdminPassword, req.AdminEmail)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.InstallConfigFailed), nil)
|
||||
inputData := &migrations.InitNeedUserInputData{}
|
||||
_ = copier.Copy(inputData, req)
|
||||
if err := migrations.NewMentor(ctx, engine, inputData).InitDB(); err != nil {
|
||||
log.Error("init database error: ", err.Error())
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.InstallConfigFailed), schema.ErrTypeAlert)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+136
-447
@@ -1,188 +1,200 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/data"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/service/permission"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultSEORobotTxt = `User-agent: *
|
||||
Disallow: /admin
|
||||
Disallow: /search
|
||||
Disallow: /install
|
||||
Disallow: /review
|
||||
Disallow: /users/login
|
||||
Disallow: /users/register
|
||||
Disallow: /users/account-recovery
|
||||
Disallow: /users/oauth/*
|
||||
Disallow: /users/*/*
|
||||
Disallow: /answer/api
|
||||
Disallow: /*?code*
|
||||
|
||||
Sitemap: `
|
||||
)
|
||||
|
||||
var tables = []interface{}{
|
||||
&entity.Activity{},
|
||||
&entity.Answer{},
|
||||
&entity.Collection{},
|
||||
&entity.CollectionGroup{},
|
||||
&entity.Comment{},
|
||||
&entity.Config{},
|
||||
&entity.Meta{},
|
||||
&entity.Notification{},
|
||||
&entity.Question{},
|
||||
&entity.Report{},
|
||||
&entity.Revision{},
|
||||
&entity.SiteInfo{},
|
||||
&entity.Tag{},
|
||||
&entity.TagRel{},
|
||||
&entity.Uniqid{},
|
||||
&entity.User{},
|
||||
&entity.Version{},
|
||||
&entity.Role{},
|
||||
&entity.RolePowerRel{},
|
||||
&entity.Power{},
|
||||
&entity.UserRoleRel{},
|
||||
&entity.PluginConfig{},
|
||||
&entity.UserExternalLogin{},
|
||||
type Mentor struct {
|
||||
ctx context.Context
|
||||
engine *xorm.Engine
|
||||
userData *InitNeedUserInputData
|
||||
err error
|
||||
Done bool
|
||||
}
|
||||
|
||||
// InitDB init db
|
||||
func InitDB(dataConf *data.Database) (err error) {
|
||||
engine, err := data.NewDB(false, dataConf)
|
||||
if err != nil {
|
||||
fmt.Println("new database failed: ", err.Error())
|
||||
return err
|
||||
}
|
||||
func NewMentor(ctx context.Context, engine *xorm.Engine, data *InitNeedUserInputData) *Mentor {
|
||||
return &Mentor{ctx: ctx, engine: engine, userData: data}
|
||||
}
|
||||
|
||||
exist, err := engine.IsTableExist(&entity.Version{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("check table exists failed: %s", err)
|
||||
type InitNeedUserInputData struct {
|
||||
Language string
|
||||
SiteName string
|
||||
SiteURL string
|
||||
ContactEmail string
|
||||
AdminName string
|
||||
AdminPassword string
|
||||
AdminEmail string
|
||||
}
|
||||
|
||||
func (m *Mentor) InitDB() error {
|
||||
m.do("check table exist", m.checkTableExist)
|
||||
m.do("sync table", m.syncTable)
|
||||
m.do("init version table", m.initVersionTable)
|
||||
m.do("init admin user", m.initAdminUser)
|
||||
m.do("init config", m.initConfig)
|
||||
m.do("init default privileges config", m.initDefaultRankPrivileges)
|
||||
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)
|
||||
m.do("init site info privilege rank", m.initSiteInfoPrivilegeRank)
|
||||
return m.err
|
||||
}
|
||||
|
||||
func (m *Mentor) do(taskName string, fn func()) {
|
||||
if m.err != nil || m.Done {
|
||||
return
|
||||
}
|
||||
if exist {
|
||||
fn()
|
||||
if m.err != nil {
|
||||
m.err = fmt.Errorf("%s failed: %s", taskName, m.err)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mentor) checkTableExist() {
|
||||
m.Done, m.err = m.engine.Context(m.ctx).IsTableExist(&entity.Version{})
|
||||
if m.Done {
|
||||
fmt.Println("[database] already exists")
|
||||
return nil
|
||||
}
|
||||
|
||||
err = engine.Sync(tables...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sync table failed: %s", err)
|
||||
}
|
||||
_, err = engine.InsertOne(&entity.Version{ID: 1, VersionNumber: ExpectedVersion()})
|
||||
if err != nil {
|
||||
return fmt.Errorf("init version table failed: %s", err)
|
||||
}
|
||||
|
||||
err = initAdminUser(engine)
|
||||
if err != nil {
|
||||
return fmt.Errorf("init admin user failed: %s", err)
|
||||
}
|
||||
|
||||
err = initConfigTable(engine)
|
||||
if err != nil {
|
||||
return fmt.Errorf("init config table: %s", err)
|
||||
}
|
||||
|
||||
err = initRolePower(engine)
|
||||
if err != nil {
|
||||
return fmt.Errorf("init role and power failed: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initAdminUser(engine *xorm.Engine) error {
|
||||
_, err := engine.InsertOne(&entity.User{
|
||||
func (m *Mentor) syncTable() {
|
||||
m.err = m.engine.Context(m.ctx).Sync(tables...)
|
||||
}
|
||||
|
||||
func (m *Mentor) initVersionTable() {
|
||||
_, m.err = m.engine.Context(m.ctx).Insert(&entity.Version{ID: 1, VersionNumber: ExpectedVersion()})
|
||||
}
|
||||
|
||||
func (m *Mentor) initAdminUser() {
|
||||
generateFromPassword, _ := bcrypt.GenerateFromPassword([]byte(m.userData.AdminPassword), bcrypt.DefaultCost)
|
||||
_, m.err = m.engine.Context(m.ctx).Insert(&entity.User{
|
||||
ID: "1",
|
||||
Username: "admin",
|
||||
Pass: "$2a$10$.gnUnpW.8ssRNaEvx.XwvOR2NuPsGzFLWWX2rqSIVAdIvLNZZYs5y", // admin
|
||||
EMail: "admin@admin.com",
|
||||
Username: m.userData.AdminName,
|
||||
Pass: string(generateFromPassword),
|
||||
EMail: m.userData.AdminEmail,
|
||||
MailStatus: 1,
|
||||
NoticeStatus: 1,
|
||||
Status: 1,
|
||||
Rank: 1,
|
||||
DisplayName: "admin",
|
||||
DisplayName: m.userData.AdminName,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func initSiteInfo(engine *xorm.Engine, language, siteName, siteURL, contactEmail string) error {
|
||||
func (m *Mentor) initConfig() {
|
||||
_, m.err = m.engine.Context(m.ctx).Insert(defaultConfigTable)
|
||||
}
|
||||
|
||||
func (m *Mentor) initDefaultRankPrivileges() {
|
||||
chooseOption := schema.DefaultPrivilegeOptions.Choose(schema.PrivilegeLevel2)
|
||||
for _, privilege := range chooseOption.Privileges {
|
||||
_, err := m.engine.Context(m.ctx).Update(
|
||||
&entity.Config{Value: fmt.Sprintf("%d", privilege.Value)},
|
||||
&entity.Config{Key: privilege.Key},
|
||||
)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mentor) initRole() {
|
||||
_, m.err = m.engine.Context(m.ctx).Insert(roles)
|
||||
}
|
||||
|
||||
func (m *Mentor) initPower() {
|
||||
_, m.err = m.engine.Context(m.ctx).Insert(powers)
|
||||
}
|
||||
|
||||
func (m *Mentor) initRolePowerRel() {
|
||||
_, m.err = m.engine.Context(m.ctx).Insert(rolePowerRels)
|
||||
}
|
||||
|
||||
func (m *Mentor) initAdminUserRoleRel() {
|
||||
_, m.err = m.engine.Context(m.ctx).Insert(adminUserRoleRel)
|
||||
}
|
||||
|
||||
func (m *Mentor) initSiteInfoInterface() {
|
||||
interfaceData := map[string]string{
|
||||
"language": language,
|
||||
"language": m.userData.Language,
|
||||
"time_zone": "UTC",
|
||||
}
|
||||
interfaceDataBytes, _ := json.Marshal(interfaceData)
|
||||
_, err := engine.InsertOne(&entity.SiteInfo{
|
||||
_, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
|
||||
Type: "interface",
|
||||
Content: string(interfaceDataBytes),
|
||||
Status: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mentor) initSiteInfoGeneralData() {
|
||||
generalData := map[string]string{
|
||||
"name": siteName,
|
||||
"site_url": siteURL,
|
||||
"contact_email": contactEmail,
|
||||
"name": m.userData.SiteName,
|
||||
"site_url": m.userData.SiteURL,
|
||||
"contact_email": m.userData.ContactEmail,
|
||||
}
|
||||
generalDataBytes, _ := json.Marshal(generalData)
|
||||
_, err = engine.InsertOne(&entity.SiteInfo{
|
||||
_, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
|
||||
Type: "general",
|
||||
Content: string(generalDataBytes),
|
||||
Status: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mentor) initSiteInfoLoginConfig() {
|
||||
loginConfig := map[string]bool{
|
||||
"allow_new_registrations": true,
|
||||
"allow_email_registrations": true,
|
||||
"login_required": false,
|
||||
}
|
||||
loginConfigDataBytes, _ := json.Marshal(loginConfig)
|
||||
_, err = engine.InsertOne(&entity.SiteInfo{
|
||||
_, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
|
||||
Type: "login",
|
||||
Content: string(loginConfigDataBytes),
|
||||
Status: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mentor) initSiteInfoThemeConfig() {
|
||||
themeConfig := `{"theme":"default","theme_config":{"default":{"navbar_style":"colored","primary_color":"#0033ff"}}}`
|
||||
_, err = engine.InsertOne(&entity.SiteInfo{
|
||||
_, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
|
||||
Type: "theme",
|
||||
Content: themeConfig,
|
||||
Status: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
seoData := map[string]string{
|
||||
"robots": defaultSEORobotTxt + siteURL + "/sitemap.xml",
|
||||
func (m *Mentor) initSiteInfoSEOConfig() {
|
||||
seoData := map[string]interface{}{
|
||||
"permalink": 1,
|
||||
"robots": defaultSEORobotTxt + m.userData.SiteURL + "/sitemap.xml",
|
||||
}
|
||||
seoDataBytes, _ := json.Marshal(seoData)
|
||||
_, err = engine.InsertOne(&entity.SiteInfo{
|
||||
_, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
|
||||
Type: "seo",
|
||||
Content: string(seoDataBytes),
|
||||
Status: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mentor) initSiteInfoUsersConfig() {
|
||||
usersData := map[string]any{
|
||||
"default_avatar": "gravatar",
|
||||
"default_gravatar_base_url": "https://www.gravatar.com/avatar/",
|
||||
"gravatar_base_url": "https://www.gravatar.com/avatar/",
|
||||
"allow_update_display_name": true,
|
||||
"allow_update_username": true,
|
||||
"allow_update_avatar": true,
|
||||
@@ -191,344 +203,21 @@ func initSiteInfo(engine *xorm.Engine, language, siteName, siteURL, contactEmail
|
||||
"allow_update_location": true,
|
||||
}
|
||||
usersDataBytes, _ := json.Marshal(usersData)
|
||||
_, err = engine.InsertOne(&entity.SiteInfo{
|
||||
_, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
|
||||
Type: "users",
|
||||
Content: string(usersDataBytes),
|
||||
Status: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func updateAdminInfo(engine *xorm.Engine, adminName, adminPassword, adminEmail string) error {
|
||||
generateFromPassword, err := bcrypt.GenerateFromPassword([]byte(adminPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
func (m *Mentor) initSiteInfoPrivilegeRank() {
|
||||
privilegeRankData := map[string]interface{}{
|
||||
"level": schema.PrivilegeLevel2,
|
||||
}
|
||||
adminPassword = string(generateFromPassword)
|
||||
|
||||
// update admin info
|
||||
_, err = engine.ID("1").Update(&entity.User{
|
||||
Username: adminName,
|
||||
Pass: adminPassword,
|
||||
EMail: adminEmail,
|
||||
DisplayName: adminName,
|
||||
privilegeRankDataBytes, _ := json.Marshal(privilegeRankData)
|
||||
_, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
|
||||
Type: "privileges",
|
||||
Content: string(privilegeRankDataBytes),
|
||||
Status: 1,
|
||||
})
|
||||
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: `1500`},
|
||||
{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`},
|
||||
}
|
||||
)
|
||||
@@ -15,14 +15,14 @@ const minDBVersion = 0
|
||||
type Migration interface {
|
||||
Version() string
|
||||
Description() string
|
||||
Migrate(*xorm.Engine) error
|
||||
Migrate(ctx context.Context, x *xorm.Engine) error
|
||||
ShouldCleanCache() bool
|
||||
}
|
||||
|
||||
type migration struct {
|
||||
version string
|
||||
description string
|
||||
migrate func(*xorm.Engine) error
|
||||
migrate func(ctx context.Context, x *xorm.Engine) error
|
||||
shouldCleanCache bool
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ func (m *migration) Description() string {
|
||||
}
|
||||
|
||||
// Migrate executes the migration
|
||||
func (m *migration) Migrate(x *xorm.Engine) error {
|
||||
return m.migrate(x)
|
||||
func (m *migration) Migrate(ctx context.Context, x *xorm.Engine) error {
|
||||
return m.migrate(ctx, x)
|
||||
}
|
||||
|
||||
// ShouldCleanCache should clean the cache
|
||||
@@ -47,12 +47,12 @@ func (m *migration) ShouldCleanCache() bool {
|
||||
}
|
||||
|
||||
// NewMigration creates a new migration
|
||||
func NewMigration(version, desc string, fn func(*xorm.Engine) error, shouldCleanCache bool) Migration {
|
||||
func NewMigration(version, desc string, fn func(ctx context.Context, x *xorm.Engine) error, shouldCleanCache bool) Migration {
|
||||
return &migration{version: version, description: desc, migrate: fn, shouldCleanCache: shouldCleanCache}
|
||||
}
|
||||
|
||||
// Use noopMigration when there is a migration that has been no-oped
|
||||
var noopMigration = func(_ *xorm.Engine) error { return nil }
|
||||
var noopMigration = func(_ context.Context, _ *xorm.Engine) error { return nil }
|
||||
|
||||
var migrations = []Migration{
|
||||
// 0->1
|
||||
@@ -70,6 +70,11 @@ var migrations = []Migration{
|
||||
NewMigration("v1.1.0-beta.1", "update user pin hide features", updateRolePinAndHideFeatures, true),
|
||||
NewMigration("v1.1.0-beta.2", "update question post time", updateQuestionPostTime, true),
|
||||
NewMigration("v1.1.0", "add gravatar base url", updateCount, true),
|
||||
NewMigration("v1.1.1", "update the length of revision content", updateTheLengthOfRevisionContent, false),
|
||||
}
|
||||
|
||||
func GetMigrations() []Migration {
|
||||
return migrations
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current db version
|
||||
@@ -99,12 +104,12 @@ func ExpectedVersion() int64 {
|
||||
}
|
||||
|
||||
// Migrate database to current version
|
||||
func Migrate(dbConf *data.Database, cacheConf *data.CacheConf, upgradeToSpecificVersion string) error {
|
||||
func Migrate(debug bool, dbConf *data.Database, cacheConf *data.CacheConf, upgradeToSpecificVersion string) error {
|
||||
cache, cacheCleanup, err := data.NewCache(cacheConf)
|
||||
if err != nil {
|
||||
fmt.Println("new check failed:", err.Error())
|
||||
}
|
||||
engine, err := data.NewDB(false, dbConf)
|
||||
engine, err := data.NewDB(debug, dbConf)
|
||||
if err != nil {
|
||||
fmt.Println("new database failed: ", err.Error())
|
||||
return err
|
||||
@@ -130,7 +135,7 @@ func Migrate(dbConf *data.Database, cacheConf *data.CacheConf, upgradeToSpecific
|
||||
currentDBVersion, currentDBVersion+1, expectedVersion)
|
||||
migrationFunc := migrations[currentDBVersion]
|
||||
fmt.Printf("[migrate] try to migrate Answer version %s, description: %s\n", migrationFunc.Version(), migrationFunc.Description())
|
||||
if err := migrationFunc.Migrate(engine); err != nil {
|
||||
if err := migrationFunc.Migrate(context.Background(), engine); err != nil {
|
||||
fmt.Printf("[migrate] migrate to db version %d failed: %s\n", currentDBVersion+1, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addUserLanguage(x *xorm.Engine) error {
|
||||
func addUserLanguage(ctx context.Context, x *xorm.Engine) error {
|
||||
type User struct {
|
||||
ID string `xorm:"not null pk autoincr BIGINT(20) id"`
|
||||
Username string `xorm:"not null default '' VARCHAR(50) UNIQUE username"`
|
||||
Language string `xorm:"not null default '' VARCHAR(100) language"`
|
||||
}
|
||||
return x.Sync(new(User))
|
||||
return x.Context(ctx).Sync(new(User))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
@@ -11,11 +12,11 @@ import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addLoginLimitations(x *xorm.Engine) error {
|
||||
func addLoginLimitations(ctx context.Context, x *xorm.Engine) error {
|
||||
loginSiteInfo := &entity.SiteInfo{
|
||||
Type: constant.SiteTypeLogin,
|
||||
}
|
||||
exist, err := x.Get(loginSiteInfo)
|
||||
exist, err := x.Context(ctx).Get(loginSiteInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
@@ -26,7 +27,7 @@ func addLoginLimitations(x *xorm.Engine) error {
|
||||
content.AllowEmailDomains = make([]string, 0)
|
||||
data, _ := json.Marshal(content)
|
||||
loginSiteInfo.Content = string(data)
|
||||
_, err = x.ID(loginSiteInfo.ID).Cols("content").Update(loginSiteInfo)
|
||||
_, err = x.Context(ctx).ID(loginSiteInfo.ID).Cols("content").Update(loginSiteInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update site info failed: %w", err)
|
||||
}
|
||||
@@ -35,7 +36,7 @@ func addLoginLimitations(x *xorm.Engine) error {
|
||||
interfaceSiteInfo := &entity.SiteInfo{
|
||||
Type: constant.SiteTypeInterface,
|
||||
}
|
||||
exist, err = x.Get(interfaceSiteInfo)
|
||||
exist, err = x.Context(ctx).Get(interfaceSiteInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
@@ -52,7 +53,7 @@ func addLoginLimitations(x *xorm.Engine) error {
|
||||
}
|
||||
data, _ := json.Marshal(siteUsers)
|
||||
|
||||
exist, err = x.Get(&entity.SiteInfo{Type: constant.SiteTypeUsers})
|
||||
exist, err = x.Context(ctx).Get(&entity.SiteInfo{Type: constant.SiteTypeUsers})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
@@ -62,7 +63,7 @@ func addLoginLimitations(x *xorm.Engine) error {
|
||||
Content: string(data),
|
||||
Status: 1,
|
||||
}
|
||||
_, err = x.InsertOne(usersSiteInfo)
|
||||
_, err = x.Context(ctx).Insert(usersSiteInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("insert site info failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
@@ -8,8 +9,7 @@ import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func updateRolePinAndHideFeatures(x *xorm.Engine) error {
|
||||
|
||||
func updateRolePinAndHideFeatures(ctx context.Context, x *xorm.Engine) error {
|
||||
defaultConfigTable := []*entity.Config{
|
||||
{ID: 119, Key: "question.pin", Value: `0`},
|
||||
{ID: 120, Key: "question.unpin", Value: `0`},
|
||||
@@ -21,18 +21,18 @@ func updateRolePinAndHideFeatures(x *xorm.Engine) error {
|
||||
{ID: 126, Key: "rank.question.hide", Value: `-1`},
|
||||
}
|
||||
for _, c := range defaultConfigTable {
|
||||
exist, err := x.Get(&entity.Config{ID: c.ID})
|
||||
exist, err := x.Context(ctx).Get(&entity.Config{ID: c.ID})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if exist {
|
||||
if _, err = x.Update(c, &entity.Config{ID: c.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Update(c, &entity.Config{ID: c.ID}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("update config failed: %w", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err = x.Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
if _, err = x.Context(ctx).Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
log.Errorf("insert %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("add config failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -37,9 +38,9 @@ func (QuestionPostTime) TableName() string {
|
||||
return "question"
|
||||
}
|
||||
|
||||
func updateQuestionPostTime(x *xorm.Engine) error {
|
||||
func updateQuestionPostTime(ctx context.Context, x *xorm.Engine) error {
|
||||
questionList := make([]QuestionPostTime, 0)
|
||||
err := x.Find(&questionList, &entity.Question{})
|
||||
err := x.Context(ctx).Find(&questionList, &entity.Question{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get questions failed: %w", err)
|
||||
}
|
||||
@@ -50,7 +51,7 @@ func updateQuestionPostTime(x *xorm.Engine) error {
|
||||
} else if !item.CreatedAt.IsZero() {
|
||||
item.PostUpdateTime = item.CreatedAt
|
||||
}
|
||||
if _, err = x.Update(item, &QuestionPostTime{ID: item.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Update(item, &QuestionPostTime{ID: item.ID}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", item, err)
|
||||
return fmt.Errorf("update question failed: %w", err)
|
||||
}
|
||||
|
||||
+45
-44
@@ -1,6 +1,7 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
@@ -13,8 +14,8 @@ import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func updateCount(x *xorm.Engine) error {
|
||||
fns := []func(*xorm.Engine) error{
|
||||
func updateCount(ctx context.Context, x *xorm.Engine) error {
|
||||
fns := []func(ctx context.Context, x *xorm.Engine) error{
|
||||
inviteAnswer,
|
||||
addPrivilegeForInviteSomeoneToAnswer,
|
||||
addGravatarBaseURL,
|
||||
@@ -25,18 +26,18 @@ func updateCount(x *xorm.Engine) error {
|
||||
inBoxData,
|
||||
}
|
||||
for _, fn := range fns {
|
||||
if err := fn(x); err != nil {
|
||||
if err := fn(ctx, x); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addGravatarBaseURL(x *xorm.Engine) error {
|
||||
func addGravatarBaseURL(ctx context.Context, x *xorm.Engine) error {
|
||||
usersSiteInfo := &entity.SiteInfo{
|
||||
Type: constant.SiteTypeUsers,
|
||||
}
|
||||
exist, err := x.Get(usersSiteInfo)
|
||||
exist, err := x.Context(ctx).Get(usersSiteInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
@@ -47,7 +48,7 @@ func addGravatarBaseURL(x *xorm.Engine) error {
|
||||
data, _ := json.Marshal(content)
|
||||
usersSiteInfo.Content = string(data)
|
||||
|
||||
_, err = x.ID(usersSiteInfo.ID).Cols("content").Update(usersSiteInfo)
|
||||
_, err = x.Context(ctx).ID(usersSiteInfo.ID).Cols("content").Update(usersSiteInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update site info failed: %w", err)
|
||||
}
|
||||
@@ -55,20 +56,20 @@ func addGravatarBaseURL(x *xorm.Engine) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func addPrivilegeForInviteSomeoneToAnswer(x *xorm.Engine) error {
|
||||
func addPrivilegeForInviteSomeoneToAnswer(ctx context.Context, x *xorm.Engine) error {
|
||||
// add rank for invite to answer
|
||||
powers := []*entity.Power{
|
||||
{ID: 38, Name: "invite someone to answer", PowerType: permission.AnswerInviteSomeoneToAnswer, Description: "invite someone to answer"},
|
||||
}
|
||||
for _, power := range powers {
|
||||
exist, err := x.Get(&entity.Power{PowerType: power.PowerType})
|
||||
exist, err := x.Context(ctx).Get(&entity.Power{PowerType: power.PowerType})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
_, err = x.ID(power.ID).Update(power)
|
||||
_, err = x.Context(ctx).ID(power.ID).Update(power)
|
||||
} else {
|
||||
_, err = x.Insert(power)
|
||||
_, err = x.Context(ctx).Insert(power)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -79,14 +80,14 @@ func addPrivilegeForInviteSomeoneToAnswer(x *xorm.Engine) error {
|
||||
{RoleID: 3, PowerType: permission.AnswerInviteSomeoneToAnswer},
|
||||
}
|
||||
for _, rel := range rolePowerRels {
|
||||
exist, err := x.Get(&entity.RolePowerRel{RoleID: rel.RoleID, PowerType: rel.PowerType})
|
||||
exist, err := x.Context(ctx).Get(&entity.RolePowerRel{RoleID: rel.RoleID, PowerType: rel.PowerType})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
continue
|
||||
}
|
||||
_, err = x.Insert(rel)
|
||||
_, err = x.Context(ctx).Insert(rel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -96,27 +97,27 @@ func addPrivilegeForInviteSomeoneToAnswer(x *xorm.Engine) error {
|
||||
{ID: 127, Key: "rank.answer.invite_someone_to_answer", Value: `1000`},
|
||||
}
|
||||
for _, c := range defaultConfigTable {
|
||||
exist, err := x.Get(&entity.Config{ID: c.ID})
|
||||
exist, err := x.Context(ctx).Get(&entity.Config{ID: c.ID})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if exist {
|
||||
if _, err = x.Update(c, &entity.Config{ID: c.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Update(c, &entity.Config{ID: c.ID}); err != nil {
|
||||
return fmt.Errorf("update config failed: %w", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err = x.Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
if _, err = x.Context(ctx).Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
return fmt.Errorf("add config failed: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateQuestionCount(x *xorm.Engine) error {
|
||||
func updateQuestionCount(ctx context.Context, x *xorm.Engine) error {
|
||||
//question answer count
|
||||
answers := make([]AnswerV13, 0)
|
||||
err := x.Find(&answers, &AnswerV13{Status: entity.AnswerStatusAvailable})
|
||||
err := x.Context(ctx).Find(&answers, &AnswerV13{Status: entity.AnswerStatusAvailable})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get answers failed: %w", err)
|
||||
}
|
||||
@@ -130,7 +131,7 @@ func updateQuestionCount(x *xorm.Engine) error {
|
||||
}
|
||||
}
|
||||
questionList := make([]QuestionV13, 0)
|
||||
err = x.Find(&questionList, &QuestionV13{})
|
||||
err = x.Context(ctx).Find(&questionList, &QuestionV13{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get questions failed: %w", err)
|
||||
}
|
||||
@@ -138,7 +139,7 @@ func updateQuestionCount(x *xorm.Engine) error {
|
||||
_, ok := questionAnswerCount[item.ID]
|
||||
if ok {
|
||||
item.AnswerCount = questionAnswerCount[item.ID]
|
||||
if _, err = x.Cols("answer_count").Update(item, &QuestionV13{ID: item.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Cols("answer_count").Update(item, &QuestionV13{ID: item.ID}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", item, err)
|
||||
return fmt.Errorf("update question failed: %w", err)
|
||||
}
|
||||
@@ -149,9 +150,9 @@ func updateQuestionCount(x *xorm.Engine) error {
|
||||
}
|
||||
|
||||
// updateTagCount update tag count
|
||||
func updateTagCount(x *xorm.Engine) error {
|
||||
func updateTagCount(ctx context.Context, x *xorm.Engine) error {
|
||||
tagRelList := make([]entity.TagRel, 0)
|
||||
err := x.Find(&tagRelList, &entity.TagRel{})
|
||||
err := x.Context(ctx).Find(&tagRelList, &entity.TagRel{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get tag rel failed: %w", err)
|
||||
}
|
||||
@@ -164,7 +165,7 @@ func updateTagCount(x *xorm.Engine) error {
|
||||
questionsHideMap[item.ObjectID] = false
|
||||
}
|
||||
questionList := make([]QuestionV13, 0)
|
||||
err = x.In("id", questionIDs).In("question.status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed}).Find(&questionList, &QuestionV13{})
|
||||
err = x.Context(ctx).In("id", questionIDs).In("question.status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed}).Find(&questionList, &QuestionV13{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get questions failed: %w", err)
|
||||
}
|
||||
@@ -180,7 +181,7 @@ func updateTagCount(x *xorm.Engine) error {
|
||||
|
||||
for id, ok := range questionsHideMap {
|
||||
if ok {
|
||||
if _, err = x.Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusHide}, &entity.TagRel{ObjectID: id}); err != nil {
|
||||
if _, err = x.Context(ctx).Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusHide}, &entity.TagRel{ObjectID: id}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", id, err)
|
||||
}
|
||||
}
|
||||
@@ -188,7 +189,7 @@ func updateTagCount(x *xorm.Engine) error {
|
||||
|
||||
for id, ok := range questionsAvailableMap {
|
||||
if !ok {
|
||||
if _, err = x.Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusDeleted}, &entity.TagRel{ObjectID: id}); err != nil {
|
||||
if _, err = x.Context(ctx).Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusDeleted}, &entity.TagRel{ObjectID: id}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", id, err)
|
||||
}
|
||||
}
|
||||
@@ -196,7 +197,7 @@ func updateTagCount(x *xorm.Engine) error {
|
||||
|
||||
//select tag count
|
||||
newTagRelList := make([]entity.TagRel, 0)
|
||||
err = x.Find(&newTagRelList, &entity.TagRel{Status: entity.TagRelStatusAvailable})
|
||||
err = x.Context(ctx).Find(&newTagRelList, &entity.TagRel{Status: entity.TagRelStatusAvailable})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get tag rel failed: %w", err)
|
||||
}
|
||||
@@ -210,7 +211,7 @@ func updateTagCount(x *xorm.Engine) error {
|
||||
}
|
||||
}
|
||||
TagList := make([]entity.Tag, 0)
|
||||
err = x.Find(&TagList, &entity.Tag{})
|
||||
err = x.Context(ctx).Find(&TagList, &entity.Tag{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get tag failed: %w", err)
|
||||
}
|
||||
@@ -218,13 +219,13 @@ func updateTagCount(x *xorm.Engine) error {
|
||||
_, ok := tagCountMap[tag.ID]
|
||||
if ok {
|
||||
tag.QuestionCount = tagCountMap[tag.ID]
|
||||
if _, err = x.Update(tag, &entity.Tag{ID: tag.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Update(tag, &entity.Tag{ID: tag.ID}); err != nil {
|
||||
log.Errorf("update %+v tag failed: %s", tag.ID, err)
|
||||
return fmt.Errorf("update tag failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
tag.QuestionCount = 0
|
||||
if _, err = x.Cols("question_count").Update(tag, &entity.Tag{ID: tag.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Cols("question_count").Update(tag, &entity.Tag{ID: tag.ID}); err != nil {
|
||||
log.Errorf("update %+v tag failed: %s", tag.ID, err)
|
||||
return fmt.Errorf("update tag failed: %w", err)
|
||||
}
|
||||
@@ -234,9 +235,9 @@ func updateTagCount(x *xorm.Engine) error {
|
||||
}
|
||||
|
||||
// updateUserQuestionCount update user question count
|
||||
func updateUserQuestionCount(x *xorm.Engine) error {
|
||||
func updateUserQuestionCount(ctx context.Context, x *xorm.Engine) error {
|
||||
questionList := make([]QuestionV13, 0)
|
||||
err := x.In("status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed}).Find(&questionList, &QuestionV13{})
|
||||
err := x.Context(ctx).In("status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed}).Find(&questionList, &QuestionV13{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get question failed: %w", err)
|
||||
}
|
||||
@@ -250,7 +251,7 @@ func updateUserQuestionCount(x *xorm.Engine) error {
|
||||
}
|
||||
}
|
||||
userList := make([]entity.User, 0)
|
||||
err = x.Find(&userList, &entity.User{})
|
||||
err = x.Context(ctx).Find(&userList, &entity.User{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get user failed: %w", err)
|
||||
}
|
||||
@@ -258,13 +259,13 @@ func updateUserQuestionCount(x *xorm.Engine) error {
|
||||
_, ok := userQuestionCountMap[user.ID]
|
||||
if ok {
|
||||
user.QuestionCount = userQuestionCountMap[user.ID]
|
||||
if _, err = x.Cols("question_count").Update(user, &entity.User{ID: user.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Cols("question_count").Update(user, &entity.User{ID: user.ID}); err != nil {
|
||||
log.Errorf("update %+v user failed: %s", user.ID, err)
|
||||
return fmt.Errorf("update user failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
user.QuestionCount = 0
|
||||
if _, err = x.Cols("question_count").Update(user, &entity.User{ID: user.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Cols("question_count").Update(user, &entity.User{ID: user.ID}); err != nil {
|
||||
log.Errorf("update %+v user failed: %s", user.ID, err)
|
||||
return fmt.Errorf("update user failed: %w", err)
|
||||
}
|
||||
@@ -286,9 +287,9 @@ func (AnswerV13) TableName() string {
|
||||
}
|
||||
|
||||
// updateUserAnswerCount update user answer count
|
||||
func updateUserAnswerCount(x *xorm.Engine) error {
|
||||
func updateUserAnswerCount(ctx context.Context, x *xorm.Engine) error {
|
||||
answers := make([]AnswerV13, 0)
|
||||
err := x.Find(&answers, &AnswerV13{Status: entity.AnswerStatusAvailable})
|
||||
err := x.Context(ctx).Find(&answers, &AnswerV13{Status: entity.AnswerStatusAvailable})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get answers failed: %w", err)
|
||||
}
|
||||
@@ -302,7 +303,7 @@ func updateUserAnswerCount(x *xorm.Engine) error {
|
||||
}
|
||||
}
|
||||
userList := make([]entity.User, 0)
|
||||
err = x.Find(&userList, &entity.User{})
|
||||
err = x.Context(ctx).Find(&userList, &entity.User{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get user failed: %w", err)
|
||||
}
|
||||
@@ -310,13 +311,13 @@ func updateUserAnswerCount(x *xorm.Engine) error {
|
||||
_, ok := userAnswerCount[user.ID]
|
||||
if ok {
|
||||
user.AnswerCount = userAnswerCount[user.ID]
|
||||
if _, err = x.Cols("answer_count").Update(user, &entity.User{ID: user.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Cols("answer_count").Update(user, &entity.User{ID: user.ID}); err != nil {
|
||||
log.Errorf("update %+v user failed: %s", user.ID, err)
|
||||
return fmt.Errorf("update user failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
user.AnswerCount = 0
|
||||
if _, err = x.Cols("answer_count").Update(user, &entity.User{ID: user.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Cols("answer_count").Update(user, &entity.User{ID: user.ID}); err != nil {
|
||||
log.Errorf("update %+v user failed: %s", user.ID, err)
|
||||
return fmt.Errorf("update user failed: %w", err)
|
||||
}
|
||||
@@ -354,8 +355,8 @@ func (QuestionV13) TableName() string {
|
||||
return "question"
|
||||
}
|
||||
|
||||
func inviteAnswer(x *xorm.Engine) error {
|
||||
err := x.Sync(new(QuestionV13))
|
||||
func inviteAnswer(ctx context.Context, x *xorm.Engine) error {
|
||||
err := x.Context(ctx).Sync(new(QuestionV13))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -363,7 +364,7 @@ func inviteAnswer(x *xorm.Engine) error {
|
||||
}
|
||||
|
||||
// inBoxData Classify messages
|
||||
func inBoxData(x *xorm.Engine) error {
|
||||
func inBoxData(ctx context.Context, x *xorm.Engine) error {
|
||||
type Notification struct {
|
||||
ID string `xorm:"not null pk autoincr BIGINT(20) id"`
|
||||
CreatedAt time.Time `xorm:"created TIMESTAMP created_at"`
|
||||
@@ -376,12 +377,12 @@ func inBoxData(x *xorm.Engine) error {
|
||||
IsRead int `xorm:"not null default 1 INT(11) is_read"`
|
||||
Status int `xorm:"not null default 1 INT(11) status"`
|
||||
}
|
||||
err := x.Sync(new(Notification))
|
||||
err := x.Context(ctx).Sync(new(Notification))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msglist := make([]entity.Notification, 0)
|
||||
err = x.Find(&msglist, &entity.Notification{})
|
||||
err = x.Context(ctx).Find(&msglist, &entity.Notification{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get Notification failed: %w", err)
|
||||
}
|
||||
@@ -394,7 +395,7 @@ func inBoxData(x *xorm.Engine) error {
|
||||
_, ok := constant.NotificationMsgTypeMapping[Content.NotificationAction]
|
||||
if ok {
|
||||
v.MsgType = constant.NotificationMsgTypeMapping[Content.NotificationAction]
|
||||
if _, err = x.Update(v, &entity.Notification{ID: v.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Update(v, &entity.Notification{ID: v.ID}); err != nil {
|
||||
log.Errorf("update %+v Notification failed: %s", v.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"xorm.io/xorm/schemas"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func updateTheLengthOfRevisionContent(ctx context.Context, x *xorm.Engine) (err error) {
|
||||
sess := x.Context(ctx)
|
||||
if x.Dialect().URI().DBType == schemas.MYSQL {
|
||||
_, err = sess.Exec("ALTER TABLE `revision` CHANGE `content` `content` MEDIUMTEXT NOT NULL;")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type RevisionV14 struct {
|
||||
ID string `xorm:"not null pk autoincr BIGINT(20) id"`
|
||||
CreatedAt time.Time `xorm:"created TIMESTAMP created_at"`
|
||||
UpdatedAt time.Time `xorm:"updated TIMESTAMP updated_at"`
|
||||
UserID string `xorm:"not null default 0 BIGINT(20) user_id"`
|
||||
ObjectType int `xorm:"not null default 0 INT(11) object_type"`
|
||||
ObjectID string `xorm:"not null default 0 BIGINT(20) INDEX object_id"`
|
||||
Title string `xorm:"not null default '' VARCHAR(255) title"`
|
||||
Content string `xorm:"not null MEDIUMTEXT content"`
|
||||
Log string `xorm:"VARCHAR(255) log"`
|
||||
Status int `xorm:"not null default 1 INT(11) status"`
|
||||
ReviewUserID int64 `xorm:"not null default 0 BIGINT(20) review_user_id"`
|
||||
}
|
||||
|
||||
func (RevisionV14) TableName() string {
|
||||
return "revision"
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addTagRecommendedAndReserved(x *xorm.Engine) error {
|
||||
func addTagRecommendedAndReserved(ctx context.Context, x *xorm.Engine) error {
|
||||
type Tag struct {
|
||||
ID string `xorm:"not null pk comment('tag_id') BIGINT(20) id"`
|
||||
SlugName string `xorm:"not null default '' unique VARCHAR(35) slug_name"`
|
||||
Recommend bool `xorm:"not null default false BOOL recommend"`
|
||||
Reserved bool `xorm:"not null default false BOOL reserved"`
|
||||
}
|
||||
return x.Sync(new(Tag))
|
||||
return x.Context(ctx).Sync(new(Tag))
|
||||
}
|
||||
|
||||
+13
-12
@@ -1,6 +1,7 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -10,28 +11,28 @@ import (
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
func addActivityTimeline(x *xorm.Engine) (err error) {
|
||||
func addActivityTimeline(ctx context.Context, x *xorm.Engine) (err error) {
|
||||
switch x.Dialect().URI().DBType {
|
||||
case schemas.MYSQL:
|
||||
_, err = x.Exec("ALTER TABLE `answer` CHANGE `updated_at` `updated_at` TIMESTAMP NULL DEFAULT NULL")
|
||||
_, err = x.Context(ctx).Exec("ALTER TABLE `answer` CHANGE `updated_at` `updated_at` TIMESTAMP NULL DEFAULT NULL")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = x.Exec("ALTER TABLE `question` CHANGE `updated_at` `updated_at` TIMESTAMP NULL DEFAULT NULL")
|
||||
_, err = x.Context(ctx).Exec("ALTER TABLE `question` CHANGE `updated_at` `updated_at` TIMESTAMP NULL DEFAULT NULL")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case schemas.POSTGRES:
|
||||
_, err = x.Exec(`ALTER TABLE "answer" ALTER COLUMN "updated_at" DROP NOT NULL, ALTER COLUMN "updated_at" SET DEFAULT NULL`)
|
||||
_, err = x.Context(ctx).Exec(`ALTER TABLE "answer" ALTER COLUMN "updated_at" DROP NOT NULL, ALTER COLUMN "updated_at" SET DEFAULT NULL`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = x.Exec(`ALTER TABLE "question" ALTER COLUMN "updated_at" DROP NOT NULL, ALTER COLUMN "updated_at" SET DEFAULT NULL`)
|
||||
_, err = x.Context(ctx).Exec(`ALTER TABLE "question" ALTER COLUMN "updated_at" DROP NOT NULL, ALTER COLUMN "updated_at" SET DEFAULT NULL`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case schemas.SQLITE:
|
||||
_, err = x.Exec(`DROP INDEX "IDX_answer_user_id";
|
||||
_, err = x.Context(ctx).Exec(`DROP INDEX "IDX_answer_user_id";
|
||||
|
||||
ALTER TABLE "answer" RENAME TO "_answer_old_v3";
|
||||
|
||||
@@ -98,7 +99,7 @@ ON "question" (
|
||||
ID int `xorm:"not null pk autoincr INT(11) id"`
|
||||
Key string `xorm:"unique VARCHAR(128) key"`
|
||||
}
|
||||
if err := x.Sync(new(Config)); err != nil {
|
||||
if err := x.Context(ctx).Sync(new(Config)); err != nil {
|
||||
return fmt.Errorf("sync config table failed: %w", err)
|
||||
}
|
||||
defaultConfigTable := []*entity.Config{
|
||||
@@ -117,7 +118,7 @@ ON "question" (
|
||||
{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: 51, Key: "rank.tag.add", Value: `1500`},
|
||||
{ID: 52, Key: "rank.tag.edit", Value: `100`},
|
||||
{ID: 53, Key: "rank.tag.delete", Value: `-1`},
|
||||
{ID: 54, Key: "rank.tag.synonym", Value: `20000`},
|
||||
@@ -155,18 +156,18 @@ ON "question" (
|
||||
{ID: 114, Key: "rank.tag.audit", Value: `20000`},
|
||||
}
|
||||
for _, c := range defaultConfigTable {
|
||||
exist, err := x.Get(&entity.Config{ID: c.ID, Key: c.Key})
|
||||
exist, err := x.Context(ctx).Get(&entity.Config{ID: c.ID, Key: c.Key})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if exist {
|
||||
if _, err = x.Update(c, &entity.Config{ID: c.ID, Key: c.Key}); err != nil {
|
||||
if _, err = x.Context(ctx).Update(c, &entity.Config{ID: c.ID, Key: c.Key}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("update config failed: %w", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err = x.Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
if _, err = x.Context(ctx).Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
log.Errorf("insert %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("add config failed: %w", err)
|
||||
}
|
||||
@@ -205,7 +206,7 @@ ON "question" (
|
||||
LastEditUserID string `xorm:"not null default 0 BIGINT(20) last_edit_user_id"`
|
||||
}
|
||||
|
||||
err = x.Sync(new(Activity), new(Revision), new(Tag), new(Question), new(Answer))
|
||||
err = x.Context(ctx).Sync(new(Activity), new(Revision), new(Tag), new(Question), new(Answer))
|
||||
if err != nil {
|
||||
return fmt.Errorf("sync table failed %w", err)
|
||||
}
|
||||
|
||||
+15
-14
@@ -1,6 +1,7 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
@@ -9,8 +10,8 @@ import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addRoleFeatures(x *xorm.Engine) error {
|
||||
err := x.Sync(new(entity.Role), new(entity.RolePowerRel), new(entity.Power), new(entity.UserRoleRel))
|
||||
func addRoleFeatures(ctx context.Context, x *xorm.Engine) error {
|
||||
err := x.Context(ctx).Sync(new(entity.Role), new(entity.RolePowerRel), new(entity.Power), new(entity.UserRoleRel))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -23,14 +24,14 @@ func addRoleFeatures(x *xorm.Engine) error {
|
||||
|
||||
// insert default roles
|
||||
for _, role := range roles {
|
||||
exist, err := x.Get(&entity.Role{ID: role.ID, Name: role.Name})
|
||||
exist, err := x.Context(ctx).Get(&entity.Role{ID: role.ID, Name: role.Name})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
continue
|
||||
}
|
||||
_, err = x.Insert(role)
|
||||
_, err = x.Context(ctx).Insert(role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -73,14 +74,14 @@ func addRoleFeatures(x *xorm.Engine) error {
|
||||
}
|
||||
// insert default powers
|
||||
for _, power := range powers {
|
||||
exist, err := x.Get(&entity.Power{ID: power.ID})
|
||||
exist, err := x.Context(ctx).Get(&entity.Power{ID: power.ID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
_, err = x.ID(power.ID).Update(power)
|
||||
_, err = x.Context(ctx).ID(power.ID).Update(power)
|
||||
} else {
|
||||
_, err = x.Insert(power)
|
||||
_, err = x.Context(ctx).Insert(power)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -160,14 +161,14 @@ func addRoleFeatures(x *xorm.Engine) error {
|
||||
|
||||
// insert default powers
|
||||
for _, rel := range rolePowerRels {
|
||||
exist, err := x.Get(&entity.RolePowerRel{RoleID: rel.RoleID, PowerType: rel.PowerType})
|
||||
exist, err := x.Context(ctx).Get(&entity.RolePowerRel{RoleID: rel.RoleID, PowerType: rel.PowerType})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
continue
|
||||
}
|
||||
_, err = x.Insert(rel)
|
||||
_, err = x.Context(ctx).Insert(rel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -178,12 +179,12 @@ func addRoleFeatures(x *xorm.Engine) error {
|
||||
RoleID: 2,
|
||||
}
|
||||
|
||||
exist, err := x.Get(adminUserRoleRel)
|
||||
exist, err := x.Context(ctx).Get(adminUserRoleRel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exist {
|
||||
_, err = x.Insert(adminUserRoleRel)
|
||||
_, err = x.Context(ctx).Insert(adminUserRoleRel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -195,18 +196,18 @@ func addRoleFeatures(x *xorm.Engine) error {
|
||||
{ID: 117, Key: "rank.tag.use_reserved_tag", Value: `-1`},
|
||||
}
|
||||
for _, c := range defaultConfigTable {
|
||||
exist, err := x.Get(&entity.Config{ID: c.ID, Key: c.Key})
|
||||
exist, err := x.Context(ctx).Get(&entity.Config{ID: c.ID, Key: c.Key})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if exist {
|
||||
if _, err = x.Update(c, &entity.Config{ID: c.ID, Key: c.Key}); err != nil {
|
||||
if _, err = x.Context(ctx).Update(c, &entity.Config{ID: c.ID, Key: c.Key}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("update config failed: %w", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err = x.Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
if _, err = x.Context(ctx).Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
log.Errorf("insert %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("add config failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
@@ -8,7 +9,7 @@ import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addThemeAndPrivateMode(x *xorm.Engine) error {
|
||||
func addThemeAndPrivateMode(ctx context.Context, x *xorm.Engine) error {
|
||||
loginConfig := map[string]bool{
|
||||
"allow_new_registrations": true,
|
||||
"login_required": false,
|
||||
@@ -19,12 +20,12 @@ func addThemeAndPrivateMode(x *xorm.Engine) error {
|
||||
Content: string(loginConfigDataBytes),
|
||||
Status: 1,
|
||||
}
|
||||
exist, err := x.Get(&entity.SiteInfo{Type: siteInfo.Type})
|
||||
exist, err := x.Context(ctx).Get(&entity.SiteInfo{Type: siteInfo.Type})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if !exist {
|
||||
_, err = x.InsertOne(siteInfo)
|
||||
_, err = x.Context(ctx).Insert(siteInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("insert site info failed: %w", err)
|
||||
}
|
||||
@@ -36,12 +37,12 @@ func addThemeAndPrivateMode(x *xorm.Engine) error {
|
||||
Content: themeConfig,
|
||||
Status: 1,
|
||||
}
|
||||
exist, err = x.Get(&entity.SiteInfo{Type: themeSiteInfo.Type})
|
||||
exist, err = x.Context(ctx).Get(&entity.SiteInfo{Type: themeSiteInfo.Type})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if !exist {
|
||||
_, err = x.InsertOne(themeSiteInfo)
|
||||
_, err = x.Context(ctx).Insert(themeSiteInfo)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
@@ -8,15 +9,15 @@ import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addNewAnswerNotification(x *xorm.Engine) error {
|
||||
func addNewAnswerNotification(ctx context.Context, x *xorm.Engine) error {
|
||||
cond := &entity.Config{Key: "email.config"}
|
||||
exists, err := x.Get(cond)
|
||||
exists, err := x.Context(ctx).Get(cond)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get email config failed: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
// This should be impossible except that the config was deleted manually by user.
|
||||
_, err = x.InsertOne(&entity.Config{
|
||||
_, err = x.Context(ctx).Insert(&entity.Config{
|
||||
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>"}`,
|
||||
})
|
||||
@@ -33,7 +34,7 @@ func addNewAnswerNotification(x *xorm.Engine) error {
|
||||
m["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>"
|
||||
|
||||
val, _ := json.Marshal(m)
|
||||
_, err = x.ID(cond.ID).Update(&entity.Config{Value: string(val)})
|
||||
_, err = x.Context(ctx).ID(cond.ID).Update(&entity.Config{Value: string(val)})
|
||||
if err != nil {
|
||||
return fmt.Errorf("update email config failed: %v", err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
@@ -8,23 +9,23 @@ import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addPlugin(x *xorm.Engine) error {
|
||||
func addPlugin(ctx context.Context, x *xorm.Engine) error {
|
||||
defaultConfigTable := []*entity.Config{
|
||||
{ID: 118, Key: "plugin.status", Value: `{}`},
|
||||
}
|
||||
for _, c := range defaultConfigTable {
|
||||
exist, err := x.Get(&entity.Config{ID: c.ID, Key: c.Key})
|
||||
exist, err := x.Context(ctx).Get(&entity.Config{ID: c.ID, Key: c.Key})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if exist {
|
||||
continue
|
||||
}
|
||||
if _, err = x.Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
if _, err = x.Context(ctx).Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
log.Errorf("insert %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("add config failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return x.Sync(new(entity.PluginConfig), new(entity.UserExternalLogin))
|
||||
return x.Context(ctx).Sync(new(entity.PluginConfig), new(entity.UserExternalLogin))
|
||||
}
|
||||
|
||||
+11
-11
@@ -1,6 +1,7 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -10,8 +11,7 @@ import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addRolePinAndHideFeatures(x *xorm.Engine) error {
|
||||
|
||||
func addRolePinAndHideFeatures(ctx context.Context, x *xorm.Engine) error {
|
||||
powers := []*entity.Power{
|
||||
{ID: 34, Name: "question pin", PowerType: permission.QuestionPin, Description: "top the question"},
|
||||
{ID: 35, Name: "question hide", PowerType: permission.QuestionHide, Description: "hide the question"},
|
||||
@@ -20,14 +20,14 @@ func addRolePinAndHideFeatures(x *xorm.Engine) error {
|
||||
}
|
||||
// insert default powers
|
||||
for _, power := range powers {
|
||||
exist, err := x.Get(&entity.Power{ID: power.ID})
|
||||
exist, err := x.Context(ctx).Get(&entity.Power{ID: power.ID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
_, err = x.ID(power.ID).Update(power)
|
||||
_, err = x.Context(ctx).ID(power.ID).Update(power)
|
||||
} else {
|
||||
_, err = x.Insert(power)
|
||||
_, err = x.Context(ctx).Insert(power)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -49,14 +49,14 @@ func addRolePinAndHideFeatures(x *xorm.Engine) error {
|
||||
|
||||
// insert default powers
|
||||
for _, rel := range rolePowerRels {
|
||||
exist, err := x.Get(&entity.RolePowerRel{RoleID: rel.RoleID, PowerType: rel.PowerType})
|
||||
exist, err := x.Context(ctx).Get(&entity.RolePowerRel{RoleID: rel.RoleID, PowerType: rel.PowerType})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
continue
|
||||
}
|
||||
_, err = x.Insert(rel)
|
||||
_, err = x.Context(ctx).Insert(rel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -73,18 +73,18 @@ func addRolePinAndHideFeatures(x *xorm.Engine) error {
|
||||
{ID: 126, Key: "rank.question.hide", Value: `-1`},
|
||||
}
|
||||
for _, c := range defaultConfigTable {
|
||||
exist, err := x.Get(&entity.Config{ID: c.ID})
|
||||
exist, err := x.Context(ctx).Get(&entity.Config{ID: c.ID})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if exist {
|
||||
if _, err = x.Update(c, &entity.Config{ID: c.ID}); err != nil {
|
||||
if _, err = x.Context(ctx).Update(c, &entity.Config{ID: c.ID}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("update config failed: %w", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err = x.Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
if _, err = x.Context(ctx).Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
log.Errorf("insert %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("add config failed: %w", err)
|
||||
}
|
||||
@@ -113,7 +113,7 @@ func addRolePinAndHideFeatures(x *xorm.Engine) error {
|
||||
PostUpdateTime time.Time `xorm:"post_update_time TIMESTAMP"`
|
||||
RevisionID string `xorm:"not null default 0 BIGINT(20) revision_id"`
|
||||
}
|
||||
err := x.Sync(new(Question))
|
||||
err := x.Context(ctx).Sync(new(Question))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
@@ -8,9 +9,9 @@ import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func updateAcceptAnswerRank(x *xorm.Engine) error {
|
||||
func updateAcceptAnswerRank(ctx context.Context, x *xorm.Engine) error {
|
||||
c := &entity.Config{ID: 44, Key: "rank.answer.accept", Value: `-1`}
|
||||
if _, err := x.Update(c, &entity.Config{ID: 44, Key: "rank.answer.accept"}); err != nil {
|
||||
if _, err := x.Context(ctx).Update(c, &entity.Config{ID: 44, Key: "rank.answer.accept"}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("update config failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ package activity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"time"
|
||||
"xorm.io/builder"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/constant"
|
||||
"github.com/answerdev/answer/internal/base/data"
|
||||
@@ -15,343 +18,344 @@ import (
|
||||
"github.com/answerdev/answer/internal/service/rank"
|
||||
"github.com/answerdev/answer/pkg/converter"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// AnswerActivityRepo answer accepted
|
||||
type AnswerActivityRepo struct {
|
||||
data *data.Data
|
||||
activityRepo activity_common.ActivityRepo
|
||||
userRankRepo rank.UserRankRepo
|
||||
data *data.Data
|
||||
activityRepo activity_common.ActivityRepo
|
||||
userRankRepo rank.UserRankRepo
|
||||
notificationQueueService notice_queue.NotificationQueueService
|
||||
}
|
||||
|
||||
const (
|
||||
acceptAction = "accept"
|
||||
acceptedAction = "accepted"
|
||||
)
|
||||
|
||||
var (
|
||||
acceptActionList = []string{acceptAction, acceptedAction}
|
||||
)
|
||||
|
||||
// NewAnswerActivityRepo new repository
|
||||
func NewAnswerActivityRepo(
|
||||
data *data.Data,
|
||||
activityRepo activity_common.ActivityRepo,
|
||||
userRankRepo rank.UserRankRepo,
|
||||
notificationQueueService notice_queue.NotificationQueueService,
|
||||
) activity.AnswerActivityRepo {
|
||||
return &AnswerActivityRepo{
|
||||
data: data,
|
||||
activityRepo: activityRepo,
|
||||
userRankRepo: userRankRepo,
|
||||
data: data,
|
||||
activityRepo: activityRepo,
|
||||
userRankRepo: userRankRepo,
|
||||
notificationQueueService: notificationQueueService,
|
||||
}
|
||||
}
|
||||
|
||||
// NewQuestionActivityRepo new repository
|
||||
func NewQuestionActivityRepo(
|
||||
data *data.Data,
|
||||
activityRepo activity_common.ActivityRepo,
|
||||
userRankRepo rank.UserRankRepo,
|
||||
) activity.QuestionActivityRepo {
|
||||
return &AnswerActivityRepo{
|
||||
data: data,
|
||||
activityRepo: activityRepo,
|
||||
userRankRepo: userRankRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (ar *AnswerActivityRepo) DeleteQuestion(ctx context.Context, questionID string) (err error) {
|
||||
questionInfo := &entity.Question{}
|
||||
exist, err := ar.data.DB.Context(ctx).Where("id = ?", questionID).Get(questionInfo)
|
||||
func (ar *AnswerActivityRepo) SaveAcceptAnswerActivity(ctx context.Context, op *schema.AcceptAnswerOperationInfo) (
|
||||
err error) {
|
||||
// pre check
|
||||
noNeedToDo, err := ar.activityPreCheck(ctx, op)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
return err
|
||||
}
|
||||
if !exist {
|
||||
if noNeedToDo {
|
||||
return nil
|
||||
}
|
||||
|
||||
// get all this object activity
|
||||
activityList := make([]*entity.Activity, 0)
|
||||
session := ar.data.DB.Context(ctx).Where("has_rank = 1")
|
||||
session.Where("cancelled = ?", entity.ActivityAvailable)
|
||||
err = session.Find(&activityList, &entity.Activity{ObjectID: questionID})
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
if len(activityList) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Infof("questionInfo %s deleted will rollback activity %d", questionID, len(activityList))
|
||||
|
||||
ar.data.DB.ShowSQL(true)
|
||||
// save activity
|
||||
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||
session = session.Context(ctx)
|
||||
for _, act := range activityList {
|
||||
log.Infof("user %s rollback rank %d", act.UserID, -act.Rank)
|
||||
_, e := ar.userRankRepo.TriggerUserRank(
|
||||
ctx, session, act.UserID, -act.Rank, act.ActivityType)
|
||||
if e != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||
}
|
||||
|
||||
if _, e := session.Where("id = ?", act.ID).Cols("cancelled", "cancelled_at").
|
||||
Update(&entity.Activity{Cancelled: entity.ActivityCancelled, CancelledAt: time.Now()}); e != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||
}
|
||||
userInfoMapping, err := ar.acquireUserInfo(session, op.GetUserIDs())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ar.saveActivitiesAvailable(session, op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ar.changeUserRank(ctx, session, op, userInfoMapping)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
|
||||
// get all answers
|
||||
answerList := make([]*entity.Answer, 0)
|
||||
err = ar.data.DB.Context(ctx).Find(&answerList, &entity.Answer{QuestionID: questionID})
|
||||
// notification
|
||||
ar.sendAcceptAnswerNotification(ctx, op)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ar *AnswerActivityRepo) SaveCancelAcceptAnswerActivity(ctx context.Context, op *schema.AcceptAnswerOperationInfo) (
|
||||
err error) {
|
||||
// pre check
|
||||
activities, err := ar.getExistActivity(ctx, op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var userIDs []string
|
||||
for _, act := range activities {
|
||||
if act.Cancelled == entity.ActivityCancelled {
|
||||
continue
|
||||
}
|
||||
userIDs = append(userIDs, act.UserID)
|
||||
}
|
||||
if len(userIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// save activity
|
||||
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||
session = session.Context(ctx)
|
||||
|
||||
userInfoMapping, err := ar.acquireUserInfo(session, userIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ar.cancelActivities(session, activities)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ar.rollbackUserRank(ctx, session, activities, userInfoMapping)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
for _, answerInfo := range answerList {
|
||||
err = ar.DeleteAnswer(ctx, answerInfo.ID)
|
||||
|
||||
// notification
|
||||
ar.sendCancelAcceptAnswerNotification(ctx, op)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ar *AnswerActivityRepo) activityPreCheck(ctx context.Context, op *schema.AcceptAnswerOperationInfo) (
|
||||
noNeedToDo bool, err error) {
|
||||
activities, err := ar.getExistActivity(ctx, op)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
done := 0
|
||||
for _, act := range activities {
|
||||
if act.Cancelled == entity.ActivityAvailable {
|
||||
done++
|
||||
}
|
||||
}
|
||||
return done == len(op.Activities), nil
|
||||
}
|
||||
|
||||
func (ar *AnswerActivityRepo) acquireUserInfo(session *xorm.Session, userIDs []string) (map[string]*entity.User, error) {
|
||||
us := make([]*entity.User, 0)
|
||||
err := session.In("id", userIDs).ForUpdate().Find(&us)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users := make(map[string]*entity.User, 0)
|
||||
for _, u := range us {
|
||||
users[u.ID] = u
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// saveActivitiesAvailable save activities
|
||||
// If activity not exist it will be created or else will be updated
|
||||
// If this activity is already exist, set activity rank to 0
|
||||
// So after this function, the activity rank will be correct for update user rank
|
||||
func (ar *AnswerActivityRepo) saveActivitiesAvailable(session *xorm.Session, op *schema.AcceptAnswerOperationInfo) (
|
||||
err error) {
|
||||
for _, act := range op.Activities {
|
||||
existsActivity := &entity.Activity{}
|
||||
exist, err := session.
|
||||
Where(builder.Eq{"object_id": op.AnswerObjectID}).
|
||||
And(builder.Eq{"user_id": act.ActivityUserID}).
|
||||
And(builder.Eq{"trigger_user_id": act.TriggerUserID}).
|
||||
And(builder.Eq{"activity_type": act.ActivityType}).
|
||||
Get(existsActivity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist && existsActivity.Cancelled == entity.ActivityAvailable {
|
||||
act.Rank = 0
|
||||
continue
|
||||
}
|
||||
if exist {
|
||||
bean := &entity.Activity{
|
||||
Cancelled: entity.ActivityAvailable,
|
||||
Rank: act.Rank,
|
||||
HasRank: act.HasRank(),
|
||||
}
|
||||
session.Where("id = ?", existsActivity.ID)
|
||||
if _, err = session.Cols("`cancelled`", "`rank`", "`has_rank`").Update(bean); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
insertActivity := entity.Activity{
|
||||
ObjectID: op.AnswerObjectID,
|
||||
OriginalObjectID: act.OriginalObjectID,
|
||||
UserID: act.ActivityUserID,
|
||||
TriggerUserID: converter.StringToInt64(act.TriggerUserID),
|
||||
ActivityType: act.ActivityType,
|
||||
Rank: act.Rank,
|
||||
HasRank: act.HasRank(),
|
||||
Cancelled: entity.ActivityAvailable,
|
||||
}
|
||||
_, err = session.Insert(&insertActivity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// cancelActivities cancel activities
|
||||
// If this activity is already cancelled, set activity rank to 0
|
||||
// So after this function, the activity rank will be correct for update user rank
|
||||
func (ar *AnswerActivityRepo) cancelActivities(session *xorm.Session, activities []*entity.Activity) (err error) {
|
||||
for _, act := range activities {
|
||||
t := &entity.Activity{}
|
||||
exist, err := session.ID(act.ID).Get(t)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
if !exist {
|
||||
log.Error(fmt.Errorf("%s activity not exist", act.ID))
|
||||
return fmt.Errorf("%s activity not exist", act.ID)
|
||||
}
|
||||
// If this activity is already cancelled, set activity rank to 0
|
||||
if t.Cancelled == entity.ActivityCancelled {
|
||||
act.Rank = 0
|
||||
}
|
||||
if _, err = session.ID(act.ID).Cols("cancelled", "cancelled_at").
|
||||
Update(&entity.Activity{
|
||||
Cancelled: entity.ActivityCancelled,
|
||||
CancelledAt: time.Now(),
|
||||
}); err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// AcceptAnswer accept other answer
|
||||
func (ar *AnswerActivityRepo) AcceptAnswer(ctx context.Context,
|
||||
answerObjID, questionObjID, questionUserID, answerUserID string, isSelf bool,
|
||||
) (err error) {
|
||||
addActivityList := make([]*entity.Activity, 0)
|
||||
for _, action := range acceptActionList {
|
||||
// get accept answer need add rank amount
|
||||
activityType, deltaRank, hasRank, e := ar.activityRepo.GetActivityTypeByObjID(ctx, answerObjID, action)
|
||||
if e != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||
func (ar *AnswerActivityRepo) changeUserRank(ctx context.Context, session *xorm.Session,
|
||||
op *schema.AcceptAnswerOperationInfo,
|
||||
userInfoMapping map[string]*entity.User) (err error) {
|
||||
for _, act := range op.Activities {
|
||||
if act.Rank == 0 {
|
||||
continue
|
||||
}
|
||||
addActivity := &entity.Activity{
|
||||
ObjectID: answerObjID,
|
||||
OriginalObjectID: questionObjID,
|
||||
ActivityType: activityType,
|
||||
Rank: deltaRank,
|
||||
HasRank: hasRank,
|
||||
user := userInfoMapping[act.ActivityUserID]
|
||||
if user == nil {
|
||||
continue
|
||||
}
|
||||
if action == acceptAction {
|
||||
addActivity.UserID = questionUserID
|
||||
addActivity.TriggerUserID = converter.StringToInt64(answerUserID)
|
||||
addActivity.OriginalObjectID = questionObjID // if activity is 'accept' means this question is accept the answer.
|
||||
} else {
|
||||
addActivity.UserID = answerUserID
|
||||
addActivity.TriggerUserID = converter.StringToInt64(answerUserID)
|
||||
addActivity.OriginalObjectID = answerObjID // if activity is 'accepted' means this answer was accepted.
|
||||
if err = ar.userRankRepo.ChangeUserRank(ctx, session,
|
||||
act.ActivityUserID, user.Rank, act.Rank); err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
if isSelf {
|
||||
addActivity.Rank = 0
|
||||
addActivity.HasRank = 0
|
||||
}
|
||||
addActivityList = append(addActivityList, addActivity)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||
session = session.Context(ctx)
|
||||
for _, addActivity := range addActivityList {
|
||||
existsActivity, exists, e := ar.activityRepo.GetActivity(
|
||||
ctx, session, answerObjID, addActivity.UserID, addActivity.ActivityType)
|
||||
if e != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||
}
|
||||
if exists && existsActivity.Cancelled == entity.ActivityAvailable {
|
||||
continue
|
||||
}
|
||||
|
||||
// trigger user rank and send notification
|
||||
if addActivity.Rank != 0 {
|
||||
reachStandard, e := ar.userRankRepo.TriggerUserRank(
|
||||
ctx, session, addActivity.UserID, addActivity.Rank, addActivity.ActivityType)
|
||||
if e != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||
}
|
||||
if reachStandard {
|
||||
addActivity.Rank = 0
|
||||
}
|
||||
}
|
||||
|
||||
if exists {
|
||||
if _, e = session.Where("id = ?", existsActivity.ID).Cols("`cancelled`").
|
||||
Update(&entity.Activity{Cancelled: entity.ActivityAvailable}); e != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||
}
|
||||
} else {
|
||||
if _, e = session.Insert(addActivity); e != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||
}
|
||||
}
|
||||
func (ar *AnswerActivityRepo) rollbackUserRank(ctx context.Context, session *xorm.Session,
|
||||
activities []*entity.Activity,
|
||||
userInfoMapping map[string]*entity.User) (err error) {
|
||||
for _, act := range activities {
|
||||
if act.Rank == 0 {
|
||||
continue
|
||||
}
|
||||
user := userInfoMapping[act.UserID]
|
||||
if user == nil {
|
||||
continue
|
||||
}
|
||||
if err = ar.userRankRepo.ChangeUserRank(ctx, session,
|
||||
act.UserID, user.Rank, -act.Rank); err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, act := range addActivityList {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ar *AnswerActivityRepo) getExistActivity(ctx context.Context, op *schema.AcceptAnswerOperationInfo) ([]*entity.Activity, error) {
|
||||
var activities []*entity.Activity
|
||||
for _, action := range op.Activities {
|
||||
t := &entity.Activity{}
|
||||
exist, err := ar.data.DB.Context(ctx).
|
||||
Where(builder.Eq{"user_id": action.ActivityUserID}).
|
||||
And(builder.Eq{"trigger_user_id": action.TriggerUserID}).
|
||||
And(builder.Eq{"activity_type": action.ActivityType}).
|
||||
And(builder.Eq{"object_id": op.AnswerObjectID}).
|
||||
Get(t)
|
||||
if err != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
if exist {
|
||||
activities = append(activities, t)
|
||||
}
|
||||
}
|
||||
return activities, nil
|
||||
}
|
||||
|
||||
func (ar *AnswerActivityRepo) sendAcceptAnswerNotification(
|
||||
ctx context.Context, op *schema.AcceptAnswerOperationInfo) {
|
||||
for _, act := range op.Activities {
|
||||
msg := &schema.NotificationMsg{
|
||||
Type: schema.NotificationTypeAchievement,
|
||||
ObjectID: act.ObjectID,
|
||||
ReceiverUserID: act.UserID,
|
||||
ObjectID: op.AnswerObjectID,
|
||||
ReceiverUserID: act.ActivityUserID,
|
||||
}
|
||||
if act.UserID == questionUserID {
|
||||
msg.TriggerUserID = answerUserID
|
||||
if act.ActivityUserID == op.QuestionUserID {
|
||||
msg.TriggerUserID = op.AnswerUserID
|
||||
msg.ObjectType = constant.AnswerObjectType
|
||||
} else {
|
||||
msg.TriggerUserID = questionUserID
|
||||
msg.TriggerUserID = op.QuestionUserID
|
||||
msg.ObjectType = constant.AnswerObjectType
|
||||
}
|
||||
if msg.TriggerUserID != msg.ReceiverUserID {
|
||||
notice_queue.AddNotification(msg)
|
||||
ar.notificationQueueService.Send(ctx, msg)
|
||||
}
|
||||
}
|
||||
|
||||
for _, act := range addActivityList {
|
||||
for _, act := range op.Activities {
|
||||
msg := &schema.NotificationMsg{
|
||||
ReceiverUserID: act.UserID,
|
||||
ReceiverUserID: act.ActivityUserID,
|
||||
Type: schema.NotificationTypeInbox,
|
||||
ObjectID: act.ObjectID,
|
||||
ObjectID: op.AnswerObjectID,
|
||||
}
|
||||
if act.UserID != questionUserID {
|
||||
msg.TriggerUserID = questionUserID
|
||||
if act.ActivityUserID != op.QuestionUserID {
|
||||
msg.TriggerUserID = op.QuestionUserID
|
||||
msg.ObjectType = constant.AnswerObjectType
|
||||
msg.NotificationAction = constant.NotificationAcceptAnswer
|
||||
notice_queue.AddNotification(msg)
|
||||
ar.notificationQueueService.Send(ctx, msg)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// CancelAcceptAnswer accept other answer
|
||||
func (ar *AnswerActivityRepo) CancelAcceptAnswer(ctx context.Context,
|
||||
answerObjID, questionObjID, questionUserID, answerUserID string,
|
||||
) (err error) {
|
||||
addActivityList := make([]*entity.Activity, 0)
|
||||
for _, action := range acceptActionList {
|
||||
// get accept answer need add rank amount
|
||||
activityType, deltaRank, hasRank, e := ar.activityRepo.GetActivityTypeByObjID(ctx, answerObjID, action)
|
||||
if e != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||
}
|
||||
addActivity := &entity.Activity{
|
||||
ObjectID: answerObjID,
|
||||
ActivityType: activityType,
|
||||
Rank: -deltaRank,
|
||||
HasRank: hasRank,
|
||||
}
|
||||
if action == acceptAction {
|
||||
addActivity.UserID = questionUserID
|
||||
addActivity.OriginalObjectID = questionObjID
|
||||
} else {
|
||||
addActivity.UserID = answerUserID
|
||||
addActivity.OriginalObjectID = answerObjID
|
||||
}
|
||||
addActivityList = append(addActivityList, addActivity)
|
||||
}
|
||||
|
||||
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||
session = session.Context(ctx)
|
||||
for _, addActivity := range addActivityList {
|
||||
existsActivity, exists, e := ar.activityRepo.GetActivity(
|
||||
ctx, session, answerObjID, addActivity.UserID, addActivity.ActivityType)
|
||||
if e != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||
}
|
||||
if exists && existsActivity.Cancelled == entity.ActivityCancelled {
|
||||
continue
|
||||
}
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
if existsActivity.Rank != 0 {
|
||||
_, e = ar.userRankRepo.TriggerUserRank(
|
||||
ctx, session, addActivity.UserID, addActivity.Rank, addActivity.ActivityType)
|
||||
if e != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||
}
|
||||
}
|
||||
|
||||
if _, e := session.Where("id = ?", existsActivity.ID).Cols("cancelled", "cancelled_at").
|
||||
Update(&entity.Activity{Cancelled: entity.ActivityCancelled, CancelledAt: time.Now()}); e != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, act := range addActivityList {
|
||||
func (ar *AnswerActivityRepo) sendCancelAcceptAnswerNotification(
|
||||
ctx context.Context, op *schema.AcceptAnswerOperationInfo) {
|
||||
for _, act := range op.Activities {
|
||||
msg := &schema.NotificationMsg{
|
||||
ReceiverUserID: act.UserID,
|
||||
ReceiverUserID: act.ActivityUserID,
|
||||
Type: schema.NotificationTypeAchievement,
|
||||
ObjectID: act.ObjectID,
|
||||
ObjectID: op.AnswerObjectID,
|
||||
}
|
||||
if act.UserID == questionUserID {
|
||||
msg.TriggerUserID = answerUserID
|
||||
if act.ActivityUserID == op.QuestionObjectID {
|
||||
msg.TriggerUserID = op.AnswerObjectID
|
||||
msg.ObjectType = constant.QuestionObjectType
|
||||
} else {
|
||||
msg.TriggerUserID = questionUserID
|
||||
msg.TriggerUserID = op.QuestionObjectID
|
||||
msg.ObjectType = constant.AnswerObjectType
|
||||
}
|
||||
if msg.TriggerUserID != msg.ReceiverUserID {
|
||||
notice_queue.AddNotification(msg)
|
||||
ar.notificationQueueService.Send(ctx, msg)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (ar *AnswerActivityRepo) DeleteAnswer(ctx context.Context, answerID string) (err error) {
|
||||
answerInfo := &entity.Answer{}
|
||||
exist, err := ar.data.DB.Context(ctx).Where("id = ?", answerID).Get(answerInfo)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
if !exist {
|
||||
return nil
|
||||
}
|
||||
|
||||
// get all this object activity
|
||||
activityList := make([]*entity.Activity, 0)
|
||||
session := ar.data.DB.Context(ctx).Where("has_rank = 1")
|
||||
session.Where("cancelled = ?", entity.ActivityAvailable)
|
||||
err = session.Find(&activityList, &entity.Activity{ObjectID: answerID})
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
if len(activityList) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Infof("answerInfo %s deleted will rollback activity %d", answerID, len(activityList))
|
||||
|
||||
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||
session = session.Context(ctx)
|
||||
for _, act := range activityList {
|
||||
log.Infof("user %s rollback rank %d", act.UserID, -act.Rank)
|
||||
_, e := ar.userRankRepo.TriggerUserRank(
|
||||
ctx, session, act.UserID, -act.Rank, act.ActivityType)
|
||||
if e != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||
}
|
||||
|
||||
if _, e := session.Where("id = ?", act.ID).Cols("cancelled", "cancelled_at").
|
||||
Update(&entity.Activity{Cancelled: entity.ActivityCancelled, CancelledAt: time.Now()}); e != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func (ar *FollowRepo) Follow(ctx context.Context, objectID, userID string) error
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
activityType, err := ar.activityRepo.GetActivityTypeByObjKey(ctx, objectTypeStr, "follow")
|
||||
activityType, err := ar.activityRepo.GetActivityTypeByObjectType(ctx, objectTypeStr, "follow")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -110,7 +110,7 @@ func (ar *FollowRepo) FollowCancel(ctx context.Context, objectID, userID string)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
activityType, err := ar.activityRepo.GetActivityTypeByObjKey(ctx, objectTypeStr, "follow")
|
||||
activityType, err := ar.activityRepo.GetActivityTypeByObjectType(ctx, objectTypeStr, "follow")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package activity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"xorm.io/builder"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/data"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
@@ -41,43 +43,58 @@ func NewUserActiveActivityRepo(
|
||||
}
|
||||
}
|
||||
|
||||
// UserActive accept other answer
|
||||
// UserActive user active
|
||||
func (ar *UserActiveActivityRepo) UserActive(ctx context.Context, userID string) (err error) {
|
||||
cfg, err := ar.configService.GetConfigByKey(ctx, UserActivated)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
activityType := cfg.ID
|
||||
deltaRank := cfg.GetIntValue()
|
||||
addActivity := &entity.Activity{
|
||||
UserID: userID,
|
||||
ObjectID: "0",
|
||||
OriginalObjectID: "0",
|
||||
ActivityType: activityType,
|
||||
Rank: deltaRank,
|
||||
ActivityType: cfg.ID,
|
||||
Rank: cfg.GetIntValue(),
|
||||
HasRank: 1,
|
||||
}
|
||||
|
||||
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||
session = session.Context(ctx)
|
||||
|
||||
_, exists, err := ar.activityRepo.GetActivity(ctx, session, "0", addActivity.UserID, activityType)
|
||||
user := &entity.User{}
|
||||
exist, err := session.ID(userID).ForUpdate().Get(user)
|
||||
if err != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
return nil, err
|
||||
}
|
||||
if exists {
|
||||
if !exist {
|
||||
return nil, fmt.Errorf("user not exist")
|
||||
}
|
||||
|
||||
existsActivity := &entity.Activity{}
|
||||
exist, err = session.
|
||||
And(builder.Eq{"user_id": addActivity.UserID}).
|
||||
And(builder.Eq{"activity_type": addActivity.ActivityType}).
|
||||
Get(existsActivity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exist {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
_, err = ar.userRankRepo.TriggerUserRank(ctx, session, addActivity.UserID, addActivity.Rank, activityType)
|
||||
err = ar.userRankRepo.ChangeUserRank(ctx, session, addActivity.UserID, user.Rank, addActivity.Rank)
|
||||
if err != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = session.Insert(addActivity)
|
||||
if err != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
return err
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+348
-356
@@ -2,395 +2,174 @@ package activity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"fmt"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"time"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/constant"
|
||||
"github.com/answerdev/answer/internal/service/notice_queue"
|
||||
"github.com/answerdev/answer/pkg/converter"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/pager"
|
||||
"github.com/answerdev/answer/internal/service/config"
|
||||
"github.com/answerdev/answer/internal/service/notice_queue"
|
||||
"github.com/answerdev/answer/internal/service/rank"
|
||||
"github.com/answerdev/answer/pkg/obj"
|
||||
|
||||
"xorm.io/builder"
|
||||
|
||||
"github.com/answerdev/answer/internal/service/activity_common"
|
||||
"github.com/answerdev/answer/internal/service/unique"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/data"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service"
|
||||
"github.com/answerdev/answer/internal/service/activity_common"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// VoteRepo activity repository
|
||||
type VoteRepo struct {
|
||||
data *data.Data
|
||||
uniqueIDRepo unique.UniqueIDRepo
|
||||
configService *config.ConfigService
|
||||
activityRepo activity_common.ActivityRepo
|
||||
userRankRepo rank.UserRankRepo
|
||||
voteCommon activity_common.VoteRepo
|
||||
data *data.Data
|
||||
activityRepo activity_common.ActivityRepo
|
||||
userRankRepo rank.UserRankRepo
|
||||
notificationQueueService notice_queue.NotificationQueueService
|
||||
}
|
||||
|
||||
// NewVoteRepo new repository
|
||||
func NewVoteRepo(
|
||||
data *data.Data,
|
||||
uniqueIDRepo unique.UniqueIDRepo,
|
||||
configService *config.ConfigService,
|
||||
activityRepo activity_common.ActivityRepo,
|
||||
userRankRepo rank.UserRankRepo,
|
||||
voteCommon activity_common.VoteRepo,
|
||||
notificationQueueService notice_queue.NotificationQueueService,
|
||||
) service.VoteRepo {
|
||||
return &VoteRepo{
|
||||
data: data,
|
||||
uniqueIDRepo: uniqueIDRepo,
|
||||
configService: configService,
|
||||
activityRepo: activityRepo,
|
||||
userRankRepo: userRankRepo,
|
||||
voteCommon: voteCommon,
|
||||
data: data,
|
||||
activityRepo: activityRepo,
|
||||
userRankRepo: userRankRepo,
|
||||
notificationQueueService: notificationQueueService,
|
||||
}
|
||||
}
|
||||
|
||||
var LimitUpActions = map[string][]string{
|
||||
"question": {"vote_up", "voted_up"},
|
||||
"answer": {"vote_up", "voted_up"},
|
||||
"comment": {"vote_up"},
|
||||
}
|
||||
func (vr *VoteRepo) Vote(ctx context.Context, op *schema.VoteOperationInfo) (err error) {
|
||||
noNeedToVote, err := vr.votePreCheck(ctx, op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if noNeedToVote {
|
||||
return nil
|
||||
}
|
||||
|
||||
var LimitDownActions = map[string][]string{
|
||||
"question": {"vote_down", "voted_down"},
|
||||
"answer": {"vote_down", "voted_down"},
|
||||
"comment": {"vote_down"},
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) vote(ctx context.Context, objectID string, userID, objectUserID string, actions []string) (resp *schema.VoteResp, err error) {
|
||||
resp = &schema.VoteResp{}
|
||||
achievementNotificationUserIDs := make([]string, 0)
|
||||
sendInboxNotification := false
|
||||
upVote := false
|
||||
maxDailyRank, err := vr.userRankRepo.GetMaxDailyRank(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var userIDs []string
|
||||
for _, activity := range op.Activities {
|
||||
userIDs = append(userIDs, activity.ActivityUserID)
|
||||
}
|
||||
|
||||
_, err = vr.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||
session = session.Context(ctx)
|
||||
result = nil
|
||||
for _, action := range actions {
|
||||
var (
|
||||
existsActivity entity.Activity
|
||||
insertActivity entity.Activity
|
||||
has bool
|
||||
triggerUserID,
|
||||
activityUserID string
|
||||
activityType, deltaRank, hasRank int
|
||||
)
|
||||
|
||||
activityUserID, activityType, deltaRank, hasRank, err = vr.CheckRank(ctx, objectID, objectUserID, userID, action)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
triggerUserID = userID
|
||||
if userID == activityUserID {
|
||||
triggerUserID = "0"
|
||||
}
|
||||
|
||||
// check is voted up
|
||||
has, _ = session.
|
||||
Where(builder.Eq{"object_id": objectID}).
|
||||
And(builder.Eq{"user_id": activityUserID}).
|
||||
And(builder.Eq{"trigger_user_id": triggerUserID}).
|
||||
And(builder.Eq{"activity_type": activityType}).
|
||||
Get(&existsActivity)
|
||||
|
||||
// is is voted,return
|
||||
if has && existsActivity.Cancelled == entity.ActivityAvailable {
|
||||
return
|
||||
}
|
||||
|
||||
insertActivity = entity.Activity{
|
||||
ObjectID: objectID,
|
||||
OriginalObjectID: objectID,
|
||||
UserID: activityUserID,
|
||||
TriggerUserID: converter.StringToInt64(triggerUserID),
|
||||
ActivityType: activityType,
|
||||
Rank: deltaRank,
|
||||
HasRank: hasRank,
|
||||
Cancelled: entity.ActivityAvailable,
|
||||
}
|
||||
|
||||
// trigger user rank and send notification
|
||||
if hasRank != 0 {
|
||||
var isReachStandard bool
|
||||
isReachStandard, err = vr.userRankRepo.TriggerUserRank(ctx, session, activityUserID, deltaRank, activityType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isReachStandard {
|
||||
insertActivity.Rank = 0
|
||||
}
|
||||
achievementNotificationUserIDs = append(achievementNotificationUserIDs, activityUserID)
|
||||
}
|
||||
|
||||
if has {
|
||||
if _, err = session.Where("id = ?", existsActivity.ID).Cols("`cancelled`").
|
||||
Update(&entity.Activity{
|
||||
Cancelled: entity.ActivityAvailable,
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
_, err = session.Insert(&insertActivity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sendInboxNotification = true
|
||||
}
|
||||
|
||||
// update votes
|
||||
if action == constant.ActVoteDown || action == constant.ActVoteUp {
|
||||
votes := 1
|
||||
if action == constant.ActVoteDown {
|
||||
upVote = false
|
||||
votes = -1
|
||||
} else {
|
||||
upVote = true
|
||||
}
|
||||
err = vr.updateVotes(ctx, session, objectID, votes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
userInfoMapping, err := vr.acquireUserInfo(session, userIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
|
||||
err = vr.setActivityRankToZeroIfUserReachLimit(ctx, session, op, userInfoMapping, maxDailyRank)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sendInboxNotification, err = vr.saveActivitiesAvailable(session, op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = vr.changeUserRank(ctx, session, op, userInfoMapping)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err = vr.GetVoteResultByObjectId(ctx, objectID)
|
||||
resp.VoteStatus = vr.voteCommon.GetVoteStatus(ctx, objectID, userID)
|
||||
|
||||
for _, activityUserID := range achievementNotificationUserIDs {
|
||||
vr.sendNotification(ctx, activityUserID, objectUserID, objectID)
|
||||
for _, activity := range op.Activities {
|
||||
if activity.Rank == 0 {
|
||||
continue
|
||||
}
|
||||
vr.sendAchievementNotification(ctx, activity.ActivityUserID, op.ObjectCreatorUserID, op.ObjectID)
|
||||
}
|
||||
if sendInboxNotification {
|
||||
vr.sendVoteInboxNotification(userID, objectUserID, objectID, upVote)
|
||||
vr.sendVoteInboxNotification(ctx, op.OperatingUserID, op.ObjectCreatorUserID, op.ObjectID, op.VoteUp)
|
||||
}
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) voteCancel(ctx context.Context, objectID string, userID, objectUserID string, actions []string) (resp *schema.VoteResp, err error) {
|
||||
resp = &schema.VoteResp{}
|
||||
notificationUserIDs := make([]string, 0)
|
||||
func (vr *VoteRepo) CancelVote(ctx context.Context, op *schema.VoteOperationInfo) (err error) {
|
||||
// Pre-Check
|
||||
// 1. check if the activity exist
|
||||
// 2. check if the activity is not cancelled
|
||||
// 3. if all activities are cancelled, return directly
|
||||
activities, err := vr.getExistActivity(ctx, op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var userIDs []string
|
||||
for _, activity := range activities {
|
||||
if activity.Cancelled == entity.ActivityCancelled {
|
||||
continue
|
||||
}
|
||||
userIDs = append(userIDs, activity.UserID)
|
||||
}
|
||||
if len(userIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = vr.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||
session = session.Context(ctx)
|
||||
for _, action := range actions {
|
||||
var (
|
||||
existsActivity entity.Activity
|
||||
has bool
|
||||
triggerUserID,
|
||||
activityUserID string
|
||||
activityType,
|
||||
deltaRank, hasRank int
|
||||
)
|
||||
result = nil
|
||||
|
||||
activityUserID, activityType, deltaRank, hasRank, err = vr.CheckRank(ctx, objectID, objectUserID, userID, action)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
triggerUserID = userID
|
||||
if userID == activityUserID {
|
||||
triggerUserID = "0"
|
||||
}
|
||||
|
||||
has, err = session.
|
||||
Where(builder.Eq{"user_id": activityUserID}).
|
||||
And(builder.Eq{"trigger_user_id": triggerUserID}).
|
||||
And(builder.Eq{"activity_type": activityType}).
|
||||
And(builder.Eq{"object_id": objectID}).
|
||||
Get(&existsActivity)
|
||||
|
||||
if !has {
|
||||
return
|
||||
}
|
||||
|
||||
if existsActivity.Cancelled == entity.ActivityCancelled {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = session.Where("id = ?", existsActivity.ID).Cols("cancelled", "cancelled_at").
|
||||
Update(&entity.Activity{
|
||||
Cancelled: entity.ActivityCancelled,
|
||||
CancelledAt: time.Now(),
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// trigger user rank and send notification
|
||||
if hasRank != 0 && existsActivity.Rank != 0 {
|
||||
_, err = vr.userRankRepo.TriggerUserRank(ctx, session, activityUserID, -deltaRank, activityType)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
notificationUserIDs = append(notificationUserIDs, activityUserID)
|
||||
}
|
||||
|
||||
// update votes
|
||||
if action == "vote_down" || action == "vote_up" {
|
||||
votes := -1
|
||||
if action == "vote_down" {
|
||||
votes = 1
|
||||
}
|
||||
err = vr.updateVotes(ctx, session, objectID, votes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
userInfoMapping, err := vr.acquireUserInfo(session, userIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
err = vr.cancelActivities(session, activities)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = vr.rollbackUserRank(ctx, session, activities, userInfoMapping)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
return err
|
||||
}
|
||||
resp, err = vr.GetVoteResultByObjectId(ctx, objectID)
|
||||
resp.VoteStatus = vr.voteCommon.GetVoteStatus(ctx, objectID, userID)
|
||||
|
||||
for _, activityUserID := range notificationUserIDs {
|
||||
vr.sendNotification(ctx, activityUserID, objectUserID, objectID)
|
||||
for _, activity := range activities {
|
||||
if activity.Rank == 0 {
|
||||
continue
|
||||
}
|
||||
vr.sendAchievementNotification(ctx, activity.UserID, op.ObjectCreatorUserID, op.ObjectID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) GetAndSaveVoteResult(ctx context.Context, objectID, objectType string) (
|
||||
up, down int64, err error) {
|
||||
up = vr.countVoteUp(ctx, objectID, objectType)
|
||||
down = vr.countVoteDown(ctx, objectID, objectType)
|
||||
err = vr.updateVotes(ctx, objectID, objectType, int(up-down))
|
||||
return
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) VoteUp(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) {
|
||||
resp = &schema.VoteResp{}
|
||||
objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
|
||||
if err != nil {
|
||||
err = errors.BadRequest(reason.ObjectNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
actions, ok := LimitUpActions[objectType]
|
||||
if !ok {
|
||||
err = errors.BadRequest(reason.DisallowVote)
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = vr.VoteDownCancel(ctx, objectID, userID, objectUserID)
|
||||
return vr.vote(ctx, objectID, userID, objectUserID, actions)
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) VoteDown(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) {
|
||||
resp = &schema.VoteResp{}
|
||||
objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
|
||||
if err != nil {
|
||||
err = errors.BadRequest(reason.ObjectNotFound)
|
||||
return
|
||||
}
|
||||
actions, ok := LimitDownActions[objectType]
|
||||
if !ok {
|
||||
err = errors.BadRequest(reason.DisallowVote)
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = vr.VoteUpCancel(ctx, objectID, userID, objectUserID)
|
||||
return vr.vote(ctx, objectID, userID, objectUserID, actions)
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) VoteUpCancel(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) {
|
||||
var objectType string
|
||||
resp = &schema.VoteResp{}
|
||||
|
||||
objectType, err = obj.GetObjectTypeStrByObjectID(objectID)
|
||||
if err != nil {
|
||||
err = errors.BadRequest(reason.ObjectNotFound)
|
||||
return
|
||||
}
|
||||
actions, ok := LimitUpActions[objectType]
|
||||
if !ok {
|
||||
err = errors.BadRequest(reason.DisallowVote)
|
||||
return
|
||||
}
|
||||
|
||||
return vr.voteCancel(ctx, objectID, userID, objectUserID, actions)
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) VoteDownCancel(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) {
|
||||
var objectType string
|
||||
resp = &schema.VoteResp{}
|
||||
|
||||
objectType, err = obj.GetObjectTypeStrByObjectID(objectID)
|
||||
if err != nil {
|
||||
err = errors.BadRequest(reason.ObjectNotFound)
|
||||
return
|
||||
}
|
||||
actions, ok := LimitDownActions[objectType]
|
||||
if !ok {
|
||||
err = errors.BadRequest(reason.DisallowVote)
|
||||
return
|
||||
}
|
||||
|
||||
return vr.voteCancel(ctx, objectID, userID, objectUserID, actions)
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) CheckRank(ctx context.Context, objectID, objectUserID, userID string, action string) (activityUserID string, activityType, rank, hasRank int, err error) {
|
||||
activityType, rank, hasRank, err = vr.activityRepo.GetActivityTypeByObjID(ctx, objectID, action)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
activityUserID = userID
|
||||
if strings.Contains(action, "voted") {
|
||||
activityUserID = objectUserID
|
||||
}
|
||||
|
||||
return activityUserID, activityType, rank, hasRank, nil
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) GetVoteResultByObjectId(ctx context.Context, objectID string) (resp *schema.VoteResp, err error) {
|
||||
resp = &schema.VoteResp{}
|
||||
for _, action := range []string{"vote_up", "vote_down"} {
|
||||
var (
|
||||
activity entity.Activity
|
||||
votes int64
|
||||
activityType int
|
||||
)
|
||||
|
||||
activityType, _, _, _ = vr.activityRepo.GetActivityTypeByObjID(ctx, objectID, action)
|
||||
|
||||
votes, err = vr.data.DB.Context(ctx).Where(builder.Eq{"object_id": objectID}).
|
||||
And(builder.Eq{"activity_type": activityType}).
|
||||
And(builder.Eq{"cancelled": 0}).
|
||||
Count(&activity)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if action == "vote_up" {
|
||||
resp.UpVotes = int(votes)
|
||||
} else {
|
||||
resp.DownVotes = int(votes)
|
||||
}
|
||||
}
|
||||
|
||||
resp.Votes = resp.UpVotes - resp.DownVotes
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) ListUserVotes(
|
||||
ctx context.Context,
|
||||
userID string,
|
||||
req schema.GetVoteWithPageReq,
|
||||
activityTypes []int,
|
||||
) (voteList []entity.Activity, total int64, err error) {
|
||||
func (vr *VoteRepo) ListUserVotes(ctx context.Context, userID string,
|
||||
page int, pageSize int, activityTypes []int) (voteList []*entity.Activity, total int64, err error) {
|
||||
session := vr.data.DB.Context(ctx)
|
||||
cond := builder.
|
||||
And(
|
||||
@@ -399,46 +178,259 @@ func (vr *VoteRepo) ListUserVotes(
|
||||
builder.In("activity_type", activityTypes),
|
||||
)
|
||||
|
||||
session.Where(cond).OrderBy("updated_at desc")
|
||||
session.Where(cond).Desc("updated_at")
|
||||
|
||||
total, err = pager.Help(req.Page, req.PageSize, &voteList, &entity.Activity{}, session)
|
||||
total, err = pager.Help(page, pageSize, &voteList, &entity.Activity{}, session)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// updateVotes
|
||||
// if votes < 0 Decr object vote_count,otherwise Incr object vote_count
|
||||
func (vr *VoteRepo) updateVotes(ctx context.Context, session *xorm.Session, objectID string, votes int) (err error) {
|
||||
var (
|
||||
objectType string
|
||||
e error
|
||||
)
|
||||
func (vr *VoteRepo) votePreCheck(ctx context.Context, op *schema.VoteOperationInfo) (noNeedToVote bool, err error) {
|
||||
activities, err := vr.getExistActivity(ctx, op)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
done := 0
|
||||
for _, activity := range activities {
|
||||
if activity.Cancelled == entity.ActivityAvailable {
|
||||
done++
|
||||
}
|
||||
}
|
||||
return done == len(op.Activities), nil
|
||||
}
|
||||
|
||||
objectType, err = obj.GetObjectTypeStrByObjectID(objectID)
|
||||
func (vr *VoteRepo) acquireUserInfo(session *xorm.Session, userIDs []string) (map[string]*entity.User, error) {
|
||||
us := make([]*entity.User, 0)
|
||||
err := session.In("id", userIDs).ForUpdate().Find(&us)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users := make(map[string]*entity.User, 0)
|
||||
for _, u := range us {
|
||||
users[u.ID] = u
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) setActivityRankToZeroIfUserReachLimit(ctx context.Context, session *xorm.Session,
|
||||
op *schema.VoteOperationInfo, userInfoMapping map[string]*entity.User, maxDailyRank int) (err error) {
|
||||
// check if user reach daily rank limit
|
||||
for _, activity := range op.Activities {
|
||||
if activity.Rank > 0 {
|
||||
// check if reach max daily rank
|
||||
reach, err := vr.userRankRepo.CheckReachLimit(ctx, session, activity.ActivityUserID, maxDailyRank)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
if reach {
|
||||
activity.Rank = 0
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// If user rank is lower than 1 after this action, then user rank will be set to 1 only.
|
||||
userCurrentScore := userInfoMapping[activity.ActivityUserID].Rank
|
||||
if userCurrentScore+activity.Rank < 1 {
|
||||
activity.Rank = 1 - userCurrentScore
|
||||
}
|
||||
}
|
||||
}
|
||||
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 {
|
||||
bean := &entity.Activity{
|
||||
Cancelled: entity.ActivityAvailable,
|
||||
Rank: activity.Rank,
|
||||
HasRank: activity.HasRank(),
|
||||
}
|
||||
session.Where("id = ?", existsActivity.ID)
|
||||
if _, err = session.Cols("`cancelled`", "`rank`", "`has_rank`").
|
||||
Update(bean); err != nil {
|
||||
return false, err
|
||||
}
|
||||
} else {
|
||||
insertActivity := entity.Activity{
|
||||
ObjectID: op.ObjectID,
|
||||
OriginalObjectID: op.ObjectID,
|
||||
UserID: activity.ActivityUserID,
|
||||
TriggerUserID: converter.StringToInt64(activity.TriggerUserID),
|
||||
ActivityType: activity.ActivityType,
|
||||
Rank: activity.Rank,
|
||||
HasRank: activity.HasRank(),
|
||||
Cancelled: entity.ActivityAvailable,
|
||||
}
|
||||
_, err = session.Insert(&insertActivity)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
newAct = true
|
||||
}
|
||||
}
|
||||
return newAct, nil
|
||||
}
|
||||
|
||||
// cancelActivities cancel activities
|
||||
// If this activity is already cancelled, set activity rank to 0
|
||||
// So after this function, the activity rank will be correct for update user rank
|
||||
func (vr *VoteRepo) cancelActivities(session *xorm.Session, activities []*entity.Activity) (err error) {
|
||||
for _, activity := range activities {
|
||||
t := &entity.Activity{}
|
||||
exist, err := session.ID(activity.ID).Get(t)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
if !exist {
|
||||
log.Error(fmt.Errorf("%s activity not exist", activity.ID))
|
||||
return fmt.Errorf("%s activity not exist", activity.ID)
|
||||
}
|
||||
// If this activity is already cancelled, set activity rank to 0
|
||||
if t.Cancelled == entity.ActivityCancelled {
|
||||
activity.Rank = 0
|
||||
}
|
||||
if _, err = session.ID(activity.ID).Cols("cancelled", "cancelled_at").
|
||||
Update(&entity.Activity{
|
||||
Cancelled: entity.ActivityCancelled,
|
||||
CancelledAt: time.Now(),
|
||||
}); err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) getExistActivity(ctx context.Context, op *schema.VoteOperationInfo) ([]*entity.Activity, error) {
|
||||
var activities []*entity.Activity
|
||||
for _, action := range op.Activities {
|
||||
t := &entity.Activity{}
|
||||
exist, err := vr.data.DB.Context(ctx).
|
||||
Where(builder.Eq{"user_id": action.ActivityUserID}).
|
||||
And(builder.Eq{"trigger_user_id": action.TriggerUserID}).
|
||||
And(builder.Eq{"activity_type": action.ActivityType}).
|
||||
And(builder.Eq{"object_id": op.ObjectID}).
|
||||
Get(t)
|
||||
if err != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
if exist {
|
||||
activities = append(activities, t)
|
||||
}
|
||||
}
|
||||
return activities, nil
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) countVoteUp(ctx context.Context, objectID, objectType string) (count int64) {
|
||||
count, err := vr.countVote(ctx, objectID, objectType, constant.ActVoteUp)
|
||||
if err != nil {
|
||||
log.Errorf("get vote up count error: %v", err)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) countVoteDown(ctx context.Context, objectID, objectType string) (count int64) {
|
||||
count, err := vr.countVote(ctx, objectID, objectType, constant.ActVoteDown)
|
||||
if err != nil {
|
||||
log.Errorf("get vote down count error: %v", err)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) countVote(ctx context.Context, objectID, objectType, action string) (count int64, err error) {
|
||||
activity := &entity.Activity{}
|
||||
activityType, _ := vr.activityRepo.GetActivityTypeByObjectType(ctx, objectType, action)
|
||||
count, err = vr.data.DB.Context(ctx).Where(builder.Eq{"object_id": objectID}).
|
||||
And(builder.Eq{"activity_type": activityType}).
|
||||
And(builder.Eq{"cancelled": 0}).
|
||||
Count(activity)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) updateVotes(ctx context.Context, objectID, objectType string, voteCount int) (err error) {
|
||||
session := vr.data.DB.Context(ctx)
|
||||
switch objectType {
|
||||
case "question":
|
||||
_, err = session.Where("id = ?", objectID).Incr("vote_count", votes).Update(&entity.Question{})
|
||||
case "answer":
|
||||
_, err = session.Where("id = ?", objectID).Incr("vote_count", votes).Update(&entity.Answer{})
|
||||
case "comment":
|
||||
_, err = session.Where("id = ?", objectID).Incr("vote_count", votes).Update(&entity.Comment{})
|
||||
default:
|
||||
e = errors.BadRequest(reason.DisallowVote)
|
||||
case constant.QuestionObjectType:
|
||||
_, err = session.ID(objectID).Cols("vote_count").Update(&entity.Question{VoteCount: voteCount})
|
||||
case constant.AnswerObjectType:
|
||||
_, err = session.ID(objectID).Cols("vote_count").Update(&entity.Answer{VoteCount: voteCount})
|
||||
case constant.CommentObjectType:
|
||||
_, err = session.ID(objectID).Cols("vote_count").Update(&entity.Comment{VoteCount: voteCount})
|
||||
}
|
||||
|
||||
if e != nil {
|
||||
err = e
|
||||
} else if err != nil {
|
||||
err = errors.BadRequest(reason.DatabaseError).WithError(err).WithStack()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// sendNotification send rank triggered notification
|
||||
func (vr *VoteRepo) sendNotification(ctx context.Context, activityUserID, objectUserID, objectID string) {
|
||||
func (vr *VoteRepo) sendAchievementNotification(ctx context.Context, activityUserID, objectUserID, objectID string) {
|
||||
objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -451,10 +443,10 @@ func (vr *VoteRepo) sendNotification(ctx context.Context, activityUserID, object
|
||||
ObjectID: objectID,
|
||||
ObjectType: objectType,
|
||||
}
|
||||
notice_queue.AddNotification(msg)
|
||||
vr.notificationQueueService.Send(ctx, msg)
|
||||
}
|
||||
|
||||
func (vr *VoteRepo) sendVoteInboxNotification(triggerUserID, receiverUserID, objectID string, upvote bool) {
|
||||
func (vr *VoteRepo) sendVoteInboxNotification(ctx context.Context, triggerUserID, receiverUserID, objectID string, upvote bool) {
|
||||
if triggerUserID == receiverUserID {
|
||||
return
|
||||
}
|
||||
@@ -487,6 +479,6 @@ func (vr *VoteRepo) sendVoteInboxNotification(triggerUserID, receiverUserID, obj
|
||||
}
|
||||
}
|
||||
if len(msg.NotificationAction) > 0 {
|
||||
notice_queue.AddNotification(msg)
|
||||
vr.notificationQueueService.Send(ctx, msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,12 +41,12 @@ func NewActivityRepo(
|
||||
|
||||
func (ar *ActivityRepo) GetActivityTypeByObjID(ctx context.Context, objectID string, action string) (
|
||||
activityType, rank, hasRank int, err error) {
|
||||
objectKey, err := obj.GetObjectTypeStrByObjectID(objectID)
|
||||
objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
confKey := fmt.Sprintf("%s.%s", objectKey, action)
|
||||
confKey := fmt.Sprintf("%s.%s", objectType, action)
|
||||
cfg, err := ar.configService.GetConfigByKey(ctx, confKey)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -59,11 +59,11 @@ func (ar *ActivityRepo) GetActivityTypeByObjID(ctx context.Context, objectID str
|
||||
return
|
||||
}
|
||||
|
||||
func (ar *ActivityRepo) GetActivityTypeByObjKey(ctx context.Context, objectKey, action string) (activityType int, err error) {
|
||||
configKey := fmt.Sprintf("%s.%s", objectKey, action)
|
||||
func (ar *ActivityRepo) GetActivityTypeByObjectType(ctx context.Context, objectType, action string) (activityType int, err error) {
|
||||
configKey := fmt.Sprintf("%s.%s", objectType, action)
|
||||
cfg, err := ar.configService.GetConfigByKey(ctx, configKey)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
return 0, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return cfg.ID, nil
|
||||
}
|
||||
@@ -71,7 +71,7 @@ func (ar *ActivityRepo) GetActivityTypeByObjKey(ctx context.Context, objectKey,
|
||||
func (ar *ActivityRepo) GetActivityTypeByConfigKey(ctx context.Context, configKey string) (activityType int, err error) {
|
||||
cfg, err := ar.configService.GetConfigByKey(ctx, configKey)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
return 0, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return cfg.ID, nil
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/answerdev/answer/internal/service/unique"
|
||||
"github.com/answerdev/answer/pkg/obj"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
// FollowRepo follow repository
|
||||
@@ -71,11 +72,12 @@ func (ar *FollowRepo) GetFollowAmount(ctx context.Context, objectID string) (fol
|
||||
func (ar *FollowRepo) GetFollowUserIDs(ctx context.Context, objectID string) (userIDs []string, err error) {
|
||||
objectTypeStr, err := obj.GetObjectTypeStrByObjectID(objectID)
|
||||
if err != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
return nil, err
|
||||
}
|
||||
activityType, err := ar.activityRepo.GetActivityTypeByObjKey(ctx, objectTypeStr, "follow")
|
||||
activityType, err := ar.activityRepo.GetActivityTypeByObjectType(ctx, objectTypeStr, "follow")
|
||||
if err != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
log.Errorf("can't get activity type by object key: %s", objectTypeStr)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userIDs = make([]string, 0)
|
||||
@@ -94,7 +96,7 @@ func (ar *FollowRepo) GetFollowUserIDs(ctx context.Context, objectID string) (us
|
||||
// GetFollowIDs get all follow id list
|
||||
func (ar *FollowRepo) GetFollowIDs(ctx context.Context, userID, objectKey string) (followIDs []string, err error) {
|
||||
followIDs = make([]string, 0)
|
||||
activityType, err := ar.activityRepo.GetActivityTypeByObjKey(ctx, objectKey, "follow")
|
||||
activityType, err := ar.activityRepo.GetActivityTypeByObjectType(ctx, objectKey, "follow")
|
||||
if err != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
@@ -116,7 +118,7 @@ func (ar *FollowRepo) IsFollowed(ctx context.Context, userID, objectID string) (
|
||||
return false, err
|
||||
}
|
||||
|
||||
activityType, err := ar.activityRepo.GetActivityTypeByObjKey(ctx, objectKey, "follow")
|
||||
activityType, err := ar.activityRepo.GetActivityTypeByObjectType(ctx, objectKey, "follow")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -2,14 +2,12 @@ package answer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"github.com/answerdev/answer/plugin"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"xorm.io/builder"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/constant"
|
||||
"github.com/answerdev/answer/internal/base/data"
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
"github.com/answerdev/answer/internal/base/pager"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
@@ -58,8 +56,11 @@ func (ar *answerRepo) AddAnswer(ctx context.Context, answer *entity.Answer) (err
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
answer.ID = uid.EnShortID(answer.ID)
|
||||
answer.QuestionID = uid.EnShortID(answer.QuestionID)
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
answer.ID = uid.EnShortID(answer.ID)
|
||||
answer.QuestionID = uid.EnShortID(answer.QuestionID)
|
||||
}
|
||||
_ = ar.updateSearch(ctx, answer.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -74,6 +75,7 @@ func (ar *answerRepo) RemoveAnswer(ctx context.Context, id string) (err error) {
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = ar.updateSearch(ctx, answer.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -85,6 +87,7 @@ func (ar *answerRepo) UpdateAnswer(ctx context.Context, answer *entity.Answer, C
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = ar.updateSearch(ctx, answer.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -96,6 +99,7 @@ func (ar *answerRepo) UpdateAnswerStatus(ctx context.Context, answer *entity.Ans
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = ar.updateSearch(ctx, answer.ID)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -109,9 +113,10 @@ func (ar *answerRepo) GetAnswer(ctx context.Context, id string) (
|
||||
if err != nil {
|
||||
return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
answer.ID = uid.EnShortID(answer.ID)
|
||||
answer.QuestionID = uid.EnShortID(answer.QuestionID)
|
||||
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
answer.ID = uid.EnShortID(answer.ID)
|
||||
answer.QuestionID = uid.EnShortID(answer.QuestionID)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -134,9 +139,11 @@ func (ar *answerRepo) GetAnswerList(ctx context.Context, answer *entity.Answer)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
for _, item := range answerList {
|
||||
item.ID = uid.EnShortID(item.ID)
|
||||
item.QuestionID = uid.EnShortID(item.QuestionID)
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
for _, item := range answerList {
|
||||
item.ID = uid.EnShortID(item.ID)
|
||||
item.QuestionID = uid.EnShortID(item.QuestionID)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -150,9 +157,11 @@ func (ar *answerRepo) GetAnswerPage(ctx context.Context, page, pageSize int, ans
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
for _, item := range answerList {
|
||||
item.ID = uid.EnShortID(item.ID)
|
||||
item.QuestionID = uid.EnShortID(item.QuestionID)
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
for _, item := range answerList {
|
||||
item.ID = uid.EnShortID(item.ID)
|
||||
item.QuestionID = uid.EnShortID(item.QuestionID)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -180,6 +189,7 @@ func (ar *answerRepo) UpdateAccepted(ctx context.Context, id string, questionID
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
}
|
||||
_ = ar.updateSearch(ctx, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -191,8 +201,10 @@ func (ar *answerRepo) GetByID(ctx context.Context, id string) (*entity.Answer, b
|
||||
if err != nil {
|
||||
return &resp, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
resp.ID = uid.EnShortID(resp.ID)
|
||||
resp.QuestionID = uid.EnShortID(resp.QuestionID)
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
resp.ID = uid.EnShortID(resp.ID)
|
||||
resp.QuestionID = uid.EnShortID(resp.QuestionID)
|
||||
}
|
||||
return &resp, has, nil
|
||||
}
|
||||
|
||||
@@ -222,8 +234,10 @@ func (ar *answerRepo) GetByUserIDQuestionID(ctx context.Context, userID string,
|
||||
if err != nil {
|
||||
return &resp, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
resp.ID = uid.EnShortID(resp.ID)
|
||||
resp.QuestionID = uid.EnShortID(resp.QuestionID)
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
resp.ID = uid.EnShortID(resp.ID)
|
||||
resp.QuestionID = uid.EnShortID(resp.QuestionID)
|
||||
}
|
||||
return &resp, has, nil
|
||||
}
|
||||
|
||||
@@ -274,87 +288,109 @@ func (ar *answerRepo) SearchList(ctx context.Context, search *entity.AnswerSearc
|
||||
if err != nil {
|
||||
return rows, count, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
for _, item := range rows {
|
||||
item.ID = uid.EnShortID(item.ID)
|
||||
item.QuestionID = uid.EnShortID(item.QuestionID)
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
for _, item := range rows {
|
||||
item.ID = uid.EnShortID(item.ID)
|
||||
item.QuestionID = uid.EnShortID(item.QuestionID)
|
||||
}
|
||||
}
|
||||
return rows, count, nil
|
||||
}
|
||||
|
||||
func (ar *answerRepo) AdminSearchList(ctx context.Context, search *entity.AdminAnswerSearch) ([]*entity.Answer, int64, error) {
|
||||
var (
|
||||
count int64
|
||||
err error
|
||||
session = ar.data.DB.Context(ctx).Table([]string{entity.Answer{}.TableName(), "a"}).Select("a.*")
|
||||
)
|
||||
if search.QuestionID != "" {
|
||||
search.QuestionID = uid.DeShortID(search.QuestionID)
|
||||
}
|
||||
|
||||
session.Where(builder.Eq{
|
||||
"a.status": search.Status,
|
||||
})
|
||||
|
||||
rows := make([]*entity.Answer, 0)
|
||||
if search.Page > 0 {
|
||||
search.Page = search.Page - 1
|
||||
} else {
|
||||
search.Page = 0
|
||||
}
|
||||
if search.PageSize == 0 {
|
||||
search.PageSize = constant.DefaultPageSize
|
||||
}
|
||||
|
||||
// search by question title like or answer id
|
||||
if len(search.Query) > 0 {
|
||||
// check id search
|
||||
var (
|
||||
idSearch = false
|
||||
id = ""
|
||||
)
|
||||
|
||||
if strings.Contains(search.Query, "answer:") {
|
||||
idSearch = true
|
||||
id = strings.TrimSpace(strings.TrimPrefix(search.Query, "answer:"))
|
||||
id = uid.DeShortID(id)
|
||||
for _, r := range id {
|
||||
if !unicode.IsDigit(r) {
|
||||
idSearch = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if idSearch {
|
||||
session.And(builder.Eq{
|
||||
"id": id,
|
||||
})
|
||||
} else {
|
||||
session.Join("LEFT", []string{entity.Question{}.TableName(), "q"}, "q.id = a.question_id")
|
||||
session.And(builder.Like{
|
||||
"q.title", search.Query,
|
||||
})
|
||||
func (ar *answerRepo) AdminSearchList(ctx context.Context, req *schema.AdminAnswerPageReq) (
|
||||
resp []*entity.Answer, total int64, err error) {
|
||||
cond := &entity.Answer{}
|
||||
session := ar.data.DB.Context(ctx)
|
||||
if len(req.QuestionID) == 0 && len(req.AnswerID) == 0 {
|
||||
session.Join("INNER", "question", "answer.question_id = question.id")
|
||||
if len(req.QuestionTitle) > 0 {
|
||||
session.Where("question.title like ?", "%"+req.QuestionTitle+"%")
|
||||
}
|
||||
}
|
||||
|
||||
// check search by question id
|
||||
if len(search.QuestionID) > 0 {
|
||||
session.And(builder.Eq{
|
||||
"question_id": search.QuestionID,
|
||||
})
|
||||
if len(req.AnswerID) > 0 {
|
||||
cond.ID = req.AnswerID
|
||||
}
|
||||
if len(req.QuestionID) > 0 {
|
||||
session.Where("answer.question_id = ?", req.QuestionID)
|
||||
}
|
||||
if req.Status > 0 {
|
||||
cond.Status = req.Status
|
||||
}
|
||||
session.Desc("answer.created_at")
|
||||
|
||||
offset := search.Page * search.PageSize
|
||||
session.
|
||||
OrderBy("a.created_at desc").
|
||||
Limit(search.PageSize, offset)
|
||||
count, err = session.FindAndCount(&rows)
|
||||
resp = make([]*entity.Answer, 0)
|
||||
total, err = pager.Help(req.Page, req.PageSize, &resp, cond, session)
|
||||
if err != nil {
|
||||
return rows, count, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
return nil, 0, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
for _, item := range rows {
|
||||
item.ID = uid.EnShortID(item.ID)
|
||||
item.QuestionID = uid.EnShortID(item.QuestionID)
|
||||
}
|
||||
return rows, count, nil
|
||||
return resp, total, nil
|
||||
}
|
||||
|
||||
// updateSearch update search, if search plugin not enable, do nothing
|
||||
func (ar *answerRepo) updateSearch(ctx context.Context, answerID string) (err error) {
|
||||
answerID = uid.DeShortID(answerID)
|
||||
// check search plugin
|
||||
var (
|
||||
s plugin.Search
|
||||
)
|
||||
_ = plugin.CallSearch(func(search plugin.Search) error {
|
||||
s = search
|
||||
return nil
|
||||
})
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
answer, exist, err := ar.GetAnswer(ctx, answerID)
|
||||
if !exist {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get question
|
||||
var (
|
||||
question *entity.Question
|
||||
)
|
||||
exist, err = ar.data.DB.Context(ctx).Where("id = ?", answer.QuestionID).Get(&question)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
if !exist {
|
||||
return
|
||||
}
|
||||
|
||||
// get tags
|
||||
var (
|
||||
tagListList = make([]*entity.TagRel, 0)
|
||||
tags = make([]string, 0)
|
||||
)
|
||||
st := ar.data.DB.Context(ctx).Where("object_id = ?", uid.DeShortID(question.ID))
|
||||
st.Where("status = ?", entity.TagRelStatusAvailable)
|
||||
err = st.Find(&tagListList)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
for _, tag := range tagListList {
|
||||
tags = append(tags, tag.TagID)
|
||||
}
|
||||
|
||||
content := &plugin.SearchContent{
|
||||
ObjectID: answerID,
|
||||
Title: question.Title,
|
||||
Type: constant.AnswerObjectType,
|
||||
Content: answer.ParsedText,
|
||||
Answers: 0,
|
||||
Status: plugin.SearchContentStatus(answer.Status),
|
||||
Tags: tags,
|
||||
QuestionID: answer.QuestionID,
|
||||
UserID: answer.UserID,
|
||||
Views: int64(question.ViewCount),
|
||||
Created: answer.CreatedAt.Unix(),
|
||||
Active: answer.UpdatedAt.Unix(),
|
||||
Score: int64(answer.VoteCount),
|
||||
HasAccepted: answer.Accepted == schema.AnswerAcceptedEnable,
|
||||
}
|
||||
err = s.UpdateContent(ctx, answerID, content)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ package auth
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/answerdev/answer/internal/service/auth"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/constant"
|
||||
"github.com/answerdev/answer/internal/base/data"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/service/auth"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
@@ -18,6 +18,13 @@ type authRepo struct {
|
||||
data *data.Data
|
||||
}
|
||||
|
||||
// NewAuthRepo new repository
|
||||
func NewAuthRepo(data *data.Data) auth.AuthRepo {
|
||||
return &authRepo{
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserCacheInfo get user cache info
|
||||
func (ar *authRepo) GetUserCacheInfo(ctx context.Context, accessToken string) (userInfo *entity.UserCacheInfo, err error) {
|
||||
userInfoCache, err := ar.data.Cache.GetString(ctx, constant.UserTokenCacheKey+accessToken)
|
||||
@@ -174,10 +181,3 @@ func (ar *authRepo) RemoveUserTokens(ctx context.Context, userID string, remainT
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// NewAuthRepo new repository
|
||||
func NewAuthRepo(data *data.Data) auth.AuthRepo {
|
||||
return &authRepo{
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@ package captcha
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/data"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/service/action"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
@@ -24,26 +26,41 @@ func NewCaptchaRepo(data *data.Data) action.CaptchaRepo {
|
||||
}
|
||||
}
|
||||
|
||||
func (cr *captchaRepo) SetActionType(ctx context.Context, ip, actionType string, amount int) (err error) {
|
||||
cacheKey := fmt.Sprintf("ActionRecord:%s@", ip)
|
||||
err = cr.data.Cache.SetInt64(ctx, cacheKey, int64(amount), 6*time.Minute)
|
||||
func (cr *captchaRepo) SetActionType(ctx context.Context, unit, actionType, config string, amount int) (err error) {
|
||||
now := time.Now()
|
||||
cacheKey := fmt.Sprintf("ActionRecord:%s@%s@%s", unit, actionType, now.Format("2006-1-02"))
|
||||
value := &entity.ActionRecordInfo{}
|
||||
value.LastTime = now.Unix()
|
||||
value.Num = amount
|
||||
valueStr, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
err = cr.data.Cache.SetString(ctx, cacheKey, string(valueStr), 6*time.Minute)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cr *captchaRepo) GetActionType(ctx context.Context, ip, actionType string) (amount int, err error) {
|
||||
cacheKey := fmt.Sprintf("ActionRecord:%s@", ip)
|
||||
res, err := cr.data.Cache.GetInt64(ctx, cacheKey)
|
||||
func (cr *captchaRepo) GetActionType(ctx context.Context, unit, actionType string) (actioninfo *entity.ActionRecordInfo, err error) {
|
||||
now := time.Now()
|
||||
cacheKey := fmt.Sprintf("ActionRecord:%s@%s@%s", unit, actionType, now.Format("2006-1-02"))
|
||||
actioninfo = &entity.ActionRecordInfo{}
|
||||
res, err := cr.data.Cache.GetString(ctx, cacheKey)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return int(res), nil
|
||||
err = json.Unmarshal([]byte(res), actioninfo)
|
||||
if err != nil {
|
||||
return actioninfo, nil
|
||||
}
|
||||
return actioninfo, nil
|
||||
}
|
||||
|
||||
func (cr *captchaRepo) DelActionType(ctx context.Context, ip, actionType string) (err error) {
|
||||
cacheKey := fmt.Sprintf("ActionRecord:%s@", ip)
|
||||
func (cr *captchaRepo) DelActionType(ctx context.Context, unit, actionType string) (err error) {
|
||||
now := time.Now()
|
||||
cacheKey := fmt.Sprintf("ActionRecord:%s@%s@%s", unit, actionType, now.Format("2006-1-02"))
|
||||
err = cr.data.Cache.Del(ctx, cacheKey)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
|
||||
@@ -37,7 +37,7 @@ func (cr configRepo) GetConfigByID(ctx context.Context, id int) (c *entity.Confi
|
||||
}
|
||||
|
||||
c = &entity.Config{}
|
||||
exist, err := cr.data.DB.ID(id).Get(c)
|
||||
exist, err := cr.data.DB.Context(ctx).ID(id).Get(c)
|
||||
if err != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ var ProviderSetRepo = wire.NewSet(
|
||||
activity.NewVoteRepo,
|
||||
activity.NewFollowRepo,
|
||||
activity.NewAnswerActivityRepo,
|
||||
activity.NewQuestionActivityRepo,
|
||||
activity.NewUserActiveActivityRepo,
|
||||
activity.NewActivityRepo,
|
||||
tag.NewTagRepo,
|
||||
|
||||
@@ -2,11 +2,15 @@ package question
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/answerdev/answer/plugin"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
"xorm.io/builder"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/constant"
|
||||
@@ -50,7 +54,10 @@ func (qr *questionRepo) AddQuestion(ctx context.Context, question *entity.Questi
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
question.ID = uid.EnShortID(question.ID)
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
question.ID = uid.EnShortID(question.ID)
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -71,7 +78,10 @@ func (qr *questionRepo) UpdateQuestion(ctx context.Context, question *entity.Que
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
question.ID = uid.EnShortID(question.ID)
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
question.ID = uid.EnShortID(question.ID)
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -82,6 +92,7 @@ func (qr *questionRepo) UpdatePvCount(ctx context.Context, questionID string) (e
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -93,6 +104,7 @@ func (qr *questionRepo) UpdateAnswerCount(ctx context.Context, questionID string
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -114,6 +126,7 @@ func (qr *questionRepo) UpdateQuestionStatus(ctx context.Context, question *enti
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -123,6 +136,7 @@ func (qr *questionRepo) UpdateQuestionStatusWithOutUpdateTime(ctx context.Contex
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -141,6 +155,7 @@ func (qr *questionRepo) UpdateAccepted(ctx context.Context, question *entity.Que
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -150,6 +165,7 @@ func (qr *questionRepo) UpdateLastAnswer(ctx context.Context, question *entity.Q
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -164,7 +180,9 @@ func (qr *questionRepo) GetQuestion(ctx context.Context, id string) (
|
||||
if err != nil {
|
||||
return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
question.ID = uid.EnShortID(question.ID)
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
question.ID = uid.EnShortID(question.ID)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -175,8 +193,10 @@ func (qr *questionRepo) SearchByTitleLike(ctx context.Context, title string) (qu
|
||||
if err != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
for _, item := range questionList {
|
||||
item.ID = uid.EnShortID(item.ID)
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
for _, item := range questionList {
|
||||
item.ID = uid.EnShortID(item.ID)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -190,8 +210,10 @@ func (qr *questionRepo) FindByID(ctx context.Context, id []string) (questionList
|
||||
if err != nil {
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
for _, item := range questionList {
|
||||
item.ID = uid.EnShortID(item.ID)
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
for _, item := range questionList {
|
||||
item.ID = uid.EnShortID(item.ID)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -211,65 +233,71 @@ func (qr *questionRepo) GetQuestionList(ctx context.Context, question *entity.Qu
|
||||
}
|
||||
|
||||
func (qr *questionRepo) GetQuestionCount(ctx context.Context) (count int64, err error) {
|
||||
questionList := make([]*entity.Question, 0)
|
||||
|
||||
count, err = qr.data.DB.Context(ctx).In("question.status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed}).FindAndCount(&questionList)
|
||||
session := qr.data.DB.Context(ctx)
|
||||
session.In("status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed})
|
||||
count, err = session.Count(&entity.Question{Show: entity.QuestionShow})
|
||||
if err != nil {
|
||||
return count, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
return 0, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (qr *questionRepo) GetUserQuestionCount(ctx context.Context, userID string) (count int64, err error) {
|
||||
questionList := make([]*entity.Question, 0)
|
||||
count, err = qr.data.DB.In("question.status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed}).And("question.user_id = ?", userID).FindAndCount(&questionList)
|
||||
session := qr.data.DB.Context(ctx)
|
||||
session.In("status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed})
|
||||
count, err = session.Count(&entity.Question{UserID: userID})
|
||||
if err != nil {
|
||||
return count, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (qr *questionRepo) GetQuestionCountByIDs(ctx context.Context, ids []string) (count int64, err error) {
|
||||
questionList := make([]*entity.Question, 0)
|
||||
count, err = qr.data.DB.In("question.status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed}).In("id = ?", ids).Count(&questionList)
|
||||
if err != nil {
|
||||
return count, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (qr *questionRepo) GetQuestionIDsPage(ctx context.Context, page, pageSize int) (questionIDList []*schema.SiteMapQuestionInfo, err error) {
|
||||
func (qr *questionRepo) SitemapQuestions(ctx context.Context, page, pageSize int) (
|
||||
questionIDList []*schema.SiteMapQuestionInfo, err error) {
|
||||
page = page - 1
|
||||
questionIDList = make([]*schema.SiteMapQuestionInfo, 0)
|
||||
|
||||
// try to get sitemap data from cache
|
||||
cacheKey := fmt.Sprintf(constant.SiteMapQuestionCacheKeyPrefix, page)
|
||||
cacheData, err := qr.data.Cache.GetString(ctx, cacheKey)
|
||||
if err == nil && len(cacheKey) > 0 {
|
||||
_ = json.Unmarshal([]byte(cacheData), &questionIDList)
|
||||
return questionIDList, nil
|
||||
}
|
||||
|
||||
// get sitemap data from db
|
||||
rows := make([]*entity.Question, 0)
|
||||
if page > 0 {
|
||||
page = page - 1
|
||||
} else {
|
||||
page = 0
|
||||
}
|
||||
if pageSize == 0 {
|
||||
pageSize = constant.DefaultPageSize
|
||||
}
|
||||
offset := page * pageSize
|
||||
session := qr.data.DB.Context(ctx).Table("question")
|
||||
session = session.In("question.status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed})
|
||||
session.And("question.show = ?", entity.QuestionShow)
|
||||
session = session.Limit(pageSize, offset)
|
||||
session = session.OrderBy("question.created_at asc")
|
||||
err = session.Select("id,title,created_at,post_update_time").Find(&rows)
|
||||
session := qr.data.DB.Context(ctx)
|
||||
session.Select("id,title,created_at,post_update_time")
|
||||
session.Where("`show` = ?", entity.QuestionShow)
|
||||
session.Where("status = ? OR status = ?", entity.QuestionStatusAvailable, entity.QuestionStatusClosed)
|
||||
session.Limit(pageSize, page*pageSize)
|
||||
session.Asc("created_at")
|
||||
err = session.Find(&rows)
|
||||
if err != nil {
|
||||
return questionIDList, err
|
||||
}
|
||||
|
||||
// warp data
|
||||
for _, question := range rows {
|
||||
item := &schema.SiteMapQuestionInfo{}
|
||||
item.ID = uid.EnShortID(question.ID)
|
||||
item.Title = htmltext.UrlTitle(question.Title)
|
||||
updateTime := fmt.Sprintf("%v", question.PostUpdateTime.Format(time.RFC3339))
|
||||
if question.PostUpdateTime.Unix() < 1 {
|
||||
updateTime = fmt.Sprintf("%v", question.CreatedAt.Format(time.RFC3339))
|
||||
item := &schema.SiteMapQuestionInfo{ID: question.ID}
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
item.ID = uid.EnShortID(question.ID)
|
||||
}
|
||||
item.Title = htmltext.UrlTitle(question.Title)
|
||||
if question.PostUpdateTime.IsZero() {
|
||||
item.UpdateTime = question.CreatedAt.Format(time.RFC3339)
|
||||
} else {
|
||||
item.UpdateTime = question.PostUpdateTime.Format(time.RFC3339)
|
||||
}
|
||||
item.UpdateTime = updateTime
|
||||
questionIDList = append(questionIDList, item)
|
||||
}
|
||||
|
||||
// set sitemap data to cache
|
||||
cacheDataByte, _ := json.Marshal(questionIDList)
|
||||
if err := qr.data.Cache.SetString(ctx, cacheKey, string(cacheDataByte), constant.SiteMapQuestionCacheTime); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return questionIDList, nil
|
||||
}
|
||||
|
||||
@@ -312,13 +340,15 @@ func (qr *questionRepo) GetQuestionPage(ctx context.Context, page, pageSize int,
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
for _, item := range questionList {
|
||||
item.ID = uid.EnShortID(item.ID)
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
for _, item := range questionList {
|
||||
item.ID = uid.EnShortID(item.ID)
|
||||
}
|
||||
}
|
||||
return questionList, total, err
|
||||
}
|
||||
|
||||
func (qr *questionRepo) AdminSearchList(ctx context.Context, search *schema.AdminQuestionSearch) ([]*entity.Question, int64, error) {
|
||||
func (qr *questionRepo) AdminQuestionPage(ctx context.Context, search *schema.AdminQuestionPageReq) ([]*entity.Question, int64, error) {
|
||||
var (
|
||||
count int64
|
||||
err error
|
||||
@@ -379,8 +409,64 @@ func (qr *questionRepo) AdminSearchList(ctx context.Context, search *schema.Admi
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
return rows, count, err
|
||||
}
|
||||
for _, item := range rows {
|
||||
item.ID = uid.EnShortID(item.ID)
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
for _, item := range rows {
|
||||
item.ID = uid.EnShortID(item.ID)
|
||||
}
|
||||
}
|
||||
return rows, count, nil
|
||||
}
|
||||
|
||||
// updateSearch update search, if search plugin not enable, do nothing
|
||||
func (qr *questionRepo) updateSearch(ctx context.Context, questionID string) (err error) {
|
||||
// check search plugin
|
||||
var s plugin.Search
|
||||
_ = plugin.CallSearch(func(search plugin.Search) error {
|
||||
s = search
|
||||
return nil
|
||||
})
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
questionID = uid.DeShortID(questionID)
|
||||
question, exist, err := qr.GetQuestion(ctx, questionID)
|
||||
if !exist {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get tags
|
||||
var (
|
||||
tagListList = make([]*entity.TagRel, 0)
|
||||
tags = make([]string, 0)
|
||||
)
|
||||
session := qr.data.DB.Context(ctx).Where("object_id = ?", questionID)
|
||||
session.Where("status = ?", entity.TagRelStatusAvailable)
|
||||
err = session.Find(&tagListList)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
for _, tag := range tagListList {
|
||||
tags = append(tags, tag.TagID)
|
||||
}
|
||||
content := &plugin.SearchContent{
|
||||
ObjectID: questionID,
|
||||
Title: question.Title,
|
||||
Type: constant.QuestionObjectType,
|
||||
Content: question.ParsedText,
|
||||
Answers: int64(question.AnswerCount),
|
||||
Status: plugin.SearchContentStatus(question.Status),
|
||||
Tags: tags,
|
||||
QuestionID: questionID,
|
||||
UserID: question.UserID,
|
||||
Views: int64(question.ViewCount),
|
||||
Created: question.CreatedAt.Unix(),
|
||||
Active: question.UpdatedAt.Unix(),
|
||||
Score: int64(question.VoteCount),
|
||||
HasAccepted: question.AcceptedAnswerID != "" && question.AcceptedAnswerID != "0",
|
||||
}
|
||||
err = s.UpdateContent(ctx, questionID, content)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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.SumInt(&entity.Activity{}, "`rank`")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if int(earned) < maxDailyRank {
|
||||
return false, nil
|
||||
}
|
||||
log.Infof("user %s today has rank %d is reach stand %d", userID, earned, maxDailyRank)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// ChangeUserRank change user rank
|
||||
func (ur *UserRankRepo) ChangeUserRank(
|
||||
ctx context.Context, session *xorm.Session, userID string, userCurrentScore, deltaRank int) (err error) {
|
||||
// IMPORTANT: If user center enabled the rank agent, then we should not change user rank.
|
||||
if plugin.RankAgentEnabled() || deltaRank == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If user rank is lower than 1 after this action, then user rank will be set to 1 only.
|
||||
if deltaRank < 0 && userCurrentScore+deltaRank < 1 {
|
||||
deltaRank = 1 - userCurrentScore
|
||||
}
|
||||
|
||||
_, err = session.ID(userID).Incr("`rank`", deltaRank).Update(&entity.User{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TriggerUserRank trigger user rank change
|
||||
// session is need provider, it means this action must be success or failure
|
||||
// if outer action is failed then this action is need rollback
|
||||
@@ -38,10 +88,7 @@ func (ur *UserRankRepo) TriggerUserRank(ctx context.Context,
|
||||
session *xorm.Session, userID string, deltaRank int, activityType int,
|
||||
) (isReachStandard bool, err error) {
|
||||
// IMPORTANT: If user center enabled the rank agent, then we should not change user rank.
|
||||
if plugin.RankAgentEnabled() {
|
||||
return false, nil
|
||||
}
|
||||
if deltaRank == 0 {
|
||||
if plugin.RankAgentEnabled() || deltaRank == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -114,7 +161,7 @@ func (ur *UserRankRepo) checkUserTodayRank(ctx context.Context,
|
||||
LessVal: start,
|
||||
MoreVal: end,
|
||||
})
|
||||
earned, err := session.Sum(&entity.Activity{}, "rank")
|
||||
earned, err := session.Sum(&entity.Activity{}, "`rank`")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -137,7 +184,7 @@ func (ur *UserRankRepo) UserRankPage(ctx context.Context, userID string, page, p
|
||||
) {
|
||||
rankPage = make([]*entity.Activity, 0)
|
||||
|
||||
session := ur.data.DB.Context(ctx).Where(builder.Eq{"has_rank": 1}.And(builder.Eq{"cancelled": 0})).And(builder.Gt{"rank": 0})
|
||||
session := ur.data.DB.Context(ctx).Where(builder.Eq{"has_rank": 1}.And(builder.Eq{"cancelled": 0})).And(builder.Gt{"`rank`": 0})
|
||||
session.Desc("created_at")
|
||||
|
||||
cond := &entity.Activity{UserID: userID}
|
||||
|
||||
@@ -16,7 +16,7 @@ var (
|
||||
|
||||
func Test_captchaRepo_DelActionType(t *testing.T) {
|
||||
captchaRepo := captcha.NewCaptchaRepo(testDataSource)
|
||||
err := captchaRepo.SetActionType(context.TODO(), ip, actionType, amount)
|
||||
err := captchaRepo.SetActionType(context.TODO(), ip, actionType, "", amount)
|
||||
assert.NoError(t, err)
|
||||
|
||||
gotAmount, err := captchaRepo.GetActionType(context.TODO(), ip, actionType)
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
package repo_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_configRepo_Get(t *testing.T) {
|
||||
configRepo := config_common.NewConfigRepo(testDataSource)
|
||||
_, err := configRepo.Get("email.config")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_configRepo_GetArrayString(t *testing.T) {
|
||||
configRepo := config_common.NewConfigRepo(testDataSource)
|
||||
got, err := configRepo.GetArrayString("daily_rank_limit.exclude")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(got))
|
||||
assert.Equal(t, "answer.accepted", got[0])
|
||||
}
|
||||
|
||||
func Test_configRepo_GetConfigById(t *testing.T) {
|
||||
configRepo := config_common.NewConfigRepo(testDataSource)
|
||||
|
||||
closeInfo := &schema.GetReportTypeResp{}
|
||||
err := configRepo.GetJsonConfigByIDAndSetToObject(74, closeInfo)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "needs close", closeInfo.Name)
|
||||
}
|
||||
|
||||
func Test_configRepo_GetConfigType(t *testing.T) {
|
||||
configRepo := config_common.NewConfigRepo(testDataSource)
|
||||
configType, err := configRepo.GetConfigType("answer.accepted")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, configType)
|
||||
}
|
||||
|
||||
func Test_configRepo_GetInt(t *testing.T) {
|
||||
configRepo := config_common.NewConfigRepo(testDataSource)
|
||||
got, err := configRepo.GetInt("answer.accepted")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 15, got)
|
||||
}
|
||||
|
||||
func Test_configRepo_GetString(t *testing.T) {
|
||||
configRepo := config_common.NewConfigRepo(testDataSource)
|
||||
_, err := configRepo.GetString("email.config")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_configRepo_SetConfig(t *testing.T) {
|
||||
configRepo := config_common.NewConfigRepo(testDataSource)
|
||||
got, err := configRepo.GetString("email.config")
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = configRepo.SetConfig("email.config", got)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -4,13 +4,16 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/answerdev/answer/internal/repo/config"
|
||||
serviceconfig "github.com/answerdev/answer/internal/service/config"
|
||||
|
||||
"github.com/answerdev/answer/internal/repo/reason"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_reasonRepo_ListReasons(t *testing.T) {
|
||||
configRepo := config_common.NewConfigRepo(testDataSource)
|
||||
reasonRepo := reason.NewReasonRepo(configRepo)
|
||||
configRepo := config.NewConfigRepo(testDataSource)
|
||||
reasonRepo := reason.NewReasonRepo(serviceconfig.NewConfigService(configRepo))
|
||||
reasonItems, err := reasonRepo.ListReasons(context.TODO(), "question", "close")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 4, len(reasonItems))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package repo_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -55,6 +56,10 @@ func TestMain(t *testing.M) {
|
||||
// Use sqlite3 to test.
|
||||
dbSetting = dbSettingMapping[string(schemas.SQLITE)]
|
||||
}
|
||||
if dbSetting.Driver == string(schemas.SQLITE) {
|
||||
os.RemoveAll(dbSetting.Connection)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if tearDown != nil {
|
||||
tearDown()
|
||||
@@ -160,8 +165,15 @@ func initDatabase(dbSetting TestDBSetting) (dbEngine *xorm.Engine, err error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("connection to database failed: %s", err)
|
||||
}
|
||||
err = migrations.InitDB(dataConf)
|
||||
if err != nil {
|
||||
if err := migrations.NewMentor(context.TODO(), dbEngine, &migrations.InitNeedUserInputData{
|
||||
Language: "en_US",
|
||||
SiteName: "ANSWER",
|
||||
SiteURL: "http://127.0.0.1:8080/",
|
||||
ContactEmail: "answer@answer.com",
|
||||
AdminName: "admin",
|
||||
AdminPassword: "admin",
|
||||
AdminEmail: "answer@answer.com",
|
||||
}).InitDB(); err != nil {
|
||||
return nil, fmt.Errorf("migrations init database failed: %s", err)
|
||||
}
|
||||
return dbEngine, nil
|
||||
|
||||
@@ -2,9 +2,12 @@ package repo_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/answerdev/answer/internal/repo/unique"
|
||||
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/repo/tag"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -14,29 +17,29 @@ var (
|
||||
tagRelOnce sync.Once
|
||||
testTagRelList = []*entity.TagRel{
|
||||
{
|
||||
ObjectID: "1",
|
||||
TagID: "1",
|
||||
ObjectID: "10010000000000001",
|
||||
TagID: "10030000000000001",
|
||||
Status: entity.TagRelStatusAvailable,
|
||||
},
|
||||
{
|
||||
ObjectID: "2",
|
||||
TagID: "2",
|
||||
ObjectID: "10010000000000002",
|
||||
TagID: "10030000000000002",
|
||||
Status: entity.TagRelStatusAvailable,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func addTagRelList() {
|
||||
tagRelRepo := tag.NewTagRelRepo(testDataSource)
|
||||
tagRelRepo := tag.NewTagRelRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
err := tagRelRepo.AddTagRelList(context.TODO(), testTagRelList)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Fatalf("%+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_tagListRepo_BatchGetObjectTagRelList(t *testing.T) {
|
||||
tagRelOnce.Do(addTagRelList)
|
||||
tagRelRepo := tag.NewTagRelRepo(testDataSource)
|
||||
tagRelRepo := tag.NewTagRelRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
relList, err :=
|
||||
tagRelRepo.BatchGetObjectTagRelList(context.TODO(), []string{testTagRelList[0].ObjectID, testTagRelList[1].ObjectID})
|
||||
assert.NoError(t, err)
|
||||
@@ -45,15 +48,15 @@ func Test_tagListRepo_BatchGetObjectTagRelList(t *testing.T) {
|
||||
|
||||
func Test_tagListRepo_CountTagRelByTagID(t *testing.T) {
|
||||
tagRelOnce.Do(addTagRelList)
|
||||
tagRelRepo := tag.NewTagRelRepo(testDataSource)
|
||||
count, err := tagRelRepo.CountTagRelByTagID(context.TODO(), "1")
|
||||
tagRelRepo := tag.NewTagRelRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
count, err := tagRelRepo.CountTagRelByTagID(context.TODO(), "10030000000000001")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(1), count)
|
||||
}
|
||||
|
||||
func Test_tagListRepo_GetObjectTagRelList(t *testing.T) {
|
||||
tagRelOnce.Do(addTagRelList)
|
||||
tagRelRepo := tag.NewTagRelRepo(testDataSource)
|
||||
tagRelRepo := tag.NewTagRelRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
|
||||
relList, err :=
|
||||
tagRelRepo.GetObjectTagRelList(context.TODO(), testTagRelList[0].ObjectID)
|
||||
@@ -63,7 +66,7 @@ func Test_tagListRepo_GetObjectTagRelList(t *testing.T) {
|
||||
|
||||
func Test_tagListRepo_GetObjectTagRelWithoutStatus(t *testing.T) {
|
||||
tagRelOnce.Do(addTagRelList)
|
||||
tagRelRepo := tag.NewTagRelRepo(testDataSource)
|
||||
tagRelRepo := tag.NewTagRelRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
|
||||
|
||||
relList, err :=
|
||||
tagRelRepo.BatchGetObjectTagRelList(context.TODO(), []string{testTagRelList[0].ObjectID, testTagRelList[1].ObjectID})
|
||||
@@ -74,7 +77,7 @@ func Test_tagListRepo_GetObjectTagRelWithoutStatus(t *testing.T) {
|
||||
err = tagRelRepo.RemoveTagRelListByIDs(context.TODO(), ids)
|
||||
assert.NoError(t, err)
|
||||
|
||||
count, err := tagRelRepo.CountTagRelByTagID(context.TODO(), "1")
|
||||
count, err := tagRelRepo.CountTagRelByTagID(context.TODO(), "10030000000000001")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(0), count)
|
||||
|
||||
@@ -85,7 +88,7 @@ func Test_tagListRepo_GetObjectTagRelWithoutStatus(t *testing.T) {
|
||||
err = tagRelRepo.EnableTagRelByIDs(context.TODO(), ids)
|
||||
assert.NoError(t, err)
|
||||
|
||||
count, err = tagRelRepo.CountTagRelByTagID(context.TODO(), "1")
|
||||
count, err = tagRelRepo.CountTagRelByTagID(context.TODO(), "10030000000000001")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(1), count)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package repo_test
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
@@ -46,7 +47,7 @@ func addTagList() {
|
||||
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, uniqueIDRepo)
|
||||
err := tagCommonRepo.AddTagList(context.TODO(), testTagList)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Fatalf("%+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func Test_userRepo_AddUser(t *testing.T) {
|
||||
userRepo := user.NewUserRepo(testDataSource, config_common.NewConfigRepo(testDataSource))
|
||||
userRepo := user.NewUserRepo(testDataSource)
|
||||
userInfo := &entity.User{
|
||||
Username: "answer",
|
||||
Pass: "answer",
|
||||
@@ -25,7 +25,7 @@ func Test_userRepo_AddUser(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_userRepo_BatchGetByID(t *testing.T) {
|
||||
userRepo := user.NewUserRepo(testDataSource, config_common.NewConfigRepo(testDataSource))
|
||||
userRepo := user.NewUserRepo(testDataSource)
|
||||
got, err := userRepo.BatchGetByID(context.TODO(), []string{"1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(got))
|
||||
@@ -33,7 +33,7 @@ func Test_userRepo_BatchGetByID(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_userRepo_GetByEmail(t *testing.T) {
|
||||
userRepo := user.NewUserRepo(testDataSource, config_common.NewConfigRepo(testDataSource))
|
||||
userRepo := user.NewUserRepo(testDataSource)
|
||||
got, exist, err := userRepo.GetByEmail(context.TODO(), "admin@admin.com")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
@@ -41,7 +41,7 @@ func Test_userRepo_GetByEmail(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_userRepo_GetByUserID(t *testing.T) {
|
||||
userRepo := user.NewUserRepo(testDataSource, config_common.NewConfigRepo(testDataSource))
|
||||
userRepo := user.NewUserRepo(testDataSource)
|
||||
got, exist, err := userRepo.GetByUserID(context.TODO(), "1")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
@@ -49,7 +49,7 @@ func Test_userRepo_GetByUserID(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_userRepo_GetByUsername(t *testing.T) {
|
||||
userRepo := user.NewUserRepo(testDataSource, config_common.NewConfigRepo(testDataSource))
|
||||
userRepo := user.NewUserRepo(testDataSource)
|
||||
got, exist, err := userRepo.GetByUsername(context.TODO(), "admin")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
@@ -57,7 +57,7 @@ func Test_userRepo_GetByUsername(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_userRepo_IncreaseAnswerCount(t *testing.T) {
|
||||
userRepo := user.NewUserRepo(testDataSource, config_common.NewConfigRepo(testDataSource))
|
||||
userRepo := user.NewUserRepo(testDataSource)
|
||||
err := userRepo.IncreaseAnswerCount(context.TODO(), "1", 1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -68,7 +68,7 @@ func Test_userRepo_IncreaseAnswerCount(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_userRepo_IncreaseQuestionCount(t *testing.T) {
|
||||
userRepo := user.NewUserRepo(testDataSource, config_common.NewConfigRepo(testDataSource))
|
||||
userRepo := user.NewUserRepo(testDataSource)
|
||||
err := userRepo.IncreaseQuestionCount(context.TODO(), "1", 1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -79,19 +79,19 @@ func Test_userRepo_IncreaseQuestionCount(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_userRepo_UpdateEmail(t *testing.T) {
|
||||
userRepo := user.NewUserRepo(testDataSource, config_common.NewConfigRepo(testDataSource))
|
||||
userRepo := user.NewUserRepo(testDataSource)
|
||||
err := userRepo.UpdateEmail(context.TODO(), "1", "admin@admin.com")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_userRepo_UpdateEmailStatus(t *testing.T) {
|
||||
userRepo := user.NewUserRepo(testDataSource, config_common.NewConfigRepo(testDataSource))
|
||||
userRepo := user.NewUserRepo(testDataSource)
|
||||
err := userRepo.UpdateEmailStatus(context.TODO(), "1", entity.EmailStatusToBeVerified)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_userRepo_UpdateInfo(t *testing.T) {
|
||||
userRepo := user.NewUserRepo(testDataSource, config_common.NewConfigRepo(testDataSource))
|
||||
userRepo := user.NewUserRepo(testDataSource)
|
||||
err := userRepo.UpdateInfo(context.TODO(), &entity.User{ID: "1", Bio: "test"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -102,19 +102,19 @@ func Test_userRepo_UpdateInfo(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_userRepo_UpdateLastLoginDate(t *testing.T) {
|
||||
userRepo := user.NewUserRepo(testDataSource, config_common.NewConfigRepo(testDataSource))
|
||||
userRepo := user.NewUserRepo(testDataSource)
|
||||
err := userRepo.UpdateLastLoginDate(context.TODO(), "1")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_userRepo_UpdateNoticeStatus(t *testing.T) {
|
||||
userRepo := user.NewUserRepo(testDataSource, config_common.NewConfigRepo(testDataSource))
|
||||
userRepo := user.NewUserRepo(testDataSource)
|
||||
err := userRepo.UpdateNoticeStatus(context.TODO(), "1", 1)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_userRepo_UpdatePass(t *testing.T) {
|
||||
userRepo := user.NewUserRepo(testDataSource, config_common.NewConfigRepo(testDataSource))
|
||||
userRepo := user.NewUserRepo(testDataSource)
|
||||
err := userRepo.UpdatePass(context.TODO(), "1", "admin")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package search_common
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/answerdev/answer/plugin"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -426,6 +427,34 @@ func (sr *searchRepo) parseOrder(ctx context.Context, order string) (res string)
|
||||
return
|
||||
}
|
||||
|
||||
// ParseSearchPluginResult parse search plugin result
|
||||
func (sr *searchRepo) ParseSearchPluginResult(ctx context.Context, sres []plugin.SearchResult) (resp []schema.SearchResp, err error) {
|
||||
var (
|
||||
qres []map[string][]byte
|
||||
res = make([]map[string][]byte, 0)
|
||||
b *builder.Builder
|
||||
)
|
||||
for _, r := range sres {
|
||||
switch r.Type {
|
||||
case "question":
|
||||
b = builder.MySQL().Select(qFields...).From("question").Where(builder.Eq{"id": r.ID}).
|
||||
And(builder.Lt{"`status`": entity.QuestionStatusDeleted})
|
||||
case "answer":
|
||||
b = builder.MySQL().Select(aFields...).From("answer").LeftJoin("`question`", "`question`.`id` = `answer`.`question_id`").
|
||||
Where(builder.Eq{"`answer`.`id`": r.ID}).
|
||||
And(builder.Lt{"`question`.`status`": entity.QuestionStatusDeleted}).
|
||||
And(builder.Lt{"`answer`.`status`": entity.AnswerStatusDeleted}).And(builder.Eq{"`question`.`show`": entity.QuestionShow})
|
||||
}
|
||||
qres, err = sr.data.DB.Context(ctx).Query(b)
|
||||
if err != nil || len(qres) == 0 {
|
||||
continue
|
||||
}
|
||||
res = append(res, qres[0])
|
||||
}
|
||||
return sr.parseResult(ctx, res)
|
||||
}
|
||||
|
||||
// parseResult parse search result, return the data structure
|
||||
func (sr *searchRepo) parseResult(ctx context.Context, res []map[string][]byte) (resp []schema.SearchResp, err error) {
|
||||
for _, r := range res {
|
||||
var (
|
||||
|
||||
@@ -2,8 +2,8 @@ package tag
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/data"
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
tagcommon "github.com/answerdev/answer/internal/service/tag_common"
|
||||
@@ -36,8 +36,10 @@ func (tr *tagRelRepo) AddTagRelList(ctx context.Context, tagList []*entity.TagRe
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
for _, item := range tagList {
|
||||
item.ObjectID = uid.EnShortID(item.ObjectID)
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
for _, item := range tagList {
|
||||
item.ObjectID = uid.EnShortID(item.ObjectID)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -54,7 +56,7 @@ func (tr *tagRelRepo) RemoveTagRelListByObjectID(ctx context.Context, objectID s
|
||||
|
||||
func (tr *tagRelRepo) HideTagRelListByObjectID(ctx context.Context, objectID string) (err error) {
|
||||
objectID = uid.DeShortID(objectID)
|
||||
_, err = tr.data.DB.Where("object_id = ?", objectID).Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusHide})
|
||||
_, err = tr.data.DB.Context(ctx).Where("object_id = ?", objectID).Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusHide})
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
@@ -63,7 +65,7 @@ func (tr *tagRelRepo) HideTagRelListByObjectID(ctx context.Context, objectID str
|
||||
|
||||
func (tr *tagRelRepo) ShowTagRelListByObjectID(ctx context.Context, objectID string) (err error) {
|
||||
objectID = uid.DeShortID(objectID)
|
||||
_, err = tr.data.DB.Where("object_id = ?", objectID).Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusAvailable})
|
||||
_, err = tr.data.DB.Context(ctx).Where("object_id = ?", objectID).Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusAvailable})
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
@@ -89,8 +91,11 @@ func (tr *tagRelRepo) GetObjectTagRelWithoutStatus(ctx context.Context, objectID
|
||||
exist, err = session.Get(tagRel)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
return
|
||||
}
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
tagRel.ObjectID = uid.EnShortID(tagRel.ObjectID)
|
||||
}
|
||||
tagRel.ObjectID = uid.EnShortID(tagRel.ObjectID)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -112,9 +117,12 @@ func (tr *tagRelRepo) GetObjectTagRelList(ctx context.Context, objectID string)
|
||||
err = session.Find(&tagListList)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
return
|
||||
}
|
||||
for _, item := range tagListList {
|
||||
item.ObjectID = uid.EnShortID(item.ObjectID)
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
for _, item := range tagListList {
|
||||
item.ObjectID = uid.EnShortID(item.ObjectID)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -130,9 +138,12 @@ func (tr *tagRelRepo) BatchGetObjectTagRelList(ctx context.Context, objectIds []
|
||||
err = session.Find(&tagListList)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
return
|
||||
}
|
||||
for _, item := range tagListList {
|
||||
item.ObjectID = uid.EnShortID(item.ObjectID)
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
for _, item := range tagListList {
|
||||
item.ObjectID = uid.EnShortID(item.ObjectID)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/data"
|
||||
@@ -72,7 +73,7 @@ func (ur *userRepo) IncreaseQuestionCount(ctx context.Context, userID string, am
|
||||
func (ur *userRepo) UpdateQuestionCount(ctx context.Context, userID string, count int64) (err error) {
|
||||
user := &entity.User{}
|
||||
user.QuestionCount = int(count)
|
||||
_, err = ur.data.DB.Where("id = ?", userID).Cols("question_count").Update(user)
|
||||
_, err = ur.data.DB.Context(ctx).Where("id = ?", userID).Cols("question_count").Update(user)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
@@ -82,7 +83,7 @@ func (ur *userRepo) UpdateQuestionCount(ctx context.Context, userID string, coun
|
||||
func (ur *userRepo) UpdateAnswerCount(ctx context.Context, userID string, count int) (err error) {
|
||||
user := &entity.User{}
|
||||
user.AnswerCount = count
|
||||
_, err = ur.data.DB.Where("id = ?", userID).Cols("answer_count").Update(user)
|
||||
_, err = ur.data.DB.Context(ctx).Where("id = ?", userID).Cols("answer_count").Update(user)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
@@ -195,7 +196,7 @@ func (ur *userRepo) GetByUsername(ctx context.Context, username string) (userInf
|
||||
|
||||
func (ur *userRepo) GetByUsernames(ctx context.Context, usernames []string) ([]*entity.User, error) {
|
||||
list := make([]*entity.User, 0)
|
||||
err := ur.data.DB.Where("status =?", entity.UserStatusAvailable).In("username", usernames).Find(&list)
|
||||
err := ur.data.DB.Context(ctx).Where("status =?", entity.UserStatusAvailable).In("username", usernames).Find(&list)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
return list, err
|
||||
@@ -224,18 +225,16 @@ func (ur *userRepo) GetUserCount(ctx context.Context) (count int64, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (ur *userRepo) SearchUserListByName(ctx context.Context, name string) (userList []*entity.User, err error) {
|
||||
func (ur *userRepo) SearchUserListByName(ctx context.Context, name string, limit int) (userList []*entity.User, err error) {
|
||||
userList = make([]*entity.User, 0)
|
||||
if name == "" {
|
||||
return userList, nil
|
||||
}
|
||||
session := ur.data.DB.Where("")
|
||||
session.Where("username LIKE LOWER(?) or display_name LIKE ?", name+"%", name+"%").And("status =?", entity.UserStatusAvailable)
|
||||
session.Asc("username")
|
||||
session = session.Limit(5, 0)
|
||||
err = session.OrderBy("id desc").Find(&userList)
|
||||
session := ur.data.DB.Context(ctx)
|
||||
session.Where("status = ?", entity.UserStatusAvailable)
|
||||
session.Where("username LIKE ? OR display_name LIKE ?", strings.ToLower(name)+"%", name+"%")
|
||||
session.OrderBy("username ASC, id DESC")
|
||||
session.Limit(limit)
|
||||
err = session.Find(&userList)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
tryToDecorateUserListFromUserCenter(ctx, ur.data, userList)
|
||||
return
|
||||
|
||||
@@ -26,7 +26,7 @@ type AnswerAPIRouter struct {
|
||||
reasonController *controller.ReasonController
|
||||
themeController *controller_admin.ThemeController
|
||||
siteInfoController *controller_admin.SiteInfoController
|
||||
siteinfoController *controller.SiteinfoController
|
||||
siteinfoController *controller.SiteInfoController
|
||||
notificationController *controller.NotificationController
|
||||
dashboardController *controller.DashboardController
|
||||
uploadController *controller.UploadController
|
||||
@@ -55,7 +55,7 @@ func NewAnswerAPIRouter(
|
||||
reasonController *controller.ReasonController,
|
||||
themeController *controller_admin.ThemeController,
|
||||
siteInfoController *controller_admin.SiteInfoController,
|
||||
siteinfoController *controller.SiteinfoController,
|
||||
siteinfoController *controller.SiteInfoController,
|
||||
notificationController *controller.NotificationController,
|
||||
dashboardController *controller.DashboardController,
|
||||
uploadController *controller.UploadController,
|
||||
@@ -111,7 +111,6 @@ func (a *AnswerAPIRouter) RegisterMustUnAuthAnswerAPIRouter(r *gin.RouterGroup)
|
||||
routerGroup.GET("/user/register/captcha", a.userController.UserRegisterCaptcha)
|
||||
routerGroup.POST("/user/email/verification", a.userController.UserVerifyEmail)
|
||||
routerGroup.PUT("/user/email", a.userController.UserChangeEmailVerify)
|
||||
routerGroup.GET("/user/action/record", a.userController.ActionRecord)
|
||||
routerGroup.POST("/user/password/reset", a.userController.RetrievePassWord)
|
||||
routerGroup.POST("/user/password/replacement", a.userController.UseRePassWord)
|
||||
routerGroup.PUT("/user/email/notification", a.userController.UserUnsubscribeEmailNotification)
|
||||
@@ -124,6 +123,7 @@ func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) {
|
||||
r.POST("/user/email/verification/send", middleware.BanAPIForUserCenter, a.userController.UserVerifyEmailSend)
|
||||
r.GET("/personal/user/info", a.userController.GetOtherUserInfoByUsername)
|
||||
r.GET("/user/ranking", a.userController.UserRanking)
|
||||
r.GET("/user/action/record", a.userController.ActionRecord)
|
||||
|
||||
//answer
|
||||
r.GET("/answer/info", a.answerController.Get)
|
||||
@@ -244,9 +244,9 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) {
|
||||
r.GET("/question/page", a.questionController.AdminSearchList)
|
||||
r.GET("/question/page", a.questionController.AdminQuestionPage)
|
||||
r.PUT("/question/status", a.questionController.AdminSetQuestionStatus)
|
||||
r.GET("/answer/page", a.questionController.AdminSearchAnswerList)
|
||||
r.GET("/answer/page", a.questionController.AdminAnswerPage)
|
||||
r.PUT("/answer/status", a.answerController.AdminSetAnswerStatus)
|
||||
|
||||
// report
|
||||
@@ -257,6 +257,8 @@ func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) {
|
||||
r.GET("/users/page", a.adminUserController.GetUserPage)
|
||||
r.PUT("/user/status", a.adminUserController.UpdateUserStatus)
|
||||
r.PUT("/user/role", a.adminUserController.UpdateUserRole)
|
||||
r.GET("/user/activation", a.adminUserController.GetUserActivation)
|
||||
r.POST("/user/activation", a.adminUserController.SendUserActivation)
|
||||
r.POST("/user", a.adminUserController.AddUser)
|
||||
r.PUT("/user/password", a.adminUserController.UpdateUserPassword)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/answerdev/answer/internal/base/middleware"
|
||||
"github.com/answerdev/answer/internal/controller"
|
||||
templaterender "github.com/answerdev/answer/internal/controller/template_render"
|
||||
"github.com/answerdev/answer/internal/controller_admin"
|
||||
@@ -11,22 +12,25 @@ type TemplateRouter struct {
|
||||
templateController *controller.TemplateController
|
||||
templateRenderController *templaterender.TemplateRenderController
|
||||
siteInfoController *controller_admin.SiteInfoController
|
||||
authUserMiddleware *middleware.AuthUserMiddleware
|
||||
}
|
||||
|
||||
func NewTemplateRouter(
|
||||
templateController *controller.TemplateController,
|
||||
templateRenderController *templaterender.TemplateRenderController,
|
||||
siteInfoController *controller_admin.SiteInfoController,
|
||||
authUserMiddleware *middleware.AuthUserMiddleware,
|
||||
|
||||
) *TemplateRouter {
|
||||
return &TemplateRouter{
|
||||
templateController: templateController,
|
||||
templateRenderController: templateRenderController,
|
||||
siteInfoController: siteInfoController,
|
||||
authUserMiddleware: authUserMiddleware,
|
||||
}
|
||||
}
|
||||
|
||||
// TemplateRouter template router
|
||||
// RegisterTemplateRouter template router
|
||||
func (a *TemplateRouter) RegisterTemplateRouter(r *gin.RouterGroup) {
|
||||
r.GET("/sitemap.xml", a.templateController.Sitemap)
|
||||
r.GET("/sitemap/:page", a.templateController.SitemapPage)
|
||||
@@ -34,16 +38,17 @@ func (a *TemplateRouter) RegisterTemplateRouter(r *gin.RouterGroup) {
|
||||
r.GET("/robots.txt", a.siteInfoController.GetRobots)
|
||||
r.GET("/custom.css", a.siteInfoController.GetCss)
|
||||
|
||||
r.GET("/", a.templateController.Index)
|
||||
r.GET("/index", a.templateController.Index)
|
||||
|
||||
r.GET("/questions", a.templateController.QuestionList)
|
||||
r.GET("/questions/:id", a.templateController.QuestionInfo)
|
||||
r.GET("/questions/:id/:title", a.templateController.QuestionInfo)
|
||||
r.GET("/questions/:id/:title/:answerid", a.templateController.QuestionInfo)
|
||||
|
||||
r.GET("/tags", a.templateController.TagList)
|
||||
r.GET("/tags/:tag", a.templateController.TagInfo)
|
||||
r.GET("/users/:username", a.templateController.UserInfo)
|
||||
r.GET("/404", a.templateController.Page404)
|
||||
|
||||
//todo add middleware
|
||||
seo := r.Group("")
|
||||
seo.Use(a.authUserMiddleware.CheckPrivateMode())
|
||||
seo.GET("/", a.templateController.Index)
|
||||
seo.GET("/questions", a.templateController.QuestionList)
|
||||
seo.GET("/questions/:id", a.templateController.QuestionInfo)
|
||||
seo.GET("/questions/:id/:title", a.templateController.QuestionInfo)
|
||||
seo.GET("/questions/:id/:title/:answerid", a.templateController.QuestionInfo)
|
||||
seo.GET("/tags", a.templateController.TagList)
|
||||
seo.GET("/tags/:tag", a.templateController.TagInfo)
|
||||
seo.GET("/users/:username", a.templateController.UserInfo)
|
||||
}
|
||||
|
||||
@@ -21,14 +21,14 @@ const UIStaticPath = "build/static"
|
||||
|
||||
// UIRouter is an interface that provides ui static file routers
|
||||
type UIRouter struct {
|
||||
siteInfoController *controller.SiteinfoController
|
||||
siteInfoService *siteinfo_common.SiteInfoCommonService
|
||||
siteInfoController *controller.SiteInfoController
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||
}
|
||||
|
||||
// NewUIRouter creates a new UIRouter instance with the embed resources
|
||||
func NewUIRouter(
|
||||
siteInfoController *controller.SiteinfoController,
|
||||
siteInfoService *siteinfo_common.SiteInfoCommonService,
|
||||
siteInfoController *controller.SiteInfoController,
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||
) *UIRouter {
|
||||
return &UIRouter{
|
||||
siteInfoController: siteInfoController,
|
||||
|
||||
@@ -4,12 +4,13 @@ import "github.com/answerdev/answer/internal/base/constant"
|
||||
|
||||
// ActivityMsg activity message
|
||||
type ActivityMsg struct {
|
||||
UserID string `json:"user_id"`
|
||||
TriggerUserID int64 `json:"trigger_user_id"`
|
||||
ObjectID string `json:"object_id"`
|
||||
OriginalObjectID string `json:"original_object_id"`
|
||||
ActivityTypeKey constant.ActivityTypeKey `json:"activity_type_key"`
|
||||
RevisionID string `json:"revision_id"`
|
||||
UserID string
|
||||
TriggerUserID int64
|
||||
ObjectID string
|
||||
OriginalObjectID string
|
||||
ActivityTypeKey constant.ActivityTypeKey
|
||||
RevisionID string
|
||||
ExtraInfo map[string]string
|
||||
}
|
||||
|
||||
// GetObjectTimelineReq get object timeline request
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package schema
|
||||
|
||||
// AcceptAnswerOperationInfo accept answer operation info
|
||||
type AcceptAnswerOperationInfo struct {
|
||||
TriggerUserID string
|
||||
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
|
||||
}
|
||||
@@ -12,7 +12,9 @@ type RemoveAnswerReq struct {
|
||||
// user id
|
||||
UserID string `json:"-"`
|
||||
// whether user can delete it
|
||||
CanDelete bool `json:"-"`
|
||||
CanDelete bool `json:"-"`
|
||||
CaptchaID string `json:"captcha_id"` // captcha_id
|
||||
CaptchaCode string `json:"captcha_code"`
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -21,12 +23,14 @@ const (
|
||||
)
|
||||
|
||||
type AnswerAddReq struct {
|
||||
QuestionID string `json:"question_id"`
|
||||
Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"`
|
||||
HTML string `json:"-"`
|
||||
UserID string `json:"-"`
|
||||
CanEdit bool `json:"-"`
|
||||
CanDelete bool `json:"-"`
|
||||
QuestionID string `json:"question_id"`
|
||||
Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"`
|
||||
HTML string `json:"-"`
|
||||
UserID string `json:"-"`
|
||||
CanEdit bool `json:"-"`
|
||||
CanDelete bool `json:"-"`
|
||||
CaptchaID string `json:"captcha_id"` // captcha_id
|
||||
CaptchaCode string `json:"captcha_code"`
|
||||
}
|
||||
|
||||
func (req *AnswerAddReq) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
@@ -44,7 +48,9 @@ type AnswerUpdateReq struct {
|
||||
UserID string `json:"-"`
|
||||
NoNeedReview bool `json:"-"`
|
||||
// whether user can edit it
|
||||
CanEdit bool `json:"-"`
|
||||
CanEdit bool `json:"-"`
|
||||
CaptchaID string `json:"captcha_id"` // captcha_id
|
||||
CaptchaCode string `json:"captcha_code"`
|
||||
}
|
||||
|
||||
func (req *AnswerUpdateReq) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
|
||||
@@ -86,7 +86,7 @@ type UpdateUserRoleReq struct {
|
||||
|
||||
// AddUserReq add user request
|
||||
type AddUserReq struct {
|
||||
DisplayName string `validate:"required,gt=4,lte=30" json:"display_name"`
|
||||
DisplayName string `validate:"required,gte=4,lte=30" json:"display_name"`
|
||||
Email string `validate:"required,email,gt=0,lte=500" json:"email"`
|
||||
Password string `validate:"required,gte=8,lte=32" json:"password"`
|
||||
LoginUserID string `json:"-"`
|
||||
@@ -98,3 +98,18 @@ type UpdateUserPasswordReq struct {
|
||||
Password string `validate:"required,gte=8,lte=32" json:"password"`
|
||||
LoginUserID string `json:"-"`
|
||||
}
|
||||
|
||||
// GetUserActivationReq get user activation
|
||||
type GetUserActivationReq struct {
|
||||
UserID string `validate:"required" form:"user_id"`
|
||||
}
|
||||
|
||||
// GetUserActivationResp get user activation
|
||||
type GetUserActivationResp struct {
|
||||
ActivationURL string `json:"activation_url"`
|
||||
}
|
||||
|
||||
// SendUserActivationReq send user activation
|
||||
type SendUserActivationReq struct {
|
||||
UserID string `validate:"required" json:"user_id"`
|
||||
}
|
||||
|
||||
@@ -26,7 +26,9 @@ type AddCommentReq struct {
|
||||
// whether user can edit it
|
||||
CanEdit bool `json:"-"`
|
||||
// whether user can delete it
|
||||
CanDelete bool `json:"-"`
|
||||
CanDelete bool `json:"-"`
|
||||
CaptchaID string `json:"captcha_id"` // captcha_id
|
||||
CaptchaCode string `json:"captcha_code"`
|
||||
}
|
||||
|
||||
func (req *AddCommentReq) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
@@ -39,7 +41,9 @@ type RemoveCommentReq struct {
|
||||
// comment id
|
||||
CommentID string `validate:"required" json:"comment_id"`
|
||||
// user id
|
||||
UserID string `json:"-"`
|
||||
UserID string `json:"-"`
|
||||
CaptchaID string `json:"captcha_id"` // captcha_id
|
||||
CaptchaCode string `json:"captcha_code"`
|
||||
}
|
||||
|
||||
// UpdateCommentReq update comment request
|
||||
@@ -58,7 +62,9 @@ type UpdateCommentReq struct {
|
||||
// whether user can edit it
|
||||
CanEdit bool `json:"-"`
|
||||
// whether user can delete it
|
||||
CanDelete bool `json:"-"`
|
||||
CanDelete bool `json:"-"`
|
||||
CaptchaID string `json:"captcha_id"` // captcha_id
|
||||
CaptchaCode string `json:"captcha_code"`
|
||||
}
|
||||
|
||||
func (req *UpdateCommentReq) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
|
||||
@@ -5,8 +5,8 @@ import "time"
|
||||
var AppStartTime time.Time
|
||||
|
||||
const (
|
||||
DashBoardCachekey = "answer@dashboard"
|
||||
DashBoardCacheTime = 60 * time.Minute
|
||||
DashboardCacheKey = "answer:dashboard"
|
||||
DashboardCacheTime = 60 * time.Minute
|
||||
)
|
||||
|
||||
type DashboardInfo struct {
|
||||
|
||||
@@ -63,6 +63,8 @@ type NotificationMsg struct {
|
||||
NotificationAction string
|
||||
// if true no need to send notification to all followers
|
||||
NoNeedPushAllFollow bool
|
||||
// extra info
|
||||
ExtraInfo map[string]string
|
||||
}
|
||||
|
||||
type ObjectInfo struct {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/validator"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/pkg/converter"
|
||||
"github.com/answerdev/answer/pkg/uid"
|
||||
)
|
||||
|
||||
const (
|
||||
SitemapMaxSize = 50000
|
||||
SitemapCachekey = "answer@sitemap"
|
||||
SitemapPageCachekey = "answer@sitemap@page%d"
|
||||
QuestionOperationPin = "pin"
|
||||
QuestionOperationUnPin = "unpin"
|
||||
QuestionOperationHide = "hide"
|
||||
@@ -20,9 +20,11 @@ const (
|
||||
// RemoveQuestionReq delete question request
|
||||
type RemoveQuestionReq struct {
|
||||
// question id
|
||||
ID string `validate:"required" json:"id"`
|
||||
UserID string `json:"-" ` // user_id
|
||||
IsAdmin bool `json:"-"`
|
||||
ID string `validate:"required" json:"id"`
|
||||
UserID string `json:"-" ` // user_id
|
||||
IsAdmin bool `json:"-"`
|
||||
CaptchaID string `json:"captcha_id"` // captcha_id
|
||||
CaptchaCode string `json:"captcha_code"`
|
||||
}
|
||||
|
||||
type CloseQuestionReq struct {
|
||||
@@ -63,6 +65,8 @@ type QuestionAdd struct {
|
||||
// user id
|
||||
UserID string `json:"-"`
|
||||
QuestionPermission
|
||||
CaptchaID string `json:"captcha_id"` // captcha_id
|
||||
CaptchaCode string `json:"captcha_code"`
|
||||
}
|
||||
|
||||
func (req *QuestionAdd) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
@@ -90,6 +94,8 @@ type QuestionAddByAnswer struct {
|
||||
UserID string `json:"-"`
|
||||
MentionUsernameList []string `validate:"omitempty" json:"mention_username_list"`
|
||||
QuestionPermission
|
||||
CaptchaID string `json:"captcha_id"` // captcha_id
|
||||
CaptchaCode string `json:"captcha_code"`
|
||||
}
|
||||
|
||||
func (req *QuestionAddByAnswer) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
@@ -153,6 +159,8 @@ type QuestionUpdate struct {
|
||||
UserID string `json:"-"`
|
||||
NoNeedReview bool `json:"-"`
|
||||
QuestionPermission
|
||||
CaptchaID string `json:"captcha_id"` // captcha_id
|
||||
CaptchaCode string `json:"captcha_code"`
|
||||
}
|
||||
|
||||
type QuestionUpdateInviteUser struct {
|
||||
@@ -160,6 +168,8 @@ type QuestionUpdateInviteUser struct {
|
||||
InviteUser []string `validate:"omitempty" json:"invite_user"`
|
||||
UserID string `json:"-"`
|
||||
QuestionPermission
|
||||
CaptchaID string `json:"captcha_id"` // captcha_id
|
||||
CaptchaCode string `json:"captcha_code"`
|
||||
}
|
||||
|
||||
func (req *QuestionUpdate) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
@@ -361,12 +371,62 @@ type QuestionPageRespOperator struct {
|
||||
DisplayName string `json:"display_name"`
|
||||
}
|
||||
|
||||
type AdminQuestionSearch struct {
|
||||
Page int `json:"page" form:"page"` // Query number of pages
|
||||
PageSize int `json:"page_size" form:"page_size"` // Search page size
|
||||
Status int `json:"-" form:"-"`
|
||||
StatusStr string `json:"status" form:"status"` // Status 1 Available 2 closed 10 UserDeleted
|
||||
Query string `validate:"omitempty,gt=0,lte=100" json:"query" form:"query" ` //Query string
|
||||
type AdminQuestionPageReq struct {
|
||||
Page int `validate:"omitempty,min=1" form:"page"`
|
||||
PageSize int `validate:"omitempty,min=1" form:"page_size"`
|
||||
StatusCond string `validate:"omitempty,oneof=normal closed deleted" form:"status"`
|
||||
Query string `validate:"omitempty,gt=0,lte=100" json:"query" form:"query" `
|
||||
Status int `json:"-"`
|
||||
LoginUserID string `json:"-"`
|
||||
}
|
||||
|
||||
func (req *AdminQuestionPageReq) Check() (errField []*validator.FormErrorField, err error) {
|
||||
status, ok := entity.AdminQuestionSearchStatus[req.StatusCond]
|
||||
if ok {
|
||||
req.Status = status
|
||||
}
|
||||
if req.Status == 0 {
|
||||
req.Status = 1
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// AdminAnswerPageReq admin answer page req
|
||||
type AdminAnswerPageReq struct {
|
||||
Page int `validate:"omitempty,min=1" form:"page"`
|
||||
PageSize int `validate:"omitempty,min=1" form:"page_size"`
|
||||
StatusCond string `validate:"omitempty,oneof=normal deleted" form:"status"`
|
||||
Query string `validate:"omitempty,gt=0,lte=100" form:"query"`
|
||||
QuestionID string `validate:"omitempty,gt=0,lte=24" form:"question_id"`
|
||||
QuestionTitle string `json:"-"`
|
||||
AnswerID string `json:"-"`
|
||||
Status int `json:"-"`
|
||||
LoginUserID string `json:"-"`
|
||||
}
|
||||
|
||||
func (req *AdminAnswerPageReq) Check() (errField []*validator.FormErrorField, err error) {
|
||||
req.QuestionID = uid.DeShortID(req.QuestionID)
|
||||
if req.QuestionID == "0" {
|
||||
req.QuestionID = ""
|
||||
}
|
||||
|
||||
if status, ok := entity.AdminAnswerSearchStatus[req.StatusCond]; ok {
|
||||
req.Status = status
|
||||
}
|
||||
if req.Status == 0 {
|
||||
req.Status = 1
|
||||
}
|
||||
|
||||
// parse query condition
|
||||
if len(req.Query) > 0 {
|
||||
prefix := "answer:"
|
||||
if strings.Contains(req.Query, prefix) {
|
||||
req.AnswerID = uid.DeShortID(strings.TrimSpace(strings.TrimPrefix(req.Query, prefix)))
|
||||
} else {
|
||||
req.QuestionTitle = strings.TrimSpace(req.Query)
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type AdminSetQuestionStatusRequest struct {
|
||||
@@ -374,21 +434,6 @@ type AdminSetQuestionStatusRequest struct {
|
||||
QuestionID string `json:"question_id" form:"question_id"`
|
||||
}
|
||||
|
||||
type SiteMapList struct {
|
||||
QuestionIDs []*SiteMapQuestionInfo `json:"question_ids"`
|
||||
MaxPageNum []int `json:"max_page_num"`
|
||||
}
|
||||
|
||||
type SiteMapPageList struct {
|
||||
PageData []*SiteMapQuestionInfo `json:"page_data"`
|
||||
}
|
||||
|
||||
type SiteMapQuestionInfo struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
UpdateTime string `json:"time"`
|
||||
}
|
||||
|
||||
type PersonalQuestionPageReq struct {
|
||||
Page int `validate:"omitempty,min=1" form:"page"`
|
||||
PageSize int `validate:"omitempty,min=1" form:"page_size"`
|
||||
|
||||
@@ -15,7 +15,9 @@ type AddReportReq struct {
|
||||
// report content
|
||||
Content string `validate:"omitempty,gt=0,lte=500" json:"content"`
|
||||
// user id
|
||||
UserID string `json:"-"`
|
||||
UserID string `json:"-"`
|
||||
CaptchaID string `json:"captcha_id"` // captcha_id
|
||||
CaptchaCode string `json:"captcha_code"`
|
||||
}
|
||||
|
||||
// GetReportListReq get report list all request
|
||||
|
||||
@@ -1,11 +1,83 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"github.com/answerdev/answer/internal/base/constant"
|
||||
"github.com/answerdev/answer/plugin"
|
||||
)
|
||||
|
||||
type SearchDTO struct {
|
||||
UserID string // UserID current login user ID
|
||||
Query string `validate:"required,gte=1,lte=60" json:"q" form:"q"` // Query the query string
|
||||
Page int `validate:"omitempty,min=1" form:"page,default=1" json:"page"` //Query number of pages
|
||||
Size int `validate:"omitempty,min=1,max=50" form:"size,default=30" json:"size"` //Search page size
|
||||
Order string `validate:"required,oneof=newest active score relevance" form:"order,default=relevance" json:"order" enums:"newest,active,score,relevance"`
|
||||
UserID string // UserID current login user ID
|
||||
Query string `validate:"required,gte=1,lte=60" json:"q" form:"q"` // Query the query string
|
||||
Page int `validate:"omitempty,min=1" form:"page,default=1" json:"page"` //Query number of pages
|
||||
Size int `validate:"omitempty,min=1,max=50" form:"size,default=30" json:"size"` //Search page size
|
||||
Order string `validate:"required,oneof=newest active score relevance" form:"order,default=relevance" json:"order" enums:"newest,active,score,relevance"`
|
||||
CaptchaID string `json:"captcha_id"` // captcha_id
|
||||
CaptchaCode string `json:"captcha_code"`
|
||||
}
|
||||
|
||||
type SearchCondition struct {
|
||||
// search target type: all/question/answer
|
||||
TargetType string
|
||||
// search query user id
|
||||
UserID string
|
||||
// vote amount
|
||||
VoteAmount int
|
||||
// only show not accepted answer's question
|
||||
NotAccepted bool
|
||||
// view amount
|
||||
Views int
|
||||
// answer count
|
||||
AnswerAmount int
|
||||
// only show accepted answer
|
||||
Accepted bool
|
||||
// only show this question's answer
|
||||
QuestionID string
|
||||
// search query tags
|
||||
Tags []string
|
||||
// search query keywords
|
||||
Words []string
|
||||
}
|
||||
|
||||
// SearchAll check if search all
|
||||
func (s *SearchCondition) SearchAll() bool {
|
||||
return len(s.TargetType) == 0
|
||||
}
|
||||
|
||||
// SearchQuestion check if search only need question
|
||||
func (s *SearchCondition) SearchQuestion() bool {
|
||||
return s.TargetType == constant.QuestionObjectType
|
||||
}
|
||||
|
||||
// SearchAnswer check if search only need answer
|
||||
func (s *SearchCondition) SearchAnswer() bool {
|
||||
return s.TargetType == constant.AnswerObjectType
|
||||
}
|
||||
|
||||
// Convert2PluginSearchCond convert to plugin search condition
|
||||
func (s *SearchCondition) Convert2PluginSearchCond(page, pageSize int, order string) *plugin.SearchBasicCond {
|
||||
basic := &plugin.SearchBasicCond{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
Words: s.Words,
|
||||
TagIDs: s.Tags,
|
||||
UserID: s.UserID,
|
||||
Order: plugin.SearchOrderCond(order),
|
||||
QuestionID: s.QuestionID,
|
||||
VoteAmount: s.VoteAmount,
|
||||
ViewAmount: s.Views,
|
||||
AnswerAmount: s.AnswerAmount,
|
||||
}
|
||||
if s.Accepted {
|
||||
basic.AnswerAccepted = plugin.AcceptedCondTrue
|
||||
} else {
|
||||
basic.AnswerAccepted = plugin.AcceptedCondAll
|
||||
}
|
||||
if s.NotAccepted {
|
||||
basic.QuestionAccepted = plugin.AcceptedCondFalse
|
||||
} else {
|
||||
basic.QuestionAccepted = plugin.AcceptedCondAll
|
||||
}
|
||||
return basic
|
||||
}
|
||||
|
||||
type SearchObject struct {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user