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

# Conflicts:
#	go.mod
#	i18n/en_US.yaml
#	internal/base/reason/reason.go
This commit is contained in:
LinkinStars
2023-05-09 10:38:08 +08:00
28 changed files with 864 additions and 60 deletions
+3 -1
View File
@@ -220,7 +220,9 @@ 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)
pluginAPIRouter := router.NewPluginAPIRouter(connectorController)
userCenterLoginService := user_external_login2.NewUserCenterLoginService(userRepo, userCommon, userExternalLoginRepo, userActiveActivityRepo)
userCenterController := controller.NewUserCenterController(userCenterLoginService, siteInfoCommonService)
pluginAPIRouter := router.NewPluginAPIRouter(connectorController, userCenterController)
ginEngine := server.NewHTTPServer(debug, staticRouter, answerAPIRouter, swaggerRouter, uiRouter, authUserMiddleware, avatarMiddleware, templateRouter, pluginAPIRouter)
scheduledTaskManager := cron.NewScheduledTaskManager(siteInfoCommonService, questionService)
application := newApplication(serverConf, ginEngine, scheduledTaskManager)
+49 -3
View File
@@ -76,6 +76,7 @@ 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=
@@ -112,6 +113,10 @@ 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=
@@ -167,6 +172,7 @@ 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=
@@ -225,6 +231,8 @@ 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=
@@ -273,6 +281,8 @@ 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=
@@ -290,6 +300,7 @@ 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=
@@ -306,6 +317,8 @@ 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=
@@ -335,13 +348,18 @@ 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=
@@ -364,6 +382,8 @@ 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=
@@ -538,8 +558,10 @@ 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/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
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/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=
@@ -603,12 +625,23 @@ 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=
@@ -741,6 +774,7 @@ 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=
@@ -781,8 +815,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -793,6 +827,8 @@ 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=
@@ -978,6 +1014,7 @@ 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=
@@ -1111,6 +1148,7 @@ 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=
@@ -1124,6 +1162,8 @@ 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=
@@ -1142,9 +1182,14 @@ 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=
@@ -1156,6 +1201,7 @@ 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=
+5 -3
View File
@@ -12,6 +12,8 @@ backend:
other: Unauthorized.
database_error:
other: Data server error.
forbidden_error:
other: Forbidden.
action:
report:
other: Flag
@@ -552,7 +554,7 @@ ui:
tip_answer: >-
Use comments to reply to other users or notify them of changes. If you are
adding new information, edit your post instead of commenting.
tip_vote: It adds something useful to the post
tip_vote: It adds something useful to the post
edit_answer:
title: Edit Answer
default_reason: Edit answer
@@ -835,8 +837,8 @@ ui:
closed_in: Closed in
show_exist: Show existing question.
useful: Useful
question_useful: It is useful and clear
question_un_useful: It is unclear or not useful
question_useful: It is useful and clear
question_un_useful: It is unclear or not useful
answer_useful: It is useful
answer_un_useful: It is not useful
answers:
+1
View File
@@ -691,6 +691,7 @@ ui:
label: 确认新密码
settings:
page_title: 设置
goto_modify: 前往修改
nav:
profile: 我的资料
notification: 通知
@@ -0,0 +1,19 @@
package middleware
import (
"github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/plugin"
"github.com/gin-gonic/gin"
"github.com/segmentfault/pacman/errors"
)
// BanAPIWhenUserCenterEnabled ban api when user center enabled
func BanAPIWhenUserCenterEnabled(ctx *gin.Context) {
if plugin.UserCenterEnabled() {
handler.HandleResponse(ctx, errors.Forbidden(reason.ForbiddenError), nil)
ctx.Abort()
return
}
ctx.Next()
}
+26 -24
View File
@@ -11,6 +11,8 @@ const (
UnauthorizedError = "base.unauthorized_error"
// DatabaseError database error
DatabaseError = "base.database_error"
// ForbiddenError forbidden error
ForbiddenError = "base.forbidden_error"
)
const (
@@ -25,7 +27,7 @@ const (
AnswerNotFound = "error.answer.not_found"
AnswerCannotDeleted = "error.answer.cannot_deleted"
AnswerCannotUpdate = "error.answer.cannot_update"
AnswerCannotAddByClosedQuestion = "error.answer.question_closed_cannot_add"
AnswerCannotAddByClosedQuestion = "error.answer.question_closed_cannot_add"
CommentEditWithoutPermission = "error.comment.edit_without_permission"
DisallowVote = "error.object.disallow_vote"
DisallowFollow = "error.object.disallow_follow"
@@ -46,29 +48,29 @@ const (
TagNotContainSynonym = "error.tag.not_contain_synonym_tags"
TagCannotUpdate = "error.tag.cannot_update"
TagIsUsedCannotDelete = "error.tag.is_used_cannot_delete"
TagAlreadyExist = "error.tag.already_exist"
RankFailToMeetTheCondition = "error.rank.fail_to_meet_the_condition"
TagAlreadyExist = "error.tag.already_exist"
RankFailToMeetTheCondition = "error.rank.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"
ReportNotFound = "error.report.not_found"
ReadConfigFailed = "error.config.read_config_failed"
DatabaseConnectionFailed = "error.database.connection_failed"
InstallCreateTableFailed = "error.database.create_table_failed"
InstallConfigFailed = "error.install.create_config_failed"
SiteInfoNotFound = "error.site_info.not_found"
UploadFileSourceUnsupported = "error.upload.source_unsupported"
UploadFileUnsupportedFileFormat = "error.upload.unsupported_file_format"
RecommendTagNotExist = "error.tag.recommend_tag_not_found"
RecommendTagEnter = "error.tag.recommend_tag_enter"
RevisionReviewUnderway = "error.revision.review_underway"
RevisionNoPermission = "error.revision.no_permission"
UserCannotUpdateYourRole = "error.user.cannot_update_your_role"
TagCannotSetSynonymAsItself = "error.tag.cannot_set_synonym_as_itself"
NotAllowedRegistration = "error.user.not_allowed_registration"
SMTPConfigFromNameCannotBeEmail = "error.smtp.config_from_name_cannot_be_email"
AdminCannotUpdateTheirPassword = "error.admin.cannot_update_their_password"
AdminCannotModifySelfStatus = "error.admin.cannot_modify_self_status"
ThemeNotFound = "error.theme.not_found"
LangNotFound = "error.lang.not_found"
ReportHandleFailed = "error.report.handle_failed"
ReportNotFound = "error.report.not_found"
ReadConfigFailed = "error.config.read_config_failed"
DatabaseConnectionFailed = "error.database.connection_failed"
InstallCreateTableFailed = "error.database.create_table_failed"
InstallConfigFailed = "error.install.create_config_failed"
SiteInfoNotFound = "error.site_info.not_found"
UploadFileSourceUnsupported = "error.upload.source_unsupported"
UploadFileUnsupportedFileFormat = "error.upload.unsupported_file_format"
RecommendTagNotExist = "error.tag.recommend_tag_not_found"
RecommendTagEnter = "error.tag.recommend_tag_enter"
RevisionReviewUnderway = "error.revision.review_underway"
RevisionNoPermission = "error.revision.no_permission"
UserCannotUpdateYourRole = "error.user.cannot_update_your_role"
TagCannotSetSynonymAsItself = "error.tag.cannot_set_synonym_as_itself"
NotAllowedRegistration = "error.user.not_allowed_registration"
SMTPConfigFromNameCannotBeEmail = "error.smtp.config_from_name_cannot_be_email"
AdminCannotUpdateTheirPassword = "error.admin.cannot_update_their_password"
AdminCannotModifySelfStatus = "error.admin.cannot_modify_self_status"
UserExternalLoginUnbindingForbidden = "error.user.external_login_unbinding_forbidden"
)
+1
View File
@@ -25,4 +25,5 @@ var ProviderSetController = wire.NewSet(
NewActivityController,
NewTemplateController,
NewConnectorController,
NewUserCenterController,
)
@@ -0,0 +1,176 @@
package controller
import (
"fmt"
"net/http"
"github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/middleware"
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service/siteinfo_common"
"github.com/answerdev/answer/internal/service/user_external_login"
"github.com/answerdev/answer/plugin"
"github.com/gin-gonic/gin"
"github.com/segmentfault/pacman/log"
)
const (
UserCenterLoginRouter = "/user-center/login/redirect"
UserCenterSignUpRedirectRouter = "/user-center/sign-up/redirect"
)
// UserCenterController comment controller
type UserCenterController struct {
userCenterLoginService *user_external_login.UserCenterLoginService
siteInfoService *siteinfo_common.SiteInfoCommonService
}
// NewUserCenterController new controller
func NewUserCenterController(
userCenterLoginService *user_external_login.UserCenterLoginService,
siteInfoService *siteinfo_common.SiteInfoCommonService,
) *UserCenterController {
return &UserCenterController{
userCenterLoginService: userCenterLoginService,
siteInfoService: siteInfoService,
}
}
// UserCenterAgent get user center agent info
func (uc *UserCenterController) UserCenterAgent(ctx *gin.Context) {
resp := &schema.UserCenterAgentResp{}
resp.Enabled = plugin.UserCenterEnabled()
if !resp.Enabled {
handler.HandleResponse(ctx, nil, resp)
return
}
siteGeneral, err := uc.siteInfoService.GetSiteGeneral(ctx)
if err != nil {
log.Errorf("get site info failed: %v", err)
ctx.Redirect(http.StatusFound, "/50x")
return
}
resp.AgentInfo = &schema.AgentInfo{}
resp.AgentInfo.LoginRedirectURL = fmt.Sprintf("%s%s%s", siteGeneral.SiteUrl,
commonRouterPrefix, UserCenterLoginRouter)
resp.AgentInfo.SignUpRedirectURL = fmt.Sprintf("%s%s%s", siteGeneral.SiteUrl,
commonRouterPrefix, UserCenterSignUpRedirectRouter)
_ = plugin.CallUserCenter(func(uc plugin.UserCenter) error {
info := uc.Description()
resp.AgentInfo.Name = info.Name
resp.AgentInfo.Icon = info.Icon
resp.AgentInfo.Url = info.Url
resp.AgentInfo.ControlCenterItems = make([]*schema.ControlCenter, 0)
items := uc.ControlCenterItems()
for _, item := range items {
resp.AgentInfo.ControlCenterItems = append(resp.AgentInfo.ControlCenterItems, &schema.ControlCenter{
Name: item.Name,
Label: item.Label,
Url: item.Url,
})
}
return nil
})
handler.HandleResponse(ctx, nil, resp)
}
// UserCenterPersonalBranding get user center personal user info
func (uc *UserCenterController) UserCenterPersonalBranding(ctx *gin.Context) {
req := &schema.GetOtherUserInfoByUsernameReq{}
if handler.BindAndCheck(ctx, req) {
return
}
resp, err := uc.userCenterLoginService.UserCenterPersonalBranding(ctx, req.Username)
handler.HandleResponse(ctx, err, resp)
}
func (uc *UserCenterController) UserCenterLoginRedirect(ctx *gin.Context) {
var redirectURL string
_ = plugin.CallUserCenter(func(uc plugin.UserCenter) error {
info := uc.Description()
redirectURL = info.LoginRedirectURL
return nil
})
ctx.Redirect(http.StatusFound, redirectURL)
}
func (uc *UserCenterController) UserCenterSignUpRedirect(ctx *gin.Context) {
var redirectURL string
_ = plugin.CallUserCenter(func(uc plugin.UserCenter) error {
info := uc.Description()
redirectURL = info.SignUpRedirectURL
return nil
})
ctx.Redirect(http.StatusFound, redirectURL)
}
func (uc *UserCenterController) UserCenterLoginCallback(ctx *gin.Context) {
siteGeneral, err := uc.siteInfoService.GetSiteGeneral(ctx)
if err != nil {
log.Errorf("get site info failed: %v", err)
ctx.Redirect(http.StatusFound, "/50x")
return
}
userCenter, ok := plugin.GetUserCenter()
if !ok {
ctx.Redirect(http.StatusFound, "/404")
return
}
userInfo, err := userCenter.LoginCallback(ctx)
if err != nil {
log.Error(err)
ctx.Redirect(http.StatusFound, "/50x")
return
}
resp, err := uc.userCenterLoginService.ExternalLogin(ctx, userCenter.Info().SlugName, 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",
siteGeneral.SiteUrl, resp.AccessToken))
}
func (uc *UserCenterController) UserCenterSignUpCallback(ctx *gin.Context) {
siteGeneral, err := uc.siteInfoService.GetSiteGeneral(ctx)
if err != nil {
log.Errorf("get site info failed: %v", err)
ctx.Redirect(http.StatusFound, "/50x")
return
}
userCenter, ok := plugin.GetUserCenter()
if !ok {
ctx.Redirect(http.StatusFound, "/404")
return
}
userInfo, err := userCenter.SignUpCallback(ctx)
if err != nil {
log.Error(err)
ctx.Redirect(http.StatusFound, "/50x")
return
}
resp, err := uc.userCenterLoginService.ExternalLogin(ctx, userCenter.Info().SlugName, 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",
siteGeneral.SiteUrl, resp.AccessToken))
}
// UserCenterUserSettings user center user settings
func (uc *UserCenterController) UserCenterUserSettings(ctx *gin.Context) {
userID := middleware.GetLoginUserIDFromContext(ctx)
resp, err := uc.userCenterLoginService.UserCenterUserSettings(ctx, userID)
handler.HandleResponse(ctx, err, resp)
}
@@ -668,6 +668,9 @@ func (qc *QuestionController) AdminSearchAnswerList(ctx *gin.Context) {
return
}
req.QuestionID = uid.DeShortID(req.QuestionID)
if req.QuestionID == "0" {
req.QuestionID = ""
}
userID := middleware.GetLoginUserIDFromContext(ctx)
questionList, count, err := qc.questionService.AdminSearchAnswerList(ctx, req, userID)
handler.HandleResponse(ctx, err, gin.H{
@@ -3,9 +3,12 @@ package controller_admin
import (
"github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/middleware"
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service/user_admin"
"github.com/answerdev/answer/plugin"
"github.com/gin-gonic/gin"
"github.com/segmentfault/pacman/errors"
)
// UserAdminController user controller
@@ -29,6 +32,10 @@ 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() {
handler.HandleResponse(ctx, errors.Forbidden(reason.ForbiddenError), nil)
return
}
req := &schema.UpdateUserStatusReq{}
if handler.BindAndCheck(ctx, req) {
return
@@ -73,6 +80,10 @@ 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
@@ -95,6 +106,10 @@ 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
+5
View File
@@ -9,6 +9,7 @@ import (
"github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/service/config"
"github.com/answerdev/answer/internal/service/rank"
"github.com/answerdev/answer/plugin"
"github.com/jinzhu/now"
"github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/log"
@@ -36,6 +37,10 @@ func NewUserRankRepo(data *data.Data, configRepo config.ConfigRepo) rank.UserRan
func (ur *UserRankRepo) TriggerUserRank(ctx context.Context,
session *xorm.Session, userID string, deltaRank int, activityType int,
) (isReachStandard bool, err error) {
// IMPORTANT: If user center enabled the rank agent, then we should not change user rank.
if plugin.RankAgentEnabled() {
return false, nil
}
if deltaRank == 0 {
return false, nil
}
+11
View File
@@ -86,6 +86,10 @@ func (ur *userAdminRepo) GetUserInfo(ctx context.Context, userID string) (user *
if err != nil {
return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
err = tryToDecorateUserInfoFromUserCenter(ctx, ur.data, user)
if err != nil {
return nil, false, err
}
return
}
@@ -96,6 +100,11 @@ func (ur *userAdminRepo) GetUserInfoByEmail(ctx context.Context, email string) (
Where("status != ?", entity.UserStatusDeleted).Get(userInfo)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
return
}
err = tryToDecorateUserInfoFromUserCenter(ctx, ur.data, user)
if err != nil {
return nil, false, err
}
return
}
@@ -127,6 +136,8 @@ func (ur *userAdminRepo) GetUserPage(ctx context.Context, page, pageSize int, us
total, err = pager.Help(page, pageSize, &users, user, session)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
return
}
tryToDecorateUserListFromUserCenter(ctx, ur.data, users)
return
}
+110 -2
View File
@@ -7,9 +7,12 @@ import (
"github.com/answerdev/answer/internal/base/data"
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service/config"
usercommon "github.com/answerdev/answer/internal/service/user_common"
"github.com/answerdev/answer/plugin"
"github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/log"
"xorm.io/xorm"
)
@@ -137,6 +140,11 @@ func (ur *userRepo) GetByUserID(ctx context.Context, userID string) (userInfo *e
exist, err = ur.data.DB.Where("id = ?", userID).Get(userInfo)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
return
}
err = tryToDecorateUserInfoFromUserCenter(ctx, ur.data, userInfo)
if err != nil {
return nil, false, err
}
return
}
@@ -147,6 +155,7 @@ func (ur *userRepo) BatchGetByID(ctx context.Context, ids []string) ([]*entity.U
if err != nil {
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
tryToDecorateUserListFromUserCenter(ctx, ur.data, list)
return list, nil
}
@@ -156,6 +165,11 @@ func (ur *userRepo) GetByUsername(ctx context.Context, username string) (userInf
exist, err = ur.data.DB.Where("username = ?", username).Get(userInfo)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
return
}
err = tryToDecorateUserInfoFromUserCenter(ctx, ur.data, userInfo)
if err != nil {
return nil, false, err
}
return
}
@@ -171,11 +185,105 @@ func (ur *userRepo) GetByEmail(ctx context.Context, email string) (userInfo *ent
return
}
func (vr *userRepo) GetUserCount(ctx context.Context) (count int64, err error) {
func (ur *userRepo) GetUserCount(ctx context.Context) (count int64, err error) {
list := make([]*entity.User, 0)
count, err = vr.data.DB.Where("mail_status =?", entity.EmailStatusAvailable).And("status =?", entity.UserStatusAvailable).FindAndCount(&list)
count, err = ur.data.DB.Where("mail_status =?", entity.EmailStatusAvailable).And("status =?", entity.UserStatusAvailable).FindAndCount(&list)
if err != nil {
return count, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return
}
func tryToDecorateUserInfoFromUserCenter(ctx context.Context, data *data.Data, original *entity.User) (err error) {
uc, ok := plugin.GetUserCenter()
if !ok {
return nil
}
userInfo := &entity.UserExternalLogin{}
session := data.DB.Where("user_id = ?", original.ID)
session.Where("provider = ?", uc.Info().SlugName)
exist, err := session.Get(userInfo)
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
if !exist {
return nil
}
userCenterBasicUserInfo, err := uc.UserInfo(userInfo.ExternalID)
if err != nil {
log.Error(err)
return errors.BadRequest(reason.UserNotFound).WithError(err).WithStack()
}
// In general, usernames should be guaranteed unique by the User Center plugin, so there are no inconsistencies.
if original.Username != userCenterBasicUserInfo.Username {
log.Warnf("user %s username is inconsistent with user center", original.ID)
}
decorateByUserCenterUser(original, userCenterBasicUserInfo)
return nil
}
func tryToDecorateUserListFromUserCenter(ctx context.Context, data *data.Data, original []*entity.User) {
uc, ok := plugin.GetUserCenter()
if !ok {
return
}
ids := make([]string, 0)
originalUserIDMapping := make(map[string]*entity.User, 0)
for _, user := range original {
originalUserIDMapping[user.ID] = user
ids = append(ids, user.ID)
}
userExternalLoginList := make([]*entity.UserExternalLogin, 0)
session := data.DB.Where("provider = ?", uc.Info().SlugName)
session.In("user_id", ids)
err := session.Find(&userExternalLoginList)
if err != nil {
log.Error(err)
return
}
userExternalIDs := make([]string, 0)
originalExternalIDMapping := make(map[string]*entity.User, 0)
for _, u := range userExternalLoginList {
originalExternalIDMapping[u.ExternalID] = originalUserIDMapping[u.UserID]
userExternalIDs = append(userExternalIDs, u.ExternalID)
}
if len(userExternalIDs) == 0 {
return
}
ucUsers, err := uc.UserList(userExternalIDs)
if err != nil {
log.Errorf("get user list from user center failed: %v, %v", err, userExternalIDs)
return
}
for _, ucUser := range ucUsers {
decorateByUserCenterUser(originalExternalIDMapping[ucUser.ExternalID], ucUser)
}
}
func decorateByUserCenterUser(original *entity.User, ucUser *plugin.UserCenterBasicUserInfo) {
if original == nil || ucUser == nil {
return
}
// In general, usernames should be guaranteed unique by the User Center plugin, so there are no inconsistencies.
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 plugin enable rank agent, use rank from user center.
if plugin.RankAgentEnabled() {
original.Rank = ucUser.Rank
}
original.Status = int(ucUser.Status)
}
@@ -43,10 +43,10 @@ func (ur *userExternalLoginRepo) UpdateInfo(ctx context.Context, userInfo *entit
}
// GetByExternalID get by external ID
func (ur *userExternalLoginRepo) GetByExternalID(ctx context.Context, externalID string) (
func (ur *userExternalLoginRepo) GetByExternalID(ctx context.Context, provider, externalID string) (
userInfo *entity.UserExternalLogin, exist bool, err error) {
userInfo = &entity.UserExternalLogin{}
exist, err = ur.data.DB.Where("external_id = ?", externalID).Get(userInfo)
exist, err = ur.data.DB.Where("external_id = ?", externalID).Where("provider = ?", provider).Get(userInfo)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
+18 -16
View File
@@ -1,6 +1,7 @@
package router
import (
"github.com/answerdev/answer/internal/base/middleware"
"github.com/answerdev/answer/internal/controller"
"github.com/answerdev/answer/internal/controller_admin"
"github.com/gin-gonic/gin"
@@ -100,23 +101,24 @@ func (a *AnswerAPIRouter) RegisterMustUnAuthAnswerAPIRouter(r *gin.RouterGroup)
r.GET("/siteinfo/legal", a.siteinfoController.GetSiteLegalInfo)
// user
r.POST("/user/login/email", a.userController.UserEmailLogin)
r.POST("/user/register/email", a.userController.UserRegisterByEmail)
r.GET("/user/register/captcha", a.userController.UserRegisterCaptcha)
r.POST("/user/email/verification", a.userController.UserVerifyEmail)
r.PUT("/user/email", a.userController.UserChangeEmailVerify)
r.GET("/user/action/record", a.userController.ActionRecord)
r.POST("/user/password/reset", a.userController.RetrievePassWord)
r.POST("/user/password/replacement", a.userController.UseRePassWord)
r.GET("/user/info", a.userController.GetUserInfoByUserID)
r.PUT("/user/email/notification", a.userController.UserUnsubscribeEmailNotification)
routerGroup := r.Group("", middleware.BanAPIWhenUserCenterEnabled)
routerGroup.POST("/user/login/email", a.userController.UserEmailLogin)
routerGroup.POST("/user/register/email", a.userController.UserRegisterByEmail)
routerGroup.GET("/user/register/captcha", a.userController.UserRegisterCaptcha)
routerGroup.POST("/user/email/verification", a.userController.UserVerifyEmail)
routerGroup.PUT("/user/email", a.userController.UserChangeEmailVerify)
routerGroup.GET("/user/action/record", a.userController.ActionRecord)
routerGroup.POST("/user/password/reset", a.userController.RetrievePassWord)
routerGroup.POST("/user/password/replacement", a.userController.UseRePassWord)
routerGroup.PUT("/user/email/notification", a.userController.UserUnsubscribeEmailNotification)
}
func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) {
// user
r.GET("/user/logout", a.userController.UserLogout)
r.POST("/user/email/change/code", a.userController.UserChangeEmailSendCode)
r.POST("/user/email/verification/send", a.userController.UserVerifyEmailSend)
r.POST("/user/email/change/code", middleware.BanAPIWhenUserCenterEnabled, a.userController.UserChangeEmailSendCode)
r.POST("/user/email/verification/send", middleware.BanAPIWhenUserCenterEnabled, a.userController.UserVerifyEmailSend)
r.GET("/personal/user/info", a.userController.GetOtherUserInfoByUsername)
r.GET("/user/ranking", a.userController.UserRanking)
@@ -204,8 +206,8 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
r.DELETE("/answer", a.answerController.RemoveAnswer)
// user
r.PUT("/user/password", a.userController.UserModifyPassWord)
r.PUT("/user/info", a.userController.UserUpdateInfo)
r.PUT("/user/password", middleware.BanAPIWhenUserCenterEnabled, a.userController.UserModifyPassWord)
r.PUT("/user/info", middleware.BanAPIWhenUserCenterEnabled, a.userController.UserUpdateInfo)
r.PUT("/user/interface", a.userController.UserUpdateInterface)
r.POST("/user/notice/set", a.userController.UserNoticeSet)
@@ -244,10 +246,10 @@ func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) {
// user
r.GET("/users/page", a.adminUserController.GetUserPage)
r.PUT("/user/status", a.adminUserController.UpdateUserStatus)
r.PUT("/user/status", middleware.BanAPIWhenUserCenterEnabled, a.adminUserController.UpdateUserStatus)
r.PUT("/user/role", a.adminUserController.UpdateUserRole)
r.POST("/user", a.adminUserController.AddUser)
r.PUT("/user/password", a.adminUserController.UpdateUserPassword)
r.POST("/user", middleware.BanAPIWhenUserCenterEnabled, a.adminUserController.AddUser)
r.PUT("/user/password", middleware.BanAPIWhenUserCenterEnabled, a.adminUserController.UpdateUserPassword)
// reason
r.GET("/reasons", a.reasonController.Reasons)
+16 -2
View File
@@ -6,27 +6,41 @@ import (
)
type PluginAPIRouter struct {
connectorController *controller.ConnectorController
connectorController *controller.ConnectorController
userCenterController *controller.UserCenterController
}
func NewPluginAPIRouter(
connectorController *controller.ConnectorController,
userCenterController *controller.UserCenterController,
) *PluginAPIRouter {
return &PluginAPIRouter{
connectorController: connectorController,
connectorController: connectorController,
userCenterController: userCenterController,
}
}
func (pr *PluginAPIRouter) RegisterUnAuthConnectorRouter(r *gin.RouterGroup) {
// connector plugin
connectorController := pr.connectorController
r.GET(controller.ConnectorLoginRouterPrefix+":name", connectorController.ConnectorLoginDispatcher)
r.GET(controller.ConnectorRedirectRouterPrefix+":name", connectorController.ConnectorRedirectDispatcher)
r.GET("/connector/info", connectorController.ConnectorsInfo)
r.POST("/connector/binding/email", connectorController.ExternalLoginBindingUserSendEmail)
// user center plugin
r.GET("/user-center/agent", pr.userCenterController.UserCenterAgent)
r.GET("/user-center/personal/branding", pr.userCenterController.UserCenterPersonalBranding)
r.GET(controller.UserCenterLoginRouter, pr.userCenterController.UserCenterLoginRedirect)
r.GET(controller.UserCenterSignUpRedirectRouter, pr.userCenterController.UserCenterSignUpRedirect)
r.GET("/user-center/login/callback", pr.userCenterController.UserCenterLoginCallback)
r.GET("/user-center/sign-up/callback", pr.userCenterController.UserCenterSignUpCallback)
}
func (pr *PluginAPIRouter) RegisterAuthConnectorRouter(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)
}
+33
View File
@@ -0,0 +1,33 @@
package schema
type UserCenterAgentResp struct {
Enabled bool `json:"enabled"`
AgentInfo *AgentInfo `json:"agent_info"`
}
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"`
}
type ControlCenter struct {
Name string `json:"name"`
Label string `json:"label"`
Url string `json:"url"`
}
type UserCenterPersonalBranding struct {
Enabled bool `json:"enabled"`
PersonalBranding []*PersonalBranding `json:"personal_branding"`
}
type PersonalBranding struct {
Icon string `json:"icon"`
Name string `json:"name"`
Label string `json:"label"`
Url string `json:"url"`
}
@@ -56,3 +56,14 @@ type ExternalLoginUnbindingReq struct {
ExternalID string `validate:"required,gt=0,lte=128" json:"external_id"`
UserID string `json:"-"`
}
// UserCenterUserSettingsResp user center user info response
type UserCenterUserSettingsResp struct {
ProfileSettingAgent UserSettingAgent `json:"profile_setting_agent"`
AccountSettingAgent UserSettingAgent `json:"account_setting_agent"`
}
type UserSettingAgent struct {
Enabled bool `json:"enabled"`
RedirectURL string `json:"redirect_url"`
}
+12
View File
@@ -143,6 +143,13 @@ func FormatAvatarInfo(avatarJson, email string) (res string) {
}
}
func CustomAvatar(url string) *AvatarInfo {
return &AvatarInfo{
Type: AvatarTypeCustom,
Custom: url,
}
}
// GetUserStatusResp get user status info
type GetUserStatusResp struct {
// user status
@@ -316,6 +323,11 @@ type AvatarInfo struct {
Custom string `validate:"omitempty,gt=0,lte=200" json:"custom"`
}
func (a *AvatarInfo) ToJsonString() string {
data, _ := json.Marshal(a)
return string(data)
}
func (req *UpdateInfoRequest) Check() (errFields []*validator.FormErrorField, err error) {
if len(req.Username) > 0 {
if checker.IsInvalidUsername(req.Username) {
+6 -1
View File
@@ -3,6 +3,7 @@ package activity
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/answerdev/answer/internal/base/constant"
@@ -111,7 +112,11 @@ func (as *ActivityService) GetObjectTimeline(ctx context.Context, req *schema.Ge
item.Username = "N/A"
item.UserDisplayName = "N/A"
} else {
item.UserID = act.UserID
if act.TriggerUserID > 0 {
item.UserID = fmt.Sprintf("%d", act.TriggerUserID)
} else {
item.UserID = act.UserID
}
}
item.Comment = as.getTimelineActivityComment(ctx, item.ObjectID, item.ObjectType, item.ActivityType, item.RevisionID)
@@ -15,6 +15,7 @@ import (
"github.com/answerdev/answer/internal/service/object_info"
usercommon "github.com/answerdev/answer/internal/service/user_common"
"github.com/answerdev/answer/pkg/uid"
"github.com/answerdev/answer/plugin"
"github.com/goccy/go-json"
"github.com/jinzhu/copier"
"github.com/segmentfault/pacman/errors"
@@ -82,7 +83,9 @@ func (ns *NotificationCommon) HandleNotification() {
// ObjectInfo.ObjectID
// ObjectInfo.ObjectType
func (ns *NotificationCommon) AddNotification(ctx context.Context, msg *schema.NotificationMsg) error {
if msg.Type == schema.NotificationTypeAchievement && plugin.RankAgentEnabled() {
return nil
}
req := &schema.NotificationContent{
TriggerUserID: msg.TriggerUserID,
ReceiverUserID: msg.ReceiverUserID,
+1
View File
@@ -82,5 +82,6 @@ var ProviderSetService = wire.NewSet(
role.NewUserRoleRelService,
role.NewRolePowerRelService,
user_external_login.NewUserExternalLoginService,
user_external_login.NewUserCenterLoginService,
plugin_common.NewPluginCommonService,
)
+4
View File
@@ -16,6 +16,7 @@ import (
usercommon "github.com/answerdev/answer/internal/service/user_common"
"github.com/answerdev/answer/pkg/htmltext"
"github.com/answerdev/answer/pkg/uid"
"github.com/answerdev/answer/plugin"
"github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/log"
"xorm.io/xorm"
@@ -228,6 +229,9 @@ func (rs *RankService) checkUserRank(ctx context.Context, userID string, userRan
// GetRankPersonalWithPage get personal comment list page
func (rs *RankService) GetRankPersonalWithPage(ctx context.Context, req *schema.GetRankPersonalWithPageReq) (
pageModel *pager.PageModel, err error) {
if plugin.RankAgentEnabled() {
return pager.NewPageModel(0, []string{}), nil
}
if len(req.Username) > 0 {
userInfo, exist, err := rs.userCommon.GetUserBasicInfoByUserName(ctx, req.Username)
if err != nil {
@@ -0,0 +1,214 @@
package user_external_login
import (
"context"
"encoding/json"
"time"
"github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service/activity"
usercommon "github.com/answerdev/answer/internal/service/user_common"
"github.com/answerdev/answer/pkg/random"
"github.com/answerdev/answer/plugin"
"github.com/segmentfault/pacman/log"
)
// UserCenterLoginService user external login service
type UserCenterLoginService struct {
userRepo usercommon.UserRepo
userExternalLoginRepo UserExternalLoginRepo
userCommonService *usercommon.UserCommon
userActivity activity.UserActiveActivityRepo
}
// NewUserCenterLoginService new user external login service
func NewUserCenterLoginService(
userRepo usercommon.UserRepo,
userCommonService *usercommon.UserCommon,
userExternalLoginRepo UserExternalLoginRepo,
userActivity activity.UserActiveActivityRepo,
) *UserCenterLoginService {
return &UserCenterLoginService{
userRepo: userRepo,
userCommonService: userCommonService,
userExternalLoginRepo: userExternalLoginRepo,
userActivity: userActivity,
}
}
func (us *UserCenterLoginService) ExternalLogin(
ctx context.Context, provider string, basicUserInfo *plugin.UserCenterBasicUserInfo) (
resp *schema.UserExternalLoginResp, err error) {
oldExternalLoginUserInfo, exist, err := us.userExternalLoginRepo.GetByExternalID(ctx,
provider, basicUserInfo.ExternalID)
if err != nil {
return nil, err
}
if exist {
// if user is already a member, login directly
oldUserInfo, exist, err := us.userRepo.GetByUserID(ctx, oldExternalLoginUserInfo.UserID)
if err != nil {
return nil, err
}
if exist {
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)
return &schema.UserExternalLoginResp{AccessToken: accessToken}, err
}
}
oldUserInfo, err := us.registerNewUser(ctx, provider, basicUserInfo)
if err != nil {
return nil, err
}
us.activeUser(ctx, oldUserInfo)
accessToken, _, err := us.userCommonService.CacheLoginUserInfo(
ctx, oldUserInfo.ID, oldUserInfo.MailStatus, oldUserInfo.Status)
return &schema.UserExternalLoginResp{AccessToken: accessToken}, err
}
func (us *UserCenterLoginService) registerNewUser(ctx context.Context, provider string,
basicUserInfo *plugin.UserCenterBasicUserInfo) (userInfo *entity.User, err error) {
userInfo = &entity.User{}
userInfo.EMail = basicUserInfo.Email
userInfo.DisplayName = basicUserInfo.DisplayName
userInfo.Username, err = us.userCommonService.MakeUsername(ctx, basicUserInfo.Username)
if err != nil {
log.Error(err)
userInfo.Username = random.Username()
}
if len(basicUserInfo.Avatar) > 0 {
avatarInfo := &schema.AvatarInfo{
Type: schema.AvatarTypeCustom,
Custom: basicUserInfo.Avatar,
}
avatar, _ := json.Marshal(avatarInfo)
userInfo.Avatar = string(avatar)
}
userInfo.MailStatus = entity.EmailStatusAvailable
userInfo.Status = entity.UserStatusAvailable
userInfo.LastLoginDate = time.Now()
err = us.userRepo.AddUser(ctx, userInfo)
if err != nil {
return nil, err
}
metaInfo, _ := json.Marshal(basicUserInfo)
newExternalUserInfo := &entity.UserExternalLogin{
UserID: userInfo.ID,
Provider: provider,
ExternalID: basicUserInfo.ExternalID,
MetaInfo: string(metaInfo),
}
err = us.userExternalLoginRepo.AddUserExternalLogin(ctx, newExternalUserInfo)
return userInfo, nil
}
func (us *UserCenterLoginService) activeUser(ctx context.Context, oldUserInfo *entity.User) {
if err := us.userActivity.UserActive(ctx, oldUserInfo.ID); err != nil {
log.Error(err)
}
}
func (us *UserCenterLoginService) UserCenterUserSettings(ctx context.Context, userID string) (
resp *schema.UserCenterUserSettingsResp, err error) {
resp = &schema.UserCenterUserSettingsResp{}
userCenter, ok := plugin.GetUserCenter()
if !ok {
return resp, nil
}
// get external login info
externalLoginList, err := us.userExternalLoginRepo.GetUserExternalLoginList(ctx, userID)
if err != nil {
return nil, err
}
var externalInfo *entity.UserExternalLogin
for _, t := range externalLoginList {
if t.Provider == userCenter.Info().SlugName {
externalInfo = t
}
}
if externalInfo == nil {
return resp, nil
}
settings, err := userCenter.UserSettings(externalInfo.ExternalID)
if err != nil {
log.Error(err)
return resp, nil
}
if len(settings.AccountSettingRedirectURL) > 0 {
resp.AccountSettingAgent = schema.UserSettingAgent{
Enabled: true,
RedirectURL: settings.AccountSettingRedirectURL,
}
}
if len(settings.ProfileSettingRedirectURL) > 0 {
resp.ProfileSettingAgent = schema.UserSettingAgent{
Enabled: true,
RedirectURL: settings.ProfileSettingRedirectURL,
}
}
return resp, nil
}
func (us *UserCenterLoginService) UserCenterPersonalBranding(ctx context.Context, username string) (
resp *schema.UserCenterPersonalBranding, err error) {
resp = &schema.UserCenterPersonalBranding{
PersonalBranding: make([]*schema.PersonalBranding, 0),
}
userCenter, ok := plugin.GetUserCenter()
if !ok {
return
}
userInfo, exist, err := us.userRepo.GetByUsername(ctx, username)
if err != nil {
return nil, err
}
if !exist {
return resp, nil
}
// get external login info
externalLoginList, err := us.userExternalLoginRepo.GetUserExternalLoginList(ctx, userInfo.ID)
if err != nil {
return nil, err
}
var externalInfo *entity.UserExternalLogin
for _, t := range externalLoginList {
if t.Provider == userCenter.Info().SlugName {
externalInfo = t
}
}
if externalInfo == nil {
return resp, nil
}
resp.Enabled = true
branding := userCenter.PersonalBranding(externalInfo.ExternalID)
for _, t := range branding {
resp.PersonalBranding = append(resp.PersonalBranding, &schema.PersonalBranding{
Icon: t.Icon,
Name: t.Name,
Label: t.Label,
Url: t.Url,
})
}
return resp, nil
}
@@ -23,9 +23,8 @@ import (
type UserExternalLoginRepo interface {
AddUserExternalLogin(ctx context.Context, user *entity.UserExternalLogin) (err error)
UpdateInfo(ctx context.Context, userInfo *entity.UserExternalLogin) (err error)
GetByExternalID(ctx context.Context, externalID string) (userInfo *entity.UserExternalLogin, exist bool, err error)
GetUserExternalLoginList(ctx context.Context, userID string) (
resp []*entity.UserExternalLogin, err error)
GetByExternalID(ctx context.Context, provider, externalID string) (userInfo *entity.UserExternalLogin, exist bool, err error)
GetUserExternalLoginList(ctx context.Context, userID string) (resp []*entity.UserExternalLogin, err error)
DeleteUserExternalLogin(ctx context.Context, userID, externalID string) (err error)
SetCacheUserExternalLoginInfo(ctx context.Context, key string, info *schema.ExternalLoginUserInfoCache) (err error)
GetCacheUserExternalLoginInfo(ctx context.Context, key string) (info *schema.ExternalLoginUserInfoCache, err error)
@@ -64,7 +63,8 @@ func NewUserExternalLoginService(
func (us *UserExternalLoginService) ExternalLogin(
ctx context.Context, externalUserInfo *schema.ExternalLoginUserInfoCache) (
resp *schema.UserExternalLoginResp, err error) {
oldExternalLoginUserInfo, exist, err := us.userExternalLoginRepo.GetByExternalID(ctx, externalUserInfo.ExternalID)
oldExternalLoginUserInfo, exist, err := us.userExternalLoginRepo.GetByExternalID(ctx,
externalUserInfo.Provider, externalUserInfo.ExternalID)
if err != nil {
return nil, err
}
@@ -75,6 +75,9 @@ func (us *UserExternalLoginService) ExternalLogin(
return nil, err
}
if exist && oldUserInfo.Status != entity.UserStatusDeleted {
if err := us.userRepo.UpdateLastLoginDate(ctx, oldUserInfo.ID); err != nil {
log.Errorf("update user last login date failed: %v", err)
}
newMailStatus, err := us.activeUser(ctx, oldUserInfo, externalUserInfo)
if err != nil {
log.Error(err)
@@ -156,7 +159,9 @@ func (us *UserExternalLoginService) registerNewUser(ctx context.Context,
func (us *UserExternalLoginService) bindOldUser(ctx context.Context,
externalUserInfo *schema.ExternalLoginUserInfoCache, oldUserInfo *entity.User) (err error) {
oldExternalUserInfo, exist, err := us.userExternalLoginRepo.GetByExternalID(ctx, externalUserInfo.ExternalID)
oldExternalUserInfo, exist, err := us.userExternalLoginRepo.GetByExternalID(ctx,
externalUserInfo.Provider,
externalUserInfo.ExternalID)
if err != nil {
return err
}
+7
View File
@@ -22,6 +22,7 @@ import (
usercommon "github.com/answerdev/answer/internal/service/user_common"
"github.com/answerdev/answer/internal/service/user_external_login"
"github.com/answerdev/answer/pkg/checker"
"github.com/answerdev/answer/plugin"
"github.com/google/uuid"
"github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/log"
@@ -681,6 +682,9 @@ func (us *UserService) UserUnsubscribeEmailNotification(
func (us *UserService) getActivityUserRankStat(ctx context.Context, startTime, endTime time.Time, limit int,
userIDExist map[string]bool) (rankStat []*entity.ActivityUserRankStat, userIDs []string, err error) {
if plugin.RankAgentEnabled() {
return make([]*entity.ActivityUserRankStat, 0), make([]string, 0), nil
}
rankStat, err = us.activityRepo.GetUsersWhoHasGainedTheMostReputation(ctx, startTime, endTime, limit)
if err != nil {
return nil, nil, err
@@ -700,6 +704,9 @@ func (us *UserService) getActivityUserRankStat(ctx context.Context, startTime, e
func (us *UserService) getActivityUserVoteStat(ctx context.Context, startTime, endTime time.Time, limit int,
userIDExist map[string]bool) (voteStat []*entity.ActivityUserVoteStat, userIDs []string, err error) {
if plugin.RankAgentEnabled() {
return make([]*entity.ActivityUserVoteStat, 0), make([]string, 0), nil
}
voteStat, err = us.activityRepo.GetUsersWhoHasVoteMost(ctx, startTime, endTime, limit)
if err != nil {
return nil, nil, err
+4
View File
@@ -48,6 +48,10 @@ func Register(p Base) {
if _, ok := p.(Cache); ok {
registerCache(p.(Cache))
}
if _, ok := p.(UserCenter); ok {
registerUserCenter(p.(UserCenter))
}
}
type Stack[T Base] struct {
+98
View File
@@ -0,0 +1,98 @@
package plugin
type UserCenter interface {
Base
// Description returns the description of the user center, including the name, icon, url, etc.
Description() UserCenterDesc
// ControlCenterItems returns the items that will be displayed in the control center
ControlCenterItems() []ControlCenter
// LoginCallback is called when the user center login callback is called
LoginCallback(ctx *GinContext) (userInfo *UserCenterBasicUserInfo, err error)
// SignUpCallback is called when the user center sign up callback is called
SignUpCallback(ctx *GinContext) (userInfo *UserCenterBasicUserInfo, err error)
// UserInfo returns the user information
UserInfo(externalID string) (userInfo *UserCenterBasicUserInfo, err error)
// 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)
}
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"`
}
type UserStatus int
const (
UserStatusAvailable UserStatus = 1
UserStatusSuspended UserStatus = 9
UserStatusDeleted UserStatus = 10
)
type UserCenterBasicUserInfo struct {
ExternalID string `json:"external_id"`
Username string `json:"username"`
DisplayName string `json:"display_name"`
Email string `json:"email"`
Rank int `json:"rank"`
Avatar string `json:"avatar"`
Mobile string `json:"mobile"`
Status UserStatus `json:"status"`
}
type ControlCenter struct {
Name string `json:"name"`
Label string `json:"label"`
Url string `json:"url"`
}
type SettingInfo struct {
ProfileSettingRedirectURL string `json:"profile_setting_redirect_url"`
AccountSettingRedirectURL string `json:"account_setting_redirect_url"`
}
type PersonalBranding struct {
Icon string `json:"icon"`
Name string `json:"name"`
Label string `json:"label"`
Url string `json:"url"`
}
var (
// CallUserCenter is a function that calls all registered parsers
CallUserCenter,
registerUserCenter = MakePlugin[UserCenter](false)
)
func UserCenterEnabled() (enabled bool) {
_ = CallUserCenter(func(fn UserCenter) error {
enabled = true
return nil
})
return
}
func RankAgentEnabled() (enabled bool) {
_ = CallUserCenter(func(fn UserCenter) error {
enabled = fn.Description().RankAgentEnabled
return nil
})
return
}
func GetUserCenter() (uc UserCenter, ok bool) {
_ = CallUserCenter(func(fn UserCenter) error {
uc = fn
ok = true
return nil
})
return
}