Merge remote-tracking branch 'github/feat/1.1.2/user-center' into beta.2/1.1.0

# Conflicts:
#	docs/docs.go
#	docs/swagger.json
#	docs/swagger.yaml
#	go.mod
#	i18n/en_US.yaml
#	internal/base/reason/reason.go
#	internal/migrations/init.go
#	internal/migrations/migrations.go
This commit is contained in:
LinkinStars 2023-05-09 10:49:00 +08:00
commit b0a04bbf11
60 changed files with 1656 additions and 360 deletions

View File

@ -193,7 +193,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
reasonService := reason2.NewReasonService(reasonRepo) reasonService := reason2.NewReasonService(reasonRepo)
reasonController := controller.NewReasonController(reasonService) reasonController := controller.NewReasonController(reasonService)
themeController := controller_admin.NewThemeController() themeController := controller_admin.NewThemeController()
siteInfoService := siteinfo.NewSiteInfoService(siteInfoRepo, siteInfoCommonService, emailService, tagCommonService) siteInfoService := siteinfo.NewSiteInfoService(siteInfoRepo, siteInfoCommonService, emailService, tagCommonService, configRepo)
siteInfoController := controller_admin.NewSiteInfoController(siteInfoService) siteInfoController := controller_admin.NewSiteInfoController(siteInfoService)
siteinfoController := controller.NewSiteinfoController(siteInfoCommonService) siteinfoController := controller.NewSiteinfoController(siteInfoCommonService)
notificationRepo := notification.NewNotificationRepo(dataData) notificationRepo := notification.NewNotificationRepo(dataData)
@ -220,7 +220,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
templateController := controller.NewTemplateController(templateRenderController, siteInfoCommonService) templateController := controller.NewTemplateController(templateRenderController, siteInfoCommonService)
templateRouter := router.NewTemplateRouter(templateController, templateRenderController, siteInfoController) templateRouter := router.NewTemplateRouter(templateController, templateRenderController, siteInfoController)
connectorController := controller.NewConnectorController(siteInfoCommonService, emailService, userExternalLoginService) connectorController := controller.NewConnectorController(siteInfoCommonService, emailService, userExternalLoginService)
userCenterLoginService := user_external_login2.NewUserCenterLoginService(userRepo, userCommon, userExternalLoginRepo, userActiveActivityRepo) userCenterLoginService := user_external_login2.NewUserCenterLoginService(userRepo, userCommon, userExternalLoginRepo, userActiveActivityRepo, siteInfoCommonService)
userCenterController := controller.NewUserCenterController(userCenterLoginService, siteInfoCommonService) userCenterController := controller.NewUserCenterController(userCenterLoginService, siteInfoCommonService)
pluginAPIRouter := router.NewPluginAPIRouter(connectorController, userCenterController) pluginAPIRouter := router.NewPluginAPIRouter(connectorController, userCenterController)
ginEngine := server.NewHTTPServer(debug, staticRouter, answerAPIRouter, swaggerRouter, uiRouter, authUserMiddleware, avatarMiddleware, templateRouter, pluginAPIRouter) ginEngine := server.NewHTTPServer(debug, staticRouter, answerAPIRouter, swaggerRouter, uiRouter, authUserMiddleware, avatarMiddleware, templateRouter, pluginAPIRouter)

View File

@ -662,6 +662,77 @@ const docTemplate = `{
} }
} }
}, },
"/answer/admin/api/setting/privileges": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "GetPrivilegesConfig get privileges config",
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "GetPrivilegesConfig get privileges config",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/handler.RespBody"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/schema.GetPrivilegesConfigResp"
}
}
}
]
}
}
}
},
"put": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "update privileges config",
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "update privileges config",
"parameters": [
{
"description": "config",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/schema.UpdatePrivilegesConfigReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.RespBody"
}
}
}
}
},
"/answer/admin/api/setting/smtp": { "/answer/admin/api/setting/smtp": {
"get": { "get": {
"security": [ "security": [
@ -1301,6 +1372,77 @@ const docTemplate = `{
} }
} }
}, },
"/answer/admin/api/siteinfo/users": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "get site user config",
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "get site user config",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/handler.RespBody"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/schema.SiteUsersResp"
}
}
}
]
}
}
}
},
"put": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "update site info config about users",
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "update site info config about users",
"parameters": [
{
"description": "users info",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/schema.SiteUsersReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.RespBody"
}
}
}
}
},
"/answer/admin/api/siteinfo/write": { "/answer/admin/api/siteinfo/write": {
"get": { "get": {
"security": [ "security": [
@ -2861,7 +3003,7 @@ const docTemplate = `{
"ApiKeyAuth": [] "ApiKeyAuth": []
} }
], ],
"description": "list personal answers", "description": "UserAnswerList",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -2869,9 +3011,9 @@ const docTemplate = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"Personal" "api-answer"
], ],
"summary": "list personal answers", "summary": "UserAnswerList",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@ -2903,8 +3045,8 @@ const docTemplate = `{
{ {
"type": "string", "type": "string",
"default": "20", "default": "20",
"description": "page_size", "description": "pagesize",
"name": "page_size", "name": "pagesize",
"in": "query", "in": "query",
"required": true "required": true
} }
@ -2926,7 +3068,7 @@ const docTemplate = `{
"ApiKeyAuth": [] "ApiKeyAuth": []
} }
], ],
"description": "list personal collections", "description": "UserCollectionList",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -2936,7 +3078,7 @@ const docTemplate = `{
"tags": [ "tags": [
"Collection" "Collection"
], ],
"summary": "list personal collections", "summary": "UserCollectionList",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@ -2949,8 +3091,8 @@ const docTemplate = `{
{ {
"type": "string", "type": "string",
"default": "20", "default": "20",
"description": "page_size", "description": "pagesize",
"name": "page_size", "name": "pagesize",
"in": "query", "in": "query",
"required": true "required": true
} }
@ -5668,7 +5810,7 @@ const docTemplate = `{
"ApiKeyAuth": [] "ApiKeyAuth": []
} }
], ],
"description": "list personal questions", "description": "UserList",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -5676,9 +5818,9 @@ const docTemplate = `{
"application/json" "application/json"
], ],
"tags": [ "tags": [
"Personal" "Question"
], ],
"summary": "list personal questions", "summary": "UserList",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@ -5710,8 +5852,8 @@ const docTemplate = `{
{ {
"type": "string", "type": "string",
"default": "20", "default": "20",
"description": "page_size", "description": "pagesize",
"name": "page_size", "name": "pagesize",
"in": "query", "in": "query",
"required": true "required": true
} }
@ -5748,6 +5890,20 @@ const docTemplate = `{
} }
}, },
"definitions": { "definitions": {
"constant.Privilege": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"label": {
"type": "string"
},
"value": {
"type": "integer"
}
}
},
"handler.RespBody": { "handler.RespBody": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -6229,9 +6385,20 @@ const docTemplate = `{
} }
} }
}, },
"schema.ConfigFieldUIOptionAction": {
"type": "object",
"properties": {
"url": {
"type": "string"
}
}
},
"schema.ConfigFieldUIOptions": { "schema.ConfigFieldUIOptions": {
"type": "object", "type": "object",
"properties": { "properties": {
"action": {
"$ref": "#/definitions/schema.ConfigFieldUIOptionAction"
},
"input_type": { "input_type": {
"type": "string" "type": "string"
}, },
@ -6243,6 +6410,12 @@ const docTemplate = `{
}, },
"rows": { "rows": {
"type": "string" "type": "string"
},
"text": {
"type": "string"
},
"variant": {
"type": "string"
} }
} }
}, },
@ -6649,6 +6822,20 @@ const docTemplate = `{
} }
} }
}, },
"schema.GetPrivilegesConfigResp": {
"type": "object",
"properties": {
"options": {
"type": "array",
"items": {
"$ref": "#/definitions/schema.PrivilegeOption"
}
},
"selected_level": {
"type": "integer"
}
}
},
"schema.GetRankPersonalWithPageResp": { "schema.GetRankPersonalWithPageResp": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -7351,6 +7538,23 @@ const docTemplate = `{
} }
} }
}, },
"schema.PrivilegeOption": {
"type": "object",
"properties": {
"level": {
"type": "integer"
},
"level_desc": {
"type": "string"
},
"privileges": {
"type": "array",
"items": {
"$ref": "#/definitions/constant.Privilege"
}
}
}
},
"schema.QuestionAdd": { "schema.QuestionAdd": {
"type": "object", "type": "object",
"required": [ "required": [
@ -7418,6 +7622,10 @@ const docTemplate = `{
"schema.QuestionPageReq": { "schema.QuestionPageReq": {
"type": "object", "type": "object",
"properties": { "properties": {
"inDays": {
"type": "integer",
"minimum": 1
},
"orderCond": { "orderCond": {
"type": "string", "type": "string",
"enum": [ "enum": [
@ -7800,6 +8008,10 @@ const docTemplate = `{
"custom_header": { "custom_header": {
"type": "string", "type": "string",
"maxLength": 65536 "maxLength": 65536
},
"custom_sidebar": {
"type": "string",
"maxLength": 65536
} }
} }
}, },
@ -7821,6 +8033,10 @@ const docTemplate = `{
"custom_header": { "custom_header": {
"type": "string", "type": "string",
"maxLength": 65536 "maxLength": 65536
},
"custom_sidebar": {
"type": "string",
"maxLength": 65536
} }
} }
}, },
@ -7908,6 +8124,9 @@ const docTemplate = `{
"site_seo": { "site_seo": {
"$ref": "#/definitions/schema.SiteSeoReq" "$ref": "#/definitions/schema.SiteSeoReq"
}, },
"site_users": {
"$ref": "#/definitions/schema.SiteUsersResp"
},
"theme": { "theme": {
"$ref": "#/definitions/schema.SiteThemeResp" "$ref": "#/definitions/schema.SiteThemeResp"
}, },
@ -7919,18 +8138,10 @@ const docTemplate = `{
"schema.SiteInterfaceReq": { "schema.SiteInterfaceReq": {
"type": "object", "type": "object",
"required": [ "required": [
"default_avatar",
"language", "language",
"time_zone" "time_zone"
], ],
"properties": { "properties": {
"default_avatar": {
"type": "string",
"enum": [
"system",
"gravatar"
]
},
"language": { "language": {
"type": "string", "type": "string",
"maxLength": 128 "maxLength": 128
@ -7944,18 +8155,10 @@ const docTemplate = `{
"schema.SiteInterfaceResp": { "schema.SiteInterfaceResp": {
"type": "object", "type": "object",
"required": [ "required": [
"default_avatar",
"language", "language",
"time_zone" "time_zone"
], ],
"properties": { "properties": {
"default_avatar": {
"type": "string",
"enum": [
"system",
"gravatar"
]
},
"language": { "language": {
"type": "string", "type": "string",
"maxLength": 128 "maxLength": 128
@ -8003,6 +8206,15 @@ const docTemplate = `{
"schema.SiteLoginReq": { "schema.SiteLoginReq": {
"type": "object", "type": "object",
"properties": { "properties": {
"allow_email_domains": {
"type": "array",
"items": {
"type": "string"
}
},
"allow_email_registrations": {
"type": "boolean"
},
"allow_new_registrations": { "allow_new_registrations": {
"type": "boolean" "type": "boolean"
}, },
@ -8014,6 +8226,15 @@ const docTemplate = `{
"schema.SiteLoginResp": { "schema.SiteLoginResp": {
"type": "object", "type": "object",
"properties": { "properties": {
"allow_email_domains": {
"type": "array",
"items": {
"type": "string"
}
},
"allow_email_registrations": {
"type": "boolean"
},
"allow_new_registrations": { "allow_new_registrations": {
"type": "boolean" "type": "boolean"
}, },
@ -8090,6 +8311,72 @@ const docTemplate = `{
} }
} }
}, },
"schema.SiteUsersReq": {
"type": "object",
"required": [
"default_avatar"
],
"properties": {
"allow_update_avatar": {
"type": "boolean"
},
"allow_update_bio": {
"type": "boolean"
},
"allow_update_display_name": {
"type": "boolean"
},
"allow_update_location": {
"type": "boolean"
},
"allow_update_username": {
"type": "boolean"
},
"allow_update_website": {
"type": "boolean"
},
"default_avatar": {
"type": "string",
"enum": [
"system",
"gravatar"
]
}
}
},
"schema.SiteUsersResp": {
"type": "object",
"required": [
"default_avatar"
],
"properties": {
"allow_update_avatar": {
"type": "boolean"
},
"allow_update_bio": {
"type": "boolean"
},
"allow_update_display_name": {
"type": "boolean"
},
"allow_update_location": {
"type": "boolean"
},
"allow_update_username": {
"type": "boolean"
},
"allow_update_website": {
"type": "boolean"
},
"default_avatar": {
"type": "string",
"enum": [
"system",
"gravatar"
]
}
}
},
"schema.SiteWriteReq": { "schema.SiteWriteReq": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -8324,6 +8611,19 @@ const docTemplate = `{
} }
} }
}, },
"schema.UpdatePrivilegesConfigReq": {
"type": "object",
"required": [
"level"
],
"properties": {
"level": {
"type": "integer",
"maximum": 3,
"minimum": 1
}
}
},
"schema.UpdateSMTPConfigReq": { "schema.UpdateSMTPConfigReq": {
"type": "object", "type": "object",
"properties": { "properties": {

47
go.sum
View File

@ -76,7 +76,6 @@ github.com/anargu/gin-brotli v0.0.0-20220116052358-12bf532d5267/go.mod h1:Yj3yPP
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@ -113,10 +112,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
@ -172,7 +167,6 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
@ -231,8 +225,6 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 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.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 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
@ -281,8 +273,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
@ -300,7 +290,6 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/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 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@ -317,8 +306,6 @@ 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-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-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-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 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 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
@ -348,18 +335,13 @@ github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
@ -382,8 +364,6 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imroc/req/v3 v3.33.1 h1:BZnyl+K0hXcJlZBHY2CqbPgmVc1pPJDzjn6aJfB6shI=
github.com/imroc/req/v3 v3.33.1/go.mod h1:cZ+7C3L/AYOr4tLGG16hZF90F1WzAdAdzt1xFSlizXY=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
@ -558,10 +538,9 @@ github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:v
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
@ -625,23 +604,12 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 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/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U=
github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc=
github.com/quic-go/qtls-go1-19 v0.2.0 h1:Cvn2WdhyViFUHoOqK52i51k4nDX8EwIh5VJiVM4nttk=
github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI=
github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
github.com/quic-go/quic-go v0.32.0 h1:lY02md31s1JgPiiyfqJijpu/UX/Iun304FI3yUqX7tA=
github.com/quic-go/quic-go v0.32.0/go.mod h1:/fCsKANhQIeD5l76c2JFU+07gVE3KaA0FP+0zMWwfwo=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 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 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= 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/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= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
@ -774,7 +742,6 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@ -827,8 +794,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@ -1014,7 +979,6 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
@ -1148,7 +1112,6 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
@ -1162,8 +1125,6 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I=
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
@ -1182,14 +1143,9 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ=
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -1201,7 +1157,6 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 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.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 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=

View File

@ -25,6 +25,8 @@ backend:
other: Close other: Close
reopen: reopen:
other: Reopen other: Reopen
forbidden_error:
other: Forbidden.
pin: pin:
other: Pin other: Pin
hide: hide:
@ -48,6 +50,58 @@ backend:
other: Have the full power to access the site. other: Have the full power to access the site.
moderator: moderator:
other: Has access to all posts except admin settings. other: Has access to all posts except admin settings.
privilege:
level_1:
description:
other: Level 1 (less reputation required for private team, group)
level_2:
description:
other: Level 2 (low reputation required for startup community)
level_3:
description:
other: Level 3 (high reputation required for mature community)
rank_question_add_label:
other: Ask question
rank_answer_add_label:
other: Write answer
rank_comment_add_label:
other: Write comment
rank_report_add_label:
other: Flag
rank_comment_vote_up_label:
other: Upvote comment
rank_link_url_limit_label:
other: Post more than 2 links at a time
rank_question_vote_up_label:
other: Upvote question
rank_answer_vote_up_label:
other: Upvote answer
rank_question_vote_down_label:
other: Downvote question
rank_answer_vote_down_label:
other: Downvote answer
rank_tag_add_label:
other: Create new tag
rank_tag_edit_label:
other: Edit tag description (need to review)
rank_question_edit_label:
other: Edit other's question (need to review)
rank_answer_edit_label:
other: Edit other's answer (need to review)
rank_question_edit_without_review_label:
other: Edit other's question without review
rank_answer_edit_without_review_label:
other: Edit other's answer without review
rank_question_audit_label:
other: Review question edits
rank_answer_audit_label:
other: Review answer edits
rank_tag_audit_label:
other: Review tag edits
rank_tag_edit_without_review_label:
other: Edit tag description without review
rank_tag_synonym_label:
other: Manage tag synonyms
email: email:
other: Email other: Email
password: password:
@ -85,6 +139,8 @@ backend:
other: Email should be verified. other: Email should be verified.
verify_url_expired: verify_url_expired:
other: Email verified URL has expired, please resend the email. other: Email verified URL has expired, please resend the email.
illegal_email_domain_error:
other: Email is not allowed from that email domain. Please use another one.
lang: lang:
not_found: not_found:
other: Language file not found. other: Language file not found.
@ -176,6 +232,10 @@ backend:
other: You cannot modify your role. other: You cannot modify your role.
not_allowed_registration: not_allowed_registration:
other: Currently the site is not open for registration other: Currently the site is not open for registration
access_denied:
other: Access denied
page_access_denied:
other: You do not have access to this page.
config: config:
read_config_failed: read_config_failed:
other: Read config failed other: Read config failed
@ -281,6 +341,16 @@ backend:
other: Your answer has been deleted other: Your answer has been deleted
your_comment_was_deleted: your_comment_was_deleted:
other: Your comment has been deleted other: Your comment has been deleted
up_voted_question:
other: upvoted question
down_voted_question:
other: downvoted question
up_voted_answer:
other: upvoted answer
down_voted_answer:
other: downvoted answer
up_voted_comment:
other: upvoted comment
# The following fields are used for interface presentation(Front-end) # The following fields are used for interface presentation(Front-end)
ui: ui:

View File

@ -45,6 +45,58 @@ backend:
other: 拥有管理网站的全部权限。 other: 拥有管理网站的全部权限。
moderator: moderator:
other: 拥有访问除管理员设置以外的所有权限。 other: 拥有访问除管理员设置以外的所有权限。
privilege:
level_1:
description:
other: 等级1创业社区所需的声望最低
level_2:
description:
other: 等级2创业社区所需的声望较低
level_3:
description:
other: 等级3成熟社区所需的声望较高
rank_question_add_label:
other: 提问
rank_answer_add_label:
other: 回答问题
rank_comment_add_label:
other: 发表评论
rank_report_add_label:
other: 举报
rank_comment_vote_up_label:
other: 评论点赞
rank_link_url_limit_label:
other: 一次发布超过两个链接
rank_question_vote_up_label:
other: 问题点赞
rank_answer_vote_up_label:
other: 答案点赞
rank_question_vote_down_label:
other: 问题点踩
rank_answer_vote_down_label:
other: 答案点踩
rank_tag_add_label:
other: 创建新标签
rank_tag_edit_label:
other: 编辑标签描述(需要审核)
rank_question_edit_label:
other: 编辑他人提问(需要审核)
rank_answer_edit_label:
other: 编辑他人回答(需要审核)
rank_question_edit_without_review_label:
other: 编辑他人提问(无需审核)
rank_answer_edit_without_review_label:
other: 编辑他人回答(无需审核)
rank_question_audit_label:
other: 审核问题编辑
rank_answer_audit_label:
other: 审核答案编辑
rank_tag_audit_label:
other: 审核标签编辑
rank_tag_edit_without_review_label:
other: 编辑标签描述(无需审核)
rank_tag_synonym_label:
other: 管理标签同义词
email: email:
other: 邮箱 other: 邮箱
password: password:
@ -82,6 +134,8 @@ backend:
other: 邮箱需要验证。 other: 邮箱需要验证。
verify_url_expired: verify_url_expired:
other: 邮箱验证的网址已过期,请重新发送邮件。 other: 邮箱验证的网址已过期,请重新发送邮件。
illegal_email_domain_error:
other: 该域名的邮箱无法使用。请尝试更换其他邮箱。
lang: lang:
not_found: not_found:
other: 语言未找到 other: 语言未找到
@ -171,6 +225,10 @@ backend:
other: 您不能修改自己的角色。 other: 您不能修改自己的角色。
not_allowed_registration: not_allowed_registration:
other: 目前该站点未开放注册 other: 目前该站点未开放注册
access_denied:
other: 访问被拒绝
page_access_denied:
other: 你没有权限进入这个页面。
config: config:
read_config_failed: read_config_failed:
other: 读取配置失败 other: 读取配置失败
@ -271,6 +329,16 @@ backend:
other: 你的答案已被删除 other: 你的答案已被删除
your_comment_was_deleted: your_comment_was_deleted:
other: 你的评论已被删除 other: 你的评论已被删除
up_voted_question:
other: 赞了问题
down_voted_question:
other: 踩了问题
up_voted_answer:
other: 赞了答案
down_voted_answer:
other: 踩了答案
up_voted_comment:
other: 赞了评论
#The following fields are used for interface presentation(Front-end) #The following fields are used for interface presentation(Front-end)
ui: ui:
how_to_format: how_to_format:
@ -1063,6 +1131,9 @@ ui:
installed_plugins: 插件列表 installed_plugins: 插件列表
website_welcome: 欢迎来到 {{site_name}} website_welcome: 欢迎来到 {{site_name}}
plugins: plugins:
login: 登录
qrcode_login_tip: 请使用 {{ agentName }} 扫描二维码登录
login_failed_email_tip: 登录失败, 请允许该应用程序访问您的电子邮件信息,然后再试一次。
oauth: oauth:
connect: 连接到 {{ auth_name }} connect: 连接到 {{ auth_name }}
remove: 解绑 {{ auth_name }} remove: 解绑 {{ auth_name }}
@ -1396,6 +1467,30 @@ ui:
deactivate: 停用 deactivate: 停用
activate: 启用 activate: 启用
settings: 设置 settings: 设置
settings_users:
title: 用户
avatar:
label: 默认头像
text: 未设置自定义头像的用户所展示的头像。
profile_editable:
title: 可编辑的个人资料
allow_update_display_name:
label: 允许用户更改显示名称
allow_update_username:
label: 允许用户更改用户名
allow_update_avatar:
label: 允许用户更改头像
allow_update_bio:
label: 允许用户更改自我介绍
allow_update_website:
label: 允许用户更改个人网站
allow_update_location:
label: 允许用户更改所在地
privilege:
title: 声望权限
level:
label: 所需声望等级
text: 选择所需的声望等级以获取权限
form: form:
optional: (选填) optional: (选填)
empty: 不能为空 empty: 不能为空

View File

@ -66,6 +66,8 @@ const (
SiteTypeLogin = "login" SiteTypeLogin = "login"
SiteTypeCustomCssHTML = "css-html" SiteTypeCustomCssHTML = "css-html"
SiteTypeTheme = "theme" SiteTypeTheme = "theme"
SiteTypePrivileges = "privileges"
SiteTypeUsers = "users"
) )
func ExistInPathIgnore(name string) bool { func ExistInPathIgnore(name string) bool {

View File

@ -1,28 +1,38 @@
package constant package constant
const ( const (
// UpdateQuestion update question // NotificationUpdateQuestion update question
UpdateQuestion = "notification.action.update_question" NotificationUpdateQuestion = "notification.action.update_question"
// AnswerTheQuestion answer the question // NotificationAnswerTheQuestion answer the question
AnswerTheQuestion = "notification.action.answer_the_question" NotificationAnswerTheQuestion = "notification.action.answer_the_question"
// UpdateAnswer update answer // NotificationUpVotedTheQuestion up voted the question
UpdateAnswer = "notification.action.update_answer" NotificationUpVotedTheQuestion = "notification.action.up_voted_question"
// AcceptAnswer accept answer // NotificationDownVotedTheQuestion down voted the question
AcceptAnswer = "notification.action.accept_answer" NotificationDownVotedTheQuestion = "notification.action.down_voted_question"
// CommentQuestion comment question // NotificationUpdateAnswer update answer
CommentQuestion = "notification.action.comment_question" NotificationUpdateAnswer = "notification.action.update_answer"
// CommentAnswer comment answer // NotificationAcceptAnswer accept answer
CommentAnswer = "notification.action.comment_answer" NotificationAcceptAnswer = "notification.action.accept_answer"
// ReplyToYou reply to you // NotificationUpVotedTheAnswer up voted the answer
ReplyToYou = "notification.action.reply_to_you" NotificationUpVotedTheAnswer = "notification.action.up_voted_answer"
// MentionYou mention you // NotificationDownVotedTheAnswer down voted the answer
MentionYou = "notification.action.mention_you" NotificationDownVotedTheAnswer = "notification.action.down_voted_answer"
// YourQuestionIsClosed your question is closed // NotificationCommentQuestion comment question
YourQuestionIsClosed = "notification.action.your_question_is_closed" NotificationCommentQuestion = "notification.action.comment_question"
// YourQuestionWasDeleted your question was deleted // NotificationCommentAnswer comment answer
YourQuestionWasDeleted = "notification.action.your_question_was_deleted" NotificationCommentAnswer = "notification.action.comment_answer"
// YourAnswerWasDeleted your answer was deleted // NotificationUpVotedTheComment up voted the comment
YourAnswerWasDeleted = "notification.action.your_answer_was_deleted" NotificationUpVotedTheComment = "notification.action.up_voted_comment"
// YourCommentWasDeleted your comment was deleted // NotificationReplyToYou reply to you
YourCommentWasDeleted = "notification.action.your_comment_was_deleted" NotificationReplyToYou = "notification.action.reply_to_you"
// NotificationMentionYou mention you
NotificationMentionYou = "notification.action.mention_you"
// NotificationYourQuestionIsClosed your question is closed
NotificationYourQuestionIsClosed = "notification.action.your_question_is_closed"
// NotificationYourQuestionWasDeleted your question was deleted
NotificationYourQuestionWasDeleted = "notification.action.your_question_was_deleted"
// NotificationYourAnswerWasDeleted your answer was deleted
NotificationYourAnswerWasDeleted = "notification.action.your_answer_was_deleted"
// NotificationYourCommentWasDeleted your comment was deleted
NotificationYourCommentWasDeleted = "notification.action.your_comment_was_deleted"
) )

View File

@ -0,0 +1,70 @@
package constant
import "github.com/answerdev/answer/internal/base/reason"
type Privilege struct {
Key string `json:"key"`
Label string `json:"label"`
Value int `json:"value"`
}
const (
RankQuestionAddKey = "rank.question.add"
RankQuestionEditKey = "rank.question.edit"
RankQuestionDeleteKey = "rank.question.delete"
RankQuestionVoteUpKey = "rank.question.vote_up"
RankQuestionVoteDownKey = "rank.question.vote_down"
RankAnswerAddKey = "rank.answer.add"
RankAnswerEditKey = "rank.answer.edit"
RankAnswerDeleteKey = "rank.answer.delete"
RankAnswerAcceptKey = "rank.answer.accept"
RankAnswerVoteUpKey = "rank.answer.vote_up"
RankAnswerVoteDownKey = "rank.answer.vote_down"
RankCommentAddKey = "rank.comment.add"
RankCommentEditKey = "rank.comment.edit"
RankCommentDeleteKey = "rank.comment.delete"
RankReportAddKey = "rank.report.add"
RankTagAddKey = "rank.tag.add"
RankTagEditKey = "rank.tag.edit"
RankTagDeleteKey = "rank.tag.delete"
RankTagSynonymKey = "rank.tag.synonym"
RankLinkUrlLimitKey = "rank.link.url_limit"
RankVoteDetailKey = "rank.vote.detail"
RankCommentVoteUpKey = "rank.comment.vote_up"
RankCommentVoteDownKey = "rank.comment.vote_down"
RankQuestionEditWithoutReviewKey = "rank.question.edit_without_review"
RankAnswerEditWithoutReviewKey = "rank.answer.edit_without_review"
RankTagEditWithoutReviewKey = "rank.tag.edit_without_review"
RankAnswerAuditKey = "rank.answer.audit"
RankQuestionAuditKey = "rank.question.audit"
RankTagAuditKey = "rank.tag.audit"
RankQuestionCloseKey = "rank.question.close"
RankQuestionReopenKey = "rank.question.reopen"
RankTagUseReservedTagKey = "rank.tag.use_reserved_tag"
)
var (
RankAllPrivileges = []*Privilege{
{Label: reason.RankQuestionAddLabel, Key: RankQuestionAddKey},
{Label: reason.RankAnswerAddLabel, Key: RankAnswerAddKey},
{Label: reason.RankCommentAddLabel, Key: RankCommentAddKey},
{Label: reason.RankReportAddLabel, Key: RankReportAddKey},
{Label: reason.RankCommentVoteUpLabel, Key: RankCommentVoteUpKey},
{Label: reason.RankLinkUrlLimitLabel, Key: RankLinkUrlLimitKey},
{Label: reason.RankQuestionVoteUpLabel, Key: RankQuestionVoteUpKey},
{Label: reason.RankAnswerVoteUpLabel, Key: RankAnswerVoteUpKey},
{Label: reason.RankQuestionVoteDownLabel, Key: RankQuestionVoteDownKey},
{Label: reason.RankAnswerVoteDownLabel, Key: RankAnswerVoteDownKey},
{Label: reason.RankTagAddLabel, Key: RankTagAddKey},
{Label: reason.RankTagEditLabel, Key: RankTagEditKey},
{Label: reason.RankQuestionEditLabel, Key: RankQuestionEditKey},
{Label: reason.RankAnswerEditLabel, Key: RankAnswerEditKey},
{Label: reason.RankQuestionEditWithoutReviewLabel, Key: RankQuestionEditWithoutReviewKey},
{Label: reason.RankAnswerEditWithoutReviewLabel, Key: RankAnswerEditWithoutReviewKey},
{Label: reason.RankQuestionAuditLabel, Key: RankQuestionAuditKey},
{Label: reason.RankAnswerAuditLabel, Key: RankAnswerAuditKey},
{Label: reason.RankTagAuditLabel, Key: RankTagAuditKey},
{Label: reason.RankTagEditWithoutReviewLabel, Key: RankTagEditWithoutReviewKey},
{Label: reason.RankTagSynonymLabel, Key: RankTagSynonymKey},
}
)

View File

@ -1,5 +1,6 @@
package constant package constant
var ( var (
DefaultAvatar = "system" DefaultAvatar = "system"
DefaultSiteURL = ""
) )

View File

@ -8,9 +8,13 @@ import (
"github.com/segmentfault/pacman/errors" "github.com/segmentfault/pacman/errors"
) )
// BanAPIWhenUserCenterEnabled ban api when user center enabled // BanAPIForUserCenter ban api for user center
func BanAPIWhenUserCenterEnabled(ctx *gin.Context) { func BanAPIForUserCenter(ctx *gin.Context) {
if plugin.UserCenterEnabled() { uc, ok := plugin.GetUserCenter()
if !ok {
return
}
if !uc.Description().EnabledOriginalUserSystem {
handler.HandleResponse(ctx, errors.Forbidden(reason.ForbiddenError), nil) handler.HandleResponse(ctx, errors.Forbidden(reason.ForbiddenError), nil)
ctx.Abort() ctx.Abort()
return return

View File

@ -0,0 +1,29 @@
package reason
const (
PrivilegeLevel1Desc = "privilege.level_1.description"
PrivilegeLevel2Desc = "privilege.level_2.description"
PrivilegeLevel3Desc = "privilege.level_3.description"
RankQuestionAddLabel = "privilege.rank_question_add_label"
RankAnswerAddLabel = "privilege.rank_answer_add_label"
RankCommentAddLabel = "privilege.rank_comment_add_label"
RankReportAddLabel = "privilege.rank_report_add_label"
RankCommentVoteUpLabel = "privilege.rank_comment_vote_up_label"
RankLinkUrlLimitLabel = "privilege.rank_link_url_limit_label"
RankQuestionVoteUpLabel = "privilege.rank_question_vote_up_label"
RankAnswerVoteUpLabel = "privilege.rank_answer_vote_up_label"
RankQuestionVoteDownLabel = "privilege.rank_question_vote_down_label"
RankAnswerVoteDownLabel = "privilege.rank_answer_vote_down_label"
RankTagAddLabel = "privilege.rank_tag_add_label"
RankTagEditLabel = "privilege.rank_tag_edit_label"
RankQuestionEditLabel = "privilege.rank_question_edit_label"
RankAnswerEditLabel = "privilege.rank_answer_edit_label"
RankQuestionEditWithoutReviewLabel = "privilege.rank_question_edit_without_review_label"
RankAnswerEditWithoutReviewLabel = "privilege.rank_answer_edit_without_review_label"
RankQuestionAuditLabel = "privilege.rank_question_audit_label"
RankAnswerAuditLabel = "privilege.rank_answer_audit_label"
RankTagAuditLabel = "privilege.rank_tag_audit_label"
RankTagEditWithoutReviewLabel = "privilege.rank_tag_edit_without_review_label"
RankTagSynonymLabel = "privilege.rank_tag_synonym_label"
)

View File

@ -42,6 +42,7 @@ const (
EmailDuplicate = "error.email.duplicate" EmailDuplicate = "error.email.duplicate"
EmailVerifyURLExpired = "error.email.verify_url_expired" EmailVerifyURLExpired = "error.email.verify_url_expired"
EmailNeedToBeVerified = "error.email.need_to_be_verified" EmailNeedToBeVerified = "error.email.need_to_be_verified"
EmailIllegalDomainError = "error.email.illegal_email_domain_error"
UserSuspended = "error.user.suspended" UserSuspended = "error.user.suspended"
ObjectNotFound = "error.object.not_found" ObjectNotFound = "error.object.not_found"
TagNotFound = "error.tag.not_found" TagNotFound = "error.tag.not_found"
@ -50,7 +51,7 @@ const (
TagIsUsedCannotDelete = "error.tag.is_used_cannot_delete" TagIsUsedCannotDelete = "error.tag.is_used_cannot_delete"
TagAlreadyExist = "error.tag.already_exist" TagAlreadyExist = "error.tag.already_exist"
RankFailToMeetTheCondition = "error.rank.fail_to_meet_the_condition" RankFailToMeetTheCondition = "error.rank.fail_to_meet_the_condition"
VoteRankFailToMeetTheCondition = "error.rank.vote_fail_to_meet_the_condition" VoteRankFailToMeetTheCondition = "error.rank.vote_fail_to_meet_the_condition"
ThemeNotFound = "error.theme.not_found" ThemeNotFound = "error.theme.not_found"
LangNotFound = "error.lang.not_found" LangNotFound = "error.lang.not_found"
ReportHandleFailed = "error.report.handle_failed" ReportHandleFailed = "error.report.handle_failed"
@ -73,4 +74,6 @@ const (
AdminCannotUpdateTheirPassword = "error.admin.cannot_update_their_password" AdminCannotUpdateTheirPassword = "error.admin.cannot_update_their_password"
AdminCannotModifySelfStatus = "error.admin.cannot_modify_self_status" AdminCannotModifySelfStatus = "error.admin.cannot_modify_self_status"
UserExternalLoginUnbindingForbidden = "error.user.external_login_unbinding_forbidden" UserExternalLoginUnbindingForbidden = "error.user.external_login_unbinding_forbidden"
UserAccessDenied = "error.user.access_denied"
UserPageAccessDenied = "error.user.page_access_denied"
) )

View File

@ -7,6 +7,7 @@ import (
brotli "github.com/anargu/gin-brotli" brotli "github.com/anargu/gin-brotli"
"github.com/answerdev/answer/internal/base/middleware" "github.com/answerdev/answer/internal/base/middleware"
"github.com/answerdev/answer/internal/router" "github.com/answerdev/answer/internal/router"
"github.com/answerdev/answer/plugin"
"github.com/answerdev/answer/ui" "github.com/answerdev/answer/ui"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -66,6 +67,14 @@ func NewHTTPServer(debug bool,
// plugin routes // plugin routes
pluginAPIRouter.RegisterUnAuthConnectorRouter(mustUnAuthV1) pluginAPIRouter.RegisterUnAuthConnectorRouter(mustUnAuthV1)
pluginAPIRouter.RegisterAuthConnectorRouter(authV1) pluginAPIRouter.RegisterAuthUserConnectorRouter(authV1)
pluginAPIRouter.RegisterAuthAdminConnectorRouter(adminauthV1)
_ = plugin.CallAgent(func(agent plugin.Agent) error {
agent.RegisterUnAuthRouter(mustUnAuthV1)
agent.RegisterAuthUserRouter(authV1)
agent.RegisterAuthAdminRouter(adminauthV1)
return nil
})
return r return r
} }

View File

@ -130,7 +130,7 @@ func (cc *ConnectorController) ConnectorRedirect(connector plugin.Connector) (fn
return return
} }
if len(resp.AccessToken) > 0 { if len(resp.AccessToken) > 0 {
ctx.Redirect(http.StatusFound, fmt.Sprintf("%s/users/oauth?access_token=%s", ctx.Redirect(http.StatusFound, fmt.Sprintf("%s/users/auth-landing?access_token=%s",
siteGeneral.SiteUrl, resp.AccessToken)) siteGeneral.SiteUrl, resp.AccessToken))
} else { } else {
ctx.Redirect(http.StatusFound, fmt.Sprintf("%s/users/confirm-email?binding_key=%s", ctx.Redirect(http.StatusFound, fmt.Sprintf("%s/users/confirm-email?binding_key=%s",

View File

@ -60,9 +60,11 @@ func (uc *UserCenterController) UserCenterAgent(ctx *gin.Context) {
_ = plugin.CallUserCenter(func(uc plugin.UserCenter) error { _ = plugin.CallUserCenter(func(uc plugin.UserCenter) error {
info := uc.Description() info := uc.Description()
resp.AgentInfo.Name = info.Name resp.AgentInfo.Name = info.Name
resp.AgentInfo.DisplayName = info.DisplayName.Translate(ctx)
resp.AgentInfo.Icon = info.Icon resp.AgentInfo.Icon = info.Icon
resp.AgentInfo.Url = info.Url resp.AgentInfo.Url = info.Url
resp.AgentInfo.ControlCenterItems = make([]*schema.ControlCenter, 0) resp.AgentInfo.ControlCenterItems = make([]*schema.ControlCenter, 0)
resp.AgentInfo.EnabledOriginalUserSystem = info.EnabledOriginalUserSystem
items := uc.ControlCenterItems() items := uc.ControlCenterItems()
for _, item := range items { for _, item := range items {
resp.AgentInfo.ControlCenterItems = append(resp.AgentInfo.ControlCenterItems, &schema.ControlCenter{ resp.AgentInfo.ControlCenterItems = append(resp.AgentInfo.ControlCenterItems, &schema.ControlCenter{
@ -90,8 +92,8 @@ func (uc *UserCenterController) UserCenterPersonalBranding(ctx *gin.Context) {
func (uc *UserCenterController) UserCenterLoginRedirect(ctx *gin.Context) { func (uc *UserCenterController) UserCenterLoginRedirect(ctx *gin.Context) {
var redirectURL string var redirectURL string
_ = plugin.CallUserCenter(func(uc plugin.UserCenter) error { _ = plugin.CallUserCenter(func(userCenter plugin.UserCenter) error {
info := uc.Description() info := userCenter.Description()
redirectURL = info.LoginRedirectURL redirectURL = info.LoginRedirectURL
return nil return nil
}) })
@ -100,9 +102,9 @@ func (uc *UserCenterController) UserCenterLoginRedirect(ctx *gin.Context) {
func (uc *UserCenterController) UserCenterSignUpRedirect(ctx *gin.Context) { func (uc *UserCenterController) UserCenterSignUpRedirect(ctx *gin.Context) {
var redirectURL string var redirectURL string
_ = plugin.CallUserCenter(func(uc plugin.UserCenter) error { _ = plugin.CallUserCenter(func(userCenter plugin.UserCenter) error {
info := uc.Description() info := userCenter.Description()
redirectURL = info.SignUpRedirectURL redirectURL = info.LoginRedirectURL
return nil return nil
}) })
ctx.Redirect(http.StatusFound, redirectURL) ctx.Redirect(http.StatusFound, redirectURL)
@ -124,17 +126,24 @@ func (uc *UserCenterController) UserCenterLoginCallback(ctx *gin.Context) {
userInfo, err := userCenter.LoginCallback(ctx) userInfo, err := userCenter.LoginCallback(ctx)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
ctx.Redirect(http.StatusFound, "/50x") if !ctx.IsAborted() {
ctx.Redirect(http.StatusFound, "/50x")
}
return return
} }
resp, err := uc.userCenterLoginService.ExternalLogin(ctx, userCenter.Info().SlugName, userInfo) resp, err := uc.userCenterLoginService.ExternalLogin(ctx, userCenter, userInfo)
if err != nil { if err != nil {
log.Errorf("external login failed: %v", err) log.Errorf("external login failed: %v", err)
ctx.Redirect(http.StatusFound, "/50x") ctx.Redirect(http.StatusFound, "/50x")
return return
} }
ctx.Redirect(http.StatusFound, fmt.Sprintf("%s/users/oauth?access_token=%s", if len(resp.ErrMsg) > 0 {
ctx.Redirect(http.StatusFound, fmt.Sprintf("/50x?title=%s&msg=%s", resp.ErrTitle, resp.ErrMsg))
return
}
userCenter.AfterLogin(userInfo.ExternalID, resp.AccessToken)
ctx.Redirect(http.StatusFound, fmt.Sprintf("%s/users/auth-landing?access_token=%s",
siteGeneral.SiteUrl, resp.AccessToken)) siteGeneral.SiteUrl, resp.AccessToken))
} }
@ -158,13 +167,18 @@ func (uc *UserCenterController) UserCenterSignUpCallback(ctx *gin.Context) {
return return
} }
resp, err := uc.userCenterLoginService.ExternalLogin(ctx, userCenter.Info().SlugName, userInfo) resp, err := uc.userCenterLoginService.ExternalLogin(ctx, userCenter, userInfo)
if err != nil { if err != nil {
log.Errorf("external login failed: %v", err) log.Errorf("external login failed: %v", err)
ctx.Redirect(http.StatusFound, "/50x") ctx.Redirect(http.StatusFound, "/50x")
return return
} }
ctx.Redirect(http.StatusFound, fmt.Sprintf("%s/users/oauth?access_token=%s", if len(resp.ErrMsg) > 0 {
ctx.Redirect(http.StatusFound, fmt.Sprintf("/50x?title=%s&msg=%s", resp.ErrTitle, resp.ErrMsg))
return
}
userCenter.AfterLogin(userInfo.ExternalID, resp.AccessToken)
ctx.Redirect(http.StatusFound, fmt.Sprintf("%s/users/auth-landing?access_token=%s",
siteGeneral.SiteUrl, resp.AccessToken)) siteGeneral.SiteUrl, resp.AccessToken))
} }
@ -174,3 +188,9 @@ func (uc *UserCenterController) UserCenterUserSettings(ctx *gin.Context) {
resp, err := uc.userCenterLoginService.UserCenterUserSettings(ctx, userID) resp, err := uc.userCenterLoginService.UserCenterUserSettings(ctx, userID)
handler.HandleResponse(ctx, err, resp) handler.HandleResponse(ctx, err, resp)
} }
// UserCenterAdminFunctionAgent user center admin function agent
func (uc *UserCenterController) UserCenterAdminFunctionAgent(ctx *gin.Context) {
resp, err := uc.userCenterLoginService.UserCenterAdminFunctionAgent(ctx)
handler.HandleResponse(ctx, err, resp)
}

View File

@ -64,6 +64,10 @@ func (sc *SiteinfoController) GetSiteInfo(ctx *gin.Context) {
if err != nil { if err != nil {
log.Error(err) log.Error(err)
} }
resp.SiteUsers, err = sc.siteInfoService.GetSiteUsers(ctx)
if err != nil {
log.Error(err)
}
handler.HandleResponse(ctx, nil, resp) handler.HandleResponse(ctx, nil, resp)
} }

View File

@ -13,6 +13,7 @@ import (
"github.com/answerdev/answer/internal/service/export" "github.com/answerdev/answer/internal/service/export"
"github.com/answerdev/answer/internal/service/siteinfo_common" "github.com/answerdev/answer/internal/service/siteinfo_common"
"github.com/answerdev/answer/internal/service/uploader" "github.com/answerdev/answer/internal/service/uploader"
"github.com/answerdev/answer/pkg/checker"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/segmentfault/pacman/errors" "github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/log" "github.com/segmentfault/pacman/log"
@ -223,7 +224,7 @@ func (uc *UserController) UserRegisterByEmail(ctx *gin.Context) {
handler.HandleResponse(ctx, err, nil) handler.HandleResponse(ctx, err, nil)
return return
} }
if !siteInfo.AllowNewRegistrations { if !siteInfo.AllowNewRegistrations || !siteInfo.AllowEmailRegistrations {
handler.HandleResponse(ctx, errors.BadRequest(reason.NotAllowedRegistration), nil) handler.HandleResponse(ctx, errors.BadRequest(reason.NotAllowedRegistration), nil)
return return
} }
@ -232,6 +233,10 @@ func (uc *UserController) UserRegisterByEmail(ctx *gin.Context) {
if handler.BindAndCheck(ctx, req) { if handler.BindAndCheck(ctx, req) {
return return
} }
if !checker.EmailInAllowEmailDomain(req.Email, siteInfo.AllowEmailDomains) {
handler.HandleResponse(ctx, errors.BadRequest(reason.EmailIllegalDomainError), nil)
return
}
req.IP = ctx.ClientIP() req.IP = ctx.ClientIP()
captchaPass := uc.actionService.UserRegisterVerifyCaptcha(ctx, req.CaptchaID, req.CaptchaCode) captchaPass := uc.actionService.UserRegisterVerifyCaptcha(ctx, req.CaptchaID, req.CaptchaCode)
if !captchaPass { if !captchaPass {
@ -489,6 +494,16 @@ func (uc *UserController) UserChangeEmailSendCode(ctx *gin.Context) {
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil) handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
return return
} }
// check whether email allow register or not
siteInfo, err := uc.siteInfoCommonService.GetSiteLogin(ctx)
if err != nil {
handler.HandleResponse(ctx, err, nil)
return
}
if !checker.EmailInAllowEmailDomain(req.Email, siteInfo.AllowEmailDomains) {
handler.HandleResponse(ctx, errors.BadRequest(reason.EmailIllegalDomainError), nil)
return
}
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecordTypeEmail, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode) captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecordTypeEmail, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode)
if !captchaPass { if !captchaPass {

View File

@ -139,6 +139,19 @@ func (sc *SiteInfoController) GetSiteTheme(ctx *gin.Context) {
handler.HandleResponse(ctx, err, resp) handler.HandleResponse(ctx, err, resp)
} }
// GetSiteUsers get site user config
// @Summary get site user config
// @Description get site user config
// @Security ApiKeyAuth
// @Tags admin
// @Produce json
// @Success 200 {object} handler.RespBody{data=schema.SiteUsersResp}
// @Router /answer/admin/api/siteinfo/users [get]
func (sc *SiteInfoController) GetSiteUsers(ctx *gin.Context) {
resp, err := sc.siteInfoService.GetSiteUsers(ctx)
handler.HandleResponse(ctx, err, resp)
}
// GetRobots get site robots information // GetRobots get site robots information
// @Summary get site robots information // @Summary get site robots information
// @Description get site robots information // @Description get site robots information
@ -336,6 +349,24 @@ func (sc *SiteInfoController) SaveSiteTheme(ctx *gin.Context) {
handler.HandleResponse(ctx, err, nil) handler.HandleResponse(ctx, err, nil)
} }
// UpdateSiteUsers update site config about users
// @Summary update site info config about users
// @Description update site info config about users
// @Security ApiKeyAuth
// @Tags admin
// @Produce json
// @Param data body schema.SiteUsersReq true "users info"
// @Success 200 {object} handler.RespBody{}
// @Router /answer/admin/api/siteinfo/users [put]
func (sc *SiteInfoController) UpdateSiteUsers(ctx *gin.Context) {
req := &schema.SiteUsersReq{}
if handler.BindAndCheck(ctx, req) {
return
}
err := sc.siteInfoService.SaveSiteUsers(ctx, req)
handler.HandleResponse(ctx, err, nil)
}
// GetSMTPConfig get smtp config // GetSMTPConfig get smtp config
// @Summary GetSMTPConfig get smtp config // @Summary GetSMTPConfig get smtp config
// @Description GetSMTPConfig get smtp config // @Description GetSMTPConfig get smtp config
@ -366,3 +397,34 @@ func (sc *SiteInfoController) UpdateSMTPConfig(ctx *gin.Context) {
err := sc.siteInfoService.UpdateSMTPConfig(ctx, req) err := sc.siteInfoService.UpdateSMTPConfig(ctx, req)
handler.HandleResponse(ctx, err, nil) handler.HandleResponse(ctx, err, nil)
} }
// GetPrivilegesConfig get privileges config
// @Summary GetPrivilegesConfig get privileges config
// @Description GetPrivilegesConfig get privileges config
// @Security ApiKeyAuth
// @Tags admin
// @Produce json
// @Success 200 {object} handler.RespBody{data=schema.GetPrivilegesConfigResp}
// @Router /answer/admin/api/setting/privileges [get]
func (sc *SiteInfoController) GetPrivilegesConfig(ctx *gin.Context) {
resp, err := sc.siteInfoService.GetPrivilegesConfig(ctx)
handler.HandleResponse(ctx, err, resp)
}
// UpdatePrivilegesConfig update privileges config
// @Summary update privileges config
// @Description update privileges config
// @Security ApiKeyAuth
// @Tags admin
// @Produce json
// @Param data body schema.UpdatePrivilegesConfigReq true "config"
// @Success 200 {object} handler.RespBody{}
// @Router /answer/admin/api/setting/privileges [put]
func (sc *SiteInfoController) UpdatePrivilegesConfig(ctx *gin.Context) {
req := &schema.UpdatePrivilegesConfigReq{}
if handler.BindAndCheck(ctx, req) {
return
}
err := sc.siteInfoService.UpdatePrivilegesConfig(ctx, req)
handler.HandleResponse(ctx, err, nil)
}

View File

@ -32,7 +32,7 @@ func NewUserAdminController(userService *user_admin.UserAdminService) *UserAdmin
// @Success 200 {object} handler.RespBody // @Success 200 {object} handler.RespBody
// @Router /answer/admin/api/user/status [put] // @Router /answer/admin/api/user/status [put]
func (uc *UserAdminController) UpdateUserStatus(ctx *gin.Context) { func (uc *UserAdminController) UpdateUserStatus(ctx *gin.Context) {
if plugin.UserCenterEnabled() { if u, ok := plugin.GetUserCenter(); ok && u.Description().UserStatusAgentEnabled {
handler.HandleResponse(ctx, errors.Forbidden(reason.ForbiddenError), nil) handler.HandleResponse(ctx, errors.Forbidden(reason.ForbiddenError), nil)
return return
} }
@ -80,10 +80,6 @@ func (uc *UserAdminController) UpdateUserRole(ctx *gin.Context) {
// @Success 200 {object} handler.RespBody // @Success 200 {object} handler.RespBody
// @Router /answer/admin/api/user [post] // @Router /answer/admin/api/user [post]
func (uc *UserAdminController) AddUser(ctx *gin.Context) { func (uc *UserAdminController) AddUser(ctx *gin.Context) {
if plugin.UserCenterEnabled() {
handler.HandleResponse(ctx, errors.Forbidden(reason.ForbiddenError), nil)
return
}
req := &schema.AddUserReq{} req := &schema.AddUserReq{}
if handler.BindAndCheck(ctx, req) { if handler.BindAndCheck(ctx, req) {
return return
@ -106,10 +102,6 @@ func (uc *UserAdminController) AddUser(ctx *gin.Context) {
// @Success 200 {object} handler.RespBody // @Success 200 {object} handler.RespBody
// @Router /answer/admin/api/user/password [put] // @Router /answer/admin/api/user/password [put]
func (uc *UserAdminController) UpdateUserPassword(ctx *gin.Context) { func (uc *UserAdminController) UpdateUserPassword(ctx *gin.Context) {
if plugin.UserCenterEnabled() {
handler.HandleResponse(ctx, errors.Forbidden(reason.ForbiddenError), nil)
return
}
req := &schema.UpdateUserPasswordReq{} req := &schema.UpdateUserPasswordReq{}
if handler.BindAndCheck(ctx, req) { if handler.BindAndCheck(ctx, req) {
return return

View File

@ -6,4 +6,5 @@ type UserCacheInfo struct {
UserStatus int `json:"user_status"` UserStatus int `json:"user_status"`
EmailStatus int `json:"email_status"` EmailStatus int `json:"email_status"`
RoleID int `json:"role_id"` RoleID int `json:"role_id"`
ExternalID string `json:"external_id"`
} }

View File

@ -143,8 +143,9 @@ func initSiteInfo(engine *xorm.Engine, language, siteName, siteURL, contactEmail
} }
loginConfig := map[string]bool{ loginConfig := map[string]bool{
"allow_new_registrations": true, "allow_new_registrations": true,
"login_required": false, "allow_email_registrations": true,
"login_required": false,
} }
loginConfigDataBytes, _ := json.Marshal(loginConfig) loginConfigDataBytes, _ := json.Marshal(loginConfig)
_, err = engine.InsertOne(&entity.SiteInfo{ _, err = engine.InsertOne(&entity.SiteInfo{
@ -178,6 +179,25 @@ func initSiteInfo(engine *xorm.Engine, language, siteName, siteURL, contactEmail
if err != nil { if err != nil {
return err return err
} }
usersData := map[string]any{
"default_avatar": "gravatar",
"allow_update_display_name": true,
"allow_update_username": true,
"allow_update_avatar": true,
"allow_update_bio": true,
"allow_update_website": true,
"allow_update_location": true,
}
usersDataBytes, _ := json.Marshal(usersData)
_, err = engine.InsertOne(&entity.SiteInfo{
Type: "users",
Content: string(usersDataBytes),
Status: 1,
})
if err != nil {
return err
}
return err return err
} }
@ -346,10 +366,14 @@ func initConfigTable(engine *xorm.Engine) error {
{ID: 116, Key: "rank.question.reopen", Value: `-1`}, {ID: 116, Key: "rank.question.reopen", Value: `-1`},
{ID: 117, Key: "rank.tag.use_reserved_tag", Value: `-1`}, {ID: 117, Key: "rank.tag.use_reserved_tag", Value: `-1`},
{ID: 118, Key: "plugin.status", Value: `{}`}, {ID: 118, Key: "plugin.status", Value: `{}`},
{ID: 119, Key: "question.pin", Value: `-1`}, {ID: 119, Key: "question.pin", Value: `0`},
{ID: 120, Key: "question.unpin", Value: `-1`}, {ID: 120, Key: "question.unpin", Value: `0`},
{ID: 121, Key: "question.show", Value: `-1`}, {ID: 121, Key: "question.show", Value: `0`},
{ID: 122, Key: "question.hide", Value: `-1`}, {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`},
} }
_, err := engine.Insert(defaultConfigTable) _, err := engine.Insert(defaultConfigTable)
return err return err

View File

@ -61,6 +61,7 @@ var migrations = []Migration{
NewMigration("add plugin", addPlugin, false), NewMigration("add plugin", addPlugin, false),
NewMigration("update user pin hide features", updateRolePinAndHideFeatures, true), NewMigration("update user pin hide features", updateRolePinAndHideFeatures, true),
NewMigration("update question post time", updateQuestionPostTime, true), NewMigration("update question post time", updateQuestionPostTime, true),
NewMigration("add login limitations", addLoginLimitations, true),
} }
// GetCurrentDBVersion returns the current db version // GetCurrentDBVersion returns the current db version

View File

@ -0,0 +1,69 @@
package migrations
import (
"encoding/json"
"fmt"
"github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/schema"
"github.com/tidwall/gjson"
"xorm.io/xorm"
)
func addLoginLimitations(x *xorm.Engine) error {
loginSiteInfo := &entity.SiteInfo{
Type: constant.SiteTypeLogin,
}
exist, err := x.Get(loginSiteInfo)
if err != nil {
return fmt.Errorf("get config failed: %w", err)
}
if exist {
content := &schema.SiteLoginReq{}
_ = json.Unmarshal([]byte(loginSiteInfo.Content), content)
content.AllowEmailRegistrations = true
content.AllowEmailDomains = make([]string, 0)
_, err = x.ID(loginSiteInfo.ID).Cols("content").Update(loginSiteInfo)
if err != nil {
return fmt.Errorf("update site info failed: %w", err)
}
}
interfaceSiteInfo := &entity.SiteInfo{
Type: constant.SiteTypeInterface,
}
exist, err = x.Get(interfaceSiteInfo)
if err != nil {
return fmt.Errorf("get config failed: %w", err)
}
siteUsers := &schema.SiteUsersReq{
AllowUpdateDisplayName: true,
AllowUpdateUsername: true,
AllowUpdateAvatar: true,
AllowUpdateBio: true,
AllowUpdateWebsite: true,
AllowUpdateLocation: true,
}
if exist {
siteUsers.DefaultAvatar = gjson.Get(interfaceSiteInfo.Content, "default_avatar").String()
}
data, _ := json.Marshal(siteUsers)
exist, err = x.Get(&entity.SiteInfo{Type: constant.SiteTypeUsers})
if err != nil {
return fmt.Errorf("get config failed: %w", err)
}
if !exist {
usersSiteInfo := &entity.SiteInfo{
Type: constant.SiteTypeUsers,
Content: string(data),
Status: 1,
}
_, err = x.InsertOne(usersSiteInfo)
if err != nil {
return fmt.Errorf("insert site info failed: %w", err)
}
}
return nil
}

View File

@ -18,10 +18,6 @@ func addPlugin(x *xorm.Engine) error {
return fmt.Errorf("get config failed: %w", err) return fmt.Errorf("get config failed: %w", err)
} }
if exist { if exist {
if _, err = x.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 continue
} }
if _, err = x.Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil { if _, err = x.Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {

View File

@ -202,7 +202,9 @@ func (ar *AnswerActivityRepo) AcceptAnswer(ctx context.Context,
msg.TriggerUserID = questionUserID msg.TriggerUserID = questionUserID
msg.ObjectType = constant.AnswerObjectType msg.ObjectType = constant.AnswerObjectType
} }
notice_queue.AddNotification(msg) if msg.TriggerUserID != msg.ReceiverUserID {
notice_queue.AddNotification(msg)
}
} }
for _, act := range addActivityList { for _, act := range addActivityList {
@ -214,7 +216,7 @@ func (ar *AnswerActivityRepo) AcceptAnswer(ctx context.Context,
if act.UserID != questionUserID { if act.UserID != questionUserID {
msg.TriggerUserID = questionUserID msg.TriggerUserID = questionUserID
msg.ObjectType = constant.AnswerObjectType msg.ObjectType = constant.AnswerObjectType
msg.NotificationAction = constant.AcceptAnswer msg.NotificationAction = constant.NotificationAcceptAnswer
notice_queue.AddNotification(msg) notice_queue.AddNotification(msg)
} }
} }

View File

@ -5,6 +5,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/pkg/converter" "github.com/answerdev/answer/pkg/converter"
"github.com/answerdev/answer/internal/base/pager" "github.com/answerdev/answer/internal/base/pager"
@ -70,7 +71,9 @@ var LimitDownActions = map[string][]string{
func (vr *VoteRepo) vote(ctx context.Context, objectID string, userID, objectUserID string, actions []string) (resp *schema.VoteResp, err error) { func (vr *VoteRepo) vote(ctx context.Context, objectID string, userID, objectUserID string, actions []string) (resp *schema.VoteResp, err error) {
resp = &schema.VoteResp{} resp = &schema.VoteResp{}
notificationUserIDs := make([]string, 0) achievementNotificationUserIDs := make([]string, 0)
sendInboxNotification := false
upVote := false
_, err = vr.data.DB.Transaction(func(session *xorm.Session) (result any, err error) { _, err = vr.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
result = nil result = nil
for _, action := range actions { for _, action := range actions {
@ -127,7 +130,7 @@ func (vr *VoteRepo) vote(ctx context.Context, objectID string, userID, objectUse
if isReachStandard { if isReachStandard {
insertActivity.Rank = 0 insertActivity.Rank = 0
} }
notificationUserIDs = append(notificationUserIDs, activityUserID) achievementNotificationUserIDs = append(achievementNotificationUserIDs, activityUserID)
} }
if has { if has {
@ -142,13 +145,17 @@ func (vr *VoteRepo) vote(ctx context.Context, objectID string, userID, objectUse
if err != nil { if err != nil {
return nil, err return nil, err
} }
sendInboxNotification = true
} }
// update votes // update votes
if action == "vote_down" || action == "vote_up" { if action == constant.ActVoteDown || action == constant.ActVoteUp {
votes := 1 votes := 1
if action == "vote_down" { if action == constant.ActVoteDown {
upVote = false
votes = -1 votes = -1
} else {
upVote = true
} }
err = vr.updateVotes(ctx, session, objectID, votes) err = vr.updateVotes(ctx, session, objectID, votes)
if err != nil { if err != nil {
@ -165,9 +172,12 @@ func (vr *VoteRepo) vote(ctx context.Context, objectID string, userID, objectUse
resp, err = vr.GetVoteResultByObjectId(ctx, objectID) resp, err = vr.GetVoteResultByObjectId(ctx, objectID)
resp.VoteStatus = vr.voteCommon.GetVoteStatus(ctx, objectID, userID) resp.VoteStatus = vr.voteCommon.GetVoteStatus(ctx, objectID, userID)
for _, activityUserID := range notificationUserIDs { for _, activityUserID := range achievementNotificationUserIDs {
vr.sendNotification(ctx, activityUserID, objectUserID, objectID) vr.sendNotification(ctx, activityUserID, objectUserID, objectID)
} }
if sendInboxNotification {
vr.sendVoteInboxNotification(userID, objectUserID, objectID, upVote)
}
return return
} }
@ -441,3 +451,40 @@ func (vr *VoteRepo) sendNotification(ctx context.Context, activityUserID, object
} }
notice_queue.AddNotification(msg) notice_queue.AddNotification(msg)
} }
func (vr *VoteRepo) sendVoteInboxNotification(triggerUserID, receiverUserID, objectID string, upvote bool) {
if triggerUserID == receiverUserID {
return
}
objectType, _ := obj.GetObjectTypeStrByObjectID(objectID)
msg := &schema.NotificationMsg{
TriggerUserID: triggerUserID,
ReceiverUserID: receiverUserID,
Type: schema.NotificationTypeInbox,
ObjectID: objectID,
ObjectType: objectType,
}
if objectType == constant.QuestionObjectType {
if upvote {
msg.NotificationAction = constant.NotificationUpVotedTheQuestion
} else {
msg.NotificationAction = constant.NotificationDownVotedTheQuestion
}
}
if objectType == constant.AnswerObjectType {
if upvote {
msg.NotificationAction = constant.NotificationUpVotedTheAnswer
} else {
msg.NotificationAction = constant.NotificationDownVotedTheAnswer
}
}
if objectType == constant.CommentObjectType {
if upvote {
msg.NotificationAction = constant.NotificationUpVotedTheComment
}
}
if len(msg.NotificationAction) > 0 {
notice_queue.AddNotification(msg)
}
}

View File

@ -255,7 +255,7 @@ func (qr *questionRepo) GetQuestionIDsPage(ctx context.Context, page, pageSize i
} }
// GetQuestionPage query question page // GetQuestionPage query question page
func (qr *questionRepo) GetQuestionPage(ctx context.Context, page, pageSize int, userID, tagID, orderCond string) ( func (qr *questionRepo) GetQuestionPage(ctx context.Context, page, pageSize int, userID, tagID, orderCond string, inDays int) (
questionList []*entity.Question, total int64, err error) { questionList []*entity.Question, total int64, err error) {
questionList = make([]*entity.Question, 0) questionList = make([]*entity.Question, 0)
@ -271,6 +271,9 @@ func (qr *questionRepo) GetQuestionPage(ctx context.Context, page, pageSize int,
} else { } else {
session.And("question.show = ?", entity.QuestionShow) session.And("question.show = ?", entity.QuestionShow)
} }
if inDays > 0 {
session.And("question.created_at > ?", time.Now().AddDate(0, 0, -inDays))
}
switch orderCond { switch orderCond {
case "newest": case "newest":

View File

@ -86,6 +86,9 @@ func (ur *userAdminRepo) GetUserInfo(ctx context.Context, userID string) (user *
if err != nil { if err != nil {
return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
} }
if !exist {
return
}
err = tryToDecorateUserInfoFromUserCenter(ctx, ur.data, user) err = tryToDecorateUserInfoFromUserCenter(ctx, ur.data, user)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
@ -102,6 +105,9 @@ func (ur *userAdminRepo) GetUserInfoByEmail(ctx context.Context, email string) (
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
return return
} }
if !exist {
return
}
err = tryToDecorateUserInfoFromUserCenter(ctx, ur.data, user) err = tryToDecorateUserInfoFromUserCenter(ctx, ur.data, user)
if err != nil { if err != nil {
return nil, false, err return nil, false, err

View File

@ -10,6 +10,7 @@ import (
"github.com/answerdev/answer/internal/schema" "github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service/config" "github.com/answerdev/answer/internal/service/config"
usercommon "github.com/answerdev/answer/internal/service/user_common" usercommon "github.com/answerdev/answer/internal/service/user_common"
"github.com/answerdev/answer/pkg/converter"
"github.com/answerdev/answer/plugin" "github.com/answerdev/answer/plugin"
"github.com/segmentfault/pacman/errors" "github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/log" "github.com/segmentfault/pacman/log"
@ -195,6 +196,9 @@ func (ur *userRepo) GetUserCount(ctx context.Context) (count int64, err error) {
} }
func tryToDecorateUserInfoFromUserCenter(ctx context.Context, data *data.Data, original *entity.User) (err error) { func tryToDecorateUserInfoFromUserCenter(ctx context.Context, data *data.Data, original *entity.User) (err error) {
if original == nil {
return nil
}
uc, ok := plugin.GetUserCenter() uc, ok := plugin.GetUserCenter()
if !ok { if !ok {
return nil return nil
@ -276,14 +280,27 @@ func decorateByUserCenterUser(original *entity.User, ucUser *plugin.UserCenterBa
if original.Username != ucUser.Username { if original.Username != ucUser.Username {
log.Warnf("user %s username is inconsistent with user center", original.ID) log.Warnf("user %s username is inconsistent with user center", original.ID)
} }
original.DisplayName = ucUser.DisplayName if len(ucUser.DisplayName) > 0 {
original.EMail = ucUser.Email original.DisplayName = ucUser.DisplayName
original.Avatar = schema.CustomAvatar(ucUser.Avatar).ToJsonString() }
original.Mobile = ucUser.Mobile if len(ucUser.Email) > 0 {
original.EMail = ucUser.Email
}
if len(ucUser.Avatar) > 0 {
original.Avatar = schema.CustomAvatar(ucUser.Avatar).ToJsonString()
}
if len(ucUser.Mobile) > 0 {
original.Mobile = ucUser.Mobile
}
if len(ucUser.Bio) > 0 {
original.BioHTML = converter.Markdown2HTML(ucUser.Bio) + original.BioHTML
}
// If plugin enable rank agent, use rank from user center. // If plugin enable rank agent, use rank from user center.
if plugin.RankAgentEnabled() { if plugin.RankAgentEnabled() {
original.Rank = ucUser.Rank original.Rank = ucUser.Rank
} }
original.Status = int(ucUser.Status) if ucUser.Status != plugin.UserStatusAvailable {
original.Status = int(ucUser.Status)
}
} }

View File

@ -102,7 +102,7 @@ func (a *AnswerAPIRouter) RegisterMustUnAuthAnswerAPIRouter(r *gin.RouterGroup)
// user // user
r.GET("/user/info", a.userController.GetUserInfoByUserID) r.GET("/user/info", a.userController.GetUserInfoByUserID)
routerGroup := r.Group("", middleware.BanAPIWhenUserCenterEnabled) routerGroup := r.Group("", middleware.BanAPIForUserCenter)
routerGroup.POST("/user/login/email", a.userController.UserEmailLogin) routerGroup.POST("/user/login/email", a.userController.UserEmailLogin)
routerGroup.POST("/user/register/email", a.userController.UserRegisterByEmail) routerGroup.POST("/user/register/email", a.userController.UserRegisterByEmail)
routerGroup.GET("/user/register/captcha", a.userController.UserRegisterCaptcha) routerGroup.GET("/user/register/captcha", a.userController.UserRegisterCaptcha)
@ -117,8 +117,8 @@ func (a *AnswerAPIRouter) RegisterMustUnAuthAnswerAPIRouter(r *gin.RouterGroup)
func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) { func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) {
// user // user
r.GET("/user/logout", a.userController.UserLogout) r.GET("/user/logout", a.userController.UserLogout)
r.POST("/user/email/change/code", middleware.BanAPIWhenUserCenterEnabled, a.userController.UserChangeEmailSendCode) r.POST("/user/email/change/code", middleware.BanAPIForUserCenter, a.userController.UserChangeEmailSendCode)
r.POST("/user/email/verification/send", middleware.BanAPIWhenUserCenterEnabled, a.userController.UserVerifyEmailSend) r.POST("/user/email/verification/send", middleware.BanAPIForUserCenter, a.userController.UserVerifyEmailSend)
r.GET("/personal/user/info", a.userController.GetOtherUserInfoByUsername) r.GET("/personal/user/info", a.userController.GetOtherUserInfoByUsername)
r.GET("/user/ranking", a.userController.UserRanking) r.GET("/user/ranking", a.userController.UserRanking)
@ -206,8 +206,8 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
r.DELETE("/answer", a.answerController.RemoveAnswer) r.DELETE("/answer", a.answerController.RemoveAnswer)
// user // user
r.PUT("/user/password", middleware.BanAPIWhenUserCenterEnabled, a.userController.UserModifyPassWord) r.PUT("/user/password", middleware.BanAPIForUserCenter, a.userController.UserModifyPassWord)
r.PUT("/user/info", middleware.BanAPIWhenUserCenterEnabled, a.userController.UserUpdateInfo) r.PUT("/user/info", a.userController.UserUpdateInfo)
r.PUT("/user/interface", a.userController.UserUpdateInterface) r.PUT("/user/interface", a.userController.UserUpdateInterface)
r.POST("/user/notice/set", a.userController.UserNoticeSet) r.POST("/user/notice/set", a.userController.UserNoticeSet)
@ -246,10 +246,10 @@ func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) {
// user // user
r.GET("/users/page", a.adminUserController.GetUserPage) r.GET("/users/page", a.adminUserController.GetUserPage)
r.PUT("/user/status", middleware.BanAPIWhenUserCenterEnabled, a.adminUserController.UpdateUserStatus) r.PUT("/user/status", a.adminUserController.UpdateUserStatus)
r.PUT("/user/role", a.adminUserController.UpdateUserRole) r.PUT("/user/role", a.adminUserController.UpdateUserRole)
r.POST("/user", middleware.BanAPIWhenUserCenterEnabled, a.adminUserController.AddUser) r.POST("/user", a.adminUserController.AddUser)
r.PUT("/user/password", middleware.BanAPIWhenUserCenterEnabled, a.adminUserController.UpdateUserPassword) r.PUT("/user/password", a.adminUserController.UpdateUserPassword)
// reason // reason
r.GET("/reasons", a.reasonController.Reasons) r.GET("/reasons", a.reasonController.Reasons)
@ -262,25 +262,29 @@ func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) {
// siteinfo // siteinfo
r.GET("/siteinfo/general", a.siteInfoController.GetGeneral) r.GET("/siteinfo/general", a.siteInfoController.GetGeneral)
r.GET("/siteinfo/interface", a.siteInfoController.GetInterface)
r.GET("/siteinfo/branding", a.siteInfoController.GetSiteBranding)
r.GET("/siteinfo/write", a.siteInfoController.GetSiteWrite)
r.GET("/siteinfo/legal", a.siteInfoController.GetSiteLegal)
r.GET("/siteinfo/seo", a.siteInfoController.GetSeo)
r.GET("/siteinfo/login", a.siteInfoController.GetSiteLogin)
r.GET("/siteinfo/custom-css-html", a.siteInfoController.GetSiteCustomCssHTML)
r.GET("/siteinfo/theme", a.siteInfoController.GetSiteTheme)
r.PUT("/siteinfo/general", a.siteInfoController.UpdateGeneral) r.PUT("/siteinfo/general", a.siteInfoController.UpdateGeneral)
r.GET("/siteinfo/interface", a.siteInfoController.GetInterface)
r.PUT("/siteinfo/interface", a.siteInfoController.UpdateInterface) r.PUT("/siteinfo/interface", a.siteInfoController.UpdateInterface)
r.GET("/siteinfo/branding", a.siteInfoController.GetSiteBranding)
r.PUT("/siteinfo/branding", a.siteInfoController.UpdateBranding) r.PUT("/siteinfo/branding", a.siteInfoController.UpdateBranding)
r.GET("/siteinfo/write", a.siteInfoController.GetSiteWrite)
r.PUT("/siteinfo/write", a.siteInfoController.UpdateSiteWrite) r.PUT("/siteinfo/write", a.siteInfoController.UpdateSiteWrite)
r.GET("/siteinfo/legal", a.siteInfoController.GetSiteLegal)
r.PUT("/siteinfo/legal", a.siteInfoController.UpdateSiteLegal) r.PUT("/siteinfo/legal", a.siteInfoController.UpdateSiteLegal)
r.PUT("/siteinfo/login", a.siteInfoController.UpdateSiteLogin) r.GET("/siteinfo/seo", a.siteInfoController.GetSeo)
r.PUT("/siteinfo/custom-css-html", a.siteInfoController.UpdateSiteCustomCssHTML)
r.PUT("/siteinfo/theme", a.siteInfoController.SaveSiteTheme)
r.PUT("/siteinfo/seo", a.siteInfoController.UpdateSeo) r.PUT("/siteinfo/seo", a.siteInfoController.UpdateSeo)
r.GET("/siteinfo/login", a.siteInfoController.GetSiteLogin)
r.PUT("/siteinfo/login", a.siteInfoController.UpdateSiteLogin)
r.GET("/siteinfo/custom-css-html", a.siteInfoController.GetSiteCustomCssHTML)
r.PUT("/siteinfo/custom-css-html", a.siteInfoController.UpdateSiteCustomCssHTML)
r.GET("/siteinfo/theme", a.siteInfoController.GetSiteTheme)
r.PUT("/siteinfo/theme", a.siteInfoController.SaveSiteTheme)
r.GET("/siteinfo/users", a.siteInfoController.GetSiteUsers)
r.PUT("/siteinfo/users", a.siteInfoController.UpdateSiteUsers)
r.GET("/setting/smtp", a.siteInfoController.GetSMTPConfig) r.GET("/setting/smtp", a.siteInfoController.GetSMTPConfig)
r.PUT("/setting/smtp", a.siteInfoController.UpdateSMTPConfig) r.PUT("/setting/smtp", a.siteInfoController.UpdateSMTPConfig)
r.GET("/setting/privileges", a.siteInfoController.GetPrivilegesConfig)
r.PUT("/setting/privileges", a.siteInfoController.UpdatePrivilegesConfig)
// dashboard // dashboard
r.GET("/dashboard", a.dashboardController.DashboardInfo) r.GET("/dashboard", a.dashboardController.DashboardInfo)

View File

@ -37,10 +37,14 @@ func (pr *PluginAPIRouter) RegisterUnAuthConnectorRouter(r *gin.RouterGroup) {
r.GET("/user-center/sign-up/callback", pr.userCenterController.UserCenterSignUpCallback) r.GET("/user-center/sign-up/callback", pr.userCenterController.UserCenterSignUpCallback)
} }
func (pr *PluginAPIRouter) RegisterAuthConnectorRouter(r *gin.RouterGroup) { func (pr *PluginAPIRouter) RegisterAuthUserConnectorRouter(r *gin.RouterGroup) {
connectorController := pr.connectorController connectorController := pr.connectorController
r.GET("/connector/user/info", connectorController.ConnectorsUserInfo) r.GET("/connector/user/info", connectorController.ConnectorsUserInfo)
r.DELETE("/connector/user/unbinding", connectorController.ExternalLoginUnbinding) r.DELETE("/connector/user/unbinding", connectorController.ExternalLoginUnbinding)
r.GET("/user-center/user/settings", pr.userCenterController.UserCenterUserSettings) r.GET("/user-center/user/settings", pr.userCenterController.UserCenterUserSettings)
} }
func (pr *PluginAPIRouter) RegisterAuthAdminConnectorRouter(r *gin.RouterGroup) {
r.GET("/user-center/agent", pr.userCenterController.UserCenterAdminFunctionAgent)
}

View File

@ -56,10 +56,31 @@ func (g *GetPluginConfigResp) SetConfigFields(ctx *gin.Context, fields []plugin.
UIOptions: ConfigFieldUIOptions{ UIOptions: ConfigFieldUIOptions{
Rows: field.UIOptions.Rows, Rows: field.UIOptions.Rows,
InputType: string(field.UIOptions.InputType), InputType: string(field.UIOptions.InputType),
Variant: field.UIOptions.Variant,
}, },
} }
configField.UIOptions.Placeholder = field.UIOptions.Placeholder.Translate(ctx) configField.UIOptions.Placeholder = field.UIOptions.Placeholder.Translate(ctx)
configField.UIOptions.Label = field.UIOptions.Label.Translate(ctx) configField.UIOptions.Label = field.UIOptions.Label.Translate(ctx)
configField.UIOptions.Text = field.UIOptions.Text.Translate(ctx)
if field.UIOptions.Action != nil {
uiOptionAction := &UIOptionAction{
Url: field.UIOptions.Action.Url,
Method: field.UIOptions.Action.Method,
}
if field.UIOptions.Action.Loading != nil {
uiOptionAction.Loading = &LoadingAction{
Text: field.UIOptions.Action.Loading.Text.Translate(ctx),
State: string(field.UIOptions.Action.Loading.State),
}
}
if field.UIOptions.Action.OnComplete != nil {
uiOptionAction.OnCompleteAction = &OnCompleteAction{
ToastReturnMessage: field.UIOptions.Action.OnComplete.ToastReturnMessage,
RefreshFormConfig: field.UIOptions.Action.OnComplete.RefreshFormConfig,
}
}
configField.UIOptions.Action = uiOptionAction
}
for _, option := range field.Options { for _, option := range field.Options {
configField.Options = append(configField.Options, ConfigFieldOption{ configField.Options = append(configField.Options, ConfigFieldOption{
@ -83,10 +104,13 @@ type ConfigField struct {
} }
type ConfigFieldUIOptions struct { type ConfigFieldUIOptions struct {
Placeholder string `json:"placeholder,omitempty"` Placeholder string `json:"placeholder,omitempty"`
Rows string `json:"rows,omitempty"` Rows string `json:"rows,omitempty"`
InputType string `json:"input_type,omitempty"` InputType string `json:"input_type,omitempty"`
Label string `json:"label,omitempty"` Label string `json:"label,omitempty"`
Action *UIOptionAction `json:"action,omitempty"`
Variant string `json:"variant,omitempty"`
Text string `json:"text,omitempty"`
} }
type ConfigFieldOption struct { type ConfigFieldOption struct {
@ -94,6 +118,23 @@ type ConfigFieldOption struct {
Value string `json:"value"` Value string `json:"value"`
} }
type UIOptionAction struct {
Url string `json:"url"`
Method string `json:"method,omitempty"`
Loading *LoadingAction `json:"loading,omitempty"`
OnCompleteAction *OnCompleteAction `json:"on_complete,omitempty"`
}
type LoadingAction struct {
Text string `json:"text"`
State string `json:"state"`
}
type OnCompleteAction struct {
ToastReturnMessage bool `json:"toast_return_message"`
RefreshFormConfig bool `json:"refresh_form_config"`
}
type UpdatePluginConfigReq struct { type UpdatePluginConfigReq struct {
PluginSlugName string `validate:"required,gt=1,lte=100" json:"plugin_slug_name"` PluginSlugName string `validate:"required,gt=1,lte=100" json:"plugin_slug_name"`
ConfigFields map[string]any `json:"config_fields"` ConfigFields map[string]any `json:"config_fields"`

View File

@ -6,12 +6,14 @@ type UserCenterAgentResp struct {
} }
type AgentInfo struct { type AgentInfo struct {
Name string `json:"name"` Name string `json:"name"`
Icon string `json:"icon"` DisplayName string `json:"display_name"`
Url string `json:"url"` Icon string `json:"icon"`
LoginRedirectURL string `json:"login_redirect_url"` Url string `json:"url"`
SignUpRedirectURL string `json:"sign_up_redirect_url"` LoginRedirectURL string `json:"login_redirect_url"`
ControlCenterItems []*ControlCenter `json:"control_center"` SignUpRedirectURL string `json:"sign_up_redirect_url"`
ControlCenterItems []*ControlCenter `json:"control_center"`
EnabledOriginalUserSystem bool `json:"enabled_original_user_system"`
} }
type ControlCenter struct { type ControlCenter struct {

View File

@ -297,6 +297,7 @@ type QuestionPageReq struct {
OrderCond string `validate:"omitempty,oneof=newest active frequent score unanswered" form:"order"` OrderCond string `validate:"omitempty,oneof=newest active frequent score unanswered" form:"order"`
Tag string `validate:"omitempty,gt=0,lte=100" form:"tag"` Tag string `validate:"omitempty,gt=0,lte=100" form:"tag"`
Username string `validate:"omitempty,gt=0,lte=100" form:"username"` Username string `validate:"omitempty,gt=0,lte=100" form:"username"`
InDays int `validate:"omitempty,min=1" form:"in_days"`
LoginUserID string `json:"-"` LoginUserID string `json:"-"`
UserIDBeSearched string `json:"-"` UserIDBeSearched string `json:"-"`

View File

@ -6,6 +6,7 @@ import (
"net/mail" "net/mail"
"net/url" "net/url"
"github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/base/handler" "github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/reason" "github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/base/translator" "github.com/answerdev/answer/internal/base/translator"
@ -42,9 +43,8 @@ func (r *SiteGeneralReq) FormatSiteUrl() {
// SiteInterfaceReq site interface request // SiteInterfaceReq site interface request
type SiteInterfaceReq struct { type SiteInterfaceReq struct {
Language string `validate:"required,gt=1,lte=128" form:"language" json:"language"` Language string `validate:"required,gt=1,lte=128" form:"language" json:"language"`
TimeZone string `validate:"required,gt=1,lte=128" form:"time_zone" json:"time_zone"` TimeZone string `validate:"required,gt=1,lte=128" form:"time_zone" json:"time_zone"`
DefaultAvatar string `validate:"required,oneof=system gravatar" form:"default_avatar" json:"default_avatar"`
} }
// SiteBrandingReq site branding request // SiteBrandingReq site branding request
@ -92,18 +92,32 @@ type GetSiteLegalInfoResp struct {
PrivacyPolicyParsedText string `json:"privacy_policy_parsed_text,omitempty"` PrivacyPolicyParsedText string `json:"privacy_policy_parsed_text,omitempty"`
} }
// SiteUsersReq site users config request
type SiteUsersReq struct {
DefaultAvatar string `validate:"required,oneof=system gravatar" form:"default_avatar" json:"default_avatar"`
AllowUpdateDisplayName bool `form:"allow_update_display_name" json:"allow_update_display_name"`
AllowUpdateUsername bool `form:"allow_update_username" json:"allow_update_username"`
AllowUpdateAvatar bool `form:"allow_update_avatar" json:"allow_update_avatar"`
AllowUpdateBio bool `form:"allow_update_bio" json:"allow_update_bio"`
AllowUpdateWebsite bool `form:"allow_update_website" json:"allow_update_website"`
AllowUpdateLocation bool `form:"allow_update_location" json:"allow_update_location"`
}
// SiteLoginReq site login request // SiteLoginReq site login request
type SiteLoginReq struct { type SiteLoginReq struct {
AllowNewRegistrations bool `json:"allow_new_registrations"` AllowNewRegistrations bool `json:"allow_new_registrations"`
LoginRequired bool `json:"login_required"` AllowEmailRegistrations bool `json:"allow_email_registrations"`
LoginRequired bool `json:"login_required"`
AllowEmailDomains []string `json:"allow_email_domains"`
} }
// SiteCustomCssHTMLReq site custom css html // SiteCustomCssHTMLReq site custom css html
type SiteCustomCssHTMLReq struct { type SiteCustomCssHTMLReq struct {
CustomHead string `validate:"omitempty,gt=0,lte=65536" json:"custom_head"` CustomHead string `validate:"omitempty,gt=0,lte=65536" json:"custom_head"`
CustomCss string `validate:"omitempty,gt=0,lte=65536" json:"custom_css"` CustomCss string `validate:"omitempty,gt=0,lte=65536" json:"custom_css"`
CustomHeader string `validate:"omitempty,gt=0,lte=65536" json:"custom_header"` CustomHeader string `validate:"omitempty,gt=0,lte=65536" json:"custom_header"`
CustomFooter string `validate:"omitempty,gt=0,lte=65536" json:"custom_footer"` CustomFooter string `validate:"omitempty,gt=0,lte=65536" json:"custom_footer"`
CustomSideBar string `validate:"omitempty,gt=0,lte=65536" json:"custom_sidebar"`
} }
// SiteThemeReq site theme config // SiteThemeReq site theme config
@ -127,6 +141,9 @@ type SiteLoginResp SiteLoginReq
// SiteCustomCssHTMLResp site custom css html response // SiteCustomCssHTMLResp site custom css html response
type SiteCustomCssHTMLResp SiteCustomCssHTMLReq type SiteCustomCssHTMLResp SiteCustomCssHTMLReq
// SiteUsersResp site users response
type SiteUsersResp SiteUsersReq
// SiteThemeResp site theme response // SiteThemeResp site theme response
type SiteThemeResp struct { type SiteThemeResp struct {
ThemeOptions []*ThemeOption `json:"theme_options"` ThemeOptions []*ThemeOption `json:"theme_options"`
@ -169,6 +186,7 @@ type SiteInfoResp struct {
Theme *SiteThemeResp `json:"theme"` Theme *SiteThemeResp `json:"theme"`
CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"` CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"`
SiteSeo *SiteSeoReq `json:"site_seo"` SiteSeo *SiteSeoReq `json:"site_seo"`
SiteUsers *SiteUsersResp `json:"site_users"`
Version string `json:"version"` Version string `json:"version"`
Revision string `json:"revision"` Revision string `json:"revision"`
} }
@ -235,3 +253,85 @@ type GetManifestJsonResp struct {
ThemeColor string `json:"theme_color"` ThemeColor string `json:"theme_color"`
BackgroundColor string `json:"background_color"` BackgroundColor string `json:"background_color"`
} }
const (
// PrivilegeLevel1 low
PrivilegeLevel1 PrivilegeLevel = 1
// PrivilegeLevel2 medium
PrivilegeLevel2 PrivilegeLevel = 2
// PrivilegeLevel3 high
PrivilegeLevel3 PrivilegeLevel = 3
)
type PrivilegeLevel int
// GetPrivilegesConfigResp get privileges config response
type GetPrivilegesConfigResp struct {
Options []*PrivilegeOption `json:"options"`
SelectedLevel PrivilegeLevel `json:"selected_level"`
}
// PrivilegeOption privilege option
type PrivilegeOption struct {
Level PrivilegeLevel `json:"level"`
LevelDesc string `json:"level_desc"`
Privileges []*constant.Privilege `json:"privileges"`
}
// UpdatePrivilegesConfigReq update privileges config request
type UpdatePrivilegesConfigReq struct {
Level PrivilegeLevel `validate:"required,min=1,max=3" json:"level"`
}
var (
DefaultPrivilegeOptions []*PrivilegeOption
privilegeOptionsLevelMapping = map[string][]int{
constant.RankQuestionAddKey: {1, 1, 1},
constant.RankAnswerAddKey: {1, 1, 1},
constant.RankCommentAddKey: {1, 1, 1},
constant.RankReportAddKey: {1, 1, 1},
constant.RankCommentVoteUpKey: {1, 1, 1},
constant.RankLinkUrlLimitKey: {1, 10, 10},
constant.RankQuestionVoteUpKey: {1, 1, 15},
constant.RankAnswerVoteUpKey: {1, 1, 15},
constant.RankQuestionVoteDownKey: {125, 125, 125},
constant.RankAnswerVoteDownKey: {125, 125, 125},
constant.RankTagAddKey: {1, 750, 1500},
constant.RankTagEditKey: {1, 50, 100},
constant.RankQuestionEditKey: {1, 100, 200},
constant.RankAnswerEditKey: {1, 100, 200},
constant.RankQuestionEditWithoutReviewKey: {1, 1000, 2000},
constant.RankAnswerEditWithoutReviewKey: {1, 1000, 2000},
constant.RankQuestionAuditKey: {1, 1000, 2000},
constant.RankAnswerAuditKey: {1, 1000, 2000},
constant.RankTagAuditKey: {1, 2500, 5000},
constant.RankTagEditWithoutReviewKey: {1, 10000, 20000},
constant.RankTagSynonymKey: {1, 10000, 20000},
}
)
func init() {
DefaultPrivilegeOptions = append(DefaultPrivilegeOptions, &PrivilegeOption{
Level: PrivilegeLevel1,
LevelDesc: reason.PrivilegeLevel1Desc,
}, &PrivilegeOption{
Level: PrivilegeLevel2,
LevelDesc: reason.PrivilegeLevel2Desc,
}, &PrivilegeOption{
Level: PrivilegeLevel3,
LevelDesc: reason.PrivilegeLevel3Desc,
})
for _, option := range DefaultPrivilegeOptions {
for _, privilege := range constant.RankAllPrivileges {
if len(privilegeOptionsLevelMapping[privilege.Key]) == 0 {
continue
}
option.Privileges = append(option.Privileges, &constant.Privilege{
Label: privilege.Label,
Value: privilegeOptionsLevelMapping[privilege.Key][option.Level-1],
Key: privilege.Key,
})
}
}
}

View File

@ -4,6 +4,9 @@ package schema
type UserExternalLoginResp struct { type UserExternalLoginResp struct {
BindingKey string `json:"binding_key"` BindingKey string `json:"binding_key"`
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
// ErrMsg error message, if not empty, means login failed and this message should be displayed.
ErrMsg string `json:"-"`
ErrTitle string `json:"-"`
} }
// ExternalLoginBindingUserSendEmailReq external login binding user request // ExternalLoginBindingUserSendEmailReq external login binding user request
@ -49,6 +52,8 @@ type ExternalLoginUserInfoCache struct {
Avatar string Avatar string
// optional. The original user information provided by the third-party login platform // optional. The original user information provided by the third-party login platform
MetaInfo string MetaInfo string
// optional. The bio provided by the third-party login platform
Bio string
} }
// ExternalLoginUnbindingReq external login unbinding user // ExternalLoginUnbindingReq external login unbinding user
@ -63,6 +68,13 @@ type UserCenterUserSettingsResp struct {
AccountSettingAgent UserSettingAgent `json:"account_setting_agent"` AccountSettingAgent UserSettingAgent `json:"account_setting_agent"`
} }
type UserCenterAdminFunctionAgentResp struct {
AllowCreateUser bool `json:"allow_create_user"`
AllowUpdateUserStatus bool `json:"allow_update_user_status"`
AllowUpdateUserPassword bool `json:"allow_update_user_password"`
AllowUpdateUserRole bool `json:"allow_update_user_role"`
}
type UserSettingAgent struct { type UserSettingAgent struct {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
RedirectURL string `json:"redirect_url"` RedirectURL string `json:"redirect_url"`

View File

@ -4,14 +4,12 @@ import (
"encoding/json" "encoding/json"
"github.com/answerdev/answer/internal/base/constant" "github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/base/validator" "github.com/answerdev/answer/internal/base/validator"
"github.com/answerdev/answer/internal/entity" "github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/pkg/checker" "github.com/answerdev/answer/pkg/checker"
"github.com/answerdev/answer/pkg/converter" "github.com/answerdev/answer/pkg/converter"
"github.com/answerdev/answer/pkg/gravatar" "github.com/answerdev/answer/pkg/gravatar"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
"github.com/segmentfault/pacman/errors"
) )
// UserVerifyEmailReq user verify email request // UserVerifyEmailReq user verify email request
@ -300,7 +298,7 @@ func (u *UserModifyPasswordReq) Check() (errFields []*validator.FormErrorField,
type UpdateInfoRequest struct { type UpdateInfoRequest struct {
// display_name // display_name
DisplayName string `validate:"required,gt=0,lte=30" json:"display_name"` DisplayName string `validate:"omitempty,gt=0,lte=30" json:"display_name"`
// username // username
Username string `validate:"omitempty,gt=3,lte=30" json:"username"` Username string `validate:"omitempty,gt=3,lte=30" json:"username"`
// avatar // avatar
@ -329,16 +327,6 @@ func (a *AvatarInfo) ToJsonString() string {
} }
func (req *UpdateInfoRequest) Check() (errFields []*validator.FormErrorField, err error) { func (req *UpdateInfoRequest) Check() (errFields []*validator.FormErrorField, err error) {
if len(req.Username) > 0 {
if checker.IsInvalidUsername(req.Username) {
errField := &validator.FormErrorField{
ErrorField: "username",
ErrorMsg: reason.UsernameInvalid,
}
errFields = append(errFields, errField)
return errFields, errors.BadRequest(reason.UsernameInvalid)
}
}
req.BioHTML = converter.Markdown2BasicHTML(req.Bio) req.BioHTML = converter.Markdown2BasicHTML(req.Bio)
return nil, nil return nil, nil
} }

View File

@ -476,7 +476,7 @@ func (as *AnswerService) AdminSetAnswerStatus(ctx context.Context, req *schema.A
msg.ReceiverUserID = answerInfo.UserID msg.ReceiverUserID = answerInfo.UserID
msg.TriggerUserID = answerInfo.UserID msg.TriggerUserID = answerInfo.UserID
msg.ObjectType = constant.AnswerObjectType msg.ObjectType = constant.AnswerObjectType
msg.NotificationAction = constant.YourAnswerWasDeleted msg.NotificationAction = constant.NotificationYourAnswerWasDeleted
notice_queue.AddNotification(msg) notice_queue.AddNotification(msg)
return nil return nil
@ -566,7 +566,7 @@ func (as *AnswerService) notificationUpdateAnswer(ctx context.Context, questionU
ObjectID: answerID, ObjectID: answerID,
} }
msg.ObjectType = constant.AnswerObjectType msg.ObjectType = constant.AnswerObjectType
msg.NotificationAction = constant.UpdateAnswer msg.NotificationAction = constant.NotificationUpdateAnswer
notice_queue.AddNotification(msg) notice_queue.AddNotification(msg)
} }
@ -583,7 +583,7 @@ func (as *AnswerService) notificationAnswerTheQuestion(ctx context.Context,
ObjectID: answerID, ObjectID: answerID,
} }
msg.ObjectType = constant.AnswerObjectType msg.ObjectType = constant.AnswerObjectType
msg.NotificationAction = constant.AnswerTheQuestion msg.NotificationAction = constant.NotificationAnswerTheQuestion
notice_queue.AddNotification(msg) notice_queue.AddNotification(msg)
userInfo, exist, err := as.userRepo.GetByUserID(ctx, questionUserID) userInfo, exist, err := as.userRepo.GetByUserID(ctx, questionUserID)

View File

@ -5,6 +5,7 @@ import (
"github.com/answerdev/answer/internal/entity" "github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/pkg/token" "github.com/answerdev/answer/pkg/token"
"github.com/answerdev/answer/plugin"
"github.com/segmentfault/pacman/log" "github.com/segmentfault/pacman/log"
) )
@ -42,7 +43,6 @@ func (as *AuthService) GetUserCacheInfo(ctx context.Context, accessToken string)
} }
cacheInfo, _ := as.authRepo.GetUserStatus(ctx, userCacheInfo.UserID) cacheInfo, _ := as.authRepo.GetUserStatus(ctx, userCacheInfo.UserID)
if cacheInfo != nil { if cacheInfo != nil {
log.Debugf("user status updated: %+v", cacheInfo)
userCacheInfo.UserStatus = cacheInfo.UserStatus userCacheInfo.UserStatus = cacheInfo.UserStatus
userCacheInfo.EmailStatus = cacheInfo.EmailStatus userCacheInfo.EmailStatus = cacheInfo.EmailStatus
userCacheInfo.RoleID = cacheInfo.RoleID userCacheInfo.RoleID = cacheInfo.RoleID
@ -52,6 +52,14 @@ func (as *AuthService) GetUserCacheInfo(ctx context.Context, accessToken string)
return nil, err return nil, err
} }
} }
// try to get user status from user center
uc, ok := plugin.GetUserCenter()
if ok && len(userCacheInfo.ExternalID) > 0 {
if userStatus := uc.UserStatus(userCacheInfo.ExternalID); userStatus != plugin.UserStatusAvailable {
userCacheInfo.UserStatus = int(userStatus)
}
}
return userCacheInfo, nil return userCacheInfo, nil
} }

View File

@ -471,7 +471,7 @@ func (cs *CommentService) notificationQuestionComment(ctx context.Context, quest
ObjectID: commentID, ObjectID: commentID,
} }
msg.ObjectType = constant.CommentObjectType msg.ObjectType = constant.CommentObjectType
msg.NotificationAction = constant.CommentQuestion msg.NotificationAction = constant.NotificationCommentQuestion
notice_queue.AddNotification(msg) notice_queue.AddNotification(msg)
receiverUserInfo, exist, err := cs.userRepo.GetByUserID(ctx, questionUserID) receiverUserInfo, exist, err := cs.userRepo.GetByUserID(ctx, questionUserID)
@ -526,7 +526,7 @@ func (cs *CommentService) notificationAnswerComment(ctx context.Context,
ObjectID: commentID, ObjectID: commentID,
} }
msg.ObjectType = constant.CommentObjectType msg.ObjectType = constant.CommentObjectType
msg.NotificationAction = constant.CommentAnswer msg.NotificationAction = constant.NotificationCommentAnswer
notice_queue.AddNotification(msg) notice_queue.AddNotification(msg)
receiverUserInfo, exist, err := cs.userRepo.GetByUserID(ctx, answerUserID) receiverUserInfo, exist, err := cs.userRepo.GetByUserID(ctx, answerUserID)
@ -578,7 +578,7 @@ func (cs *CommentService) notificationCommentReply(ctx context.Context, replyUse
ObjectID: commentID, ObjectID: commentID,
} }
msg.ObjectType = constant.CommentObjectType msg.ObjectType = constant.CommentObjectType
msg.NotificationAction = constant.ReplyToYou msg.NotificationAction = constant.NotificationReplyToYou
notice_queue.AddNotification(msg) notice_queue.AddNotification(msg)
} }
@ -599,7 +599,7 @@ func (cs *CommentService) notificationMention(
ObjectID: commentID, ObjectID: commentID,
} }
msg.ObjectType = constant.CommentObjectType msg.ObjectType = constant.CommentObjectType
msg.NotificationAction = constant.MentionYou msg.NotificationAction = constant.NotificationMentionYou
notice_queue.AddNotification(msg) notice_queue.AddNotification(msg)
alreadyNotifiedUserIDs = append(alreadyNotifiedUserIDs, userInfo.ID) alreadyNotifiedUserIDs = append(alreadyNotifiedUserIDs, userInfo.ID)
} }

View File

@ -7,14 +7,15 @@ import (
"github.com/answerdev/answer/internal/base/constant" "github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/base/data" "github.com/answerdev/answer/internal/base/data"
"github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/pager" "github.com/answerdev/answer/internal/base/pager"
"github.com/answerdev/answer/internal/base/translator" "github.com/answerdev/answer/internal/base/translator"
"github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/schema" "github.com/answerdev/answer/internal/schema"
notficationcommon "github.com/answerdev/answer/internal/service/notification_common" notficationcommon "github.com/answerdev/answer/internal/service/notification_common"
"github.com/answerdev/answer/internal/service/revision_common" "github.com/answerdev/answer/internal/service/revision_common"
"github.com/answerdev/answer/pkg/uid" "github.com/answerdev/answer/pkg/uid"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
"github.com/segmentfault/pacman/i18n"
"github.com/segmentfault/pacman/log" "github.com/segmentfault/pacman/log"
) )
@ -127,35 +128,47 @@ func (ns *NotificationService) GetNotificationPage(ctx context.Context, searchCo
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp, err = ns.formatNotificationPage(ctx, notifications)
if err != nil {
return nil, err
}
return pager.NewPageModel(total, resp), nil
}
func (ns *NotificationService) formatNotificationPage(ctx context.Context, notifications []*entity.Notification) (
resp []*schema.NotificationContent, err error) {
lang := handler.GetLangByCtx(ctx)
for _, notificationInfo := range notifications { for _, notificationInfo := range notifications {
item := &schema.NotificationContent{} item := &schema.NotificationContent{}
err := json.Unmarshal([]byte(notificationInfo.Content), item) if err := json.Unmarshal([]byte(notificationInfo.Content), item); err != nil {
if err != nil {
log.Error("NotificationContent Unmarshal Error", err.Error()) log.Error("NotificationContent Unmarshal Error", err.Error())
continue continue
} }
lang, _ := ctx.Value(constant.AcceptLanguageFlag).(i18n.Language) // If notification is downvote, the user info is not needed.
item.NotificationAction = translator.Tr(lang, item.NotificationAction) if item.NotificationAction == constant.NotificationDownVotedTheQuestion ||
item.ID = notificationInfo.ID item.NotificationAction == constant.NotificationDownVotedTheAnswer {
item.UpdateTime = notificationInfo.UpdatedAt.Unix() item.UserInfo = nil
if notificationInfo.IsRead == schema.NotificationRead {
item.IsRead = true
} }
answerID, ok := item.ObjectInfo.ObjectMap["answer"]
if ok { item.ID = notificationInfo.ID
item.NotificationAction = translator.Tr(lang, item.NotificationAction)
item.UpdateTime = notificationInfo.UpdatedAt.Unix()
item.IsRead = notificationInfo.IsRead == schema.NotificationRead
if answerID, ok := item.ObjectInfo.ObjectMap["answer"]; ok {
if item.ObjectInfo.ObjectID == answerID { if item.ObjectInfo.ObjectID == answerID {
item.ObjectInfo.ObjectID = uid.EnShortID(item.ObjectInfo.ObjectMap["answer"]) item.ObjectInfo.ObjectID = uid.EnShortID(item.ObjectInfo.ObjectMap["answer"])
} }
item.ObjectInfo.ObjectMap["answer"] = uid.EnShortID(item.ObjectInfo.ObjectMap["answer"]) item.ObjectInfo.ObjectMap["answer"] = uid.EnShortID(item.ObjectInfo.ObjectMap["answer"])
} }
questionID, ok := item.ObjectInfo.ObjectMap["question"] if questionID, ok := item.ObjectInfo.ObjectMap["question"]; ok {
if ok {
if item.ObjectInfo.ObjectID == questionID { if item.ObjectInfo.ObjectID == questionID {
item.ObjectInfo.ObjectID = uid.EnShortID(item.ObjectInfo.ObjectMap["question"]) item.ObjectInfo.ObjectID = uid.EnShortID(item.ObjectInfo.ObjectMap["question"])
} }
item.ObjectInfo.ObjectMap["question"] = uid.EnShortID(item.ObjectInfo.ObjectMap["question"]) item.ObjectInfo.ObjectMap["question"] = uid.EnShortID(item.ObjectInfo.ObjectMap["question"])
} }
resp = append(resp, item) resp = append(resp, item)
} }
return pager.NewPageModel(total, resp), nil return resp, nil
} }

View File

@ -193,10 +193,10 @@ func (ns *NotificationCommon) SendNotificationToAllFollower(ctx context.Context,
if msg.NoNeedPushAllFollow { if msg.NoNeedPushAllFollow {
return return
} }
if msg.NotificationAction != constant.UpdateQuestion && if msg.NotificationAction != constant.NotificationUpdateQuestion &&
msg.NotificationAction != constant.AnswerTheQuestion && msg.NotificationAction != constant.NotificationAnswerTheQuestion &&
msg.NotificationAction != constant.UpdateAnswer && msg.NotificationAction != constant.NotificationUpdateAnswer &&
msg.NotificationAction != constant.AcceptAnswer { msg.NotificationAction != constant.NotificationAcceptAnswer {
return return
} }
condObjectID := msg.ObjectID condObjectID := msg.ObjectID

View File

@ -10,10 +10,10 @@ const (
QuestionReopen = "question.reopen" QuestionReopen = "question.reopen"
QuestionVoteUp = "question.vote_up" QuestionVoteUp = "question.vote_up"
QuestionVoteDown = "question.vote_down" QuestionVoteDown = "question.vote_down"
QuestionPin = "question.pin" //Top the question QuestionPin = "question.pin"
QuestionUnPin = "question.unpin" //untop the question QuestionUnPin = "question.unpin"
QuestionHide = "question.hide" //hide the question QuestionHide = "question.hide"
QuestionShow = "question.show" //show the question QuestionShow = "question.show"
AnswerAdd = "answer.add" AnswerAdd = "answer.add"
AnswerEdit = "answer.edit" AnswerEdit = "answer.edit"
AnswerEditWithoutReview = "answer.edit_without_review" AnswerEditWithoutReview = "answer.edit_without_review"

View File

@ -32,7 +32,7 @@ type QuestionRepo interface {
UpdateQuestion(ctx context.Context, question *entity.Question, Cols []string) (err error) UpdateQuestion(ctx context.Context, question *entity.Question, Cols []string) (err error)
GetQuestion(ctx context.Context, id string) (question *entity.Question, exist bool, err error) GetQuestion(ctx context.Context, id string) (question *entity.Question, exist bool, err error)
GetQuestionList(ctx context.Context, question *entity.Question) (questions []*entity.Question, err error) GetQuestionList(ctx context.Context, question *entity.Question) (questions []*entity.Question, err error)
GetQuestionPage(ctx context.Context, page, pageSize int, userID, tagID, orderCond string) ( GetQuestionPage(ctx context.Context, page, pageSize int, userID, tagID, orderCond string, inDays int) (
questionList []*entity.Question, total int64, err error) questionList []*entity.Question, total int64, err error)
UpdateQuestionStatus(ctx context.Context, question *entity.Question) (err error) UpdateQuestionStatus(ctx context.Context, question *entity.Question) (err error)
UpdateQuestionStatusWithOutUpdateTime(ctx context.Context, question *entity.Question) (err error) UpdateQuestionStatusWithOutUpdateTime(ctx context.Context, question *entity.Question) (err error)

View File

@ -1002,7 +1002,7 @@ func (qs *QuestionService) GetQuestionPage(ctx context.Context, req *schema.Ques
} }
questionList, total, err := qs.questionRepo.GetQuestionPage(ctx, req.Page, req.PageSize, questionList, total, err := qs.questionRepo.GetQuestionPage(ctx, req.Page, req.PageSize,
req.UserIDBeSearched, req.TagID, req.OrderCond) req.UserIDBeSearched, req.TagID, req.OrderCond, req.InDays)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
@ -1064,7 +1064,7 @@ func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, questionI
msg.ReceiverUserID = questionInfo.UserID msg.ReceiverUserID = questionInfo.UserID
msg.TriggerUserID = questionInfo.UserID msg.TriggerUserID = questionInfo.UserID
msg.ObjectType = constant.QuestionObjectType msg.ObjectType = constant.QuestionObjectType
msg.NotificationAction = constant.YourQuestionWasDeleted msg.NotificationAction = constant.NotificationYourQuestionWasDeleted
notice_queue.AddNotification(msg) notice_queue.AddNotification(msg)
return nil return nil
} }

View File

@ -66,7 +66,7 @@ func (rh *ReportHandle) HandleObject(ctx context.Context, reported *entity.Repor
switch req.FlaggedType { switch req.FlaggedType {
case reasonDelete: case reasonDelete:
err = rh.commentRepo.RemoveComment(ctx, objectID) err = rh.commentRepo.RemoveComment(ctx, objectID)
rh.sendNotification(ctx, reportedUserID, objectID, constant.YourCommentWasDeleted) rh.sendNotification(ctx, reportedUserID, objectID, constant.NotificationYourCommentWasDeleted)
} }
} }
return return

View File

@ -209,7 +209,7 @@ func (rs *RevisionService) revisionAuditAnswer(ctx context.Context, revisionitem
ObjectID: answerinfo.ID, ObjectID: answerinfo.ID,
} }
msg.ObjectType = constant.AnswerObjectType msg.ObjectType = constant.AnswerObjectType
msg.NotificationAction = constant.UpdateAnswer msg.NotificationAction = constant.NotificationUpdateAnswer
notice_queue.AddNotification(msg) notice_queue.AddNotification(msg)
activity_queue.AddActivity(&schema.ActivityMsg{ activity_queue.AddActivity(&schema.ActivityMsg{

View File

@ -3,12 +3,15 @@ package siteinfo
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"github.com/answerdev/answer/internal/base/constant" "github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/reason" "github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/base/translator" "github.com/answerdev/answer/internal/base/translator"
"github.com/answerdev/answer/internal/entity" "github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/schema" "github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service/config"
"github.com/answerdev/answer/internal/service/export" "github.com/answerdev/answer/internal/service/export"
"github.com/answerdev/answer/internal/service/siteinfo_common" "github.com/answerdev/answer/internal/service/siteinfo_common"
tagcommon "github.com/answerdev/answer/internal/service/tag_common" tagcommon "github.com/answerdev/answer/internal/service/tag_common"
@ -23,19 +26,23 @@ type SiteInfoService struct {
siteInfoCommonService *siteinfo_common.SiteInfoCommonService siteInfoCommonService *siteinfo_common.SiteInfoCommonService
emailService *export.EmailService emailService *export.EmailService
tagCommonService *tagcommon.TagCommonService tagCommonService *tagcommon.TagCommonService
configRepo config.ConfigRepo
} }
func NewSiteInfoService( func NewSiteInfoService(
siteInfoRepo siteinfo_common.SiteInfoRepo, siteInfoRepo siteinfo_common.SiteInfoRepo,
siteInfoCommonService *siteinfo_common.SiteInfoCommonService, siteInfoCommonService *siteinfo_common.SiteInfoCommonService,
emailService *export.EmailService, emailService *export.EmailService,
tagCommonService *tagcommon.TagCommonService) *SiteInfoService { tagCommonService *tagcommon.TagCommonService,
configRepo config.ConfigRepo,
resp, err := siteInfoCommonService.GetSiteInterface(context.Background()) ) *SiteInfoService {
if err != nil { usersSiteInfo, _ := siteInfoCommonService.GetSiteUsers(context.Background())
log.Error(err) if usersSiteInfo != nil {
} else { constant.DefaultAvatar = usersSiteInfo.DefaultAvatar
constant.DefaultAvatar = resp.DefaultAvatar }
generalSiteInfo, _ := siteInfoCommonService.GetSiteGeneral(context.Background())
if generalSiteInfo != nil {
constant.DefaultSiteURL = generalSiteInfo.SiteUrl
} }
return &SiteInfoService{ return &SiteInfoService{
@ -43,6 +50,7 @@ func NewSiteInfoService(
siteInfoCommonService: siteInfoCommonService, siteInfoCommonService: siteInfoCommonService,
emailService: emailService, emailService: emailService,
tagCommonService: tagCommonService, tagCommonService: tagCommonService,
configRepo: configRepo,
} }
} }
@ -61,6 +69,11 @@ func (s *SiteInfoService) GetSiteBranding(ctx context.Context) (resp *schema.Sit
return s.siteInfoCommonService.GetSiteBranding(ctx) return s.siteInfoCommonService.GetSiteBranding(ctx)
} }
// GetSiteUsers get site info about users
func (s *SiteInfoService) GetSiteUsers(ctx context.Context) (resp *schema.SiteUsersResp, err error) {
return s.siteInfoCommonService.GetSiteUsers(ctx)
}
// GetSiteWrite get site info write // GetSiteWrite get site info write
func (s *SiteInfoService) GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error) { func (s *SiteInfoService) GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error) {
resp = &schema.SiteWriteResp{} resp = &schema.SiteWriteResp{}
@ -106,45 +119,32 @@ func (s *SiteInfoService) GetSiteTheme(ctx context.Context) (resp *schema.SiteTh
func (s *SiteInfoService) SaveSiteGeneral(ctx context.Context, req schema.SiteGeneralReq) (err error) { func (s *SiteInfoService) SaveSiteGeneral(ctx context.Context, req schema.SiteGeneralReq) (err error) {
req.FormatSiteUrl() req.FormatSiteUrl()
var ( content, _ := json.Marshal(req)
siteType = "general" data := &entity.SiteInfo{
content []byte Type: constant.SiteTypeGeneral,
)
content, _ = json.Marshal(req)
data := entity.SiteInfo{
Type: siteType,
Content: string(content), Content: string(content),
Status: 1,
}
err = s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeGeneral, data)
if err == nil {
constant.DefaultSiteURL = req.SiteUrl
} }
err = s.siteInfoRepo.SaveByType(ctx, siteType, &data)
return return
} }
func (s *SiteInfoService) SaveSiteInterface(ctx context.Context, req schema.SiteInterfaceReq) (err error) { func (s *SiteInfoService) SaveSiteInterface(ctx context.Context, req schema.SiteInterfaceReq) (err error) {
var (
siteType = "interface"
content []byte
)
// check language // check language
if !translator.CheckLanguageIsValid(req.Language) { if !translator.CheckLanguageIsValid(req.Language) {
err = errors.BadRequest(reason.LangNotFound) err = errors.BadRequest(reason.LangNotFound)
return return
} }
content, _ = json.Marshal(req) content, _ := json.Marshal(req)
data := entity.SiteInfo{ data := entity.SiteInfo{
Type: siteType, Type: constant.SiteTypeInterface,
Content: string(content), Content: string(content),
} }
return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeInterface, &data)
err = s.siteInfoRepo.SaveByType(ctx, siteType, &data)
if err == nil {
constant.DefaultAvatar = req.DefaultAvatar
}
return
} }
// SaveSiteBranding save site branding information // SaveSiteBranding save site branding information
@ -218,6 +218,21 @@ func (s *SiteInfoService) SaveSiteTheme(ctx context.Context, req *schema.SiteThe
return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeTheme, data) return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeTheme, data)
} }
// SaveSiteUsers save site users
func (s *SiteInfoService) SaveSiteUsers(ctx context.Context, req *schema.SiteUsersReq) (err error) {
content, _ := json.Marshal(req)
data := &entity.SiteInfo{
Type: constant.SiteTypeUsers,
Content: string(content),
Status: 1,
}
err = s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeUsers, data)
if err == nil {
constant.DefaultAvatar = req.DefaultAvatar
}
return err
}
// GetSMTPConfig get smtp config // GetSMTPConfig get smtp config
func (s *SiteInfoService) GetSMTPConfig(ctx context.Context) ( func (s *SiteInfoService) GetSMTPConfig(ctx context.Context) (
resp *schema.GetSMTPConfigResp, err error, resp *schema.GetSMTPConfigResp, err error,
@ -253,8 +268,11 @@ func (s *SiteInfoService) UpdateSMTPConfig(ctx context.Context, req *schema.Upda
return return
} }
func (s *SiteInfoService) GetSeo(ctx context.Context) (resp *schema.SiteSeoResp, err error) { func (s *SiteInfoService) GetSeo(ctx context.Context) (resp *schema.SiteSeoReq, err error) {
resp = &schema.SiteSeoResp{} resp = &schema.SiteSeoReq{}
if err = s.siteInfoCommonService.GetSiteInfoByType(ctx, constant.SiteTypeSeo, resp); err != nil {
return resp, err
}
loginConfig, err := s.GetSiteLogin(ctx) loginConfig, err := s.GetSiteLogin(ctx)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
@ -265,17 +283,6 @@ func (s *SiteInfoService) GetSeo(ctx context.Context) (resp *schema.SiteSeoResp,
resp.Robots = "User-agent: *\nDisallow: /" resp.Robots = "User-agent: *\nDisallow: /"
return resp, nil return resp, nil
} }
resp = &schema.SiteSeoResp{}
siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, constant.SiteTypeSeo)
if err != nil {
log.Error(err)
return resp, nil
}
if !exist {
return resp, nil
}
_ = json.Unmarshal([]byte(siteInfo.Content), resp)
return resp, nil return resp, nil
} }
@ -302,3 +309,71 @@ func (s *SiteInfoService) SaveSeo(ctx context.Context, req schema.SiteSeoReq) (e
} }
return return
} }
func (s *SiteInfoService) GetPrivilegesConfig(ctx context.Context) (resp *schema.GetPrivilegesConfigResp, err error) {
privilege := &schema.UpdatePrivilegesConfigReq{}
if err = s.siteInfoCommonService.GetSiteInfoByType(ctx, constant.SiteTypePrivileges, privilege); err != nil {
return nil, err
}
resp = &schema.GetPrivilegesConfigResp{
Options: s.translatePrivilegeOptions(ctx),
SelectedLevel: schema.PrivilegeLevel3,
}
if privilege != nil && privilege.Level > 0 {
resp.SelectedLevel = privilege.Level
}
return resp, nil
}
func (s *SiteInfoService) translatePrivilegeOptions(ctx context.Context) (options []*schema.PrivilegeOption) {
la := handler.GetLangByCtx(ctx)
for _, option := range schema.DefaultPrivilegeOptions {
op := &schema.PrivilegeOption{
Level: option.Level,
LevelDesc: translator.Tr(la, option.LevelDesc),
}
for _, privilege := range option.Privileges {
op.Privileges = append(op.Privileges, &constant.Privilege{
Key: privilege.Key,
Label: translator.Tr(la, privilege.Label),
Value: privilege.Value,
})
}
options = append(options, op)
}
return
}
func (s *SiteInfoService) UpdatePrivilegesConfig(ctx context.Context, req *schema.UpdatePrivilegesConfigReq) (err error) {
var chooseOption *schema.PrivilegeOption
for _, option := range schema.DefaultPrivilegeOptions {
if option.Level == req.Level {
chooseOption = option
break
}
}
if chooseOption == nil {
return nil
}
// update site info that user choose which privilege level
content, _ := json.Marshal(req)
data := &entity.SiteInfo{
Type: constant.SiteTypePrivileges,
Content: string(content),
Status: 1,
}
err = s.siteInfoRepo.SaveByType(ctx, constant.SiteTypePrivileges, data)
if err != nil {
return err
}
// update privilege in config
for _, privilege := range chooseOption.Privileges {
err = s.configRepo.SetConfig(privilege.Key, fmt.Sprintf("%d", privilege.Value))
if err != nil {
return err
}
}
return
}

View File

@ -43,7 +43,7 @@ func NewSiteInfoCommonService(siteInfoRepo SiteInfoRepo) *SiteInfoCommonService
// GetSiteGeneral get site info general // GetSiteGeneral get site info general
func (s *SiteInfoCommonService) GetSiteGeneral(ctx context.Context) (resp *schema.SiteGeneralResp, err error) { func (s *SiteInfoCommonService) GetSiteGeneral(ctx context.Context) (resp *schema.SiteGeneralResp, err error) {
resp = &schema.SiteGeneralResp{} resp = &schema.SiteGeneralResp{}
if err = s.getSiteInfoByType(ctx, constant.SiteTypeGeneral, resp); err != nil { if err = s.GetSiteInfoByType(ctx, constant.SiteTypeGeneral, resp); err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
@ -52,7 +52,7 @@ func (s *SiteInfoCommonService) GetSiteGeneral(ctx context.Context) (resp *schem
// GetSiteInterface get site info interface // GetSiteInterface get site info interface
func (s *SiteInfoCommonService) GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error) { func (s *SiteInfoCommonService) GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error) {
resp = &schema.SiteInterfaceResp{} resp = &schema.SiteInterfaceResp{}
if err = s.getSiteInfoByType(ctx, constant.SiteTypeInterface, resp); err != nil { if err = s.GetSiteInfoByType(ctx, constant.SiteTypeInterface, resp); err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
@ -61,7 +61,16 @@ func (s *SiteInfoCommonService) GetSiteInterface(ctx context.Context) (resp *sch
// GetSiteBranding get site info branding // GetSiteBranding get site info branding
func (s *SiteInfoCommonService) GetSiteBranding(ctx context.Context) (resp *schema.SiteBrandingResp, err error) { func (s *SiteInfoCommonService) GetSiteBranding(ctx context.Context) (resp *schema.SiteBrandingResp, err error) {
resp = &schema.SiteBrandingResp{} resp = &schema.SiteBrandingResp{}
if err = s.getSiteInfoByType(ctx, constant.SiteTypeBranding, resp); err != nil { if err = s.GetSiteInfoByType(ctx, constant.SiteTypeBranding, resp); err != nil {
return nil, err
}
return resp, nil
}
// GetSiteUsers get site info about users
func (s *SiteInfoCommonService) GetSiteUsers(ctx context.Context) (resp *schema.SiteUsersResp, err error) {
resp = &schema.SiteUsersResp{}
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeUsers, resp); err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
@ -70,7 +79,7 @@ func (s *SiteInfoCommonService) GetSiteBranding(ctx context.Context) (resp *sche
// GetSiteWrite get site info write // GetSiteWrite get site info write
func (s *SiteInfoCommonService) GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error) { func (s *SiteInfoCommonService) GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error) {
resp = &schema.SiteWriteResp{} resp = &schema.SiteWriteResp{}
if err = s.getSiteInfoByType(ctx, constant.SiteTypeWrite, resp); err != nil { if err = s.GetSiteInfoByType(ctx, constant.SiteTypeWrite, resp); err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
@ -79,7 +88,7 @@ func (s *SiteInfoCommonService) GetSiteWrite(ctx context.Context) (resp *schema.
// GetSiteLegal get site info write // GetSiteLegal get site info write
func (s *SiteInfoCommonService) GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) { func (s *SiteInfoCommonService) GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) {
resp = &schema.SiteLegalResp{} resp = &schema.SiteLegalResp{}
if err = s.getSiteInfoByType(ctx, constant.SiteTypeLegal, resp); err != nil { if err = s.GetSiteInfoByType(ctx, constant.SiteTypeLegal, resp); err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
@ -88,7 +97,7 @@ func (s *SiteInfoCommonService) GetSiteLegal(ctx context.Context) (resp *schema.
// GetSiteLogin get site login config // GetSiteLogin get site login config
func (s *SiteInfoCommonService) GetSiteLogin(ctx context.Context) (resp *schema.SiteLoginResp, err error) { func (s *SiteInfoCommonService) GetSiteLogin(ctx context.Context) (resp *schema.SiteLoginResp, err error) {
resp = &schema.SiteLoginResp{} resp = &schema.SiteLoginResp{}
if err = s.getSiteInfoByType(ctx, constant.SiteTypeLogin, resp); err != nil { if err = s.GetSiteInfoByType(ctx, constant.SiteTypeLogin, resp); err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
@ -97,7 +106,7 @@ func (s *SiteInfoCommonService) GetSiteLogin(ctx context.Context) (resp *schema.
// GetSiteCustomCssHTML get site custom css html config // GetSiteCustomCssHTML get site custom css html config
func (s *SiteInfoCommonService) GetSiteCustomCssHTML(ctx context.Context) (resp *schema.SiteCustomCssHTMLResp, err error) { func (s *SiteInfoCommonService) GetSiteCustomCssHTML(ctx context.Context) (resp *schema.SiteCustomCssHTMLResp, err error) {
resp = &schema.SiteCustomCssHTMLResp{} resp = &schema.SiteCustomCssHTMLResp{}
if err = s.getSiteInfoByType(ctx, constant.SiteTypeCustomCssHTML, resp); err != nil { if err = s.GetSiteInfoByType(ctx, constant.SiteTypeCustomCssHTML, resp); err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
@ -108,7 +117,7 @@ func (s *SiteInfoCommonService) GetSiteTheme(ctx context.Context) (resp *schema.
resp = &schema.SiteThemeResp{ resp = &schema.SiteThemeResp{
ThemeOptions: schema.GetThemeOptions, ThemeOptions: schema.GetThemeOptions,
} }
if err = s.getSiteInfoByType(ctx, constant.SiteTypeTheme, resp); err != nil { if err = s.GetSiteInfoByType(ctx, constant.SiteTypeTheme, resp); err != nil {
return nil, err return nil, err
} }
resp.TrTheme(ctx) resp.TrTheme(ctx)
@ -118,13 +127,13 @@ func (s *SiteInfoCommonService) GetSiteTheme(ctx context.Context) (resp *schema.
// GetSiteSeo get site seo // GetSiteSeo get site seo
func (s *SiteInfoCommonService) GetSiteSeo(ctx context.Context) (resp *schema.SiteSeoReq, err error) { func (s *SiteInfoCommonService) GetSiteSeo(ctx context.Context) (resp *schema.SiteSeoReq, err error) {
resp = &schema.SiteSeoReq{} resp = &schema.SiteSeoReq{}
if err = s.getSiteInfoByType(ctx, constant.SiteTypeSeo, resp); err != nil { if err = s.GetSiteInfoByType(ctx, constant.SiteTypeSeo, resp); err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
} }
func (s *SiteInfoCommonService) getSiteInfoByType(ctx context.Context, siteType string, resp interface{}) (err error) { func (s *SiteInfoCommonService) GetSiteInfoByType(ctx context.Context, siteType string, resp interface{}) (err error) {
siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, siteType) siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, siteType)
if err != nil { if err != nil {
return err return err

View File

@ -150,7 +150,7 @@ func (us *UserCommon) MakeUsername(ctx context.Context, displayName string) (use
return username + suffix, nil return username + suffix, nil
} }
func (us *UserCommon) CacheLoginUserInfo(ctx context.Context, userID string, userStatus, emailStatus int) ( func (us *UserCommon) CacheLoginUserInfo(ctx context.Context, userID string, userStatus, emailStatus int, externalID string) (
accessToken string, userCacheInfo *entity.UserCacheInfo, err error) { accessToken string, userCacheInfo *entity.UserCacheInfo, err error) {
roleID, err := us.userRoleService.GetUserRole(ctx, userID) roleID, err := us.userRoleService.GetUserRole(ctx, userID)
if err != nil { if err != nil {
@ -162,6 +162,7 @@ func (us *UserCommon) CacheLoginUserInfo(ctx context.Context, userID string, use
EmailStatus: emailStatus, EmailStatus: emailStatus,
UserStatus: userStatus, UserStatus: userStatus,
RoleID: roleID, RoleID: roleID,
ExternalID: externalID,
} }
accessToken, err = us.authService.SetUserCacheInfo(ctx, userCacheInfo) accessToken, err = us.authService.SetUserCacheInfo(ctx, userCacheInfo)

View File

@ -5,10 +5,16 @@ import (
"encoding/json" "encoding/json"
"time" "time"
"github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/base/translator"
"github.com/answerdev/answer/internal/entity" "github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/schema" "github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service/activity" "github.com/answerdev/answer/internal/service/activity"
"github.com/answerdev/answer/internal/service/siteinfo_common"
usercommon "github.com/answerdev/answer/internal/service/user_common" usercommon "github.com/answerdev/answer/internal/service/user_common"
"github.com/answerdev/answer/pkg/checker"
"github.com/answerdev/answer/pkg/converter"
"github.com/answerdev/answer/pkg/random" "github.com/answerdev/answer/pkg/random"
"github.com/answerdev/answer/plugin" "github.com/answerdev/answer/plugin"
"github.com/segmentfault/pacman/log" "github.com/segmentfault/pacman/log"
@ -20,6 +26,7 @@ type UserCenterLoginService struct {
userExternalLoginRepo UserExternalLoginRepo userExternalLoginRepo UserExternalLoginRepo
userCommonService *usercommon.UserCommon userCommonService *usercommon.UserCommon
userActivity activity.UserActiveActivityRepo userActivity activity.UserActiveActivityRepo
siteInfoCommonService *siteinfo_common.SiteInfoCommonService
} }
// NewUserCenterLoginService new user external login service // NewUserCenterLoginService new user external login service
@ -28,21 +35,38 @@ func NewUserCenterLoginService(
userCommonService *usercommon.UserCommon, userCommonService *usercommon.UserCommon,
userExternalLoginRepo UserExternalLoginRepo, userExternalLoginRepo UserExternalLoginRepo,
userActivity activity.UserActiveActivityRepo, userActivity activity.UserActiveActivityRepo,
siteInfoCommonService *siteinfo_common.SiteInfoCommonService,
) *UserCenterLoginService { ) *UserCenterLoginService {
return &UserCenterLoginService{ return &UserCenterLoginService{
userRepo: userRepo, userRepo: userRepo,
userCommonService: userCommonService, userCommonService: userCommonService,
userExternalLoginRepo: userExternalLoginRepo, userExternalLoginRepo: userExternalLoginRepo,
userActivity: userActivity, userActivity: userActivity,
siteInfoCommonService: siteInfoCommonService,
} }
} }
func (us *UserCenterLoginService) ExternalLogin( func (us *UserCenterLoginService) ExternalLogin(
ctx context.Context, provider string, basicUserInfo *plugin.UserCenterBasicUserInfo) ( ctx context.Context, userCenter plugin.UserCenter, basicUserInfo *plugin.UserCenterBasicUserInfo) (
resp *schema.UserExternalLoginResp, err error) { resp *schema.UserExternalLoginResp, err error) {
if len(basicUserInfo.Email) > 0 {
// check whether site allow register or not
siteInfo, err := us.siteInfoCommonService.GetSiteLogin(ctx)
if err != nil {
return nil, err
}
if !checker.EmailInAllowEmailDomain(basicUserInfo.Email, siteInfo.AllowEmailDomains) {
log.Debugf("email domain not allowed: %s", basicUserInfo.Email)
return &schema.UserExternalLoginResp{
ErrTitle: translator.Tr(handler.GetLangByCtx(ctx), reason.UserAccessDenied),
ErrMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.EmailIllegalDomainError),
}, nil
}
}
oldExternalLoginUserInfo, exist, err := us.userExternalLoginRepo.GetByExternalID(ctx, oldExternalLoginUserInfo, exist, err := us.userExternalLoginRepo.GetByExternalID(ctx,
provider, basicUserInfo.ExternalID) userCenter.Info().SlugName, basicUserInfo.ExternalID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -53,16 +77,28 @@ func (us *UserCenterLoginService) ExternalLogin(
return nil, err return nil, err
} }
if exist { if exist {
// if user is deleted, do not allow login
if oldUserInfo.Status == entity.UserStatusDeleted {
return &schema.UserExternalLoginResp{
ErrTitle: translator.Tr(handler.GetLangByCtx(ctx), reason.UserAccessDenied),
ErrMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.UserPageAccessDenied),
}, nil
}
if err := us.userRepo.UpdateLastLoginDate(ctx, oldUserInfo.ID); err != nil { if err := us.userRepo.UpdateLastLoginDate(ctx, oldUserInfo.ID); err != nil {
log.Errorf("update user last login date failed: %v", err) log.Errorf("update user last login date failed: %v", err)
} }
accessToken, _, err := us.userCommonService.CacheLoginUserInfo( accessToken, _, err := us.userCommonService.CacheLoginUserInfo(
ctx, oldUserInfo.ID, oldUserInfo.MailStatus, oldUserInfo.Status) ctx, oldUserInfo.ID, oldUserInfo.MailStatus, oldUserInfo.Status, oldExternalLoginUserInfo.ExternalID)
return &schema.UserExternalLoginResp{AccessToken: accessToken}, err return &schema.UserExternalLoginResp{AccessToken: accessToken}, err
} }
} }
oldUserInfo, err := us.registerNewUser(ctx, provider, basicUserInfo) // cache external user info, waiting for user enter email address.
if userCenter.Description().MustAuthEmailEnabled && len(basicUserInfo.Email) == 0 {
return &schema.UserExternalLoginResp{ErrMsg: "Requires authorized email to login"}, nil
}
oldUserInfo, err := us.registerNewUser(ctx, userCenter.Info().SlugName, basicUserInfo)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -70,7 +106,7 @@ func (us *UserCenterLoginService) ExternalLogin(
us.activeUser(ctx, oldUserInfo) us.activeUser(ctx, oldUserInfo)
accessToken, _, err := us.userCommonService.CacheLoginUserInfo( accessToken, _, err := us.userCommonService.CacheLoginUserInfo(
ctx, oldUserInfo.ID, oldUserInfo.MailStatus, oldUserInfo.Status) ctx, oldUserInfo.ID, oldUserInfo.MailStatus, oldUserInfo.Status, oldExternalLoginUserInfo.ExternalID)
return &schema.UserExternalLoginResp{AccessToken: accessToken}, err return &schema.UserExternalLoginResp{AccessToken: accessToken}, err
} }
@ -98,6 +134,8 @@ func (us *UserCenterLoginService) registerNewUser(ctx context.Context, provider
userInfo.MailStatus = entity.EmailStatusAvailable userInfo.MailStatus = entity.EmailStatusAvailable
userInfo.Status = entity.UserStatusAvailable userInfo.Status = entity.UserStatusAvailable
userInfo.LastLoginDate = time.Now() userInfo.LastLoginDate = time.Now()
userInfo.Bio = basicUserInfo.Bio
userInfo.BioHTML = converter.Markdown2HTML(basicUserInfo.Bio)
err = us.userRepo.AddUser(ctx, userInfo) err = us.userRepo.AddUser(ctx, userInfo)
if err != nil { if err != nil {
return nil, err return nil, err
@ -166,6 +204,31 @@ func (us *UserCenterLoginService) UserCenterUserSettings(ctx context.Context, us
return resp, nil return resp, nil
} }
// UserCenterAdminFunctionAgent Check in the backend administration interface if the user-related functions
// are turned off due to turning on the User Center plugin.
func (us *UserCenterLoginService) UserCenterAdminFunctionAgent(ctx context.Context) (
resp *schema.UserCenterAdminFunctionAgentResp, err error) {
resp = &schema.UserCenterAdminFunctionAgentResp{
AllowCreateUser: true,
AllowUpdateUserStatus: true,
AllowUpdateUserPassword: true,
AllowUpdateUserRole: true,
}
userCenter, ok := plugin.GetUserCenter()
if !ok {
return
}
desc := userCenter.Description()
// If user status agent is enabled, admin can not update user status in answer.
resp.AllowUpdateUserStatus = !desc.UserStatusAgentEnabled
// If original user system is enabled, admin can update user password and role in answer.
resp.AllowUpdateUserPassword = desc.EnabledOriginalUserSystem
resp.AllowUpdateUserRole = desc.EnabledOriginalUserSystem
resp.AllowCreateUser = desc.EnabledOriginalUserSystem
return resp, nil
}
func (us *UserCenterLoginService) UserCenterPersonalBranding(ctx context.Context, username string) ( func (us *UserCenterLoginService) UserCenterPersonalBranding(ctx context.Context, username string) (
resp *schema.UserCenterPersonalBranding, err error) { resp *schema.UserCenterPersonalBranding, err error) {
resp = &schema.UserCenterPersonalBranding{ resp = &schema.UserCenterPersonalBranding{

View File

@ -15,6 +15,7 @@ import (
usercommon "github.com/answerdev/answer/internal/service/user_common" usercommon "github.com/answerdev/answer/internal/service/user_common"
"github.com/answerdev/answer/pkg/random" "github.com/answerdev/answer/pkg/random"
"github.com/answerdev/answer/pkg/token" "github.com/answerdev/answer/pkg/token"
"github.com/answerdev/answer/plugin"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/segmentfault/pacman/errors" "github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/log" "github.com/segmentfault/pacman/log"
@ -83,7 +84,7 @@ func (us *UserExternalLoginService) ExternalLogin(
log.Error(err) log.Error(err)
} }
accessToken, _, err := us.userCommonService.CacheLoginUserInfo( accessToken, _, err := us.userCommonService.CacheLoginUserInfo(
ctx, oldUserInfo.ID, newMailStatus, oldUserInfo.Status) ctx, oldUserInfo.ID, newMailStatus, oldUserInfo.Status, oldExternalLoginUserInfo.ExternalID)
return &schema.UserExternalLoginResp{AccessToken: accessToken}, err return &schema.UserExternalLoginResp{AccessToken: accessToken}, err
} }
} }
@ -122,7 +123,7 @@ func (us *UserExternalLoginService) ExternalLogin(
} }
accessToken, _, err := us.userCommonService.CacheLoginUserInfo( accessToken, _, err := us.userCommonService.CacheLoginUserInfo(
ctx, oldUserInfo.ID, newMailStatus, oldUserInfo.Status) ctx, oldUserInfo.ID, newMailStatus, oldUserInfo.Status, oldExternalLoginUserInfo.ExternalID)
return &schema.UserExternalLoginResp{AccessToken: accessToken}, err return &schema.UserExternalLoginResp{AccessToken: accessToken}, err
} }
@ -150,6 +151,8 @@ func (us *UserExternalLoginService) registerNewUser(ctx context.Context,
userInfo.MailStatus = entity.EmailStatusToBeVerified userInfo.MailStatus = entity.EmailStatusToBeVerified
userInfo.Status = entity.UserStatusAvailable userInfo.Status = entity.UserStatusAvailable
userInfo.LastLoginDate = time.Now() userInfo.LastLoginDate = time.Now()
userInfo.Bio = externalUserInfo.Bio
userInfo.BioHTML = externalUserInfo.Bio
err = us.userRepo.AddUser(ctx, userInfo) err = us.userRepo.AddUser(ctx, userInfo)
if err != nil { if err != nil {
return nil, err return nil, err
@ -249,7 +252,7 @@ func (us *UserExternalLoginService) ExternalLoginBindingUserSendEmail(
return nil, err return nil, err
} }
resp.AccessToken, _, err = us.userCommonService.CacheLoginUserInfo( resp.AccessToken, _, err = us.userCommonService.CacheLoginUserInfo(
ctx, userInfo.ID, userInfo.MailStatus, userInfo.Status) ctx, userInfo.ID, userInfo.MailStatus, userInfo.Status, externalLoginInfo.ExternalID)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
} }
@ -316,3 +319,33 @@ func (us *UserExternalLoginService) ExternalLoginUnbinding(
return nil, us.userExternalLoginRepo.DeleteUserExternalLogin(ctx, req.UserID, req.ExternalID) return nil, us.userExternalLoginRepo.DeleteUserExternalLogin(ctx, req.UserID, req.ExternalID)
} }
// CheckUserStatusInUserCenter check user status in user center
func (us *UserExternalLoginService) CheckUserStatusInUserCenter(ctx context.Context, userID string) (
valid bool, externalID string, err error) {
// If enable user center plugin, user status should be checked by user center
userCenter, ok := plugin.GetUserCenter()
if !ok {
return true, "", nil
}
userInfoList, err := us.GetExternalLoginUserInfoList(ctx, userID)
if err != nil {
return false, "", err
}
var thisUcUserInfo *entity.UserExternalLogin
for _, t := range userInfoList {
if t.Provider == userCenter.Info().SlugName {
thisUcUserInfo = t
break
}
}
// If this user not login by user center, no need to check user status
if thisUcUserInfo == nil {
return true, "", nil
}
userStatus := userCenter.UserStatus(thisUcUserInfo.ExternalID)
if userStatus == plugin.UserStatusDeleted {
return false, thisUcUserInfo.ExternalID, nil
}
return true, thisUcUserInfo.ExternalID, nil
}

View File

@ -80,6 +80,9 @@ func (us *UserService) GetUserInfoByUserID(ctx context.Context, token, userID st
if !exist { if !exist {
return nil, errors.BadRequest(reason.UserNotFound) return nil, errors.BadRequest(reason.UserNotFound)
} }
if userInfo.Status == entity.UserStatusDeleted {
return nil, errors.Unauthorized(reason.UnauthorizedError)
}
roleID, err := us.userRoleService.GetUserRole(ctx, userInfo.ID) roleID, err := us.userRoleService.GetUserRole(ctx, userInfo.ID)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
@ -119,10 +122,17 @@ func (us *UserService) EmailLogin(ctx context.Context, req *schema.UserEmailLogi
if !us.verifyPassword(ctx, req.Pass, userInfo.Pass) { if !us.verifyPassword(ctx, req.Pass, userInfo.Pass) {
return nil, errors.BadRequest(reason.EmailOrPasswordWrong) return nil, errors.BadRequest(reason.EmailOrPasswordWrong)
} }
ok, externalID, err := us.userExternalLoginService.CheckUserStatusInUserCenter(ctx, userInfo.ID)
if err != nil {
return nil, err
}
if !ok {
return nil, errors.BadRequest(reason.EmailOrPasswordWrong)
}
err = us.userRepo.UpdateLastLoginDate(ctx, userInfo.ID) err = us.userRepo.UpdateLastLoginDate(ctx, userInfo.ID)
if err != nil { if err != nil {
log.Error("UpdateLastLoginDate", err.Error()) log.Errorf("update last login data failed, err: %v", err)
} }
roleID, err := us.userRoleService.GetUserRole(ctx, userInfo.ID) roleID, err := us.userRoleService.GetUserRole(ctx, userInfo.ID)
@ -137,6 +147,7 @@ func (us *UserService) EmailLogin(ctx context.Context, req *schema.UserEmailLogi
EmailStatus: userInfo.MailStatus, EmailStatus: userInfo.MailStatus,
UserStatus: userInfo.Status, UserStatus: userInfo.Status,
RoleID: roleID, RoleID: roleID,
ExternalID: externalID,
} }
resp.AccessToken, err = us.authService.SetUserCacheInfo(ctx, userCacheInfo) resp.AccessToken, err = us.authService.SetUserCacheInfo(ctx, userCacheInfo)
if err != nil { if err != nil {
@ -252,7 +263,27 @@ func (us *UserService) UserModifyPassword(ctx context.Context, req *schema.UserM
// UpdateInfo update user info // UpdateInfo update user info
func (us *UserService) UpdateInfo(ctx context.Context, req *schema.UpdateInfoRequest) ( func (us *UserService) UpdateInfo(ctx context.Context, req *schema.UpdateInfoRequest) (
errFields []*validator.FormErrorField, err error) { errFields []*validator.FormErrorField, err error) {
if len(req.Username) > 0 { siteUsers, err := us.siteInfoService.GetSiteUsers(ctx)
if err != nil {
return nil, err
}
if siteUsers.AllowUpdateUsername && len(req.Username) > 0 {
if checker.IsInvalidUsername(req.Username) {
errFields = append(errFields, &validator.FormErrorField{
ErrorField: "username",
ErrorMsg: reason.UsernameInvalid,
})
return errFields, errors.BadRequest(reason.UsernameInvalid)
}
if checker.IsReservedUsername(req.Username) {
errFields = append(errFields, &validator.FormErrorField{
ErrorField: "username",
ErrorMsg: reason.UsernameInvalid,
})
return errFields, errors.BadRequest(reason.UsernameInvalid)
}
userInfo, exist, err := us.userRepo.GetByUsername(ctx, req.Username) userInfo, exist, err := us.userRepo.GetByUsername(ctx, req.Username)
if err != nil { if err != nil {
return nil, err return nil, err
@ -264,31 +295,57 @@ func (us *UserService) UpdateInfo(ctx context.Context, req *schema.UpdateInfoReq
}) })
return errFields, errors.BadRequest(reason.UsernameDuplicate) return errFields, errors.BadRequest(reason.UsernameDuplicate)
} }
if checker.IsReservedUsername(req.Username) {
errFields = append(errFields, &validator.FormErrorField{
ErrorField: "username",
ErrorMsg: reason.UsernameInvalid,
})
return errFields, errors.BadRequest(reason.UsernameInvalid)
}
} }
avatar, err := json.Marshal(req.Avatar)
oldUserInfo, exist, err := us.userRepo.GetByUserID(ctx, req.UserID)
if err != nil { if err != nil {
return nil, errors.BadRequest(reason.UserSetAvatar).WithError(err).WithStack() return nil, err
} }
userInfo := entity.User{} if !exist {
userInfo.ID = req.UserID return nil, errors.BadRequest(reason.UserNotFound)
userInfo.Avatar = string(avatar) }
userInfo.DisplayName = req.DisplayName
userInfo.Bio = req.Bio cond := us.formatUserInfoForUpdateInfo(oldUserInfo, req, siteUsers)
userInfo.BioHTML = req.BioHTML err = us.userRepo.UpdateInfo(ctx, cond)
userInfo.Location = req.Location
userInfo.Website = req.Website
userInfo.Username = req.Username
err = us.userRepo.UpdateInfo(ctx, &userInfo)
return nil, err return nil, err
} }
func (us *UserService) formatUserInfoForUpdateInfo(
oldUserInfo *entity.User, req *schema.UpdateInfoRequest, siteUsersConf *schema.SiteUsersResp) *entity.User {
avatar, _ := json.Marshal(req.Avatar)
userInfo := &entity.User{}
userInfo.DisplayName = oldUserInfo.DisplayName
userInfo.Username = oldUserInfo.Username
userInfo.Avatar = oldUserInfo.Avatar
userInfo.Bio = oldUserInfo.Bio
userInfo.BioHTML = oldUserInfo.BioHTML
userInfo.Website = oldUserInfo.Website
userInfo.Location = oldUserInfo.Location
userInfo.ID = req.UserID
if len(req.DisplayName) > 0 && siteUsersConf.AllowUpdateDisplayName {
userInfo.DisplayName = req.DisplayName
}
if len(req.Username) > 0 && siteUsersConf.AllowUpdateUsername {
userInfo.Username = req.Username
}
if len(avatar) > 0 && siteUsersConf.AllowUpdateAvatar {
userInfo.Avatar = string(avatar)
}
if siteUsersConf.AllowUpdateBio {
userInfo.Bio = req.Bio
userInfo.BioHTML = req.BioHTML
}
if siteUsersConf.AllowUpdateWebsite {
userInfo.Website = req.Website
}
if siteUsersConf.AllowUpdateLocation {
userInfo.Location = req.Location
}
return userInfo
}
func (us *UserService) UserEmailHas(ctx context.Context, email string) (bool, error) { func (us *UserService) UserEmailHas(ctx context.Context, email string) (bool, error) {
_, has, err := us.userRepo.GetByEmail(ctx, email) _, has, err := us.userRepo.GetByEmail(ctx, email)
if err != nil { if err != nil {
@ -466,7 +523,7 @@ func (us *UserService) UserVerifyEmail(ctx context.Context, req *schema.UserVeri
} }
accessToken, userCacheInfo, err := us.userCommonService.CacheLoginUserInfo( accessToken, userCacheInfo, err := us.userCommonService.CacheLoginUserInfo(
ctx, userInfo.ID, userInfo.MailStatus, userInfo.Status) ctx, userInfo.ID, userInfo.MailStatus, userInfo.Status, "")
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -63,12 +63,12 @@ func NewVoteService(
} }
// VoteUp vote up // VoteUp vote up
func (as *VoteService) VoteUp(ctx context.Context, dto *schema.VoteDTO) (voteResp *schema.VoteResp, err error) { func (vs *VoteService) VoteUp(ctx context.Context, dto *schema.VoteDTO) (voteResp *schema.VoteResp, err error) {
voteResp = &schema.VoteResp{} voteResp = &schema.VoteResp{}
var objectUserID string var objectUserID string
objectUserID, err = as.GetObjectUserID(ctx, dto.ObjectID) objectUserID, err = vs.GetObjectUserID(ctx, dto.ObjectID)
if err != nil { if err != nil {
return return
} }
@ -80,19 +80,19 @@ func (as *VoteService) VoteUp(ctx context.Context, dto *schema.VoteDTO) (voteRes
} }
if dto.IsCancel { if dto.IsCancel {
return as.voteRepo.VoteUpCancel(ctx, dto.ObjectID, dto.UserID, objectUserID) return vs.voteRepo.VoteUpCancel(ctx, dto.ObjectID, dto.UserID, objectUserID)
} else { } else {
return as.voteRepo.VoteUp(ctx, dto.ObjectID, dto.UserID, objectUserID) return vs.voteRepo.VoteUp(ctx, dto.ObjectID, dto.UserID, objectUserID)
} }
} }
// VoteDown vote down // VoteDown vote down
func (as *VoteService) VoteDown(ctx context.Context, dto *schema.VoteDTO) (voteResp *schema.VoteResp, err error) { func (vs *VoteService) VoteDown(ctx context.Context, dto *schema.VoteDTO) (voteResp *schema.VoteResp, err error) {
voteResp = &schema.VoteResp{} voteResp = &schema.VoteResp{}
var objectUserID string var objectUserID string
objectUserID, err = as.GetObjectUserID(ctx, dto.ObjectID) objectUserID, err = vs.GetObjectUserID(ctx, dto.ObjectID)
if err != nil { if err != nil {
return return
} }
@ -104,9 +104,9 @@ func (as *VoteService) VoteDown(ctx context.Context, dto *schema.VoteDTO) (voteR
} }
if dto.IsCancel { if dto.IsCancel {
return as.voteRepo.VoteDownCancel(ctx, dto.ObjectID, dto.UserID, objectUserID) return vs.voteRepo.VoteDownCancel(ctx, dto.ObjectID, dto.UserID, objectUserID)
} else { } else {
return as.voteRepo.VoteDown(ctx, dto.ObjectID, dto.UserID, objectUserID) return vs.voteRepo.VoteDown(ctx, dto.ObjectID, dto.UserID, objectUserID)
} }
} }

17
pkg/checker/email.go Normal file
View File

@ -0,0 +1,17 @@
package checker
import "strings"
func EmailInAllowEmailDomain(email string, allowEmailDomains []string) bool {
if len(allowEmailDomains) == 0 {
return true
}
for _, domain := range allowEmailDomains {
if strings.HasSuffix(email, domain) {
return true
}
}
return false
}

24
plugin/agent.go Normal file
View File

@ -0,0 +1,24 @@
package plugin
import (
"github.com/answerdev/answer/internal/base/constant"
"github.com/gin-gonic/gin"
)
type Agent interface {
Base
RegisterUnAuthRouter(r *gin.RouterGroup)
RegisterAuthUserRouter(r *gin.RouterGroup)
RegisterAuthAdminRouter(r *gin.RouterGroup)
}
var (
CallAgent,
registerAgent = MakePlugin[Agent](true)
)
// SiteURL The site url is the domain address of the current site. e.g. http://localhost:8080
// When some Agent plugins want to redirect to the origin site, it can use this function to get the site url.
func SiteURL() string {
return constant.DefaultSiteURL
}

View File

@ -12,6 +12,7 @@ const (
ConfigTypeUpload ConfigType = "upload" ConfigTypeUpload ConfigType = "upload"
ConfigTypeTimezone ConfigType = "timezone" ConfigTypeTimezone ConfigType = "timezone"
ConfigTypeSwitch ConfigType = "switch" ConfigTypeSwitch ConfigType = "switch"
ConfigTypeButton ConfigType = "button"
) )
const ( const (
@ -43,10 +44,13 @@ type ConfigField struct {
} }
type ConfigFieldUIOptions struct { type ConfigFieldUIOptions struct {
Placeholder Translator `json:"placeholder,omitempty"` Placeholder Translator `json:"placeholder,omitempty"`
Rows string `json:"rows,omitempty"` Rows string `json:"rows,omitempty"`
InputType InputType `json:"input_type,omitempty"` InputType InputType `json:"input_type,omitempty"`
Label Translator `json:"label,omitempty"` Label Translator `json:"label,omitempty"`
Action *UIOptionAction `json:"action,omitempty"`
Variant string `json:"variant,omitempty"`
Text Translator `json:"text,omitempty"`
} }
type ConfigFieldOption struct { type ConfigFieldOption struct {
@ -54,6 +58,31 @@ type ConfigFieldOption struct {
Value string `json:"value"` Value string `json:"value"`
} }
type UIOptionAction struct {
Url string `json:"url"`
Method string `json:"method,omitempty"`
Loading *LoadingAction `json:"loading,omitempty"`
OnComplete *OnCompleteAction `json:"on_complete,omitempty"`
}
const (
LoadingActionStateNone LoadingActionType = "none"
LoadingActionStatePending LoadingActionType = "pending"
LoadingActionStateComplete LoadingActionType = "completed"
)
type LoadingActionType string
type LoadingAction struct {
Text Translator `json:"text"`
State LoadingActionType `json:"state"`
}
type OnCompleteAction struct {
ToastReturnMessage bool `json:"toast_return_message"`
RefreshFormConfig bool `json:"refresh_form_config"`
}
type Config interface { type Config interface {
Base Base

View File

@ -52,6 +52,10 @@ func Register(p Base) {
if _, ok := p.(UserCenter); ok { if _, ok := p.(UserCenter); ok {
registerUserCenter(p.(UserCenter)) registerUserCenter(p.(UserCenter))
} }
if _, ok := p.(Agent); ok {
registerAgent(p.(Agent))
}
} }
type Stack[T Base] struct { type Stack[T Base] struct {
@ -132,7 +136,7 @@ type TranslateFn func(ctx *GinContext) string
// Translator contains a function that translates the key to the current language of the context // Translator contains a function that translates the key to the current language of the context
type Translator struct { type Translator struct {
fn TranslateFn Fn TranslateFn
} }
// MakeTranslator generates a translator from the key // MakeTranslator generates a translator from the key
@ -140,13 +144,13 @@ func MakeTranslator(key string) Translator {
t := func(ctx *GinContext) string { t := func(ctx *GinContext) string {
return Translate(ctx, key) return Translate(ctx, key)
} }
return Translator{fn: t} return Translator{Fn: t}
} }
// Translate translates the key to the current language of the context // Translate translates the key to the current language of the context
func (t Translator) Translate(ctx *GinContext) string { func (t Translator) Translate(ctx *GinContext) string {
if &t == nil || t.fn == nil { if &t == nil || t.Fn == nil {
return "" return ""
} }
return t.fn(ctx) return t.Fn(ctx)
} }

View File

@ -12,21 +12,29 @@ type UserCenter interface {
SignUpCallback(ctx *GinContext) (userInfo *UserCenterBasicUserInfo, err error) SignUpCallback(ctx *GinContext) (userInfo *UserCenterBasicUserInfo, err error)
// UserInfo returns the user information // UserInfo returns the user information
UserInfo(externalID string) (userInfo *UserCenterBasicUserInfo, err error) UserInfo(externalID string) (userInfo *UserCenterBasicUserInfo, err error)
// UserStatus returns the latest user status
UserStatus(externalID string) (userStatus UserStatus)
// UserList returns the user list information // UserList returns the user list information
UserList(externalIDs []string) (userInfo []*UserCenterBasicUserInfo, err error) UserList(externalIDs []string) (userInfo []*UserCenterBasicUserInfo, err error)
// UserSettings returns the user settings // UserSettings returns the user settings
UserSettings(externalID string) (userSettings *SettingInfo, err error) UserSettings(externalID string) (userSettings *SettingInfo, err error)
// PersonalBranding returns the personal branding information // PersonalBranding returns the personal branding information
PersonalBranding(externalID string) (branding []*PersonalBranding) PersonalBranding(externalID string) (branding []*PersonalBranding)
// AfterLogin is called after the user logs in
AfterLogin(externalID, accessToken string)
} }
type UserCenterDesc struct { type UserCenterDesc struct {
Name string `json:"name"` Name string `json:"name"`
Icon string `json:"icon"` DisplayName Translator `json:"display_name"`
Url string `json:"url"` Icon string `json:"icon"`
LoginRedirectURL string `json:"login_redirect_url"` Url string `json:"url"`
SignUpRedirectURL string `json:"sign_up_redirect_url"` LoginRedirectURL string `json:"login_redirect_url"`
RankAgentEnabled bool `json:"rank_agent_enabled"` SignUpRedirectURL string `json:"sign_up_redirect_url"`
RankAgentEnabled bool `json:"rank_agent_enabled"`
UserStatusAgentEnabled bool `json:"user_status_agent_enabled"`
MustAuthEmailEnabled bool `json:"must_auth_email_enabled"`
EnabledOriginalUserSystem bool `json:"enabled_original_user_system"`
} }
type UserStatus int type UserStatus int
@ -45,6 +53,7 @@ type UserCenterBasicUserInfo struct {
Rank int `json:"rank"` Rank int `json:"rank"`
Avatar string `json:"avatar"` Avatar string `json:"avatar"`
Mobile string `json:"mobile"` Mobile string `json:"mobile"`
Bio string `json:"bio"`
Status UserStatus `json:"status"` Status UserStatus `json:"status"`
} }

View File

@ -1,26 +1,21 @@
#!/bin/bash #!/bin/bash
set -e
echo "begin build plugin"
plugin_file=./script/plugin_list plugin_file=./script/plugin_list
if [ ! -f "$plugin_file" ]; then if [ ! -f "$plugin_file" ]; then
echo "plugin_list is not exist" echo "plugin_list is not exist"
exit 0 exit 0
fi fi
num=0
for line in `cat $plugin_file` echo "plugin_list exist"
do
account=$line
accounts[$num]=$account
((num++))
done
if [ $num -eq 0 ]; then
echo "plugin_list is null"
exit 0
fi
cmd="./answer build " cmd="./answer build "
for repo in ${accounts[@]} for repo in `cat $plugin_file`
do do
echo ${repo} echo ${repo}
cmd=$cmd" --with "${repo} cmd=$cmd" --with "${repo}
done done
echo "cmd is "$cmd
$cmd $cmd
if [ ! -f "./new_answer" ]; then if [ ! -f "./new_answer" ]; then
echo "new_answer is not exist build failed" echo "new_answer is not exist build failed"