Merge branch 'release/1.1.3'

This commit is contained in:
LinkinStars 2023-09-06 16:25:17 +08:00
commit 818af49f27
61 changed files with 648 additions and 577 deletions

View File

@ -1,6 +1,6 @@
.PHONY: build clean ui
VERSION=1.1.2
VERSION=1.1.3
BIN=answer
DIR_SRC=./cmd/answer
DOCKER_CMD=docker

View File

@ -130,10 +130,10 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
userExternalLoginRepo := user_external_login.NewUserExternalLoginRepo(dataData)
userExternalLoginService := user_external_login2.NewUserExternalLoginService(userRepo, userCommon, userExternalLoginRepo, emailService, siteInfoCommonService, userActiveActivityRepo)
userNotificationConfigRepo := user_notification_config.NewUserNotificationConfigRepo(dataData)
userService := service.NewUserService(userRepo, userActiveActivityRepo, activityRepo, emailService, authService, siteInfoCommonService, userRoleRelService, userCommon, userExternalLoginService, userNotificationConfigRepo)
userNotificationConfigService := user_notification_config2.NewUserNotificationConfigService(userRepo, userNotificationConfigRepo)
userService := service.NewUserService(userRepo, userActiveActivityRepo, activityRepo, emailService, authService, siteInfoCommonService, userRoleRelService, userCommon, userExternalLoginService, userNotificationConfigRepo, userNotificationConfigService)
captchaRepo := captcha.NewCaptchaRepo(dataData)
captchaService := action.NewCaptchaService(captchaRepo)
userNotificationConfigService := user_notification_config2.NewUserNotificationConfigService(userRepo, userNotificationConfigRepo)
userController := controller.NewUserController(authService, userService, captchaService, emailService, siteInfoCommonService, userNotificationConfigService)
commentRepo := comment.NewCommentRepo(dataData, uniqueIDRepo)
commentCommonRepo := comment.NewCommentCommonRepo(dataData, uniqueIDRepo)
@ -184,7 +184,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
questionController := controller.NewQuestionController(questionService, answerService, rankService, siteInfoCommonService, captchaService)
answerController := controller.NewAnswerController(answerService, rankService, captchaService)
searchParser := search_parser.NewSearchParser(tagCommonService, userCommon)
searchRepo := search_common.NewSearchRepo(dataData, uniqueIDRepo, userCommon)
searchRepo := search_common.NewSearchRepo(dataData, uniqueIDRepo, userCommon, tagCommonService)
searchService := service.NewSearchService(searchParser, searchRepo)
searchController := controller.NewSearchController(searchService, captchaService)
serviceRevisionService := service.NewRevisionService(revisionRepo, userCommon, questionCommon, answerService, objService, questionRepo, answerRepo, tagRepo, tagCommonService, notificationQueueService, activityQueueService)

36
go.mod
View File

@ -25,7 +25,7 @@ require (
github.com/lib/pq v1.10.7
github.com/microcosm-cc/bluemonday v1.0.21
github.com/mojocn/base64Captcha v1.3.5
github.com/ory/dockertest/v3 v3.9.1
github.com/ory/dockertest/v3 v3.10.0
github.com/robfig/cron/v3 v3.0.1
github.com/scottleedavis/go-exif-remove v0.0.0-20230314195146-7e059d593405
github.com/segmentfault/pacman v1.0.5-0.20230822083413-c0075a2d401f
@ -41,8 +41,8 @@ require (
github.com/swaggo/swag v1.16.1
github.com/tidwall/gjson v1.14.4
github.com/yuin/goldmark v1.4.13
golang.org/x/crypto v0.11.0
golang.org/x/net v0.12.0
golang.org/x/crypto v0.13.0
golang.org/x/net v0.15.0
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.24.0
@ -51,22 +51,22 @@ require (
)
require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/LinkinStars/go-i18n/v2 v2.2.2 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/containerd/continuity v0.3.0 // indirect
github.com/containerd/continuity v0.4.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v20.10.14+incompatible // indirect
github.com/docker/docker v20.10.7+incompatible // indirect
github.com/docker/cli v24.0.6+incompatible // indirect
github.com/docker/docker v24.0.6+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dsoprea/go-exif v0.0.0-20190901173045-3ce78807c90f // indirect
github.com/dsoprea/go-jpeg-image-structure v0.0.0-20190422055009-d6f9ba25cf48 // indirect
github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696 // indirect
@ -87,7 +87,7 @@ require (
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
@ -100,19 +100,19 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/opencontainers/runc v1.1.2 // indirect
github.com/opencontainers/runc v1.1.9 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/afero v1.9.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
@ -124,7 +124,7 @@ require (
github.com/tidwall/pretty v1.2.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
@ -133,9 +133,9 @@ require (
golang.org/x/arch v0.3.0 // indirect
golang.org/x/image v0.1.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/tools v0.11.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/tools v0.13.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/ini.v1 v1.67.0 // indirect

112
go.sum
View File

@ -39,8 +39,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
@ -56,8 +56,8 @@ github.com/Machiel/slugify v1.0.1 h1:EfWSlRWstMadsgzmiV7d0yVd2IFlagWH68Q+DcYCm4E
github.com/Machiel/slugify v1.0.1/go.mod h1:fTFGn5uWEynW4CUMG7sWkYXOf1UgDxyTM3DbR6Qfg3k=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
@ -91,7 +91,6 @@ github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZw
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
@ -103,18 +102,16 @@ github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
@ -123,22 +120,18 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht
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=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg=
github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM=
github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM=
github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -146,14 +139,14 @@ github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/docker/cli v20.10.14+incompatible h1:dSBKJOVesDgHo7rbxlYjYsXe7gPzrTT+/cKQgpDAazg=
github.com/docker/cli v20.10.14+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v20.10.7+incompatible h1:Z6O9Nhsjv+ayUEeI1IojKbYcsGdgYSNqxe1s2MYzUhQ=
github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWTcc7GAneOY=
github.com/docker/cli v24.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE=
github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dsoprea/go-exif v0.0.0-20190901173045-3ce78807c90f h1:vqfYiZ+xF0xJvl9SZ1kovmMgKjaGZIz/Hn8JDQdyd9A=
github.com/dsoprea/go-exif v0.0.0-20190901173045-3ce78807c90f/go.mod h1:DmMpU91/Ax6BAwoRkjgRCr2rmgEgS4tsmatfV7M+U+c=
github.com/dsoprea/go-jpeg-image-structure v0.0.0-20190422055009-d6f9ba25cf48 h1:9zARagUAxQJjibcDy+0+koUMR6sbX38L49Bk2Vni628=
@ -180,7 +173,6 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
@ -246,8 +238,6 @@ github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGF
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
@ -375,8 +365,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
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=
@ -525,9 +515,8 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -537,7 +526,6 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4PfoagaA0=
github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY=
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
@ -560,10 +548,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v1.1.2 h1:2VSZwLx5k/BfsBxMMipG/LYUnmqOD/BPkIVgQUcTlLw=
github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc=
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
github.com/opencontainers/runc v1.1.9 h1:XR0VIHTGce5eWPkaPesqTBrhW2yAcaraWfsEalNwQLM=
github.com/opencontainers/runc v1.1.9/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@ -572,8 +558,8 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/ory/dockertest/v3 v3.9.1 h1:v4dkG+dlu76goxMiTT2j8zV7s4oPPEppKT8K8p2f1kY=
github.com/ory/dockertest/v3 v3.9.1/go.mod h1:42Ir9hmvaAPm0Mgibk6mBPi7SFvTXxEcnztDYOJ//uM=
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg=
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
@ -640,13 +626,8 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh
github.com/scottleedavis/go-exif-remove v0.0.0-20230314195146-7e059d593405 h1:2ieGkj4z/YPXVyQ2ayZUg3GwE1pYWd5f1RB6DzAOXKM=
github.com/scottleedavis/go-exif-remove v0.0.0-20230314195146-7e059d593405/go.mod h1:rIxVzVLKlBwLxO+lC+k/I4HJfRQcemg/f/76Xmmzsec=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
github.com/segmentfault/pacman v1.0.5-0.20230822075009-309985fb8700 h1:VqxiuNGQg86GEKxnzmJegZR2Ufr7EVXo68mdKaf+/MQ=
github.com/segmentfault/pacman v1.0.5-0.20230822075009-309985fb8700/go.mod h1:5lNp5REd8QMThmBUvR3Fi9Y3AsOB4GRq7soCB4QLqOs=
github.com/segmentfault/pacman v1.0.5-0.20230822083413-c0075a2d401f h1:9f2Bjf6bdMvNyUop32wAGJCdp+Jdm/d6nKBYvFvkRo0=
github.com/segmentfault/pacman v1.0.5-0.20230822083413-c0075a2d401f/go.mod h1:5lNp5REd8QMThmBUvR3Fi9Y3AsOB4GRq7soCB4QLqOs=
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20230822075009-309985fb8700 h1:VZpexPTcr7sOxxYUGa/9cneyCESfisAhCNbaEuTpcwI=
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20230822075009-309985fb8700/go.mod h1:rmf1TCwz67dyM+AmTwSd1BxTo2AOYHj262lP93bOZbs=
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20230822083413-c0075a2d401f h1:1KHe0uN6p798E7XJZPhZkgm/hXk5CTjisCvFMqaZSKI=
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20230822083413-c0075a2d401f/go.mod h1:rmf1TCwz67dyM+AmTwSd1BxTo2AOYHj262lP93bOZbs=
github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20221018072427-a15dd1434e05 h1:BlqTgc3/MYKG6vMI2MI+6o+7P4Gy5PXlawu185wPXAk=
@ -664,9 +645,8 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
@ -681,7 +661,6 @@ github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUq
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU=
@ -717,7 +696,6 @@ github.com/swaggo/gin-swagger v1.5.3/go.mod h1:3XJKSfHjDMB5dBo/0rrTXidPmgLeqsX89
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
github.com/swaggo/swag v1.16.1 h1:fTNRhKstPKxcnoKsytm4sahr8FaYzUcT7i1/3nd/fBg=
github.com/swaggo/swag v1.16.1/go.mod h1:9/LMvHycG3NFHfR6LwvikHv5iFvmPADQ359cKikGxto=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
@ -738,10 +716,9 @@ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
@ -777,7 +754,6 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
@ -812,8 +788,8 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
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=
@ -901,8 +877,8 @@ golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -942,14 +918,12 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -967,7 +941,6 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -975,7 +948,6 @@ golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -983,24 +955,23 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -1016,8 +987,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -1038,7 +1009,6 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -1085,8 +1055,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -1190,7 +1160,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.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.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
@ -1230,8 +1199,7 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.2.0 h1:I0DwBVMGAx26dttAj1BtJLAkVGncrkkUXfJLC4Flt/I=
gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -1292,16 +1292,16 @@ ui:
page_title: CSS and HTML
custom_css:
label: Custom CSS
text: This will insert as <link>
text: This will insert as &lt;link>
head:
label: Head
text: This will insert before </head>
text: This will insert before &lt;/head>
header:
label: Header
text: This will insert after <body>
text: This will insert after &lt;body>
footer:
label: Footer
text: This will insert before </html>.
text: This will insert before &lt;/body>.
login:
page_title: Login
membership:

View File

@ -1263,12 +1263,14 @@ ui:
site_name:
label: Site name
msg: Site name cannot be empty.
msg_max_length: Site name must be at maximum 30 characters in length.
site_url:
label: Site URL
text: The address of your site.
msg:
empty: Site URL cannot be empty.
incorrect: Site URL incorrect format.
max_length: Site URL must be at maximum 512 characters in length.
contact_email:
label: Contact email
text: Email address of key contact responsible for this site.
@ -1283,12 +1285,15 @@ ui:
label: Name
msg: Name cannot be empty.
character: 'Must use the character set "a-z", "0-9", " - . _"'
msg_max_length: Name must be at maximum 30 characters in length.
admin_password:
label: Password
text: >-
You will need this password to log in. Please store it in a secure
location.
msg: Password cannot be empty.
msg_min_length: Password must be at least 8 characters in length.
msg_max_length: Password must be at maximum 32 characters in length.
admin_email:
label: Email
text: You will need this email to log in.
@ -1652,16 +1657,16 @@ ui:
page_title: CSS and HTML
custom_css:
label: Custom CSS
text: This will insert as <link>
text: This will insert as &lt;link>
head:
label: Head
text: This will insert before </head>
text: This will insert before &lt;/head>
header:
label: Header
text: This will insert after <body>
text: This will insert after &lt;body>
footer:
label: Footer
text: This will insert before </body>.
text: This will insert before &lt;/body>.
sidebar:
label: Sidebar
text: This will insert in sidebar.

View File

@ -1607,16 +1607,16 @@ ui:
page_title: CSS 与 HTML
custom_css:
label: 自定义 CSS
text: 这将以 <link> 方式插入
text: 这将以 &lt;link> 方式插入
head:
label: 头部
text: 这将在 </head> 之前插入
text: 这将在 &lt;/head> 之前插入
header:
label: 页眉
text: 这将在 <body> 之后插入
text: 这将在 &lt;body> 之后插入
footer:
label: 页脚
text: 这将在 </body> 之前插入.
text: 这将在 &lt;/body> 之前插入.
sidebar:
label: 侧边栏
text: 这将插入侧边栏中。

View File

@ -14,6 +14,7 @@ const (
SiteInfoCacheTime = 1 * time.Hour
ConfigID2KEYCacheKeyPrefix = "answer:config:id:"
ConfigKEY2ContentCacheKeyPrefix = "answer:config:key:"
ConfigCacheTime = 1 * time.Hour
ConnectorUserExternalInfoCacheKey = "answer:connector:"
ConnectorUserExternalInfoCacheTime = 10 * time.Minute
SiteMapQuestionCacheKeyPrefix = "answer:sitemap:question:%d"

View File

@ -2,15 +2,13 @@ package handler
import (
"errors"
"net/http"
"strings"
"github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/base/validator"
"github.com/gin-gonic/gin"
myErrors "github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/log"
"net/http"
)
// HandleResponse Handle response body
@ -74,10 +72,3 @@ func BindAndCheckReturnErr(ctx *gin.Context, data interface{}) (errFields []*val
errFields, _ = validator.GetValidatorByLang(lang).Check(data)
return errFields
}
func MsgWithParameter(msg string, list map[string]string) string {
for k, v := range list {
msg = strings.Replace(msg, "{{ "+k+" }}", v, -1)
}
return msg
}

View File

@ -319,11 +319,11 @@ func (ac *AnswerController) AnswerList(ctx *gin.Context) {
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param data body schema.AnswerAcceptedReq true "AnswerAcceptedReq"
// @Param data body schema.AcceptAnswerReq true "AcceptAnswerReq"
// @Success 200 {string} string ""
// @Router /answer/api/v1/answer/acceptance [post]
func (ac *AnswerController) Accepted(ctx *gin.Context) {
req := &schema.AnswerAcceptedReq{}
req := &schema.AcceptAnswerReq{}
if handler.BindAndCheck(ctx, req) {
return
}
@ -341,7 +341,7 @@ func (ac *AnswerController) Accepted(ctx *gin.Context) {
return
}
err = ac.answerService.UpdateAccepted(ctx, req)
err = ac.answerService.AcceptAnswer(ctx, req)
handler.HandleResponse(ctx, err, nil)
}

View File

@ -157,20 +157,23 @@ func (cc *CommentController) UpdateComment(ctx *gin.Context) {
}
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
req.IsAdmin = middleware.GetIsAdminFromContext(ctx)
canList, err := cc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
permission.CommentAdd,
permission.CommentEdit,
permission.CommentDelete,
permission.LinkUrlLimit,
})
if err != nil {
handler.HandleResponse(ctx, err, nil)
return
}
linkUrlLimitUser := canList[3]
req.IsAdmin = middleware.GetIsAdminFromContext(ctx)
isAdmin := middleware.GetUserIsAdminModerator(ctx)
if !isAdmin || !linkUrlLimitUser {
req.CanEdit = canList[0] || cc.rankService.CheckOperationObjectOwner(ctx, req.UserID, req.CommentID)
linkUrlLimitUser := canList[1]
if !req.CanEdit {
handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil)
return
}
if !req.IsAdmin || !linkUrlLimitUser {
captchaPass := cc.actionService.ActionRecordVerifyCaptcha(ctx, entity.CaptchaActionEdit, req.UserID, req.CaptchaID, req.CaptchaCode)
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
@ -182,21 +185,8 @@ func (cc *CommentController) UpdateComment(ctx *gin.Context) {
}
}
req.CanAdd = canList[0]
req.CanEdit = canList[1]
req.CanDelete = canList[2]
can, err := cc.rankService.CheckOperationPermission(ctx, req.UserID, permission.CommentEdit, req.CommentID)
if err != nil {
handler.HandleResponse(ctx, err, nil)
return
}
if !can {
handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil)
return
}
resp, err := cc.commentService.UpdateComment(ctx, req)
if !isAdmin || !linkUrlLimitUser {
if !req.IsAdmin || !linkUrlLimitUser {
cc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionEdit, req.UserID)
}
handler.HandleResponse(ctx, err, resp)

View File

@ -686,9 +686,9 @@ func (qc *QuestionController) UpdateQuestionInviteUser(ctx *gin.Context) {
handler.HandleResponse(ctx, nil, nil)
}
// SearchByTitleLike add question title like
// @Summary add question title like
// @Description add question title like
// GetSimilarQuestions fuzzy query similar questions based on title
// @Summary fuzzy query similar questions based on title
// @Description fuzzy query similar questions based on title
// @Tags Question
// @Accept json
// @Produce json
@ -696,10 +696,9 @@ func (qc *QuestionController) UpdateQuestionInviteUser(ctx *gin.Context) {
// @Param title query string true "title" default(string)
// @Success 200 {object} handler.RespBody
// @Router /answer/api/v1/question/similar [get]
func (qc *QuestionController) SearchByTitleLike(ctx *gin.Context) {
func (qc *QuestionController) GetSimilarQuestions(ctx *gin.Context) {
title := ctx.Query("title")
userID := middleware.GetLoginUserIDFromContext(ctx)
resp, err := qc.questionService.SearchByTitleLike(ctx, title, userID)
resp, err := qc.questionService.GetQuestionsByTitle(ctx, title)
handler.HandleResponse(ctx, err, resp)
}

View File

@ -45,7 +45,6 @@ func (tc *TagController) SearchTagLike(ctx *gin.Context) {
if handler.BindAndCheck(ctx, req) {
return
}
req.IsAdmin = middleware.GetIsAdminFromContext(ctx)
resp, err := tc.tagCommonService.SearchTagLike(ctx, req)
handler.HandleResponse(ctx, err, resp)
}

View File

@ -161,35 +161,41 @@ func (tc *TemplateController) QuestionList(ctx *gin.Context) {
}
func (tc *TemplateController) QuestionInfoeRdirect(ctx *gin.Context, siteInfo *schema.TemplateSiteInfoResp, correctTitle bool) (jump bool, url string) {
id := ctx.Param("id")
questionID := ctx.Param("id")
title := ctx.Param("title")
answerID := uid.DeShortID(title)
titleIsAnswerID := false
needChangeShortID := false
objectType, err := obj.GetObjectTypeStrByObjectID(answerID)
if err == nil && objectType == constant.AnswerObjectType {
titleIsAnswerID = true
}
siteSeo, err := tc.siteInfoService.GetSiteSeo(ctx)
if err != nil {
return false, ""
}
isShortID := uid.IsShortID(id)
isShortID := uid.IsShortID(questionID)
if siteSeo.IsShortLink() {
if !isShortID {
id = uid.EnShortID(id)
questionID = uid.EnShortID(questionID)
needChangeShortID = true
}
if titleIsAnswerID {
answerID = uid.EnShortID(answerID)
}
} else {
if isShortID {
needChangeShortID = true
id = uid.DeShortID(id)
questionID = uid.DeShortID(questionID)
}
if titleIsAnswerID {
answerID = uid.DeShortID(answerID)
}
}
objectType, objectTypeerr := obj.GetObjectTypeStrByObjectID(uid.DeShortID(title))
if objectTypeerr == nil {
if objectType == constant.AnswerObjectType {
titleIsAnswerID = true
}
}
url = fmt.Sprintf("%s/questions/%s", siteInfo.General.SiteUrl, id)
url = fmt.Sprintf("%s/questions/%s", siteInfo.General.SiteUrl, questionID)
if siteInfo.SiteSeo.PermaLink == constant.PermaLinkQuestionID || siteInfo.SiteSeo.PermaLink == constant.PermaLinkQuestionIDByShortID {
if len(ctx.Request.URL.Query()) > 0 {
url = fmt.Sprintf("%s?%s", url, ctx.Request.URL.RawQuery)
@ -205,14 +211,14 @@ func (tc *TemplateController) QuestionInfoeRdirect(ctx *gin.Context, siteInfo *s
return true, url
} else {
detail, err := tc.templateRenderController.QuestionDetail(ctx, id)
detail, err := tc.templateRenderController.QuestionDetail(ctx, questionID)
if err != nil {
tc.Page404(ctx)
return
}
url = fmt.Sprintf("%s/%s", url, htmltext.UrlTitle(detail.Title))
if titleIsAnswerID {
url = fmt.Sprintf("%s/%s", url, title)
url = fmt.Sprintf("%s/%s", url, answerID)
}
if len(ctx.Request.URL.Query()) > 0 {

View File

@ -2,6 +2,7 @@ package entity
import (
"encoding/json"
"github.com/segmentfault/pacman/log"
"github.com/answerdev/answer/pkg/converter"
)
@ -33,6 +34,9 @@ func (c *Config) JsonString() string {
// GetIntValue get int value
func (c *Config) GetIntValue() int {
if len(c.Value) == 0 {
log.Warnf("config value is empty, key: %s, value: %s", c.Key, c.Value)
}
return converter.StringToInt(c.Value)
}

View File

@ -72,6 +72,7 @@ var migrations = []Migration{
NewMigration("v1.1.0", "add gravatar base url", updateCount, true),
NewMigration("v1.1.1", "update the length of revision content", updateTheLengthOfRevisionContent, false),
NewMigration("v1.1.2", "add notification config", addNoticeConfig, true),
NewMigration("v1.1.3", "set default user notification config", setDefaultUserNotificationConfig, false),
}
func GetMigrations() []Migration {

View File

@ -0,0 +1,38 @@
package migrations
import (
"context"
"github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/entity"
"github.com/segmentfault/pacman/log"
"xorm.io/xorm"
)
func setDefaultUserNotificationConfig(ctx context.Context, x *xorm.Engine) error {
userIDs := make([]string, 0)
err := x.Context(ctx).Table("user").Select("id").Find(&userIDs)
if err != nil {
return err
}
for _, id := range userIDs {
bean := entity.UserNotificationConfig{UserID: id, Source: string(constant.InboxSource)}
exist, err := x.Context(ctx).Get(&bean)
if err != nil {
log.Error(err)
}
if exist {
continue
}
_, err = x.Context(ctx).Insert(&entity.UserNotificationConfig{
UserID: id,
Source: string(constant.InboxSource),
Channels: `[{"key":"email","enable":true}]`,
Enabled: true,
})
if err != nil {
log.Error(err)
}
}
return nil
}

View File

@ -46,15 +46,6 @@ func NewAnswerActivityRepo(
func (ar *AnswerActivityRepo) SaveAcceptAnswerActivity(ctx context.Context, op *schema.AcceptAnswerOperationInfo) (
err error) {
// pre check
noNeedToDo, err := ar.activityPreCheck(ctx, op)
if err != nil {
return err
}
if noNeedToDo {
return nil
}
// save activity
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
session = session.Context(ctx)
@ -131,21 +122,6 @@ func (ar *AnswerActivityRepo) SaveCancelAcceptAnswerActivity(ctx context.Context
return nil
}
func (ar *AnswerActivityRepo) activityPreCheck(ctx context.Context, op *schema.AcceptAnswerOperationInfo) (
noNeedToDo bool, err error) {
activities, err := ar.getExistActivity(ctx, op)
if err != nil {
return false, err
}
done := 0
for _, act := range activities {
if act.Cancelled == entity.ActivityAvailable {
done++
}
}
return done == len(op.Activities), nil
}
func (ar *AnswerActivityRepo) acquireUserInfo(session *xorm.Session, userIDs []string) (map[string]*entity.User, error) {
us := make([]*entity.User, 0)
err := session.In("id", userIDs).ForUpdate().Find(&us)
@ -286,18 +262,17 @@ func (ar *AnswerActivityRepo) rollbackUserRank(ctx context.Context, session *xor
func (ar *AnswerActivityRepo) getExistActivity(ctx context.Context, op *schema.AcceptAnswerOperationInfo) ([]*entity.Activity, error) {
var activities []*entity.Activity
for _, action := range op.Activities {
t := &entity.Activity{}
exist, err := ar.data.DB.Context(ctx).
var t []*entity.Activity
err := ar.data.DB.Context(ctx).
Where(builder.Eq{"user_id": action.ActivityUserID}).
And(builder.Eq{"trigger_user_id": action.TriggerUserID}).
And(builder.Eq{"activity_type": action.ActivityType}).
And(builder.Eq{"object_id": op.AnswerObjectID}).
Get(t)
Find(&t)
if err != nil {
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
if exist {
activities = append(activities, t)
if len(t) > 0 {
activities = append(activities, t...)
}
}
return activities, nil

View File

@ -166,30 +166,29 @@ func (ar *answerRepo) GetAnswerPage(ctx context.Context, page, pageSize int, ans
return
}
// UpdateAccepted
// If no answer is selected, the answer id can be 0
func (ar *answerRepo) UpdateAccepted(ctx context.Context, id string, questionID string) error {
if questionID == "" {
return nil
}
id = uid.DeShortID(id)
// UpdateAcceptedStatus update all accepted status of this question's answers
func (ar *answerRepo) UpdateAcceptedStatus(ctx context.Context, acceptedAnswerID string, questionID string) error {
acceptedAnswerID = uid.DeShortID(acceptedAnswerID)
questionID = uid.DeShortID(questionID)
var data entity.Answer
data.ID = id
data.Accepted = schema.AnswerAcceptedFailed
_, err := ar.data.DB.Context(ctx).Where("question_id =?", questionID).Cols("adopted").Update(&data)
// update all this question's answer accepted status to false
_, err := ar.data.DB.Context(ctx).Where("question_id = ?", questionID).Cols("adopted").Update(&entity.Answer{
Accepted: schema.AnswerAcceptedFailed,
})
if err != nil {
return err
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
if id != "0" {
data.Accepted = schema.AnswerAcceptedEnable
_, err = ar.data.DB.Context(ctx).Where("id = ?", id).Cols("adopted").Update(&data)
// if acceptedAnswerID is not empty, update accepted status to true
if len(acceptedAnswerID) > 0 && acceptedAnswerID != "0" {
_, err = ar.data.DB.Context(ctx).Where("id = ?", acceptedAnswerID).Cols("adopted").Update(&entity.Answer{
Accepted: schema.AnswerAcceptedEnable,
})
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
}
_ = ar.updateSearch(ctx, id)
_ = ar.updateSearch(ctx, acceptedAnswerID)
return nil
}
@ -259,7 +258,7 @@ func (ar *answerRepo) SearchList(ctx context.Context, search *entity.AnswerSearc
search.PageSize = constant.DefaultPageSize
}
offset := search.Page * search.PageSize
session := ar.data.DB.Context(ctx).Where("")
session := ar.data.DB.Context(ctx)
if search.QuestionID != "" {
session = session.And("question_id = ?", search.QuestionID)

View File

@ -2,9 +2,9 @@ package collection
import (
"context"
"github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/base/data"
"github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/pager"
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/entity"
@ -142,25 +142,32 @@ func (cr *collectionRepo) GetCollectionPage(ctx context.Context, page, pageSize
session = session.OrderBy("update_time desc")
total, err = pager.Help(page, pageSize, collectionList, collection, session)
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return
}
// SearchObjectCollected check object is collected or not
func (cr *collectionRepo) SearchObjectCollected(ctx context.Context, userID string, objectIds []string) (map[string]bool, error) {
collectedMap := make(map[string]bool)
for k, object_id := range objectIds {
objectIds[k] = uid.DeShortID(object_id)
for i := 0; i < len(objectIds); i++ {
objectIds[i] = uid.DeShortID(objectIds[i])
}
list, err := cr.SearchByObjectIDsAndUser(ctx, userID, objectIds)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
return collectedMap, err
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
collectedMap := make(map[string]bool)
short := handler.GetEnableShortID(ctx)
for _, item := range list {
if short {
item.ObjectID = uid.EnShortID(item.ObjectID)
}
collectedMap[item.ObjectID] = true
}
return collectedMap, err
return collectedMap, nil
}
// SearchList

View File

@ -58,9 +58,13 @@ func (cr *commentRepo) RemoveComment(ctx context.Context, commentID string) (err
return
}
// UpdateComment update comment
func (cr *commentRepo) UpdateComment(ctx context.Context, comment *entity.Comment) (err error) {
_, err = cr.data.DB.Context(ctx).ID(comment.ID).Where("user_id = ?", comment.UserID).Update(comment)
// UpdateCommentContent update comment
func (cr *commentRepo) UpdateCommentContent(
ctx context.Context, commentID string, originalText string, parsedText string) (err error) {
_, err = cr.data.DB.Context(ctx).ID(commentID).Update(&entity.Comment{
OriginalText: originalText,
ParsedText: parsedText,
})
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
@ -69,8 +73,7 @@ func (cr *commentRepo) UpdateComment(ctx context.Context, comment *entity.Commen
// GetComment get comment one
func (cr *commentRepo) GetComment(ctx context.Context, commentID string) (
comment *entity.Comment, exist bool, err error,
) {
comment *entity.Comment, exist bool, err error) {
comment = &entity.Comment{}
exist, err = cr.data.DB.Context(ctx).ID(commentID).Get(comment)
if err != nil {

View File

@ -28,7 +28,8 @@ func NewConfigRepo(data *data.Data) config.ConfigRepo {
func (cr configRepo) GetConfigByID(ctx context.Context, id int) (c *entity.Config, err error) {
cacheKey := fmt.Sprintf("%s%d", constant.ConfigID2KEYCacheKeyPrefix, id)
if cacheData, exist, err := cr.data.Cache.GetString(ctx, cacheKey); err == nil && exist {
cacheData, exist, err := cr.data.Cache.GetString(ctx, cacheKey)
if err == nil && exist && len(cacheData) > 0 {
c = &entity.Config{}
c.BuildByJSON([]byte(cacheData))
if c.ID > 0 {
@ -37,7 +38,7 @@ func (cr configRepo) GetConfigByID(ctx context.Context, id int) (c *entity.Confi
}
c = &entity.Config{}
exist, err := cr.data.DB.Context(ctx).ID(id).Get(c)
exist, err = cr.data.DB.Context(ctx).ID(id).Get(c)
if err != nil {
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
@ -46,7 +47,7 @@ func (cr configRepo) GetConfigByID(ctx context.Context, id int) (c *entity.Confi
}
// update cache
if err := cr.data.Cache.SetString(ctx, cacheKey, c.JsonString(), -1); err != nil {
if err := cr.data.Cache.SetString(ctx, cacheKey, c.JsonString(), constant.ConfigCacheTime); err != nil {
log.Error(err)
}
return c, nil
@ -54,7 +55,8 @@ func (cr configRepo) GetConfigByID(ctx context.Context, id int) (c *entity.Confi
func (cr configRepo) GetConfigByKey(ctx context.Context, key string) (c *entity.Config, err error) {
cacheKey := constant.ConfigKEY2ContentCacheKeyPrefix + key
if cacheData, exist, err := cr.data.Cache.GetString(ctx, cacheKey); err == nil && exist {
cacheData, exist, err := cr.data.Cache.GetString(ctx, cacheKey)
if err == nil && exist && len(cacheData) > 0 {
c = &entity.Config{}
c.BuildByJSON([]byte(cacheData))
if c.ID > 0 {
@ -63,7 +65,7 @@ func (cr configRepo) GetConfigByKey(ctx context.Context, key string) (c *entity.
}
c = &entity.Config{Key: key}
exist, err := cr.data.DB.Context(ctx).Get(c)
exist, err = cr.data.DB.Context(ctx).Get(c)
if err != nil {
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
@ -72,7 +74,7 @@ func (cr configRepo) GetConfigByKey(ctx context.Context, key string) (c *entity.
}
// update cache
if err := cr.data.Cache.SetString(ctx, cacheKey, c.JsonString(), -1); err != nil {
if err := cr.data.Cache.SetString(ctx, cacheKey, c.JsonString(), constant.ConfigCacheTime); err != nil {
log.Error(err)
}
return c, nil
@ -99,11 +101,11 @@ func (cr configRepo) UpdateConfig(ctx context.Context, key string, value string)
cacheVal := oldConfig.JsonString()
// update cache
if err := cr.data.Cache.SetString(ctx,
constant.ConfigKEY2ContentCacheKeyPrefix+key, cacheVal, -1); err != nil {
constant.ConfigKEY2ContentCacheKeyPrefix+key, cacheVal, constant.ConfigCacheTime); err != nil {
log.Error(err)
}
if err := cr.data.Cache.SetString(ctx,
fmt.Sprintf("%s%d", constant.ConfigID2KEYCacheKeyPrefix, oldConfig.ID), cacheVal, -1); err != nil {
fmt.Sprintf("%s%d", constant.ConfigID2KEYCacheKeyPrefix, oldConfig.ID), cacheVal, constant.ConfigCacheTime); err != nil {
log.Error(err)
}
return

View File

@ -186,10 +186,15 @@ func (qr *questionRepo) GetQuestion(ctx context.Context, id string) (
return
}
// GetTagBySlugName get tag by slug name
func (qr *questionRepo) SearchByTitleLike(ctx context.Context, title string) (questionList []*entity.Question, err error) {
// GetQuestionsByTitle get question list by title
func (qr *questionRepo) GetQuestionsByTitle(ctx context.Context, title string, pageSize int) (
questionList []*entity.Question, err error) {
questionList = make([]*entity.Question, 0)
err = qr.data.DB.Context(ctx).Table("question").Where("title like ?", "%"+title+"%").Limit(10, 0).Find(&questionList)
session := qr.data.DB.Context(ctx)
session.Where("status != ?", entity.QuestionStatusDeleted)
session.Where("title like ?", "%"+title+"%")
session.Limit(pageSize)
err = session.Find(&questionList)
if err != nil {
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}

View File

@ -65,7 +65,7 @@ func Test_commentRepo_UpdateComment(t *testing.T) {
assert.NoError(t, err)
testCommentEntity.ParsedText = "test"
err = commentRepo.UpdateComment(context.TODO(), testCommentEntity)
err = commentRepo.UpdateCommentContent(context.TODO(), testCommentEntity, "", "")
assert.NoError(t, err)
newComment, exist, err := commonCommentRepo.GetComment(context.TODO(), testCommentEntity.ID)

View File

@ -3,6 +3,7 @@ package search_common
import (
"context"
"fmt"
tagcommon "github.com/answerdev/answer/internal/service/tag_common"
"github.com/answerdev/answer/plugin"
"strconv"
"strings"
@ -19,7 +20,6 @@ import (
usercommon "github.com/answerdev/answer/internal/service/user_common"
"github.com/answerdev/answer/pkg/converter"
"github.com/answerdev/answer/pkg/obj"
"github.com/jinzhu/copier"
"github.com/segmentfault/pacman/errors"
"xorm.io/builder"
)
@ -58,19 +58,26 @@ type searchRepo struct {
data *data.Data
userCommon *usercommon.UserCommon
uniqueIDRepo unique.UniqueIDRepo
tagCommon *tagcommon.TagCommonService
}
// NewSearchRepo new repository
func NewSearchRepo(data *data.Data, uniqueIDRepo unique.UniqueIDRepo, userCommon *usercommon.UserCommon) search_common.SearchRepo {
func NewSearchRepo(
data *data.Data,
uniqueIDRepo unique.UniqueIDRepo,
userCommon *usercommon.UserCommon,
tagCommon *tagcommon.TagCommonService,
) search_common.SearchRepo {
return &searchRepo{
data: data,
uniqueIDRepo: uniqueIDRepo,
userCommon: userCommon,
tagCommon: tagCommon,
}
}
// SearchContents search question and answer data
func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagIDs []string, userID string, votes int, page, size int, order string) (resp []schema.SearchResult, total int64, err error) {
func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagIDs []string, userID string, votes int, page, size int, order string) (resp []*schema.SearchResult, total int64, err error) {
words = filterWords(words)
var (
@ -210,7 +217,7 @@ func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagIDs
}
// SearchQuestions search question data
func (sr *searchRepo) SearchQuestions(ctx context.Context, words []string, tagIDs []string, notAccepted bool, views, answers int, page, size int, order string) (resp []schema.SearchResult, total int64, err error) {
func (sr *searchRepo) SearchQuestions(ctx context.Context, words []string, tagIDs []string, notAccepted bool, views, answers int, page, size int, order string) (resp []*schema.SearchResult, total int64, err error) {
words = filterWords(words)
var (
qfs = qFields
@ -319,7 +326,7 @@ func (sr *searchRepo) SearchQuestions(ctx context.Context, words []string, tagID
}
// SearchAnswers search answer data
func (sr *searchRepo) SearchAnswers(ctx context.Context, words []string, tagIDs []string, accepted bool, questionID string, page, size int, order string) (resp []schema.SearchResult, total int64, err error) {
func (sr *searchRepo) SearchAnswers(ctx context.Context, words []string, tagIDs []string, accepted bool, questionID string, page, size int, order string) (resp []*schema.SearchResult, total int64, err error) {
words = filterWords(words)
var (
@ -428,7 +435,7 @@ func (sr *searchRepo) parseOrder(ctx context.Context, order string) (res string)
}
// ParseSearchPluginResult parse search plugin result
func (sr *searchRepo) ParseSearchPluginResult(ctx context.Context, sres []plugin.SearchResult) (resp []schema.SearchResult, err error) {
func (sr *searchRepo) ParseSearchPluginResult(ctx context.Context, sres []plugin.SearchResult) (resp []*schema.SearchResult, err error) {
var (
qres []map[string][]byte
res = make([]map[string][]byte, 0)
@ -455,82 +462,79 @@ func (sr *searchRepo) ParseSearchPluginResult(ctx context.Context, sres []plugin
}
// parseResult parse search result, return the data structure
func (sr *searchRepo) parseResult(ctx context.Context, res []map[string][]byte) (resp []schema.SearchResult, err error) {
func (sr *searchRepo) parseResult(ctx context.Context, res []map[string][]byte) (resp []*schema.SearchResult, err error) {
questionIDs := make([]string, 0)
userIDs := make([]string, 0)
resultList := make([]*schema.SearchResult, 0)
for _, r := range res {
var (
objectKey,
status string
questionIDs = append(questionIDs, string(r["question_id"]))
userIDs = append(userIDs, string(r["user_id"]))
tp, _ := time.ParseInLocation("2006-01-02 15:04:05", string(r["created_at"]), time.Local)
object := &schema.SearchObject{
ID: string(r["id"]),
QuestionID: string(r["question_id"]),
Title: string(r["title"]),
Excerpt: htmltext.FetchExcerpt(string(r["parsed_text"]), "...", 240),
CreatedAtParsed: tp.Unix(),
UserInfo: &schema.SearchObjectUser{
ID: string(r["user_id"]),
},
Tags: make([]*schema.TagResp, 0),
VoteCount: converter.StringToInt(string(r["vote_count"])),
Accepted: string(r["accepted"]) == "2",
AnswerCount: converter.StringToInt(string(r["answer_count"])),
}
tags []schema.TagResp
tagsEntity []entity.Tag
object schema.SearchObject
)
objectKey, err = obj.GetObjectTypeStrByObjectID(string(r["id"]))
objectKey, err := obj.GetObjectTypeStrByObjectID(string(r["id"]))
if err != nil {
continue
}
tp, _ := time.ParseInLocation("2006-01-02 15:04:05", string(r["created_at"]), time.Local)
// get user info
userInfo, _, e := sr.userCommon.GetUserBasicInfoByID(ctx, string(r["user_id"]))
if e != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
return
}
// get tags
err = sr.data.DB.Context(ctx).
Select("`display_name`,`slug_name`,`main_tag_slug_name`,`recommend`,`reserved`").
Table("tag").
Join("INNER", "tag_rel", "tag.id = tag_rel.tag_id").
Where(builder.Eq{"tag_rel.object_id": r["question_id"]}).
And(builder.Eq{"tag_rel.status": entity.TagRelStatusAvailable}).
UseBool("recommend", "reserved").
OrderBy("tag.recommend DESC, tag.reserved DESC, tag.id DESC").
Find(&tagsEntity)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
return
}
_ = copier.Copy(&tags, tagsEntity)
switch objectKey {
case "question":
for k, v := range entity.AdminQuestionSearchStatus {
if v == converter.StringToInt(string(r["status"])) {
status = k
object.StatusStr = k
break
}
}
case "answer":
for k, v := range entity.AdminAnswerSearchStatus {
if v == converter.StringToInt(string(r["status"])) {
status = k
object.StatusStr = k
break
}
}
}
object = schema.SearchObject{
ID: string(r["id"]),
QuestionID: string(r["question_id"]),
Title: string(r["title"]),
Excerpt: htmltext.FetchExcerpt(string(r["parsed_text"]), "...", 240),
CreatedAtParsed: tp.Unix(),
UserInfo: userInfo,
Tags: tags,
VoteCount: converter.StringToInt(string(r["vote_count"])),
Accepted: string(r["accepted"]) == "2",
AnswerCount: converter.StringToInt(string(r["answer_count"])),
StatusStr: status,
}
resp = append(resp, schema.SearchResult{
resultList = append(resultList, &schema.SearchResult{
ObjectType: objectKey,
Object: object,
})
}
return
tagsMap, err := sr.tagCommon.BatchGetObjectTag(ctx, questionIDs)
if err != nil {
return nil, err
}
userInfoMap, err := sr.userCommon.BatchUserBasicInfoByID(ctx, userIDs)
if err != nil {
return nil, err
}
for _, item := range resultList {
tags, ok := tagsMap[item.Object.QuestionID]
if ok {
item.Object.Tags = tags
}
if userInfo := userInfoMap[item.Object.UserInfo.ID]; userInfo != nil {
item.Object.UserInfo.Username = userInfo.Username
item.Object.UserInfo.DisplayName = userInfo.DisplayName
item.Object.UserInfo.Rank = userInfo.Rank
item.Object.UserInfo.Status = userInfo.Status
}
}
return resultList, nil
}
func addRelevanceField(searchFields, words, fields []string) (res []string, args []interface{}) {

View File

@ -3,6 +3,7 @@ package tag_common
import (
"context"
"fmt"
"strings"
"github.com/answerdev/answer/internal/base/data"
"github.com/answerdev/answer/internal/base/pager"
@ -56,19 +57,28 @@ func (tr *tagCommonRepo) GetTagBySlugName(ctx context.Context, slugName string)
}
// GetTagListByName get tag list all like name
func (tr *tagCommonRepo) GetTagListByName(ctx context.Context, name string, hasReserved bool) (tagList []*entity.Tag, err error) {
tagList = make([]*entity.Tag, 0)
func (tr *tagCommonRepo) GetTagListByName(ctx context.Context, name string, recommend, reserved bool) (tagList []*entity.Tag, err error) {
cond := &entity.Tag{}
session := tr.data.DB.Context(ctx).Where("")
if name != "" {
session.Where("slug_name LIKE LOWER(?) or display_name LIKE ?", name+"%", name+"%")
} else {
session.UseBool("recommend")
session := tr.data.DB.Context(ctx)
if len(name) > 0 {
session.Where("slug_name LIKE ? OR display_name LIKE ?", strings.ToLower(name)+"%", name+"%")
}
var columns []string
if recommend {
columns = append(columns, "recommend")
cond.Recommend = true
}
if reserved {
columns = append(columns, "reserved")
cond.Reserved = true
}
if len(columns) > 0 {
session.UseBool(columns...)
}
session.Where(builder.Eq{"status": entity.TagStatusAvailable})
session.Asc("slug_name")
err = session.OrderBy("recommend desc,reserved desc,id desc").Find(&tagList, cond)
tagList = make([]*entity.Tag, 0)
err = session.OrderBy("recommend DESC,reserved DESC,slug_name ASC").Find(&tagList, cond)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}

View File

@ -131,21 +131,21 @@ func (ur *userAdminRepo) GetUserPage(ctx context.Context, page, pageSize int, us
session := ur.data.DB.Context(ctx)
switch user.Status {
case entity.UserStatusDeleted:
session.Desc("user.deleted_at")
session.Desc("`user`.deleted_at")
case entity.UserStatusSuspended:
session.Desc("user.suspended_at")
session.Desc("`user`.suspended_at")
default:
session.Desc("user.created_at")
session.Desc("`user`.created_at")
}
if len(usernameOrDisplayName) > 0 {
session.And(builder.Or(
builder.Like{"user.username", usernameOrDisplayName},
builder.Like{"user.display_name", usernameOrDisplayName},
builder.Like{"`user`.username", usernameOrDisplayName},
builder.Like{"`user`.display_name", usernameOrDisplayName},
))
}
if isStaff {
session.Join("INNER", "user_role_rel", "user.id = user_role_rel.user_id AND user_role_rel.role_id > 1")
session.Join("INNER", "user_role_rel", "`user`.id = `user_role_rel`.user_id AND `user_role_rel`.role_id > 1")
}
total, err = pager.Help(page, pageSize, &users, user, session)

View File

@ -22,6 +22,24 @@ func NewUserNotificationConfigRepo(data *data.Data) user_notification_config.Use
}
}
// Add add notification config
func (ur *userNotificationConfigRepo) Add(ctx context.Context, userIDs []string, source, channels string) (err error) {
var configs []*entity.UserNotificationConfig
for _, userID := range userIDs {
configs = append(configs, &entity.UserNotificationConfig{
UserID: userID,
Source: source,
Channels: channels,
Enabled: true,
})
}
_, err = ur.data.DB.Context(ctx).Insert(configs)
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return nil
}
// Save save notification config, if existed, update, if not exist, insert
func (ur *userNotificationConfigRepo) Save(ctx context.Context, uc *entity.UserNotificationConfig) (err error) {
old := &entity.UserNotificationConfig{UserID: uc.UserID, Source: uc.Source}

View File

@ -203,7 +203,7 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
r.PUT("/question/status", a.questionController.CloseQuestion)
r.PUT("/question/operation", a.questionController.OperationQuestion)
r.PUT("/question/reopen", a.questionController.ReopenQuestion)
r.GET("/question/similar", a.questionController.SearchByTitleLike)
r.GET("/question/similar", a.questionController.GetSimilarQuestions)
// answer
r.POST("/answer", a.answerController.Add)

View File

@ -108,12 +108,19 @@ type AdminAnswerInfo struct {
} `json:"question_info"`
}
type AnswerAcceptedReq struct {
QuestionID string `json:"question_id"`
AnswerID string `json:"answer_id"`
type AcceptAnswerReq struct {
QuestionID string `validate:"required,gt=0,lte=30" json:"question_id"`
AnswerID string `validate:"omitempty" json:"answer_id"`
UserID string `json:"-"`
}
func (req *AcceptAnswerReq) Check() (errFields []*validator.FormErrorField, err error) {
if len(req.AnswerID) == 0 {
req.AnswerID = "0"
}
return nil, nil
}
type AdminSetAnswerStatusRequest struct {
StatusStr string `json:"status"`
AnswerID string `json:"answer_id"`

View File

@ -58,11 +58,10 @@ type UpdateCommentReq struct {
UserID string `json:"-"`
IsAdmin bool `json:"-"`
CanAdd bool `json:"-"`
// whether user can edit it
CanEdit bool `json:"-"`
// whether user can delete it
CanDelete bool `json:"-"`
CaptchaID string `json:"captcha_id"` // captcha_id
CaptchaCode string `json:"captcha_code"`
}
@ -72,6 +71,15 @@ func (req *UpdateCommentReq) Check() (errFields []*validator.FormErrorField, err
return nil, nil
}
type UpdateCommentResp struct {
// comment id
CommentID string `json:"comment_id"`
// original comment content
OriginalText string `json:"original_text"`
// parsed comment content
ParsedText string `json:"parsed_text"`
}
// GetCommentListReq get comment list all request
type GetCommentListReq struct {
// user id

View File

@ -90,13 +90,21 @@ type SearchObject struct {
Accepted bool `json:"accepted"`
AnswerCount int `json:"answer_count"`
// user info
UserInfo *UserBasicInfo `json:"user_info"`
UserInfo *SearchObjectUser `json:"user_info"`
// tags
Tags []TagResp `json:"tags"`
Tags []*TagResp `json:"tags"`
// Status
StatusStr string `json:"status"`
}
type SearchObjectUser struct {
ID string `json:"id"`
Username string `json:"username"`
DisplayName string `json:"display_name"`
Rank int `json:"rank"`
Status string `json:"status"`
}
type TagResp struct {
ID string `json:"-"`
SlugName string `json:"slug_name"`
@ -111,13 +119,13 @@ type SearchResult struct {
// object_type
ObjectType string `json:"object_type"`
// this object
Object SearchObject `json:"object"`
Object *SearchObject `json:"object"`
}
type SearchResp struct {
Total int64 `json:"count"`
// search response
SearchResults []SearchResult `json:"list"`
SearchResults []*SearchResult `json:"list"`
}
type SearchDescResp struct {

View File

@ -34,6 +34,9 @@ func NewAnswerActivityService(
// AcceptAnswer accept answer change activity
func (as *AnswerActivityService) AcceptAnswer(ctx context.Context,
loginUserID, answerObjID, questionObjID, questionUserID, answerUserID string, isSelf bool) (err error) {
log.Debugf("user %s want to accept answer %s[%s] for question %s[%s]", loginUserID,
answerObjID, answerUserID,
questionObjID, questionUserID)
operationInfo := as.createAcceptAnswerOperationInfo(ctx, loginUserID,
answerObjID, questionObjID, questionUserID, answerUserID, isSelf)
return as.answerActivityRepo.SaveAcceptAnswerActivity(ctx, operationInfo)

View File

@ -17,7 +17,7 @@ type AnswerRepo interface {
GetAnswer(ctx context.Context, id string) (answer *entity.Answer, exist bool, err error)
GetAnswerList(ctx context.Context, answer *entity.Answer) (answerList []*entity.Answer, err error)
GetAnswerPage(ctx context.Context, page, pageSize int, answer *entity.Answer) (answerList []*entity.Answer, total int64, err error)
UpdateAccepted(ctx context.Context, id string, questionID string) error
UpdateAcceptedStatus(ctx context.Context, acceptedAnswerID string, questionID string) error
GetByID(ctx context.Context, id string) (*entity.Answer, bool, error)
GetCountByQuestionID(ctx context.Context, questionID string) (int64, error)
GetCountByUserID(ctx context.Context, userID string) (int64, error)
@ -83,6 +83,7 @@ func (as *AnswerCommon) ShowFormat(ctx context.Context, data *entity.Answer) *sc
info.UserID = data.UserID
info.UpdateUserID = data.LastEditUserID
info.Status = data.Status
info.MemberActions = make([]*schema.PermissionMemberAction, 0)
return &info
}

View File

@ -230,15 +230,13 @@ func (as *AnswerService) Insert(ctx context.Context, req *schema.AnswerAddReq) (
}
func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq) (string, error) {
//req.NoNeedReview //true 不需要审核
var canUpdate bool
_, existUnreviewed, err := as.revisionService.ExistUnreviewedByObjectID(ctx, req.ID)
if err != nil {
return "", err
}
if existUnreviewed {
err = errors.BadRequest(reason.AnswerCannotUpdate)
return "", err
return "", errors.BadRequest(reason.AnswerCannotUpdate)
}
questionInfo, exist, err := as.questionRepo.GetQuestion(ctx, req.QuestionID)
@ -254,12 +252,11 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq
return "", err
}
if !exist {
return "", nil
return "", errors.BadRequest(reason.AnswerNotFound)
}
if answerInfo.Status == entity.AnswerStatusDeleted {
err = errors.BadRequest(reason.AnswerCannotUpdate)
return "", err
return "", errors.BadRequest(reason.AnswerCannotUpdate)
}
//If the content is the same, ignore it
@ -267,15 +264,13 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq
return "", nil
}
now := time.Now()
insertData := new(entity.Answer)
insertData := &entity.Answer{}
insertData.ID = req.ID
insertData.UserID = answerInfo.UserID
insertData.QuestionID = req.QuestionID
insertData.OriginalText = req.Content
insertData.ParsedText = req.HTML
insertData.UpdatedAt = now
insertData.UpdatedAt = time.Now()
insertData.LastEditUserID = "0"
if answerInfo.UserID != req.UserID {
insertData.LastEditUserID = req.UserID
@ -284,7 +279,6 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq
revisionDTO := &schema.AddRevisionDTO{
UserID: req.UserID,
ObjectID: req.ID,
Title: "",
Log: req.EditSummary,
}
@ -314,7 +308,7 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq
}
if canUpdate {
as.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: insertData.UserID,
UserID: req.UserID,
ObjectID: insertData.ID,
OriginalObjectID: insertData.ID,
ActivityTypeKey: constant.ActAnswerEdited,
@ -325,47 +319,47 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq
return insertData.ID, nil
}
// UpdateAccepted
func (as *AnswerService) UpdateAccepted(ctx context.Context, req *schema.AnswerAcceptedReq) error {
if req.AnswerID == "" {
req.AnswerID = "0"
}
if req.UserID == "" {
return nil
}
newAnswerInfo := &entity.Answer{}
newAnswerInfoexist := false
var err error
if req.AnswerID != "0" {
newAnswerInfo, newAnswerInfoexist, err = as.answerRepo.GetByID(ctx, req.AnswerID)
if err != nil {
return err
}
newAnswerInfo.ID = uid.DeShortID(newAnswerInfo.ID)
if !newAnswerInfoexist {
return errors.BadRequest(reason.AnswerNotFound)
}
}
// AcceptAnswer accept answer
func (as *AnswerService) AcceptAnswer(ctx context.Context, req *schema.AcceptAnswerReq) (err error) {
// find question
questionInfo, exist, err := as.questionRepo.GetQuestion(ctx, req.QuestionID)
if err != nil {
return err
}
questionInfo.ID = uid.DeShortID(questionInfo.ID)
if !exist {
return errors.BadRequest(reason.QuestionNotFound)
}
// if questionInfo.UserID != req.UserID {
// return fmt.Errorf("no permission to set answer")
// }
questionInfo.ID = uid.DeShortID(questionInfo.ID)
if questionInfo.AcceptedAnswerID == req.AnswerID {
return nil
}
// find answer
var acceptedAnswerInfo *entity.Answer
if len(req.AnswerID) > 1 {
acceptedAnswerInfo, exist, err = as.answerRepo.GetByID(ctx, req.AnswerID)
if err != nil {
return err
}
if !exist {
return errors.BadRequest(reason.AnswerNotFound)
}
acceptedAnswerInfo.ID = uid.DeShortID(acceptedAnswerInfo.ID)
}
// update answers status
if err = as.answerRepo.UpdateAcceptedStatus(ctx, req.AnswerID, req.QuestionID); err != nil {
return err
}
// update question status
err = as.questionCommon.UpdateAccepted(ctx, req.QuestionID, req.AnswerID)
if err != nil {
log.Error("UpdateLastAnswer error", err.Error())
}
var oldAnswerInfo *entity.Answer
if len(questionInfo.AcceptedAnswerID) > 0 && questionInfo.AcceptedAnswerID != "0" {
if len(questionInfo.AcceptedAnswerID) > 1 {
oldAnswerInfo, _, err = as.answerRepo.GetByID(ctx, questionInfo.AcceptedAnswerID)
if err != nil {
return err
@ -373,17 +367,7 @@ func (as *AnswerService) UpdateAccepted(ctx context.Context, req *schema.AnswerA
oldAnswerInfo.ID = uid.DeShortID(oldAnswerInfo.ID)
}
err = as.answerRepo.UpdateAccepted(ctx, req.AnswerID, req.QuestionID)
if err != nil {
return err
}
err = as.questionCommon.UpdateAccepted(ctx, req.QuestionID, req.AnswerID)
if err != nil {
log.Error("UpdateLastAnswer error", err.Error())
}
as.updateAnswerRank(ctx, req.UserID, questionInfo, newAnswerInfo, oldAnswerInfo)
as.updateAnswerRank(ctx, req.UserID, questionInfo, acceptedAnswerInfo, oldAnswerInfo)
return nil
}
@ -398,9 +382,9 @@ func (as *AnswerService) updateAnswerRank(ctx context.Context, userID string,
log.Error(err)
}
}
if newAnswerInfo.ID != "" {
if newAnswerInfo != nil {
err := as.answerActivityService.AcceptAnswer(ctx, userID, newAnswerInfo.ID,
questionInfo.ID, questionInfo.UserID, newAnswerInfo.UserID, newAnswerInfo.UserID == userID)
questionInfo.ID, questionInfo.UserID, newAnswerInfo.UserID, newAnswerInfo.UserID == questionInfo.UserID)
if err != nil {
log.Error(err)
}
@ -443,12 +427,11 @@ func (as *AnswerService) Get(ctx context.Context, answerID, loginUserID string)
info.VoteStatus = as.voteRepo.GetVoteStatus(ctx, answerID, loginUserID)
CollectedMap, err := as.collectionCommon.SearchObjectCollected(ctx, loginUserID, []string{answerInfo.ID})
collectedMap, err := as.collectionCommon.SearchObjectCollected(ctx, loginUserID, []string{answerInfo.ID})
if err != nil {
log.Error("CollectionFunc.SearchObjectCollected error", err)
return nil, nil, has, err
}
_, ok = CollectedMap[answerInfo.ID]
if ok {
if len(collectedMap) > 0 {
info.Collected = true
}
@ -529,46 +512,28 @@ func (as *AnswerService) SearchFormatInfo(ctx context.Context, answers []*entity
item := as.ShowFormat(ctx, info)
list = append(list, item)
objectIDs = append(objectIDs, info.ID)
userIDs = append(userIDs, info.UserID)
userIDs = append(userIDs, info.LastEditUserID)
if req.UserID != "" {
item.ID = uid.DeShortID(item.ID)
item.VoteStatus = as.voteRepo.GetVoteStatus(ctx, item.ID, req.UserID)
}
userIDs = append(userIDs, info.UserID, info.LastEditUserID)
}
userInfoMap, err := as.userCommon.BatchUserBasicInfoByID(ctx, userIDs)
if err != nil {
return list, err
}
for _, item := range list {
_, ok := userInfoMap[item.UserID]
if ok {
item.UserInfo = userInfoMap[item.UserID]
}
_, ok = userInfoMap[item.UpdateUserID]
if ok {
item.UpdateUserInfo = userInfoMap[item.UpdateUserID]
}
item.UserInfo = userInfoMap[item.UserID]
item.UpdateUserInfo = userInfoMap[item.UpdateUserID]
}
if req.UserID == "" {
if len(req.UserID) == 0 {
return list, nil
}
searchObjectCollected, err := as.collectionCommon.SearchObjectCollected(ctx, req.UserID, objectIDs)
collectedMap, err := as.collectionCommon.SearchObjectCollected(ctx, req.UserID, objectIDs)
if err != nil {
return nil, err
}
for _, item := range list {
_, ok := searchObjectCollected[item.ID]
if ok {
item.Collected = true
}
}
for _, item := range list {
item.ID = uid.EnShortID(item.ID)
item.VoteStatus = as.voteRepo.GetVoteStatus(ctx, item.ID, req.UserID)
item.Collected = collectedMap[item.ID]
item.MemberActions = permission.GetAnswerPermission(ctx, req.UserID, item.UserID, req.CanEdit, req.CanDelete)
}
return list, nil

View File

@ -29,7 +29,7 @@ import (
type CommentRepo interface {
AddComment(ctx context.Context, comment *entity.Comment) (err error)
RemoveComment(ctx context.Context, commentID string) (err error)
UpdateComment(ctx context.Context, comment *entity.Comment) (err error)
UpdateCommentContent(ctx context.Context, commentID string, original string, parsedText string) (err error)
GetComment(ctx context.Context, commentID string) (comment *entity.Comment, exist bool, err error)
GetCommentPage(ctx context.Context, commentQuery *CommentQuery) (
comments []*entity.Comment, total int64, err error)
@ -224,39 +224,34 @@ func (cs *CommentService) RemoveComment(ctx context.Context, req *schema.RemoveC
// UpdateComment update comment
func (cs *CommentService) UpdateComment(ctx context.Context, req *schema.UpdateCommentReq) (
resp *schema.GetCommentResp, err error) {
resp = &schema.GetCommentResp{}
resp *schema.UpdateCommentResp, err error) {
old, exist, err := cs.commentCommonRepo.GetComment(ctx, req.CommentID)
if err != nil {
return
}
if !exist {
return resp, errors.BadRequest(reason.CommentNotFound)
}
// user can edit the comment that was posted by himself before deadline.
if !req.IsAdmin && (time.Now().After(old.CreatedAt.Add(constant.CommentEditDeadline))) {
return resp, errors.BadRequest(reason.CommentCannotEditAfterDeadline)
}
comment := &entity.Comment{}
_ = copier.Copy(comment, req)
comment.ID = req.CommentID
resp.SetFromComment(comment)
resp.MemberActions = permission.GetCommentPermission(ctx, req.UserID, resp.UserID,
time.Now(), req.CanEdit, req.CanDelete)
userInfo, exist, err := cs.userCommon.GetUserBasicInfoByID(ctx, resp.UserID)
if err != nil {
return nil, err
}
if exist {
resp.Username = userInfo.Username
resp.UserDisplayName = userInfo.DisplayName
resp.UserAvatar = userInfo.Avatar
resp.UserStatus = userInfo.Status
if !exist {
return nil, errors.BadRequest(reason.CommentNotFound)
}
return resp, cs.commentRepo.UpdateComment(ctx, comment)
// user can't edit the comment that was posted by others except admin
if !req.IsAdmin && req.UserID != old.UserID {
return nil, errors.BadRequest(reason.CommentNotFound)
}
// user can edit the comment that was posted by himself before deadline.
// admin can edit it at any time
if !req.IsAdmin && (time.Now().After(old.CreatedAt.Add(constant.CommentEditDeadline))) {
return nil, errors.BadRequest(reason.CommentCannotEditAfterDeadline)
}
if err = cs.commentRepo.UpdateCommentContent(ctx, old.ID, req.OriginalText, req.ParsedText); err != nil {
return nil, err
}
resp = &schema.UpdateCommentResp{
CommentID: old.ID,
OriginalText: req.OriginalText,
ParsedText: req.ParsedText,
}
return resp, nil
}
// GetComment get comment one

View File

@ -40,7 +40,7 @@ type QuestionRepo interface {
UpdateQuestionStatus(ctx context.Context, question *entity.Question) (err error)
UpdateQuestionStatusWithOutUpdateTime(ctx context.Context, question *entity.Question) (err error)
UpdateQuestionOperation(ctx context.Context, question *entity.Question) (err error)
SearchByTitleLike(ctx context.Context, title string) (questionList []*entity.Question, err error)
GetQuestionsByTitle(ctx context.Context, title string, pageSize int) (questionList []*entity.Question, err error)
UpdatePvCount(ctx context.Context, questionID string) (err error)
UpdateAnswerCount(ctx context.Context, questionID string, num int) (err error)
UpdateCollectionCount(ctx context.Context, questionID string, num int) (err error)
@ -282,17 +282,13 @@ func (qs *QuestionCommon) Info(ctx context.Context, questionID string, loginUser
}
showinfo.Answered = has
// login user Collected information
CollectedMap, err := qs.collectionCommon.SearchObjectCollected(ctx, loginUserID, []string{dbinfo.ID})
collectedMap, err := qs.collectionCommon.SearchObjectCollected(ctx, loginUserID, []string{dbinfo.ID})
if err != nil {
log.Error("CollectionFunc.SearchObjectCollected", err)
return nil, err
}
_, ok = CollectedMap[dbinfo.ID]
if ok {
if len(collectedMap) > 0 {
showinfo.Collected = true
}
return showinfo, nil
}
@ -406,9 +402,7 @@ func (qs *QuestionCommon) FormatQuestions(ctx context.Context, questionList []*e
item := qs.ShowFormat(ctx, questionInfo)
list = append(list, item)
objectIds = append(objectIds, item.ID)
userIds = append(userIds, item.UserID)
userIds = append(userIds, item.LastEditUserID)
userIds = append(userIds, item.LastAnsweredUserID)
userIds = append(userIds, item.UserID, item.LastEditUserID, item.LastAnsweredUserID)
}
tagsMap, err := qs.tagCommon.BatchGetObjectTag(ctx, objectIds)
if err != nil {
@ -421,38 +415,21 @@ func (qs *QuestionCommon) FormatQuestions(ctx context.Context, questionList []*e
}
for _, item := range list {
_, ok := tagsMap[item.ID]
if ok {
item.Tags = tagsMap[item.ID]
}
_, ok = userInfoMap[item.UserID]
if ok {
item.UserInfo = userInfoMap[item.UserID]
}
_, ok = userInfoMap[item.LastEditUserID]
if ok {
item.UpdateUserInfo = userInfoMap[item.LastEditUserID]
}
_, ok = userInfoMap[item.LastAnsweredUserID]
if ok {
item.LastAnsweredUserInfo = userInfoMap[item.LastAnsweredUserID]
}
item.Tags = tagsMap[item.ID]
item.UserInfo = userInfoMap[item.UserID]
item.UpdateUserInfo = userInfoMap[item.LastEditUserID]
item.LastAnsweredUserInfo = userInfoMap[item.LastAnsweredUserID]
}
if loginUserID == "" {
return list, nil
}
// //login user Collected information
CollectedMap, err := qs.collectionCommon.SearchObjectCollected(ctx, loginUserID, objectIds)
if err != nil {
log.Error("CollectionFunc.SearchObjectCollected", err)
}
collectedMap, err := qs.collectionCommon.SearchObjectCollected(ctx, loginUserID, objectIds)
if err != nil {
return nil, err
}
for _, item := range list {
_, ok := CollectedMap[item.ID]
if ok {
item.Collected = true
}
item.Collected = collectedMap[item.ID]
}
return list, nil
}

View File

@ -1103,14 +1103,18 @@ func (qs *QuestionService) SearchUserTopList(ctx context.Context, userName strin
return userQuestionlist, userAnswerlist, nil
}
// SearchByTitleLike
func (qs *QuestionService) SearchByTitleLike(ctx context.Context, title string, loginUserID string) ([]*schema.QuestionBaseInfo, error) {
list := make([]*schema.QuestionBaseInfo, 0)
dblist, err := qs.questionRepo.SearchByTitleLike(ctx, title)
if err != nil {
return list, err
// GetQuestionsByTitle get questions by title
func (qs *QuestionService) GetQuestionsByTitle(ctx context.Context, title string) (
resp []*schema.QuestionBaseInfo, err error) {
resp = make([]*schema.QuestionBaseInfo, 0)
if len(title) == 0 {
return resp, nil
}
for _, question := range dblist {
questions, err := qs.questionRepo.GetQuestionsByTitle(ctx, title, 10)
if err != nil {
return resp, err
}
for _, question := range questions {
item := &schema.QuestionBaseInfo{}
item.ID = question.ID
item.Title = question.Title
@ -1125,10 +1129,9 @@ func (qs *QuestionService) SearchByTitleLike(ctx context.Context, title string,
if question.AcceptedAnswerID != "0" {
item.AcceptedAnswer = true
}
list = append(list, item)
resp = append(resp, item)
}
return list, nil
return resp, nil
}
// SimilarQuestion

View File

@ -7,8 +7,8 @@ import (
)
type SearchRepo interface {
SearchContents(ctx context.Context, words []string, tagIDs []string, userID string, votes, page, size int, order string) (resp []schema.SearchResult, total int64, err error)
SearchQuestions(ctx context.Context, words []string, tagIDs []string, notAccepted bool, views, answers int, page, size int, order string) (resp []schema.SearchResult, total int64, err error)
SearchAnswers(ctx context.Context, words []string, tagIDs []string, accepted bool, questionID string, page, size int, order string) (resp []schema.SearchResult, total int64, err error)
ParseSearchPluginResult(ctx context.Context, sres []plugin.SearchResult) (resp []schema.SearchResult, err error)
SearchContents(ctx context.Context, words []string, tagIDs []string, userID string, votes, page, size int, order string) (resp []*schema.SearchResult, total int64, err error)
SearchQuestions(ctx context.Context, words []string, tagIDs []string, notAccepted bool, views, answers int, page, size int, order string) (resp []*schema.SearchResult, total int64, err error)
SearchAnswers(ctx context.Context, words []string, tagIDs []string, accepted bool, questionID string, page, size int, order string) (resp []*schema.SearchResult, total int64, err error)
ParseSearchPluginResult(ctx context.Context, sres []plugin.SearchResult) (resp []*schema.SearchResult, err error)
}

View File

@ -2,6 +2,7 @@ package search_parser
import (
"context"
"fmt"
"github.com/answerdev/answer/internal/base/constant"
"regexp"
"strings"
@ -105,7 +106,11 @@ func (sp *SearchParser) parseTags(ctx context.Context, query *string) (tags []st
if err != nil || !exists {
continue
}
tags = append(tags, tag.ID)
if tag.MainTagID > 0 {
tags = append(tags, fmt.Sprintf("%d", tag.MainTagID))
} else {
tags = append(tags, tag.ID)
}
}
// limit maximum 5 tags

View File

@ -24,7 +24,7 @@ type TagCommonRepo interface {
AddTagList(ctx context.Context, tagList []*entity.Tag) (err error)
GetTagListByIDs(ctx context.Context, ids []string) (tagList []*entity.Tag, err error)
GetTagBySlugName(ctx context.Context, slugName string) (tagInfo *entity.Tag, exist bool, err error)
GetTagListByName(ctx context.Context, name string, hasReserved bool) (tagList []*entity.Tag, err error)
GetTagListByName(ctx context.Context, name string, recommend, reserved bool) (tagList []*entity.Tag, err error)
GetTagListByNames(ctx context.Context, names []string) (tagList []*entity.Tag, err error)
GetTagByID(ctx context.Context, tagID string, includeDeleted bool) (tag *entity.Tag, exist bool, err error)
GetTagPage(ctx context.Context, page, pageSize int, tag *entity.Tag, queryCond string) (tagList []*entity.Tag, total int64, err error)
@ -86,7 +86,7 @@ func NewTagCommonService(
// SearchTagLike get tag list all
func (ts *TagCommonService) SearchTagLike(ctx context.Context, req *schema.SearchTagLikeReq) (resp []schema.SearchTagLikeResp, err error) {
tags, err := ts.tagCommonRepo.GetTagListByName(ctx, req.Tag, req.IsAdmin)
tags, err := ts.tagCommonRepo.GetTagListByName(ctx, req.Tag, len(req.Tag) == 0, false)
if err != nil {
return
}
@ -97,35 +97,39 @@ func (ts *TagCommonService) SearchTagLike(ctx context.Context, req *schema.Searc
mainTagId = append(mainTagId, converter.IntToString(tag.MainTagID))
}
}
mainTagList, err := ts.tagCommonRepo.GetTagListByIDs(ctx, mainTagId)
if err != nil {
return
}
mainTagMap := make(map[string]*entity.Tag)
for _, tag := range mainTagList {
mainTagMap[tag.ID] = tag
}
for _, tag := range tags {
if tag.MainTagID != 0 {
_, ok := mainTagMap[converter.IntToString(tag.MainTagID)]
if ok {
tag.SlugName = mainTagMap[converter.IntToString(tag.MainTagID)].SlugName
tag.DisplayName = mainTagMap[converter.IntToString(tag.MainTagID)].DisplayName
tag.Reserved = mainTagMap[converter.IntToString(tag.MainTagID)].Reserved
tag.Recommend = mainTagMap[converter.IntToString(tag.MainTagID)].Recommend
}
if len(mainTagId) > 0 {
mainTagList, err := ts.tagCommonRepo.GetTagListByIDs(ctx, mainTagId)
if err != nil {
return nil, err
}
for _, tag := range mainTagList {
mainTagMap[tag.ID] = tag
}
}
RepetitiveTag := make(map[string]bool)
for _, tag := range tags {
if _, ok := RepetitiveTag[tag.SlugName]; !ok {
if tag.MainTagID == 0 {
continue
}
mainTagID := converter.IntToString(tag.MainTagID)
if _, ok := mainTagMap[mainTagID]; ok {
tag.SlugName = mainTagMap[mainTagID].SlugName
tag.DisplayName = mainTagMap[mainTagID].DisplayName
tag.Reserved = mainTagMap[mainTagID].Reserved
tag.Recommend = mainTagMap[mainTagID].Recommend
}
}
resp = make([]schema.SearchTagLikeResp, 0)
repetitiveTag := make(map[string]bool)
for _, tag := range tags {
if _, ok := repetitiveTag[tag.SlugName]; !ok {
item := schema.SearchTagLikeResp{}
item.SlugName = tag.SlugName
item.DisplayName = tag.DisplayName
item.Recommend = tag.Recommend
item.Reserved = tag.Reserved
resp = append(resp, item)
RepetitiveTag[tag.SlugName] = true
repetitiveTag[tag.SlugName] = true
}
}
return resp, nil
@ -432,6 +436,9 @@ func (ts *TagCommonService) tagFormatRecommendAndReserved(ctx context.Context, t
// BatchGetObjectTag batch get object tag
func (ts *TagCommonService) BatchGetObjectTag(ctx context.Context, objectIds []string) (map[string][]*schema.TagResp, error) {
objectIDTagMap := make(map[string][]*schema.TagResp)
if len(objectIds) == 0 {
return objectIDTagMap, nil
}
objectTagRelList, err := ts.tagRelRepo.BatchGetObjectTagRelList(ctx, objectIds)
if err != nil {
return objectIDTagMap, err

View File

@ -106,9 +106,12 @@ func (us *UserCommon) UpdateQuestionCount(ctx context.Context, userID string, nu
return us.userRepo.UpdateQuestionCount(ctx, userID, num)
}
func (us *UserCommon) BatchUserBasicInfoByID(ctx context.Context, IDs []string) (map[string]*schema.UserBasicInfo, error) {
func (us *UserCommon) BatchUserBasicInfoByID(ctx context.Context, userIDs []string) (map[string]*schema.UserBasicInfo, error) {
userMap := make(map[string]*schema.UserBasicInfo)
userList, err := us.userRepo.BatchGetByID(ctx, IDs)
if len(userIDs) == 0 {
return userMap, nil
}
userList, err := us.userRepo.BatchGetByID(ctx, userIDs)
if err != nil {
return userMap, err
}

View File

@ -9,6 +9,7 @@ import (
)
type UserNotificationConfigRepo interface {
Add(ctx context.Context, userIDs []string, source, channels string) (err error)
Save(ctx context.Context, uc *entity.UserNotificationConfig) (err error)
GetByUserID(ctx context.Context, userID string) ([]*entity.UserNotificationConfig, error)
GetBySource(ctx context.Context, source constant.NotificationSource) ([]*entity.UserNotificationConfig, error)
@ -68,6 +69,13 @@ func (us *UserNotificationConfigService) UpdateUserNotificationConfig(
return nil
}
// SetDefaultUserNotificationConfig set default user notification config for user register
func (us *UserNotificationConfigService) SetDefaultUserNotificationConfig(ctx context.Context, userIDs []string) (
err error) {
return us.userNotificationConfigRepo.Add(ctx, userIDs,
string(constant.InboxSource), `[{"key":"email","enable":true}]`)
}
func (us *UserNotificationConfigService) convertToEntity(ctx context.Context, userID string,
source constant.NotificationSource, channels schema.NotificationChannels) (c *entity.UserNotificationConfig) {
c = &entity.UserNotificationConfig{

View File

@ -32,16 +32,17 @@ import (
// UserService user service
type UserService struct {
userCommonService *usercommon.UserCommon
userRepo usercommon.UserRepo
userActivity activity.UserActiveActivityRepo
activityRepo activity_common.ActivityRepo
emailService *export.EmailService
authService *auth.AuthService
siteInfoService siteinfo_common.SiteInfoCommonService
userRoleService *role.UserRoleRelService
userExternalLoginService *user_external_login.UserExternalLoginService
userNotificationConfigRepo user_notification_config.UserNotificationConfigRepo
userCommonService *usercommon.UserCommon
userRepo usercommon.UserRepo
userActivity activity.UserActiveActivityRepo
activityRepo activity_common.ActivityRepo
emailService *export.EmailService
authService *auth.AuthService
siteInfoService siteinfo_common.SiteInfoCommonService
userRoleService *role.UserRoleRelService
userExternalLoginService *user_external_login.UserExternalLoginService
userNotificationConfigRepo user_notification_config.UserNotificationConfigRepo
userNotificationConfigService *user_notification_config.UserNotificationConfigService
}
func NewUserService(userRepo usercommon.UserRepo,
@ -54,18 +55,20 @@ func NewUserService(userRepo usercommon.UserRepo,
userCommonService *usercommon.UserCommon,
userExternalLoginService *user_external_login.UserExternalLoginService,
userNotificationConfigRepo user_notification_config.UserNotificationConfigRepo,
userNotificationConfigService *user_notification_config.UserNotificationConfigService,
) *UserService {
return &UserService{
userCommonService: userCommonService,
userRepo: userRepo,
userActivity: userActivity,
activityRepo: activityRepo,
emailService: emailService,
authService: authService,
siteInfoService: siteInfoService,
userRoleService: userRoleService,
userExternalLoginService: userExternalLoginService,
userNotificationConfigRepo: userNotificationConfigRepo,
userCommonService: userCommonService,
userRepo: userRepo,
userActivity: userActivity,
activityRepo: activityRepo,
emailService: emailService,
authService: authService,
siteInfoService: siteInfoService,
userRoleService: userRoleService,
userExternalLoginService: userExternalLoginService,
userNotificationConfigRepo: userNotificationConfigRepo,
userNotificationConfigService: userNotificationConfigService,
}
}
@ -398,6 +401,9 @@ func (us *UserService) UserRegisterByEmail(ctx context.Context, registerUserInfo
if err != nil {
return nil, nil, err
}
if err := us.userNotificationConfigService.SetDefaultUserNotificationConfig(ctx, []string{userInfo.ID}); err != nil {
log.Errorf("set default user notification config failed, err: %v", err)
}
// send email
data := &schema.EmailCodeContent{

View File

@ -12,6 +12,7 @@ interface Props {
avatarSearchStr?: string;
className?: string;
avatarClass?: string;
nameMaxWidth?: string;
}
const Index: FC<Props> = ({
@ -22,11 +23,14 @@ const Index: FC<Props> = ({
className = 'small',
avatarSearchStr = 's=48',
showReputation = true,
nameMaxWidth = '300px',
}) => {
return (
<div className={`d-flex align-items-center text-secondary ${className}`}>
{data?.status !== 'deleted' ? (
<Link to={`/users/${data?.username}`}>
<Link
to={`/users/${data?.username}`}
className="d-flex align-items-center">
{showAvatar && (
<Avatar
avatar={data?.avatar}
@ -36,7 +40,9 @@ const Index: FC<Props> = ({
alt={data?.display_name}
/>
)}
<span className="me-1 text-truncate-1" style={{ maxWidth: '300px' }}>
<span
className="me-1 name-ellipsis"
style={{ maxWidth: nameMaxWidth }}>
{data?.display_name}
</span>
</Link>
@ -51,9 +57,7 @@ const Index: FC<Props> = ({
alt={data?.display_name}
/>
)}
<span className="me-1 text-truncate-1" style={{ maxWidth: '300px' }}>
{data?.display_name}
</span>
<span className="me-1 name-ellipsis">{data?.display_name}</span>
</>
)}

View File

@ -1,5 +1,5 @@
import { FC, useContext, useEffect } from 'react';
import { Dropdown, OverlayTrigger, Tooltip, Button } from 'react-bootstrap';
import { Dropdown, Button } from 'react-bootstrap';
import { EditorContext } from './EditorContext';
@ -49,28 +49,27 @@ const ToolItem: FC<IProps> = (props) => {
}, []);
const btnRender = () => (
<OverlayTrigger placement="bottom" overlay={<Tooltip>{tip}</Tooltip>}>
<Button
variant="link"
className={`p-0 b-0 btn-no-border toolbar icon-${label} ${
disable ? 'disabled' : ''
} `}
disabled={disable}
tabIndex={-1}
onClick={(e) => {
e.preventDefault();
if (typeof onClick === 'function') {
onClick();
}
}}
onBlur={(e) => {
e.preventDefault();
if (typeof onBlur === 'function') {
onBlur();
}
}}
/>
</OverlayTrigger>
<Button
variant="link"
title={tip}
className={`p-0 b-0 btn-no-border toolbar icon-${label} ${
disable ? 'disabled' : ''
} `}
disabled={disable}
tabIndex={-1}
onClick={(e) => {
e.preventDefault();
if (typeof onClick === 'function') {
onClick();
}
}}
onBlur={(e) => {
e.preventDefault();
if (typeof onBlur === 'function') {
onBlur();
}
}}
/>
);
if (!context) {

View File

@ -141,6 +141,13 @@ img[src=""] {
}
}
.name-ellipsis {
display: inline-block;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.text-truncate-1,
.text-truncate-2,
.text-truncate-3,

View File

@ -155,7 +155,7 @@ const Answers: FC = () => {
<td>{li.vote_count}</td>
<td>
<Stack>
<BaseUserCard data={li.user_info} />
<BaseUserCard data={li.user_info} nameMaxWidth="200px" />
<FormatTime
className="small text-secondary"

View File

@ -160,7 +160,7 @@ const Questions: FC = () => {
</td>
<td>
<Stack>
<BaseUserCard data={li.user_info} />
<BaseUserCard data={li.user_info} nameMaxWidth="130px" />
<FormatTime
className="small text-secondary"
time={li.create_time}

View File

@ -6,6 +6,7 @@ import { getSeoSetting, putSeoSetting } from '@/services';
import { SchemaForm, JSONSchema, initFormData, UISchema } from '@/components';
import { useToast } from '@/hooks';
import { handleFormError } from '@/utils';
import { seoSettingStore } from '@/stores';
const Index: FC = () => {
const { t } = useTranslation('translation', {
@ -64,6 +65,7 @@ const Index: FC = () => {
msg: t('update', { keyPrefix: 'toast' }),
variant: 'success',
});
seoSettingStore.getState().update(reqParams);
})
.catch((err) => {
if (err.isError) {

View File

@ -254,6 +254,7 @@ const Users: FC = () => {
avatarSearchStr="s=48"
avatarClass="me-2"
showReputation={false}
nameMaxWidth="160px"
/>
</td>
<td>{formatCount(user.rank)}</td>

View File

@ -28,6 +28,15 @@ const Index: FC<Props> = ({ visible, data, changeCallback, nextCallback }) => {
};
}
if (site_name.value && site_name.value.length > 30) {
bol = false;
data.site_url = {
value: site_name.value,
isInvalid: true,
errorMsg: t('site_name.msg_max_length'),
};
}
if (!site_url.value) {
bol = false;
data.site_url = {
@ -36,6 +45,7 @@ const Index: FC<Props> = ({ visible, data, changeCallback, nextCallback }) => {
errorMsg: t('site_name.msg.empty'),
};
}
const reg = /^(http|https):\/\//g;
if (site_url.value && !site_url.value.match(reg)) {
bol = false;
@ -44,6 +54,13 @@ const Index: FC<Props> = ({ visible, data, changeCallback, nextCallback }) => {
isInvalid: true,
errorMsg: t('site_url.msg.incorrect'),
};
} else if (site_url.value.length > 512) {
bol = false;
data.site_url = {
value: site_url.value,
isInvalid: true,
errorMsg: t('site_url.msg.max_length'),
};
}
if (!contact_email.value) {
@ -78,6 +95,13 @@ const Index: FC<Props> = ({ visible, data, changeCallback, nextCallback }) => {
isInvalid: true,
errorMsg: t('admin_name.character'),
};
} else if (data.name.value.length > 30) {
bol = false;
data.name = {
value: data.name.value,
isInvalid: true,
errorMsg: t('admin_name.msg_max_length'),
};
}
if (!password.value) {
@ -89,6 +113,24 @@ const Index: FC<Props> = ({ visible, data, changeCallback, nextCallback }) => {
};
}
if (password.value && password.value.length < 4) {
bol = false;
data.password = {
value: data.password.value,
isInvalid: true,
errorMsg: t('admin_password.msg_min_length'),
};
}
if (password.value && password.value.length > 32) {
bol = false;
data.password = {
value: data.password.value,
isInvalid: true,
errorMsg: t('admin_password.msg_max_length'),
};
}
if (!email.value) {
bol = false;
data.email = {
@ -132,7 +174,6 @@ const Index: FC<Props> = ({ visible, data, changeCallback, nextCallback }) => {
required
value={data.site_name.value}
isInvalid={data.site_name.isInvalid}
maxLength={30}
onChange={(e) => {
changeCallback({
site_name: {
@ -153,7 +194,6 @@ const Index: FC<Props> = ({ visible, data, changeCallback, nextCallback }) => {
required
value={data.site_url.value}
isInvalid={data.site_url.isInvalid}
maxLength={512}
onChange={(e) => {
changeCallback({
site_url: {
@ -220,7 +260,6 @@ const Index: FC<Props> = ({ visible, data, changeCallback, nextCallback }) => {
required
value={data.name.value}
isInvalid={data.name.isInvalid}
maxLength={30}
onChange={(e) => {
changeCallback({
name: {
@ -241,7 +280,6 @@ const Index: FC<Props> = ({ visible, data, changeCallback, nextCallback }) => {
<Form.Control
required
type="password"
maxLength={32}
value={data.password.value}
isInvalid={data.password.isInvalid}
onChange={(e) => {

View File

@ -23,7 +23,7 @@ import {
const Index: FC = () => {
const { t } = useTranslation('translation', { keyPrefix: 'install' });
const [step, setStep] = useState(1);
const [step, setStep] = useState(4);
const [loading, setLoading] = useState(true);
const [errorData, setErrorData] = useState<{ [propName: string]: any }>({
msg: '',

View File

@ -27,7 +27,7 @@ const SearchQuestion = ({ similarQuestions }) => {
<ListGroup.Item
action
as="a"
className="link-dark"
className="d-flex align-items-center link-dark"
key={item.id}
href={pathFactory.questionLanding(item.id, item.url_title)}
target="_blank">
@ -38,20 +38,24 @@ const SearchQuestion = ({ similarQuestions }) => {
: null}
</span>
{item.accepted_answer ? (
<span className="ms-3 text-success">
<span className="small ms-3 text-success">
<Icon type="bi" name="check-circle-fill" />
<span className="ms-1">{item.answer_count}</span>
<span className="ms-1">
{t('x_answers', {
keyPrefix: 'question',
count: item.answer_count,
})}
</span>
</span>
) : (
item.answer_count > 0 && (
<span className="ms-3">
<Icon
type="bi"
name="chat-square-text-fill"
className="text-secondary"
/>
<span className="ms-1 text-primary">
{item.answer_count}
<span className="small ms-3 text-secondary">
<Icon type="bi" name="chat-square-text-fill" />
<span className="ms-1">
{t('x_answers', {
keyPrefix: 'question',
count: item.answer_count,
})}
</span>
</span>
)

View File

@ -108,7 +108,7 @@ const Index: FC = () => {
return (
<Row className="pt-4 mb-5">
<Col className="page-main flex-auto">
{isLoading || listLoading ? (
{isLoading ? (
<div className="tag-box mb-5 placeholder-glow">
<div className="mb-3 h3 placeholder" style={{ width: '120px' }} />
<p

View File

@ -187,7 +187,6 @@ const Index: React.FC = () => {
tabIndex={1}
type="password"
// value={formData.pass.value}
maxLength={32}
isInvalid={formData.pass.isInvalid}
onChange={(e) =>
handleChange({

View File

@ -127,7 +127,6 @@ const Index: React.FC = () => {
autoComplete="off"
required
type="password"
maxLength={32}
isInvalid={formData.pass.isInvalid}
onChange={(e) => {
handleChange({
@ -150,7 +149,6 @@ const Index: React.FC = () => {
autoComplete="off"
required
type="password"
maxLength={32}
isInvalid={formData.passSecond.isInvalid}
onChange={(e) => {
handleChange({

View File

@ -184,7 +184,6 @@ const Index: React.FC<Props> = ({ callback }) => {
autoComplete="off"
required
type="password"
maxLength={32}
isInvalid={formData.pass.isInvalid}
value={formData.pass.value}
onChange={(e) =>

View File

@ -158,7 +158,6 @@ const Index: FC = () => {
autoComplete="new-password"
required
type="password"
maxLength={32}
isInvalid={formData.pass.isInvalid}
onChange={(e) =>
handleChange({

View File

@ -185,7 +185,6 @@ const Index: FC = () => {
autoComplete="off"
required
type="password"
maxLength={32}
isInvalid={formData.pass.isInvalid}
onChange={(e) =>
handleChange({
@ -208,7 +207,6 @@ const Index: FC = () => {
autoComplete="off"
required
type="password"
maxLength={32}
isInvalid={formData.pass2.isInvalid}
onChange={(e) =>
handleChange({

View File

@ -72,7 +72,9 @@ export const addComment = (params) => {
};
export const queryTags = (tag: string) => {
return request.get(`/answer/api/v1/question/tags?tag=${tag}`);
return request.get(
`/answer/api/v1/question/tags?tag=${encodeURIComponent(tag)}`,
);
};
export const useQueryAnswerInfo = (id: string) => {