Merge branch 'release/1.1.1' into github-main

# Conflicts:
#	i18n/en_US.yaml
This commit is contained in:
LinkinStars
2023-08-02 14:26:37 +08:00
226 changed files with 6432 additions and 4235 deletions
+2 -1
View File
@@ -1,5 +1,6 @@
{
"eslint.workingDirectories": [
"ui"
]
],
"explorer.autoReveal": "focusNoScroll"
}
+1 -1
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+42 -36
View File
@@ -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
+97 -61
View File
@@ -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
View File
@@ -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.
+4
View File
@@ -107,8 +107,12 @@ backend:
other: 管理标签同义词
email:
other: 邮箱
e_mail:
other: 邮箱
password:
other: 密码
pass:
other: 密码
email_or_password_wrong_error:
other: 邮箱和密码不匹配。
error:
+3
View File
@@ -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"
)
+11
View File
@@ -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
)
+2 -2
View File
@@ -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{
+2 -2
View File
@@ -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
}
+16
View File
@@ -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
}
+49 -3
View File
@@ -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)
+20 -23
View File
@@ -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
+1
View File
@@ -8,4 +8,5 @@ import (
var ProviderSetMiddleware = wire.NewSet(
NewAuthUserMiddleware,
NewAvatarMiddleware,
NewShortIDMiddleware,
)
+29
View File
@@ -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())
}
}
+2 -1
View File
@@ -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
View File
@@ -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)
+2 -5
View File
@@ -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
+74 -19
View File
@@ -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)
+66 -3
View File
@@ -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)
}
+2 -3
View File
@@ -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
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ var ProviderSetController = wire.NewSet(
NewRankController,
NewReasonController,
NewNotificationController,
NewSiteinfoController,
NewSiteInfoController,
NewDashboardController,
NewUploadController,
NewActivityController,
+3 -3
View File
@@ -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,
})
+2 -2
View File
@@ -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,
+119 -36
View File
@@ -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
+31 -2
View File
@@ -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)
}
+35 -5
View File
@@ -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,
})
}
+8 -8
View File
@@ -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,
+18 -14
View File
@@ -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
+32 -42
View File
@@ -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
+100 -79
View File
@@ -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)
}
+57 -26
View File
@@ -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)
}
-9
View File
@@ -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"
+22
View File
@@ -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"`
}
+1 -1
View File
@@ -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"`
+10 -9
View File
@@ -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
View File
@@ -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 doesnt 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 cant 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 cant 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 cant 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
}
+313
View File
@@ -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 doesnt 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 cant 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 cant 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 cant 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`},
}
)
+14 -9
View File
@@ -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
}
+3 -2
View File
@@ -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))
}
+7 -6
View File
@@ -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)
}
+5 -5
View File
@@ -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)
}
+4 -3
View File
@@ -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
View File
@@ -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)
}
}
+35
View File
@@ -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"
}
+3 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
+6 -5
View File
@@ -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
}
+5 -4
View File
@@ -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)
}
+5 -4
View File
@@ -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
View File
@@ -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
}
+3 -2
View File
@@ -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)
}
+266 -262
View File
@@ -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
}
+2 -2
View File
@@ -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
}
+29 -12
View File
@@ -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
View File
@@ -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
}
+7 -5
View File
@@ -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
}
+128 -92
View File
@@ -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
}
+8 -8
View File
@@ -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,
}
}
+26 -9
View File
@@ -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()
+1 -1
View File
@@ -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()
}
-1
View File
@@ -52,7 +52,6 @@ var ProviderSetRepo = wire.NewSet(
activity.NewVoteRepo,
activity.NewFollowRepo,
activity.NewAnswerActivityRepo,
activity.NewQuestionActivityRepo,
activity.NewUserActiveActivityRepo,
activity.NewActivityRepo,
tag.NewTagRepo,
+137 -51
View File
@@ -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
}
+53 -6
View File
@@ -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}
+1 -1
View File
@@ -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)
}
+5 -2
View File
@@ -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))
+14 -2
View File
@@ -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
+16 -13
View File
@@ -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)
}
+2 -1
View File
@@ -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)
}
}
+13 -13
View File
@@ -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 (
+21 -10
View File
@@ -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
}
+12 -13
View File
@@ -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
+7 -5
View File
@@ -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)
+17 -12
View File
@@ -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)
}
+4 -4
View File
@@ -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,
+7 -6
View File
@@ -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
+36
View File
@@ -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
}
+14 -8
View File
@@ -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) {
+16 -1
View File
@@ -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"`
}
+9 -3
View File
@@ -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) {
+2 -2
View File
@@ -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 {
+2
View File
@@ -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 {
+72 -27
View File
@@ -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"`
+3 -1
View File
@@ -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
+77 -5
View File
@@ -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