diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go index cab47299..e88de43f 100644 --- a/cmd/wire_gen.go +++ b/cmd/wire_gen.go @@ -193,7 +193,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, reasonService := reason2.NewReasonService(reasonRepo) reasonController := controller.NewReasonController(reasonService) 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.NewSiteinfoController(siteInfoCommonService) notificationRepo := notification.NewNotificationRepo(dataData) @@ -220,7 +220,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, templateController := controller.NewTemplateController(templateRenderController, siteInfoCommonService) templateRouter := router.NewTemplateRouter(templateController, templateRenderController, siteInfoController) connectorController := controller.NewConnectorController(siteInfoCommonService, emailService, userExternalLoginService) - userCenterLoginService := user_external_login2.NewUserCenterLoginService(userRepo, userCommon, userExternalLoginRepo, userActiveActivityRepo) + userCenterLoginService := user_external_login2.NewUserCenterLoginService(userRepo, userCommon, userExternalLoginRepo, userActiveActivityRepo, siteInfoCommonService) userCenterController := controller.NewUserCenterController(userCenterLoginService, siteInfoCommonService) pluginAPIRouter := router.NewPluginAPIRouter(connectorController, userCenterController) ginEngine := server.NewHTTPServer(debug, staticRouter, answerAPIRouter, swaggerRouter, uiRouter, authUserMiddleware, avatarMiddleware, templateRouter, pluginAPIRouter) diff --git a/docs/docs.go b/docs/docs.go index 5ab4a48e..3f07ef25 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -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": { "get": { "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": { "get": { "security": [ @@ -2861,7 +3003,7 @@ const docTemplate = `{ "ApiKeyAuth": [] } ], - "description": "list personal answers", + "description": "UserAnswerList", "consumes": [ "application/json" ], @@ -2869,9 +3011,9 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Personal" + "api-answer" ], - "summary": "list personal answers", + "summary": "UserAnswerList", "parameters": [ { "type": "string", @@ -2903,8 +3045,8 @@ const docTemplate = `{ { "type": "string", "default": "20", - "description": "page_size", - "name": "page_size", + "description": "pagesize", + "name": "pagesize", "in": "query", "required": true } @@ -2926,7 +3068,7 @@ const docTemplate = `{ "ApiKeyAuth": [] } ], - "description": "list personal collections", + "description": "UserCollectionList", "consumes": [ "application/json" ], @@ -2936,7 +3078,7 @@ const docTemplate = `{ "tags": [ "Collection" ], - "summary": "list personal collections", + "summary": "UserCollectionList", "parameters": [ { "type": "string", @@ -2949,8 +3091,8 @@ const docTemplate = `{ { "type": "string", "default": "20", - "description": "page_size", - "name": "page_size", + "description": "pagesize", + "name": "pagesize", "in": "query", "required": true } @@ -5668,7 +5810,7 @@ const docTemplate = `{ "ApiKeyAuth": [] } ], - "description": "list personal questions", + "description": "UserList", "consumes": [ "application/json" ], @@ -5676,9 +5818,9 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Personal" + "Question" ], - "summary": "list personal questions", + "summary": "UserList", "parameters": [ { "type": "string", @@ -5710,8 +5852,8 @@ const docTemplate = `{ { "type": "string", "default": "20", - "description": "page_size", - "name": "page_size", + "description": "pagesize", + "name": "pagesize", "in": "query", "required": true } @@ -5748,6 +5890,20 @@ const docTemplate = `{ } }, "definitions": { + "constant.Privilege": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "label": { + "type": "string" + }, + "value": { + "type": "integer" + } + } + }, "handler.RespBody": { "type": "object", "properties": { @@ -6229,9 +6385,20 @@ const docTemplate = `{ } } }, + "schema.ConfigFieldUIOptionAction": { + "type": "object", + "properties": { + "url": { + "type": "string" + } + } + }, "schema.ConfigFieldUIOptions": { "type": "object", "properties": { + "action": { + "$ref": "#/definitions/schema.ConfigFieldUIOptionAction" + }, "input_type": { "type": "string" }, @@ -6243,6 +6410,12 @@ const docTemplate = `{ }, "rows": { "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": { "type": "object", "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": { "type": "object", "required": [ @@ -7418,6 +7622,10 @@ const docTemplate = `{ "schema.QuestionPageReq": { "type": "object", "properties": { + "inDays": { + "type": "integer", + "minimum": 1 + }, "orderCond": { "type": "string", "enum": [ @@ -7800,6 +8008,10 @@ const docTemplate = `{ "custom_header": { "type": "string", "maxLength": 65536 + }, + "custom_sidebar": { + "type": "string", + "maxLength": 65536 } } }, @@ -7821,6 +8033,10 @@ const docTemplate = `{ "custom_header": { "type": "string", "maxLength": 65536 + }, + "custom_sidebar": { + "type": "string", + "maxLength": 65536 } } }, @@ -7908,6 +8124,9 @@ const docTemplate = `{ "site_seo": { "$ref": "#/definitions/schema.SiteSeoReq" }, + "site_users": { + "$ref": "#/definitions/schema.SiteUsersResp" + }, "theme": { "$ref": "#/definitions/schema.SiteThemeResp" }, @@ -7919,18 +8138,10 @@ const docTemplate = `{ "schema.SiteInterfaceReq": { "type": "object", "required": [ - "default_avatar", "language", "time_zone" ], "properties": { - "default_avatar": { - "type": "string", - "enum": [ - "system", - "gravatar" - ] - }, "language": { "type": "string", "maxLength": 128 @@ -7944,18 +8155,10 @@ const docTemplate = `{ "schema.SiteInterfaceResp": { "type": "object", "required": [ - "default_avatar", "language", "time_zone" ], "properties": { - "default_avatar": { - "type": "string", - "enum": [ - "system", - "gravatar" - ] - }, "language": { "type": "string", "maxLength": 128 @@ -8003,6 +8206,15 @@ const docTemplate = `{ "schema.SiteLoginReq": { "type": "object", "properties": { + "allow_email_domains": { + "type": "array", + "items": { + "type": "string" + } + }, + "allow_email_registrations": { + "type": "boolean" + }, "allow_new_registrations": { "type": "boolean" }, @@ -8014,6 +8226,15 @@ const docTemplate = `{ "schema.SiteLoginResp": { "type": "object", "properties": { + "allow_email_domains": { + "type": "array", + "items": { + "type": "string" + } + }, + "allow_email_registrations": { + "type": "boolean" + }, "allow_new_registrations": { "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": { "type": "object", "properties": { @@ -8324,6 +8611,19 @@ const docTemplate = `{ } } }, + "schema.UpdatePrivilegesConfigReq": { + "type": "object", + "required": [ + "level" + ], + "properties": { + "level": { + "type": "integer", + "maximum": 3, + "minimum": 1 + } + } + }, "schema.UpdateSMTPConfigReq": { "type": "object", "properties": { diff --git a/go.sum b/go.sum index fcf11684..cf7f1580 100644 --- a/go.sum +++ b/go.sum @@ -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.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= 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.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= 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-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-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/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= @@ -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.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.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/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 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/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 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.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 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.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 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.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 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.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.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/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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-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-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/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 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-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.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 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/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-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-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-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 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/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= 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.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= 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.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= 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.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/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 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.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 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/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= 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/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= 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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 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.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 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.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 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-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-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-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 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.3/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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 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-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-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-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 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-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-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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 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.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.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 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.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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 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.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/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.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml index 61c4df19..ee8d7088 100644 --- a/i18n/en_US.yaml +++ b/i18n/en_US.yaml @@ -25,6 +25,8 @@ backend: other: Close reopen: other: Reopen + forbidden_error: + other: Forbidden. pin: other: Pin hide: @@ -48,6 +50,58 @@ backend: other: Have the full power to access the site. moderator: 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: other: Email password: @@ -85,6 +139,8 @@ backend: other: Email should be verified. verify_url_expired: 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: not_found: other: Language file not found. @@ -176,6 +232,10 @@ backend: other: You cannot modify your role. not_allowed_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: read_config_failed: other: Read config failed @@ -281,6 +341,16 @@ backend: other: Your answer has been deleted your_comment_was_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) ui: diff --git a/i18n/zh_CN.yaml b/i18n/zh_CN.yaml index 99361a29..778026af 100644 --- a/i18n/zh_CN.yaml +++ b/i18n/zh_CN.yaml @@ -45,6 +45,58 @@ backend: other: 拥有管理网站的全部权限。 moderator: 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: other: 邮箱 password: @@ -82,6 +134,8 @@ backend: other: 邮箱需要验证。 verify_url_expired: other: 邮箱验证的网址已过期,请重新发送邮件。 + illegal_email_domain_error: + other: 该域名的邮箱无法使用。请尝试更换其他邮箱。 lang: not_found: other: 语言未找到 @@ -171,6 +225,10 @@ backend: other: 您不能修改自己的角色。 not_allowed_registration: other: 目前该站点未开放注册 + access_denied: + other: 访问被拒绝 + page_access_denied: + other: 你没有权限进入这个页面。 config: read_config_failed: other: 读取配置失败 @@ -271,6 +329,16 @@ backend: other: 你的答案已被删除 your_comment_was_deleted: 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) ui: how_to_format: @@ -1063,6 +1131,9 @@ ui: installed_plugins: 插件列表 website_welcome: 欢迎来到 {{site_name}} plugins: + login: 登录 + qrcode_login_tip: 请使用 {{ agentName }} 扫描二维码登录 + login_failed_email_tip: 登录失败, 请允许该应用程序访问您的电子邮件信息,然后再试一次。 oauth: connect: 连接到 {{ auth_name }} remove: 解绑 {{ auth_name }} @@ -1396,6 +1467,30 @@ ui: deactivate: 停用 activate: 启用 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: optional: (选填) empty: 不能为空 diff --git a/internal/base/constant/constant.go b/internal/base/constant/constant.go index a71d1a9c..b921c9f6 100644 --- a/internal/base/constant/constant.go +++ b/internal/base/constant/constant.go @@ -66,6 +66,8 @@ const ( SiteTypeLogin = "login" SiteTypeCustomCssHTML = "css-html" SiteTypeTheme = "theme" + SiteTypePrivileges = "privileges" + SiteTypeUsers = "users" ) func ExistInPathIgnore(name string) bool { diff --git a/internal/base/constant/notification.go b/internal/base/constant/notification.go index 83856f64..8f8568a8 100644 --- a/internal/base/constant/notification.go +++ b/internal/base/constant/notification.go @@ -1,28 +1,38 @@ package constant const ( - // UpdateQuestion update question - UpdateQuestion = "notification.action.update_question" - // AnswerTheQuestion answer the question - AnswerTheQuestion = "notification.action.answer_the_question" - // UpdateAnswer update answer - UpdateAnswer = "notification.action.update_answer" - // AcceptAnswer accept answer - AcceptAnswer = "notification.action.accept_answer" - // CommentQuestion comment question - CommentQuestion = "notification.action.comment_question" - // CommentAnswer comment answer - CommentAnswer = "notification.action.comment_answer" - // ReplyToYou reply to you - ReplyToYou = "notification.action.reply_to_you" - // MentionYou mention you - MentionYou = "notification.action.mention_you" - // YourQuestionIsClosed your question is closed - YourQuestionIsClosed = "notification.action.your_question_is_closed" - // YourQuestionWasDeleted your question was deleted - YourQuestionWasDeleted = "notification.action.your_question_was_deleted" - // YourAnswerWasDeleted your answer was deleted - YourAnswerWasDeleted = "notification.action.your_answer_was_deleted" - // YourCommentWasDeleted your comment was deleted - YourCommentWasDeleted = "notification.action.your_comment_was_deleted" + // NotificationUpdateQuestion update question + NotificationUpdateQuestion = "notification.action.update_question" + // NotificationAnswerTheQuestion answer the question + NotificationAnswerTheQuestion = "notification.action.answer_the_question" + // NotificationUpVotedTheQuestion up voted the question + NotificationUpVotedTheQuestion = "notification.action.up_voted_question" + // NotificationDownVotedTheQuestion down voted the question + NotificationDownVotedTheQuestion = "notification.action.down_voted_question" + // NotificationUpdateAnswer update answer + NotificationUpdateAnswer = "notification.action.update_answer" + // NotificationAcceptAnswer accept answer + NotificationAcceptAnswer = "notification.action.accept_answer" + // NotificationUpVotedTheAnswer up voted the answer + NotificationUpVotedTheAnswer = "notification.action.up_voted_answer" + // NotificationDownVotedTheAnswer down voted the answer + NotificationDownVotedTheAnswer = "notification.action.down_voted_answer" + // NotificationCommentQuestion comment question + NotificationCommentQuestion = "notification.action.comment_question" + // NotificationCommentAnswer comment answer + NotificationCommentAnswer = "notification.action.comment_answer" + // NotificationUpVotedTheComment up voted the comment + NotificationUpVotedTheComment = "notification.action.up_voted_comment" + // NotificationReplyToYou reply to you + 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" ) diff --git a/internal/base/constant/rank.go b/internal/base/constant/rank.go new file mode 100644 index 00000000..f1cbf255 --- /dev/null +++ b/internal/base/constant/rank.go @@ -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}, + } +) diff --git a/internal/base/constant/site_info.go b/internal/base/constant/site_info.go index dced604f..e6b8cd99 100644 --- a/internal/base/constant/site_info.go +++ b/internal/base/constant/site_info.go @@ -1,5 +1,6 @@ package constant var ( - DefaultAvatar = "system" + DefaultAvatar = "system" + DefaultSiteURL = "" ) diff --git a/internal/base/middleware/user_center_plugin_auth.go b/internal/base/middleware/user_center_plugin_auth.go index c6a190a4..bf19c319 100644 --- a/internal/base/middleware/user_center_plugin_auth.go +++ b/internal/base/middleware/user_center_plugin_auth.go @@ -8,9 +8,13 @@ import ( "github.com/segmentfault/pacman/errors" ) -// BanAPIWhenUserCenterEnabled ban api when user center enabled -func BanAPIWhenUserCenterEnabled(ctx *gin.Context) { - if plugin.UserCenterEnabled() { +// BanAPIForUserCenter ban api for user center +func BanAPIForUserCenter(ctx *gin.Context) { + uc, ok := plugin.GetUserCenter() + if !ok { + return + } + if !uc.Description().EnabledOriginalUserSystem { handler.HandleResponse(ctx, errors.Forbidden(reason.ForbiddenError), nil) ctx.Abort() return diff --git a/internal/base/reason/privilege.go b/internal/base/reason/privilege.go new file mode 100644 index 00000000..76d4e7bb --- /dev/null +++ b/internal/base/reason/privilege.go @@ -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" +) diff --git a/internal/base/reason/reason.go b/internal/base/reason/reason.go index 21fff40f..3c257944 100644 --- a/internal/base/reason/reason.go +++ b/internal/base/reason/reason.go @@ -42,6 +42,7 @@ const ( EmailDuplicate = "error.email.duplicate" EmailVerifyURLExpired = "error.email.verify_url_expired" EmailNeedToBeVerified = "error.email.need_to_be_verified" + EmailIllegalDomainError = "error.email.illegal_email_domain_error" UserSuspended = "error.user.suspended" ObjectNotFound = "error.object.not_found" TagNotFound = "error.tag.not_found" @@ -50,7 +51,7 @@ const ( TagIsUsedCannotDelete = "error.tag.is_used_cannot_delete" TagAlreadyExist = "error.tag.already_exist" 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" LangNotFound = "error.lang.not_found" ReportHandleFailed = "error.report.handle_failed" @@ -73,4 +74,6 @@ const ( AdminCannotUpdateTheirPassword = "error.admin.cannot_update_their_password" AdminCannotModifySelfStatus = "error.admin.cannot_modify_self_status" UserExternalLoginUnbindingForbidden = "error.user.external_login_unbinding_forbidden" + UserAccessDenied = "error.user.access_denied" + UserPageAccessDenied = "error.user.page_access_denied" ) diff --git a/internal/base/server/http.go b/internal/base/server/http.go index 53331201..1c79dbee 100644 --- a/internal/base/server/http.go +++ b/internal/base/server/http.go @@ -7,6 +7,7 @@ import ( brotli "github.com/anargu/gin-brotli" "github.com/answerdev/answer/internal/base/middleware" "github.com/answerdev/answer/internal/router" + "github.com/answerdev/answer/plugin" "github.com/answerdev/answer/ui" "github.com/gin-gonic/gin" ) @@ -66,6 +67,14 @@ func NewHTTPServer(debug bool, // plugin routes 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 } diff --git a/internal/controller/connector_controller.go b/internal/controller/connector_controller.go index 160c0f48..bb6c37ad 100644 --- a/internal/controller/connector_controller.go +++ b/internal/controller/connector_controller.go @@ -130,7 +130,7 @@ func (cc *ConnectorController) ConnectorRedirect(connector plugin.Connector) (fn return } 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)) } else { ctx.Redirect(http.StatusFound, fmt.Sprintf("%s/users/confirm-email?binding_key=%s", diff --git a/internal/controller/plugin_user_center_controller.go b/internal/controller/plugin_user_center_controller.go index 8a5dbd91..024a0692 100644 --- a/internal/controller/plugin_user_center_controller.go +++ b/internal/controller/plugin_user_center_controller.go @@ -60,9 +60,11 @@ func (uc *UserCenterController) UserCenterAgent(ctx *gin.Context) { _ = plugin.CallUserCenter(func(uc plugin.UserCenter) error { info := uc.Description() resp.AgentInfo.Name = info.Name + resp.AgentInfo.DisplayName = info.DisplayName.Translate(ctx) resp.AgentInfo.Icon = info.Icon resp.AgentInfo.Url = info.Url resp.AgentInfo.ControlCenterItems = make([]*schema.ControlCenter, 0) + resp.AgentInfo.EnabledOriginalUserSystem = info.EnabledOriginalUserSystem items := uc.ControlCenterItems() for _, item := range items { 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) { var redirectURL string - _ = plugin.CallUserCenter(func(uc plugin.UserCenter) error { - info := uc.Description() + _ = plugin.CallUserCenter(func(userCenter plugin.UserCenter) error { + info := userCenter.Description() redirectURL = info.LoginRedirectURL return nil }) @@ -100,9 +102,9 @@ func (uc *UserCenterController) UserCenterLoginRedirect(ctx *gin.Context) { func (uc *UserCenterController) UserCenterSignUpRedirect(ctx *gin.Context) { var redirectURL string - _ = plugin.CallUserCenter(func(uc plugin.UserCenter) error { - info := uc.Description() - redirectURL = info.SignUpRedirectURL + _ = plugin.CallUserCenter(func(userCenter plugin.UserCenter) error { + info := userCenter.Description() + redirectURL = info.LoginRedirectURL return nil }) ctx.Redirect(http.StatusFound, redirectURL) @@ -124,17 +126,24 @@ func (uc *UserCenterController) UserCenterLoginCallback(ctx *gin.Context) { userInfo, err := userCenter.LoginCallback(ctx) if err != nil { log.Error(err) - ctx.Redirect(http.StatusFound, "/50x") + if !ctx.IsAborted() { + ctx.Redirect(http.StatusFound, "/50x") + } return } - resp, err := uc.userCenterLoginService.ExternalLogin(ctx, userCenter.Info().SlugName, userInfo) + resp, err := uc.userCenterLoginService.ExternalLogin(ctx, userCenter, userInfo) if err != nil { log.Errorf("external login failed: %v", err) ctx.Redirect(http.StatusFound, "/50x") 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)) } @@ -158,13 +167,18 @@ func (uc *UserCenterController) UserCenterSignUpCallback(ctx *gin.Context) { return } - resp, err := uc.userCenterLoginService.ExternalLogin(ctx, userCenter.Info().SlugName, userInfo) + resp, err := uc.userCenterLoginService.ExternalLogin(ctx, userCenter, userInfo) if err != nil { log.Errorf("external login failed: %v", err) ctx.Redirect(http.StatusFound, "/50x") 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)) } @@ -174,3 +188,9 @@ func (uc *UserCenterController) UserCenterUserSettings(ctx *gin.Context) { resp, err := uc.userCenterLoginService.UserCenterUserSettings(ctx, userID) 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) +} diff --git a/internal/controller/siteinfo_controller.go b/internal/controller/siteinfo_controller.go index b0ec1315..f815bdb9 100644 --- a/internal/controller/siteinfo_controller.go +++ b/internal/controller/siteinfo_controller.go @@ -64,6 +64,10 @@ func (sc *SiteinfoController) GetSiteInfo(ctx *gin.Context) { if err != nil { log.Error(err) } + resp.SiteUsers, err = sc.siteInfoService.GetSiteUsers(ctx) + if err != nil { + log.Error(err) + } handler.HandleResponse(ctx, nil, resp) } diff --git a/internal/controller/user_controller.go b/internal/controller/user_controller.go index 56e0ef4e..8c90ce6b 100644 --- a/internal/controller/user_controller.go +++ b/internal/controller/user_controller.go @@ -13,6 +13,7 @@ import ( "github.com/answerdev/answer/internal/service/export" "github.com/answerdev/answer/internal/service/siteinfo_common" "github.com/answerdev/answer/internal/service/uploader" + "github.com/answerdev/answer/pkg/checker" "github.com/gin-gonic/gin" "github.com/segmentfault/pacman/errors" "github.com/segmentfault/pacman/log" @@ -223,7 +224,7 @@ func (uc *UserController) UserRegisterByEmail(ctx *gin.Context) { handler.HandleResponse(ctx, err, nil) return } - if !siteInfo.AllowNewRegistrations { + if !siteInfo.AllowNewRegistrations || !siteInfo.AllowEmailRegistrations { handler.HandleResponse(ctx, errors.BadRequest(reason.NotAllowedRegistration), nil) return } @@ -232,6 +233,10 @@ func (uc *UserController) UserRegisterByEmail(ctx *gin.Context) { if handler.BindAndCheck(ctx, req) { return } + if !checker.EmailInAllowEmailDomain(req.Email, siteInfo.AllowEmailDomains) { + handler.HandleResponse(ctx, errors.BadRequest(reason.EmailIllegalDomainError), nil) + return + } req.IP = ctx.ClientIP() captchaPass := uc.actionService.UserRegisterVerifyCaptcha(ctx, req.CaptchaID, req.CaptchaCode) if !captchaPass { @@ -489,6 +494,16 @@ func (uc *UserController) UserChangeEmailSendCode(ctx *gin.Context) { handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil) 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) if !captchaPass { diff --git a/internal/controller_admin/siteinfo_controller.go b/internal/controller_admin/siteinfo_controller.go index d799fe5b..c29c5b72 100644 --- a/internal/controller_admin/siteinfo_controller.go +++ b/internal/controller_admin/siteinfo_controller.go @@ -139,6 +139,19 @@ func (sc *SiteInfoController) GetSiteTheme(ctx *gin.Context) { 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 // @Summary 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) } +// 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 // @Summary 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) 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) +} diff --git a/internal/controller_admin/user_backyard_controller.go b/internal/controller_admin/user_backyard_controller.go index 76fb8378..cb8fe7d8 100644 --- a/internal/controller_admin/user_backyard_controller.go +++ b/internal/controller_admin/user_backyard_controller.go @@ -32,7 +32,7 @@ func NewUserAdminController(userService *user_admin.UserAdminService) *UserAdmin // @Success 200 {object} handler.RespBody // @Router /answer/admin/api/user/status [put] 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) return } @@ -80,10 +80,6 @@ func (uc *UserAdminController) UpdateUserRole(ctx *gin.Context) { // @Success 200 {object} handler.RespBody // @Router /answer/admin/api/user [post] func (uc *UserAdminController) AddUser(ctx *gin.Context) { - if plugin.UserCenterEnabled() { - handler.HandleResponse(ctx, errors.Forbidden(reason.ForbiddenError), nil) - return - } req := &schema.AddUserReq{} if handler.BindAndCheck(ctx, req) { return @@ -106,10 +102,6 @@ func (uc *UserAdminController) AddUser(ctx *gin.Context) { // @Success 200 {object} handler.RespBody // @Router /answer/admin/api/user/password [put] func (uc *UserAdminController) UpdateUserPassword(ctx *gin.Context) { - if plugin.UserCenterEnabled() { - handler.HandleResponse(ctx, errors.Forbidden(reason.ForbiddenError), nil) - return - } req := &schema.UpdateUserPasswordReq{} if handler.BindAndCheck(ctx, req) { return diff --git a/internal/entity/auth_user_entity.go b/internal/entity/auth_user_entity.go index a417cd2a..79d24fb1 100644 --- a/internal/entity/auth_user_entity.go +++ b/internal/entity/auth_user_entity.go @@ -6,4 +6,5 @@ type UserCacheInfo struct { UserStatus int `json:"user_status"` EmailStatus int `json:"email_status"` RoleID int `json:"role_id"` + ExternalID string `json:"external_id"` } diff --git a/internal/migrations/init.go b/internal/migrations/init.go index 324b7498..d47014f9 100644 --- a/internal/migrations/init.go +++ b/internal/migrations/init.go @@ -143,8 +143,9 @@ func initSiteInfo(engine *xorm.Engine, language, siteName, siteURL, contactEmail } loginConfig := map[string]bool{ - "allow_new_registrations": true, - "login_required": false, + "allow_new_registrations": true, + "allow_email_registrations": true, + "login_required": false, } loginConfigDataBytes, _ := json.Marshal(loginConfig) _, err = engine.InsertOne(&entity.SiteInfo{ @@ -178,6 +179,25 @@ func initSiteInfo(engine *xorm.Engine, language, siteName, siteURL, contactEmail if err != nil { 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 } @@ -346,10 +366,14 @@ func initConfigTable(engine *xorm.Engine) error { {ID: 116, Key: "rank.question.reopen", Value: `-1`}, {ID: 117, Key: "rank.tag.use_reserved_tag", Value: `-1`}, {ID: 118, Key: "plugin.status", Value: `{}`}, - {ID: 119, Key: "question.pin", Value: `-1`}, - {ID: 120, Key: "question.unpin", Value: `-1`}, - {ID: 121, Key: "question.show", Value: `-1`}, - {ID: 122, Key: "question.hide", Value: `-1`}, + {ID: 119, Key: "question.pin", Value: `0`}, + {ID: 120, Key: "question.unpin", Value: `0`}, + {ID: 121, Key: "question.show", Value: `0`}, + {ID: 122, Key: "question.hide", Value: `0`}, + {ID: 123, Key: "rank.question.pin", Value: `-1`}, + {ID: 124, Key: "rank.question.unpin", Value: `-1`}, + {ID: 125, Key: "rank.question.show", Value: `-1`}, + {ID: 126, Key: "rank.question.hide", Value: `-1`}, } _, err := engine.Insert(defaultConfigTable) return err diff --git a/internal/migrations/migrations.go b/internal/migrations/migrations.go index a82064ad..20e07eb2 100644 --- a/internal/migrations/migrations.go +++ b/internal/migrations/migrations.go @@ -61,6 +61,7 @@ var migrations = []Migration{ NewMigration("add plugin", addPlugin, false), NewMigration("update user pin hide features", updateRolePinAndHideFeatures, true), NewMigration("update question post time", updateQuestionPostTime, true), + NewMigration("add login limitations", addLoginLimitations, true), } // GetCurrentDBVersion returns the current db version diff --git a/internal/migrations/v10.go b/internal/migrations/v10.go new file mode 100644 index 00000000..7ad0c990 --- /dev/null +++ b/internal/migrations/v10.go @@ -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 +} diff --git a/internal/migrations/v7.go b/internal/migrations/v7.go index 6a2d9b58..1770a80d 100644 --- a/internal/migrations/v7.go +++ b/internal/migrations/v7.go @@ -18,10 +18,6 @@ func addPlugin(x *xorm.Engine) error { return fmt.Errorf("get config failed: %w", err) } if exist { - if _, err = x.Update(c, &entity.Config{ID: c.ID, Key: c.Key}); err != nil { - log.Errorf("update %+v config failed: %s", c, err) - return fmt.Errorf("update config failed: %w", err) - } continue } if _, err = x.Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil { diff --git a/internal/repo/activity/answer_repo.go b/internal/repo/activity/answer_repo.go index cb0d25f2..02e9c511 100644 --- a/internal/repo/activity/answer_repo.go +++ b/internal/repo/activity/answer_repo.go @@ -202,7 +202,9 @@ func (ar *AnswerActivityRepo) AcceptAnswer(ctx context.Context, msg.TriggerUserID = questionUserID msg.ObjectType = constant.AnswerObjectType } - notice_queue.AddNotification(msg) + if msg.TriggerUserID != msg.ReceiverUserID { + notice_queue.AddNotification(msg) + } } for _, act := range addActivityList { @@ -214,7 +216,7 @@ func (ar *AnswerActivityRepo) AcceptAnswer(ctx context.Context, if act.UserID != questionUserID { msg.TriggerUserID = questionUserID msg.ObjectType = constant.AnswerObjectType - msg.NotificationAction = constant.AcceptAnswer + msg.NotificationAction = constant.NotificationAcceptAnswer notice_queue.AddNotification(msg) } } diff --git a/internal/repo/activity/vote_repo.go b/internal/repo/activity/vote_repo.go index bb2f1ef0..1303f3c7 100644 --- a/internal/repo/activity/vote_repo.go +++ b/internal/repo/activity/vote_repo.go @@ -5,6 +5,7 @@ import ( "strings" "time" + "github.com/answerdev/answer/internal/base/constant" "github.com/answerdev/answer/pkg/converter" "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) { 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) { result = nil for _, action := range actions { @@ -127,7 +130,7 @@ func (vr *VoteRepo) vote(ctx context.Context, objectID string, userID, objectUse if isReachStandard { insertActivity.Rank = 0 } - notificationUserIDs = append(notificationUserIDs, activityUserID) + achievementNotificationUserIDs = append(achievementNotificationUserIDs, activityUserID) } if has { @@ -142,13 +145,17 @@ func (vr *VoteRepo) vote(ctx context.Context, objectID string, userID, objectUse if err != nil { return nil, err } + sendInboxNotification = true } // update votes - if action == "vote_down" || action == "vote_up" { + if action == constant.ActVoteDown || action == constant.ActVoteUp { votes := 1 - if action == "vote_down" { + if action == constant.ActVoteDown { + upVote = false votes = -1 + } else { + upVote = true } err = vr.updateVotes(ctx, session, objectID, votes) 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.VoteStatus = vr.voteCommon.GetVoteStatus(ctx, objectID, userID) - for _, activityUserID := range notificationUserIDs { + for _, activityUserID := range achievementNotificationUserIDs { vr.sendNotification(ctx, activityUserID, objectUserID, objectID) } + if sendInboxNotification { + vr.sendVoteInboxNotification(userID, objectUserID, objectID, upVote) + } return } @@ -441,3 +451,40 @@ func (vr *VoteRepo) sendNotification(ctx context.Context, activityUserID, object } 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) + } +} diff --git a/internal/repo/question/question_repo.go b/internal/repo/question/question_repo.go index da9d048f..2ee02fc4 100644 --- a/internal/repo/question/question_repo.go +++ b/internal/repo/question/question_repo.go @@ -255,7 +255,7 @@ func (qr *questionRepo) GetQuestionIDsPage(ctx context.Context, page, pageSize i } // 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 = make([]*entity.Question, 0) @@ -271,6 +271,9 @@ func (qr *questionRepo) GetQuestionPage(ctx context.Context, page, pageSize int, } else { session.And("question.show = ?", entity.QuestionShow) } + if inDays > 0 { + session.And("question.created_at > ?", time.Now().AddDate(0, 0, -inDays)) + } switch orderCond { case "newest": diff --git a/internal/repo/user/user_backyard_repo.go b/internal/repo/user/user_backyard_repo.go index 6c7efe3f..cebb2112 100644 --- a/internal/repo/user/user_backyard_repo.go +++ b/internal/repo/user/user_backyard_repo.go @@ -86,6 +86,9 @@ func (ur *userAdminRepo) GetUserInfo(ctx context.Context, userID string) (user * if err != nil { return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() } + if !exist { + return + } err = tryToDecorateUserInfoFromUserCenter(ctx, ur.data, user) if err != nil { 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() return } + if !exist { + return + } err = tryToDecorateUserInfoFromUserCenter(ctx, ur.data, user) if err != nil { return nil, false, err diff --git a/internal/repo/user/user_repo.go b/internal/repo/user/user_repo.go index d0226a41..b8129f20 100644 --- a/internal/repo/user/user_repo.go +++ b/internal/repo/user/user_repo.go @@ -10,6 +10,7 @@ import ( "github.com/answerdev/answer/internal/schema" "github.com/answerdev/answer/internal/service/config" usercommon "github.com/answerdev/answer/internal/service/user_common" + "github.com/answerdev/answer/pkg/converter" "github.com/answerdev/answer/plugin" "github.com/segmentfault/pacman/errors" "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) { + if original == nil { + return nil + } uc, ok := plugin.GetUserCenter() if !ok { return nil @@ -276,14 +280,27 @@ func decorateByUserCenterUser(original *entity.User, ucUser *plugin.UserCenterBa if original.Username != ucUser.Username { log.Warnf("user %s username is inconsistent with user center", original.ID) } - original.DisplayName = ucUser.DisplayName - original.EMail = ucUser.Email - original.Avatar = schema.CustomAvatar(ucUser.Avatar).ToJsonString() - original.Mobile = ucUser.Mobile + if len(ucUser.DisplayName) > 0 { + original.DisplayName = ucUser.DisplayName + } + 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.RankAgentEnabled() { original.Rank = ucUser.Rank } - original.Status = int(ucUser.Status) + if ucUser.Status != plugin.UserStatusAvailable { + original.Status = int(ucUser.Status) + } } diff --git a/internal/router/answer_api_router.go b/internal/router/answer_api_router.go index 42bb91aa..a7c2dab9 100644 --- a/internal/router/answer_api_router.go +++ b/internal/router/answer_api_router.go @@ -102,7 +102,7 @@ func (a *AnswerAPIRouter) RegisterMustUnAuthAnswerAPIRouter(r *gin.RouterGroup) // user 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/register/email", a.userController.UserRegisterByEmail) 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) { // user r.GET("/user/logout", a.userController.UserLogout) - r.POST("/user/email/change/code", middleware.BanAPIWhenUserCenterEnabled, a.userController.UserChangeEmailSendCode) - r.POST("/user/email/verification/send", middleware.BanAPIWhenUserCenterEnabled, a.userController.UserVerifyEmailSend) + r.POST("/user/email/change/code", middleware.BanAPIForUserCenter, a.userController.UserChangeEmailSendCode) + r.POST("/user/email/verification/send", middleware.BanAPIForUserCenter, a.userController.UserVerifyEmailSend) r.GET("/personal/user/info", a.userController.GetOtherUserInfoByUsername) r.GET("/user/ranking", a.userController.UserRanking) @@ -206,8 +206,8 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) { r.DELETE("/answer", a.answerController.RemoveAnswer) // user - r.PUT("/user/password", middleware.BanAPIWhenUserCenterEnabled, a.userController.UserModifyPassWord) - r.PUT("/user/info", middleware.BanAPIWhenUserCenterEnabled, a.userController.UserUpdateInfo) + r.PUT("/user/password", middleware.BanAPIForUserCenter, a.userController.UserModifyPassWord) + r.PUT("/user/info", a.userController.UserUpdateInfo) r.PUT("/user/interface", a.userController.UserUpdateInterface) r.POST("/user/notice/set", a.userController.UserNoticeSet) @@ -246,10 +246,10 @@ func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) { // user 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.POST("/user", middleware.BanAPIWhenUserCenterEnabled, a.adminUserController.AddUser) - r.PUT("/user/password", middleware.BanAPIWhenUserCenterEnabled, a.adminUserController.UpdateUserPassword) + r.POST("/user", a.adminUserController.AddUser) + r.PUT("/user/password", a.adminUserController.UpdateUserPassword) // reason r.GET("/reasons", a.reasonController.Reasons) @@ -262,25 +262,29 @@ func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) { // siteinfo 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.GET("/siteinfo/interface", a.siteInfoController.GetInterface) r.PUT("/siteinfo/interface", a.siteInfoController.UpdateInterface) + r.GET("/siteinfo/branding", a.siteInfoController.GetSiteBranding) r.PUT("/siteinfo/branding", a.siteInfoController.UpdateBranding) + r.GET("/siteinfo/write", a.siteInfoController.GetSiteWrite) r.PUT("/siteinfo/write", a.siteInfoController.UpdateSiteWrite) + r.GET("/siteinfo/legal", a.siteInfoController.GetSiteLegal) r.PUT("/siteinfo/legal", a.siteInfoController.UpdateSiteLegal) - r.PUT("/siteinfo/login", a.siteInfoController.UpdateSiteLogin) - r.PUT("/siteinfo/custom-css-html", a.siteInfoController.UpdateSiteCustomCssHTML) - r.PUT("/siteinfo/theme", a.siteInfoController.SaveSiteTheme) + r.GET("/siteinfo/seo", a.siteInfoController.GetSeo) 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.PUT("/setting/smtp", a.siteInfoController.UpdateSMTPConfig) + r.GET("/setting/privileges", a.siteInfoController.GetPrivilegesConfig) + r.PUT("/setting/privileges", a.siteInfoController.UpdatePrivilegesConfig) // dashboard r.GET("/dashboard", a.dashboardController.DashboardInfo) diff --git a/internal/router/plugin_api_router.go b/internal/router/plugin_api_router.go index 46a86528..981b34c1 100644 --- a/internal/router/plugin_api_router.go +++ b/internal/router/plugin_api_router.go @@ -37,10 +37,14 @@ func (pr *PluginAPIRouter) RegisterUnAuthConnectorRouter(r *gin.RouterGroup) { 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 r.GET("/connector/user/info", connectorController.ConnectorsUserInfo) r.DELETE("/connector/user/unbinding", connectorController.ExternalLoginUnbinding) r.GET("/user-center/user/settings", pr.userCenterController.UserCenterUserSettings) } + +func (pr *PluginAPIRouter) RegisterAuthAdminConnectorRouter(r *gin.RouterGroup) { + r.GET("/user-center/agent", pr.userCenterController.UserCenterAdminFunctionAgent) +} diff --git a/internal/schema/plugin_admin_schema.go b/internal/schema/plugin_admin_schema.go index be63d3ab..7684e264 100644 --- a/internal/schema/plugin_admin_schema.go +++ b/internal/schema/plugin_admin_schema.go @@ -56,10 +56,31 @@ func (g *GetPluginConfigResp) SetConfigFields(ctx *gin.Context, fields []plugin. UIOptions: ConfigFieldUIOptions{ Rows: field.UIOptions.Rows, InputType: string(field.UIOptions.InputType), + Variant: field.UIOptions.Variant, }, } configField.UIOptions.Placeholder = field.UIOptions.Placeholder.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 { configField.Options = append(configField.Options, ConfigFieldOption{ @@ -83,10 +104,13 @@ type ConfigField struct { } type ConfigFieldUIOptions struct { - Placeholder string `json:"placeholder,omitempty"` - Rows string `json:"rows,omitempty"` - InputType string `json:"input_type,omitempty"` - Label string `json:"label,omitempty"` + Placeholder string `json:"placeholder,omitempty"` + Rows string `json:"rows,omitempty"` + InputType string `json:"input_type,omitempty"` + Label string `json:"label,omitempty"` + Action *UIOptionAction `json:"action,omitempty"` + Variant string `json:"variant,omitempty"` + Text string `json:"text,omitempty"` } type ConfigFieldOption struct { @@ -94,6 +118,23 @@ type ConfigFieldOption struct { 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 { PluginSlugName string `validate:"required,gt=1,lte=100" json:"plugin_slug_name"` ConfigFields map[string]any `json:"config_fields"` diff --git a/internal/schema/plugin_user_center.go b/internal/schema/plugin_user_center.go index afa4727c..9a5f65a8 100644 --- a/internal/schema/plugin_user_center.go +++ b/internal/schema/plugin_user_center.go @@ -6,12 +6,14 @@ type UserCenterAgentResp struct { } type AgentInfo struct { - Name string `json:"name"` - Icon string `json:"icon"` - Url string `json:"url"` - LoginRedirectURL string `json:"login_redirect_url"` - SignUpRedirectURL string `json:"sign_up_redirect_url"` - ControlCenterItems []*ControlCenter `json:"control_center"` + Name string `json:"name"` + DisplayName string `json:"display_name"` + Icon string `json:"icon"` + Url string `json:"url"` + LoginRedirectURL string `json:"login_redirect_url"` + SignUpRedirectURL string `json:"sign_up_redirect_url"` + ControlCenterItems []*ControlCenter `json:"control_center"` + EnabledOriginalUserSystem bool `json:"enabled_original_user_system"` } type ControlCenter struct { diff --git a/internal/schema/question_schema.go b/internal/schema/question_schema.go index 9b400743..60af4f14 100644 --- a/internal/schema/question_schema.go +++ b/internal/schema/question_schema.go @@ -297,6 +297,7 @@ type QuestionPageReq struct { OrderCond string `validate:"omitempty,oneof=newest active frequent score unanswered" form:"order"` Tag string `validate:"omitempty,gt=0,lte=100" form:"tag"` Username string `validate:"omitempty,gt=0,lte=100" form:"username"` + InDays int `validate:"omitempty,min=1" form:"in_days"` LoginUserID string `json:"-"` UserIDBeSearched string `json:"-"` diff --git a/internal/schema/siteinfo_schema.go b/internal/schema/siteinfo_schema.go index 4f786009..f0b659e3 100644 --- a/internal/schema/siteinfo_schema.go +++ b/internal/schema/siteinfo_schema.go @@ -6,6 +6,7 @@ import ( "net/mail" "net/url" + "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/translator" @@ -42,9 +43,8 @@ func (r *SiteGeneralReq) FormatSiteUrl() { // SiteInterfaceReq site interface request type SiteInterfaceReq struct { - 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"` - DefaultAvatar string `validate:"required,oneof=system gravatar" form:"default_avatar" json:"default_avatar"` + 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"` } // SiteBrandingReq site branding request @@ -92,18 +92,32 @@ type GetSiteLegalInfoResp struct { 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 type SiteLoginReq struct { - AllowNewRegistrations bool `json:"allow_new_registrations"` - LoginRequired bool `json:"login_required"` + AllowNewRegistrations bool `json:"allow_new_registrations"` + AllowEmailRegistrations bool `json:"allow_email_registrations"` + LoginRequired bool `json:"login_required"` + AllowEmailDomains []string `json:"allow_email_domains"` } // SiteCustomCssHTMLReq site custom css html type SiteCustomCssHTMLReq struct { - CustomHead string `validate:"omitempty,gt=0,lte=65536" json:"custom_head"` - CustomCss string `validate:"omitempty,gt=0,lte=65536" json:"custom_css"` - CustomHeader string `validate:"omitempty,gt=0,lte=65536" json:"custom_header"` - CustomFooter string `validate:"omitempty,gt=0,lte=65536" json:"custom_footer"` + CustomHead string `validate:"omitempty,gt=0,lte=65536" json:"custom_head"` + CustomCss string `validate:"omitempty,gt=0,lte=65536" json:"custom_css"` + CustomHeader string `validate:"omitempty,gt=0,lte=65536" json:"custom_header"` + 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 @@ -127,6 +141,9 @@ type SiteLoginResp SiteLoginReq // SiteCustomCssHTMLResp site custom css html response type SiteCustomCssHTMLResp SiteCustomCssHTMLReq +// SiteUsersResp site users response +type SiteUsersResp SiteUsersReq + // SiteThemeResp site theme response type SiteThemeResp struct { ThemeOptions []*ThemeOption `json:"theme_options"` @@ -169,6 +186,7 @@ type SiteInfoResp struct { Theme *SiteThemeResp `json:"theme"` CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"` SiteSeo *SiteSeoReq `json:"site_seo"` + SiteUsers *SiteUsersResp `json:"site_users"` Version string `json:"version"` Revision string `json:"revision"` } @@ -235,3 +253,85 @@ type GetManifestJsonResp struct { ThemeColor string `json:"theme_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, + }) + } + } +} diff --git a/internal/schema/user_external_login_schema.go b/internal/schema/user_external_login_schema.go index 79cdad86..56b5fc5e 100644 --- a/internal/schema/user_external_login_schema.go +++ b/internal/schema/user_external_login_schema.go @@ -4,6 +4,9 @@ package schema type UserExternalLoginResp struct { BindingKey string `json:"binding_key"` 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 @@ -49,6 +52,8 @@ type ExternalLoginUserInfoCache struct { Avatar string // optional. The original user information provided by the third-party login platform MetaInfo string + // optional. The bio provided by the third-party login platform + Bio string } // ExternalLoginUnbindingReq external login unbinding user @@ -63,6 +68,13 @@ type UserCenterUserSettingsResp struct { 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 { Enabled bool `json:"enabled"` RedirectURL string `json:"redirect_url"` diff --git a/internal/schema/user_schema.go b/internal/schema/user_schema.go index 70130a04..f1f2bc2f 100644 --- a/internal/schema/user_schema.go +++ b/internal/schema/user_schema.go @@ -4,14 +4,12 @@ import ( "encoding/json" "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/entity" "github.com/answerdev/answer/pkg/checker" "github.com/answerdev/answer/pkg/converter" "github.com/answerdev/answer/pkg/gravatar" "github.com/jinzhu/copier" - "github.com/segmentfault/pacman/errors" ) // UserVerifyEmailReq user verify email request @@ -300,7 +298,7 @@ func (u *UserModifyPasswordReq) Check() (errFields []*validator.FormErrorField, type UpdateInfoRequest struct { // 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 string `validate:"omitempty,gt=3,lte=30" json:"username"` // avatar @@ -329,16 +327,6 @@ func (a *AvatarInfo) ToJsonString() string { } 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) return nil, nil } diff --git a/internal/service/answer_service.go b/internal/service/answer_service.go index 39433b81..2fa5c261 100644 --- a/internal/service/answer_service.go +++ b/internal/service/answer_service.go @@ -476,7 +476,7 @@ func (as *AnswerService) AdminSetAnswerStatus(ctx context.Context, req *schema.A msg.ReceiverUserID = answerInfo.UserID msg.TriggerUserID = answerInfo.UserID msg.ObjectType = constant.AnswerObjectType - msg.NotificationAction = constant.YourAnswerWasDeleted + msg.NotificationAction = constant.NotificationYourAnswerWasDeleted notice_queue.AddNotification(msg) return nil @@ -566,7 +566,7 @@ func (as *AnswerService) notificationUpdateAnswer(ctx context.Context, questionU ObjectID: answerID, } msg.ObjectType = constant.AnswerObjectType - msg.NotificationAction = constant.UpdateAnswer + msg.NotificationAction = constant.NotificationUpdateAnswer notice_queue.AddNotification(msg) } @@ -583,7 +583,7 @@ func (as *AnswerService) notificationAnswerTheQuestion(ctx context.Context, ObjectID: answerID, } msg.ObjectType = constant.AnswerObjectType - msg.NotificationAction = constant.AnswerTheQuestion + msg.NotificationAction = constant.NotificationAnswerTheQuestion notice_queue.AddNotification(msg) userInfo, exist, err := as.userRepo.GetByUserID(ctx, questionUserID) diff --git a/internal/service/auth/auth.go b/internal/service/auth/auth.go index 139c58ae..9053a507 100644 --- a/internal/service/auth/auth.go +++ b/internal/service/auth/auth.go @@ -5,6 +5,7 @@ import ( "github.com/answerdev/answer/internal/entity" "github.com/answerdev/answer/pkg/token" + "github.com/answerdev/answer/plugin" "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) if cacheInfo != nil { - log.Debugf("user status updated: %+v", cacheInfo) userCacheInfo.UserStatus = cacheInfo.UserStatus userCacheInfo.EmailStatus = cacheInfo.EmailStatus userCacheInfo.RoleID = cacheInfo.RoleID @@ -52,6 +52,14 @@ func (as *AuthService) GetUserCacheInfo(ctx context.Context, accessToken string) 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 } diff --git a/internal/service/comment/comment_service.go b/internal/service/comment/comment_service.go index 1b65c48d..654ee9f3 100644 --- a/internal/service/comment/comment_service.go +++ b/internal/service/comment/comment_service.go @@ -471,7 +471,7 @@ func (cs *CommentService) notificationQuestionComment(ctx context.Context, quest ObjectID: commentID, } msg.ObjectType = constant.CommentObjectType - msg.NotificationAction = constant.CommentQuestion + msg.NotificationAction = constant.NotificationCommentQuestion notice_queue.AddNotification(msg) receiverUserInfo, exist, err := cs.userRepo.GetByUserID(ctx, questionUserID) @@ -526,7 +526,7 @@ func (cs *CommentService) notificationAnswerComment(ctx context.Context, ObjectID: commentID, } msg.ObjectType = constant.CommentObjectType - msg.NotificationAction = constant.CommentAnswer + msg.NotificationAction = constant.NotificationCommentAnswer notice_queue.AddNotification(msg) receiverUserInfo, exist, err := cs.userRepo.GetByUserID(ctx, answerUserID) @@ -578,7 +578,7 @@ func (cs *CommentService) notificationCommentReply(ctx context.Context, replyUse ObjectID: commentID, } msg.ObjectType = constant.CommentObjectType - msg.NotificationAction = constant.ReplyToYou + msg.NotificationAction = constant.NotificationReplyToYou notice_queue.AddNotification(msg) } @@ -599,7 +599,7 @@ func (cs *CommentService) notificationMention( ObjectID: commentID, } msg.ObjectType = constant.CommentObjectType - msg.NotificationAction = constant.MentionYou + msg.NotificationAction = constant.NotificationMentionYou notice_queue.AddNotification(msg) alreadyNotifiedUserIDs = append(alreadyNotifiedUserIDs, userInfo.ID) } diff --git a/internal/service/notification/notification_service.go b/internal/service/notification/notification_service.go index 6f5e5b0e..3a8cce13 100644 --- a/internal/service/notification/notification_service.go +++ b/internal/service/notification/notification_service.go @@ -7,14 +7,15 @@ import ( "github.com/answerdev/answer/internal/base/constant" "github.com/answerdev/answer/internal/base/data" + "github.com/answerdev/answer/internal/base/handler" "github.com/answerdev/answer/internal/base/pager" "github.com/answerdev/answer/internal/base/translator" + "github.com/answerdev/answer/internal/entity" "github.com/answerdev/answer/internal/schema" notficationcommon "github.com/answerdev/answer/internal/service/notification_common" "github.com/answerdev/answer/internal/service/revision_common" "github.com/answerdev/answer/pkg/uid" "github.com/jinzhu/copier" - "github.com/segmentfault/pacman/i18n" "github.com/segmentfault/pacman/log" ) @@ -127,35 +128,47 @@ func (ns *NotificationService) GetNotificationPage(ctx context.Context, searchCo if err != nil { 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 { item := &schema.NotificationContent{} - err := json.Unmarshal([]byte(notificationInfo.Content), item) - if err != nil { + if err := json.Unmarshal([]byte(notificationInfo.Content), item); err != nil { log.Error("NotificationContent Unmarshal Error", err.Error()) continue } - lang, _ := ctx.Value(constant.AcceptLanguageFlag).(i18n.Language) - item.NotificationAction = translator.Tr(lang, item.NotificationAction) - item.ID = notificationInfo.ID - item.UpdateTime = notificationInfo.UpdatedAt.Unix() - if notificationInfo.IsRead == schema.NotificationRead { - item.IsRead = true + // If notification is downvote, the user info is not needed. + if item.NotificationAction == constant.NotificationDownVotedTheQuestion || + item.NotificationAction == constant.NotificationDownVotedTheAnswer { + item.UserInfo = nil } - 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 { item.ObjectInfo.ObjectID = uid.EnShortID(item.ObjectInfo.ObjectMap["answer"]) } item.ObjectInfo.ObjectMap["answer"] = uid.EnShortID(item.ObjectInfo.ObjectMap["answer"]) } - questionID, ok := item.ObjectInfo.ObjectMap["question"] - if ok { + if questionID, ok := item.ObjectInfo.ObjectMap["question"]; ok { if item.ObjectInfo.ObjectID == questionID { item.ObjectInfo.ObjectID = uid.EnShortID(item.ObjectInfo.ObjectMap["question"]) } item.ObjectInfo.ObjectMap["question"] = uid.EnShortID(item.ObjectInfo.ObjectMap["question"]) } + resp = append(resp, item) } - return pager.NewPageModel(total, resp), nil + return resp, nil } diff --git a/internal/service/notification_common/notification.go b/internal/service/notification_common/notification.go index ecc0a125..5deda1de 100644 --- a/internal/service/notification_common/notification.go +++ b/internal/service/notification_common/notification.go @@ -193,10 +193,10 @@ func (ns *NotificationCommon) SendNotificationToAllFollower(ctx context.Context, if msg.NoNeedPushAllFollow { return } - if msg.NotificationAction != constant.UpdateQuestion && - msg.NotificationAction != constant.AnswerTheQuestion && - msg.NotificationAction != constant.UpdateAnswer && - msg.NotificationAction != constant.AcceptAnswer { + if msg.NotificationAction != constant.NotificationUpdateQuestion && + msg.NotificationAction != constant.NotificationAnswerTheQuestion && + msg.NotificationAction != constant.NotificationUpdateAnswer && + msg.NotificationAction != constant.NotificationAcceptAnswer { return } condObjectID := msg.ObjectID diff --git a/internal/service/permission/permission_name.go b/internal/service/permission/permission_name.go index 1d49ce3f..9262e569 100644 --- a/internal/service/permission/permission_name.go +++ b/internal/service/permission/permission_name.go @@ -10,10 +10,10 @@ const ( QuestionReopen = "question.reopen" QuestionVoteUp = "question.vote_up" QuestionVoteDown = "question.vote_down" - QuestionPin = "question.pin" //Top the question - QuestionUnPin = "question.unpin" //untop the question - QuestionHide = "question.hide" //hide the question - QuestionShow = "question.show" //show the question + QuestionPin = "question.pin" + QuestionUnPin = "question.unpin" + QuestionHide = "question.hide" + QuestionShow = "question.show" AnswerAdd = "answer.add" AnswerEdit = "answer.edit" AnswerEditWithoutReview = "answer.edit_without_review" diff --git a/internal/service/question_common/question.go b/internal/service/question_common/question.go index 842670f4..ce5b77e5 100644 --- a/internal/service/question_common/question.go +++ b/internal/service/question_common/question.go @@ -32,7 +32,7 @@ type QuestionRepo interface { UpdateQuestion(ctx context.Context, question *entity.Question, Cols []string) (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) - 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) UpdateQuestionStatus(ctx context.Context, question *entity.Question) (err error) UpdateQuestionStatusWithOutUpdateTime(ctx context.Context, question *entity.Question) (err error) diff --git a/internal/service/question_service.go b/internal/service/question_service.go index 28fe4bbd..f5a828b9 100644 --- a/internal/service/question_service.go +++ b/internal/service/question_service.go @@ -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, - req.UserIDBeSearched, req.TagID, req.OrderCond) + req.UserIDBeSearched, req.TagID, req.OrderCond, req.InDays) if err != nil { return nil, 0, err } @@ -1064,7 +1064,7 @@ func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, questionI msg.ReceiverUserID = questionInfo.UserID msg.TriggerUserID = questionInfo.UserID msg.ObjectType = constant.QuestionObjectType - msg.NotificationAction = constant.YourQuestionWasDeleted + msg.NotificationAction = constant.NotificationYourQuestionWasDeleted notice_queue.AddNotification(msg) return nil } diff --git a/internal/service/report_handle_admin/report_handle.go b/internal/service/report_handle_admin/report_handle.go index c638228a..a7a57272 100644 --- a/internal/service/report_handle_admin/report_handle.go +++ b/internal/service/report_handle_admin/report_handle.go @@ -66,7 +66,7 @@ func (rh *ReportHandle) HandleObject(ctx context.Context, reported *entity.Repor switch req.FlaggedType { case reasonDelete: err = rh.commentRepo.RemoveComment(ctx, objectID) - rh.sendNotification(ctx, reportedUserID, objectID, constant.YourCommentWasDeleted) + rh.sendNotification(ctx, reportedUserID, objectID, constant.NotificationYourCommentWasDeleted) } } return diff --git a/internal/service/revision_service.go b/internal/service/revision_service.go index 3e8c3605..20d3c9ca 100644 --- a/internal/service/revision_service.go +++ b/internal/service/revision_service.go @@ -209,7 +209,7 @@ func (rs *RevisionService) revisionAuditAnswer(ctx context.Context, revisionitem ObjectID: answerinfo.ID, } msg.ObjectType = constant.AnswerObjectType - msg.NotificationAction = constant.UpdateAnswer + msg.NotificationAction = constant.NotificationUpdateAnswer notice_queue.AddNotification(msg) activity_queue.AddActivity(&schema.ActivityMsg{ diff --git a/internal/service/siteinfo/siteinfo_service.go b/internal/service/siteinfo/siteinfo_service.go index d41f4302..986beb78 100644 --- a/internal/service/siteinfo/siteinfo_service.go +++ b/internal/service/siteinfo/siteinfo_service.go @@ -3,12 +3,15 @@ package siteinfo import ( "context" "encoding/json" + "fmt" "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/translator" "github.com/answerdev/answer/internal/entity" "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/siteinfo_common" tagcommon "github.com/answerdev/answer/internal/service/tag_common" @@ -23,19 +26,23 @@ type SiteInfoService struct { siteInfoCommonService *siteinfo_common.SiteInfoCommonService emailService *export.EmailService tagCommonService *tagcommon.TagCommonService + configRepo config.ConfigRepo } func NewSiteInfoService( siteInfoRepo siteinfo_common.SiteInfoRepo, siteInfoCommonService *siteinfo_common.SiteInfoCommonService, emailService *export.EmailService, - tagCommonService *tagcommon.TagCommonService) *SiteInfoService { - - resp, err := siteInfoCommonService.GetSiteInterface(context.Background()) - if err != nil { - log.Error(err) - } else { - constant.DefaultAvatar = resp.DefaultAvatar + tagCommonService *tagcommon.TagCommonService, + configRepo config.ConfigRepo, +) *SiteInfoService { + usersSiteInfo, _ := siteInfoCommonService.GetSiteUsers(context.Background()) + if usersSiteInfo != nil { + constant.DefaultAvatar = usersSiteInfo.DefaultAvatar + } + generalSiteInfo, _ := siteInfoCommonService.GetSiteGeneral(context.Background()) + if generalSiteInfo != nil { + constant.DefaultSiteURL = generalSiteInfo.SiteUrl } return &SiteInfoService{ @@ -43,6 +50,7 @@ func NewSiteInfoService( siteInfoCommonService: siteInfoCommonService, emailService: emailService, tagCommonService: tagCommonService, + configRepo: configRepo, } } @@ -61,6 +69,11 @@ func (s *SiteInfoService) GetSiteBranding(ctx context.Context) (resp *schema.Sit 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 func (s *SiteInfoService) GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error) { 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) { req.FormatSiteUrl() - var ( - siteType = "general" - content []byte - ) - content, _ = json.Marshal(req) - - data := entity.SiteInfo{ - Type: siteType, + content, _ := json.Marshal(req) + data := &entity.SiteInfo{ + Type: constant.SiteTypeGeneral, 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 } func (s *SiteInfoService) SaveSiteInterface(ctx context.Context, req schema.SiteInterfaceReq) (err error) { - var ( - siteType = "interface" - content []byte - ) - // check language if !translator.CheckLanguageIsValid(req.Language) { err = errors.BadRequest(reason.LangNotFound) return } - content, _ = json.Marshal(req) - + content, _ := json.Marshal(req) data := entity.SiteInfo{ - Type: siteType, + Type: constant.SiteTypeInterface, Content: string(content), } - - err = s.siteInfoRepo.SaveByType(ctx, siteType, &data) - if err == nil { - constant.DefaultAvatar = req.DefaultAvatar - } - return + return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeInterface, &data) } // 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) } +// 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 func (s *SiteInfoService) GetSMTPConfig(ctx context.Context) ( resp *schema.GetSMTPConfigResp, err error, @@ -253,8 +268,11 @@ func (s *SiteInfoService) UpdateSMTPConfig(ctx context.Context, req *schema.Upda return } -func (s *SiteInfoService) GetSeo(ctx context.Context) (resp *schema.SiteSeoResp, err error) { - resp = &schema.SiteSeoResp{} +func (s *SiteInfoService) GetSeo(ctx context.Context) (resp *schema.SiteSeoReq, err error) { + resp = &schema.SiteSeoReq{} + if err = s.siteInfoCommonService.GetSiteInfoByType(ctx, constant.SiteTypeSeo, resp); err != nil { + return resp, err + } loginConfig, err := s.GetSiteLogin(ctx) if err != nil { log.Error(err) @@ -265,17 +283,6 @@ func (s *SiteInfoService) GetSeo(ctx context.Context) (resp *schema.SiteSeoResp, resp.Robots = "User-agent: *\nDisallow: /" 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 } @@ -302,3 +309,71 @@ func (s *SiteInfoService) SaveSeo(ctx context.Context, req schema.SiteSeoReq) (e } 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 +} diff --git a/internal/service/siteinfo_common/siteinfo_service.go b/internal/service/siteinfo_common/siteinfo_service.go index b2a89643..7e2934a3 100644 --- a/internal/service/siteinfo_common/siteinfo_service.go +++ b/internal/service/siteinfo_common/siteinfo_service.go @@ -43,7 +43,7 @@ func NewSiteInfoCommonService(siteInfoRepo SiteInfoRepo) *SiteInfoCommonService // GetSiteGeneral get site info general func (s *SiteInfoCommonService) GetSiteGeneral(ctx context.Context) (resp *schema.SiteGeneralResp, err error) { resp = &schema.SiteGeneralResp{} - if err = s.getSiteInfoByType(ctx, constant.SiteTypeGeneral, resp); err != nil { + if err = s.GetSiteInfoByType(ctx, constant.SiteTypeGeneral, resp); err != nil { return nil, err } return resp, nil @@ -52,7 +52,7 @@ func (s *SiteInfoCommonService) GetSiteGeneral(ctx context.Context) (resp *schem // GetSiteInterface get site info interface func (s *SiteInfoCommonService) GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error) { 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 resp, nil @@ -61,7 +61,16 @@ func (s *SiteInfoCommonService) GetSiteInterface(ctx context.Context) (resp *sch // GetSiteBranding get site info branding func (s *SiteInfoCommonService) GetSiteBranding(ctx context.Context) (resp *schema.SiteBrandingResp, err error) { 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 resp, nil @@ -70,7 +79,7 @@ func (s *SiteInfoCommonService) GetSiteBranding(ctx context.Context) (resp *sche // GetSiteWrite get site info write func (s *SiteInfoCommonService) GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error) { resp = &schema.SiteWriteResp{} - if err = s.getSiteInfoByType(ctx, constant.SiteTypeWrite, resp); err != nil { + if err = s.GetSiteInfoByType(ctx, constant.SiteTypeWrite, resp); err != nil { return nil, err } return resp, nil @@ -79,7 +88,7 @@ func (s *SiteInfoCommonService) GetSiteWrite(ctx context.Context) (resp *schema. // GetSiteLegal get site info write func (s *SiteInfoCommonService) GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) { 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 resp, nil @@ -88,7 +97,7 @@ func (s *SiteInfoCommonService) GetSiteLegal(ctx context.Context) (resp *schema. // GetSiteLogin get site login config func (s *SiteInfoCommonService) GetSiteLogin(ctx context.Context) (resp *schema.SiteLoginResp, err error) { 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 resp, nil @@ -97,7 +106,7 @@ func (s *SiteInfoCommonService) GetSiteLogin(ctx context.Context) (resp *schema. // GetSiteCustomCssHTML get site custom css html config func (s *SiteInfoCommonService) GetSiteCustomCssHTML(ctx context.Context) (resp *schema.SiteCustomCssHTMLResp, err error) { 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 resp, nil @@ -108,7 +117,7 @@ func (s *SiteInfoCommonService) GetSiteTheme(ctx context.Context) (resp *schema. resp = &schema.SiteThemeResp{ 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 } resp.TrTheme(ctx) @@ -118,13 +127,13 @@ func (s *SiteInfoCommonService) GetSiteTheme(ctx context.Context) (resp *schema. // GetSiteSeo get site seo func (s *SiteInfoCommonService) GetSiteSeo(ctx context.Context) (resp *schema.SiteSeoReq, err error) { resp = &schema.SiteSeoReq{} - if err = s.getSiteInfoByType(ctx, constant.SiteTypeSeo, resp); err != nil { + if err = s.GetSiteInfoByType(ctx, constant.SiteTypeSeo, resp); err != nil { return nil, err } return resp, nil } -func (s *SiteInfoCommonService) getSiteInfoByType(ctx context.Context, siteType string, resp interface{}) (err error) { +func (s *SiteInfoCommonService) GetSiteInfoByType(ctx context.Context, siteType string, resp interface{}) (err error) { siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, siteType) if err != nil { return err diff --git a/internal/service/user_common/user.go b/internal/service/user_common/user.go index e863c8c9..5432acf8 100644 --- a/internal/service/user_common/user.go +++ b/internal/service/user_common/user.go @@ -150,7 +150,7 @@ func (us *UserCommon) MakeUsername(ctx context.Context, displayName string) (use 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) { roleID, err := us.userRoleService.GetUserRole(ctx, userID) if err != nil { @@ -162,6 +162,7 @@ func (us *UserCommon) CacheLoginUserInfo(ctx context.Context, userID string, use EmailStatus: emailStatus, UserStatus: userStatus, RoleID: roleID, + ExternalID: externalID, } accessToken, err = us.authService.SetUserCacheInfo(ctx, userCacheInfo) diff --git a/internal/service/user_external_login/user_center_login_service.go b/internal/service/user_external_login/user_center_login_service.go index dff2be3f..14200298 100644 --- a/internal/service/user_external_login/user_center_login_service.go +++ b/internal/service/user_external_login/user_center_login_service.go @@ -5,10 +5,16 @@ import ( "encoding/json" "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/schema" "github.com/answerdev/answer/internal/service/activity" + "github.com/answerdev/answer/internal/service/siteinfo_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/plugin" "github.com/segmentfault/pacman/log" @@ -20,6 +26,7 @@ type UserCenterLoginService struct { userExternalLoginRepo UserExternalLoginRepo userCommonService *usercommon.UserCommon userActivity activity.UserActiveActivityRepo + siteInfoCommonService *siteinfo_common.SiteInfoCommonService } // NewUserCenterLoginService new user external login service @@ -28,21 +35,38 @@ func NewUserCenterLoginService( userCommonService *usercommon.UserCommon, userExternalLoginRepo UserExternalLoginRepo, userActivity activity.UserActiveActivityRepo, + siteInfoCommonService *siteinfo_common.SiteInfoCommonService, ) *UserCenterLoginService { return &UserCenterLoginService{ userRepo: userRepo, userCommonService: userCommonService, userExternalLoginRepo: userExternalLoginRepo, userActivity: userActivity, + siteInfoCommonService: siteInfoCommonService, } } 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) { + 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, - provider, basicUserInfo.ExternalID) + userCenter.Info().SlugName, basicUserInfo.ExternalID) if err != nil { return nil, err } @@ -53,16 +77,28 @@ func (us *UserCenterLoginService) ExternalLogin( return nil, err } 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 { log.Errorf("update user last login date failed: %v", err) } 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 } } - 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 { return nil, err } @@ -70,7 +106,7 @@ func (us *UserCenterLoginService) ExternalLogin( us.activeUser(ctx, oldUserInfo) 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 } @@ -98,6 +134,8 @@ func (us *UserCenterLoginService) registerNewUser(ctx context.Context, provider userInfo.MailStatus = entity.EmailStatusAvailable userInfo.Status = entity.UserStatusAvailable userInfo.LastLoginDate = time.Now() + userInfo.Bio = basicUserInfo.Bio + userInfo.BioHTML = converter.Markdown2HTML(basicUserInfo.Bio) err = us.userRepo.AddUser(ctx, userInfo) if err != nil { return nil, err @@ -166,6 +204,31 @@ func (us *UserCenterLoginService) UserCenterUserSettings(ctx context.Context, us 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) ( resp *schema.UserCenterPersonalBranding, err error) { resp = &schema.UserCenterPersonalBranding{ diff --git a/internal/service/user_external_login/user_external_login_service.go b/internal/service/user_external_login/user_external_login_service.go index d3288685..912d9135 100644 --- a/internal/service/user_external_login/user_external_login_service.go +++ b/internal/service/user_external_login/user_external_login_service.go @@ -15,6 +15,7 @@ import ( usercommon "github.com/answerdev/answer/internal/service/user_common" "github.com/answerdev/answer/pkg/random" "github.com/answerdev/answer/pkg/token" + "github.com/answerdev/answer/plugin" "github.com/google/uuid" "github.com/segmentfault/pacman/errors" "github.com/segmentfault/pacman/log" @@ -83,7 +84,7 @@ func (us *UserExternalLoginService) ExternalLogin( log.Error(err) } 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 } } @@ -122,7 +123,7 @@ func (us *UserExternalLoginService) ExternalLogin( } 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 } @@ -150,6 +151,8 @@ func (us *UserExternalLoginService) registerNewUser(ctx context.Context, userInfo.MailStatus = entity.EmailStatusToBeVerified userInfo.Status = entity.UserStatusAvailable userInfo.LastLoginDate = time.Now() + userInfo.Bio = externalUserInfo.Bio + userInfo.BioHTML = externalUserInfo.Bio err = us.userRepo.AddUser(ctx, userInfo) if err != nil { return nil, err @@ -249,7 +252,7 @@ func (us *UserExternalLoginService) ExternalLoginBindingUserSendEmail( return nil, err } 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 { log.Error(err) } @@ -316,3 +319,33 @@ func (us *UserExternalLoginService) ExternalLoginUnbinding( 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 +} diff --git a/internal/service/user_service.go b/internal/service/user_service.go index 8cf4a115..00e6b191 100644 --- a/internal/service/user_service.go +++ b/internal/service/user_service.go @@ -80,6 +80,9 @@ func (us *UserService) GetUserInfoByUserID(ctx context.Context, token, userID st if !exist { 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) if err != nil { 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) { 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) 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) @@ -137,6 +147,7 @@ func (us *UserService) EmailLogin(ctx context.Context, req *schema.UserEmailLogi EmailStatus: userInfo.MailStatus, UserStatus: userInfo.Status, RoleID: roleID, + ExternalID: externalID, } resp.AccessToken, err = us.authService.SetUserCacheInfo(ctx, userCacheInfo) if err != nil { @@ -252,7 +263,27 @@ func (us *UserService) UserModifyPassword(ctx context.Context, req *schema.UserM // UpdateInfo update user info func (us *UserService) UpdateInfo(ctx context.Context, req *schema.UpdateInfoRequest) ( 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) if err != nil { return nil, err @@ -264,31 +295,57 @@ func (us *UserService) UpdateInfo(ctx context.Context, req *schema.UpdateInfoReq }) 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 { - return nil, errors.BadRequest(reason.UserSetAvatar).WithError(err).WithStack() + return nil, err } - userInfo := entity.User{} - userInfo.ID = req.UserID - userInfo.Avatar = string(avatar) - userInfo.DisplayName = req.DisplayName - userInfo.Bio = req.Bio - userInfo.BioHTML = req.BioHTML - userInfo.Location = req.Location - userInfo.Website = req.Website - userInfo.Username = req.Username - err = us.userRepo.UpdateInfo(ctx, &userInfo) + if !exist { + return nil, errors.BadRequest(reason.UserNotFound) + } + + cond := us.formatUserInfoForUpdateInfo(oldUserInfo, req, siteUsers) + err = us.userRepo.UpdateInfo(ctx, cond) 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) { _, has, err := us.userRepo.GetByEmail(ctx, email) if err != nil { @@ -466,7 +523,7 @@ func (us *UserService) UserVerifyEmail(ctx context.Context, req *schema.UserVeri } accessToken, userCacheInfo, err := us.userCommonService.CacheLoginUserInfo( - ctx, userInfo.ID, userInfo.MailStatus, userInfo.Status) + ctx, userInfo.ID, userInfo.MailStatus, userInfo.Status, "") if err != nil { return nil, err } diff --git a/internal/service/vote_service.go b/internal/service/vote_service.go index 62c20b64..39d4282d 100644 --- a/internal/service/vote_service.go +++ b/internal/service/vote_service.go @@ -63,12 +63,12 @@ func NewVoteService( } // 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{} var objectUserID string - objectUserID, err = as.GetObjectUserID(ctx, dto.ObjectID) + objectUserID, err = vs.GetObjectUserID(ctx, dto.ObjectID) if err != nil { return } @@ -80,19 +80,19 @@ func (as *VoteService) VoteUp(ctx context.Context, dto *schema.VoteDTO) (voteRes } if dto.IsCancel { - return as.voteRepo.VoteUpCancel(ctx, dto.ObjectID, dto.UserID, objectUserID) + return vs.voteRepo.VoteUpCancel(ctx, dto.ObjectID, dto.UserID, objectUserID) } else { - return as.voteRepo.VoteUp(ctx, dto.ObjectID, dto.UserID, objectUserID) + return vs.voteRepo.VoteUp(ctx, dto.ObjectID, dto.UserID, objectUserID) } } // 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{} var objectUserID string - objectUserID, err = as.GetObjectUserID(ctx, dto.ObjectID) + objectUserID, err = vs.GetObjectUserID(ctx, dto.ObjectID) if err != nil { return } @@ -104,9 +104,9 @@ func (as *VoteService) VoteDown(ctx context.Context, dto *schema.VoteDTO) (voteR } if dto.IsCancel { - return as.voteRepo.VoteDownCancel(ctx, dto.ObjectID, dto.UserID, objectUserID) + return vs.voteRepo.VoteDownCancel(ctx, dto.ObjectID, dto.UserID, objectUserID) } else { - return as.voteRepo.VoteDown(ctx, dto.ObjectID, dto.UserID, objectUserID) + return vs.voteRepo.VoteDown(ctx, dto.ObjectID, dto.UserID, objectUserID) } } diff --git a/pkg/checker/email.go b/pkg/checker/email.go new file mode 100644 index 00000000..1c93cc66 --- /dev/null +++ b/pkg/checker/email.go @@ -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 +} diff --git a/plugin/agent.go b/plugin/agent.go new file mode 100644 index 00000000..7de90ce5 --- /dev/null +++ b/plugin/agent.go @@ -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 +} diff --git a/plugin/config.go b/plugin/config.go index d7c25cae..d637d49d 100644 --- a/plugin/config.go +++ b/plugin/config.go @@ -12,6 +12,7 @@ const ( ConfigTypeUpload ConfigType = "upload" ConfigTypeTimezone ConfigType = "timezone" ConfigTypeSwitch ConfigType = "switch" + ConfigTypeButton ConfigType = "button" ) const ( @@ -43,10 +44,13 @@ type ConfigField struct { } type ConfigFieldUIOptions struct { - Placeholder Translator `json:"placeholder,omitempty"` - Rows string `json:"rows,omitempty"` - InputType InputType `json:"input_type,omitempty"` - Label Translator `json:"label,omitempty"` + Placeholder Translator `json:"placeholder,omitempty"` + Rows string `json:"rows,omitempty"` + InputType InputType `json:"input_type,omitempty"` + Label Translator `json:"label,omitempty"` + Action *UIOptionAction `json:"action,omitempty"` + Variant string `json:"variant,omitempty"` + Text Translator `json:"text,omitempty"` } type ConfigFieldOption struct { @@ -54,6 +58,31 @@ type ConfigFieldOption struct { 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 { Base diff --git a/plugin/plugin.go b/plugin/plugin.go index 833de372..7f6e4a41 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -52,6 +52,10 @@ func Register(p Base) { if _, ok := p.(UserCenter); ok { registerUserCenter(p.(UserCenter)) } + + if _, ok := p.(Agent); ok { + registerAgent(p.(Agent)) + } } 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 type Translator struct { - fn TranslateFn + Fn TranslateFn } // MakeTranslator generates a translator from the key @@ -140,13 +144,13 @@ func MakeTranslator(key string) Translator { t := func(ctx *GinContext) string { return Translate(ctx, key) } - return Translator{fn: t} + return Translator{Fn: t} } // Translate translates the key to the current language of the context func (t Translator) Translate(ctx *GinContext) string { - if &t == nil || t.fn == nil { + if &t == nil || t.Fn == nil { return "" } - return t.fn(ctx) + return t.Fn(ctx) } diff --git a/plugin/user_center.go b/plugin/user_center.go index adc064f5..57061394 100644 --- a/plugin/user_center.go +++ b/plugin/user_center.go @@ -12,21 +12,29 @@ type UserCenter interface { SignUpCallback(ctx *GinContext) (userInfo *UserCenterBasicUserInfo, err error) // UserInfo returns the user information 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(externalIDs []string) (userInfo []*UserCenterBasicUserInfo, err error) // UserSettings returns the user settings UserSettings(externalID string) (userSettings *SettingInfo, err error) // PersonalBranding returns the personal branding information PersonalBranding(externalID string) (branding []*PersonalBranding) + // AfterLogin is called after the user logs in + AfterLogin(externalID, accessToken string) } type UserCenterDesc struct { - Name string `json:"name"` - Icon string `json:"icon"` - Url string `json:"url"` - LoginRedirectURL string `json:"login_redirect_url"` - SignUpRedirectURL string `json:"sign_up_redirect_url"` - RankAgentEnabled bool `json:"rank_agent_enabled"` + Name string `json:"name"` + DisplayName Translator `json:"display_name"` + Icon string `json:"icon"` + Url string `json:"url"` + LoginRedirectURL string `json:"login_redirect_url"` + 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 @@ -45,6 +53,7 @@ type UserCenterBasicUserInfo struct { Rank int `json:"rank"` Avatar string `json:"avatar"` Mobile string `json:"mobile"` + Bio string `json:"bio"` Status UserStatus `json:"status"` } diff --git a/script/build_plugin.sh b/script/build_plugin.sh index 010a477e..89d03cdf 100755 --- a/script/build_plugin.sh +++ b/script/build_plugin.sh @@ -1,26 +1,21 @@ #!/bin/bash +set -e +echo "begin build plugin" plugin_file=./script/plugin_list if [ ! -f "$plugin_file" ]; then echo "plugin_list is not exist" exit 0 fi -num=0 -for line in `cat $plugin_file` -do - account=$line - accounts[$num]=$account - ((num++)) -done -if [ $num -eq 0 ]; then - echo "plugin_list is null" - exit 0 -fi + +echo "plugin_list exist" cmd="./answer build " -for repo in ${accounts[@]} +for repo in `cat $plugin_file` do -echo ${repo} -cmd=$cmd" --with "${repo} + echo ${repo} + cmd=$cmd" --with "${repo} done + +echo "cmd is "$cmd $cmd if [ ! -f "./new_answer" ]; then echo "new_answer is not exist build failed"