commit 8e6c49a531ca72d732d37b3a890f08f3363cd0e1 Author: mingcheng Date: Tue Sep 27 17:59:05 2022 +0800 initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..eff2864d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# http://editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.go] +indent_style = tab +indent_size = 2 + +[{Makefile, Dockerfile}] +indent_style = tab +indent_size = 4 + +[{*.yml, *.json}] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6d0b917c --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +*.exe +*.orig +*.rej +*.so +*~ +.DS_Store +._* +/.idea +/.vscode/*.log +/cmd/answer/*.sh +/cmd/logs +/configs/* +/go.work* +/logs +/ui/build +/ui/node_modules +/vendor +Thumbs*.db +tmp +vendor/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..7a6e49c8 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,66 @@ +include: + - project: "segmentfault/devops/templates" + file: ".docker-build-push.yml" + - project: "segmentfault/devops/templates" + file: ".deploy-helm.yml" + +stages: + - compile-html + - compile-golang + - push + - deploy-dev + +"compile the html and other static files": + image: node:16 + stage: compile-html + tags: + - runner-nanjing + before_script: + - npm config set registry https://repo.huaweicloud.com/repository/npm/ + - make install-ui-packages + script: + - make ui + artifacts: + paths: + - ./build + +"compile the golang project": + image: golang:1.18 + stage: compile-golang + before_script: + - export GOPROXY="https://goproxy.cn" + - export GOPRIVATE=git.backyard.segmentfault.com + - sh ./script/prebuild.sh + script: + - make build + artifacts: + paths: + - ./answer + +"build docker images and push": + stage: push + extends: .docker-build-push + only: + - dev + - master + - main + variables: + DockerNamespace: sf_app + DockerImage: answer + DockerTag: "$CI_COMMIT_SHORT_SHA latest" + DockerfilePath: . + PushPolicy: qingcloud + +"deploy-to-local-develop-environment": + stage: deploy-dev + extends: .deploy-helm + only: + - main + variables: + LoadBalancerIP: 10.0.10.98 + KubernetesCluster: dev + KubernetesNamespace: "sf-web" + InstallArgs: --set service.loadBalancerIP=${LoadBalancerIP} --set image.tag=$CI_COMMIT_SHORT_SHA --set replicaCount=1 --set serivce.targetPort=80 + ChartName: answer + InstallPolicy: replace + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..4b4502b4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,53 @@ +FROM node:16 AS node-builder + +LABEL maintainer="mingcheng" + +COPY . /answer +WORKDIR /answer + +RUN make install-ui-packages ui +RUN mv ui/build /tmp +CMD ls -al /tmp +RUN du -sh /tmp/build + + +FROM golang:1.18 AS golang-builder +LABEL maintainer="aichy" + +ENV GOPATH /go +ENV GOROOT /usr/local/go +ENV PACKAGE github.com/segmentfault/answer +ENV GOPROXY https://goproxy.cn,direct +ENV BUILD_DIR ${GOPATH}/src/${PACKAGE} +ENV GOPRIVATE git.backyard.segmentfault.com +# Build +COPY . ${BUILD_DIR} +WORKDIR ${BUILD_DIR} +COPY --from=node-builder /tmp/build ${BUILD_DIR}/web/html +CMD ls -al ${BUILD_DIR}/web/html +RUN make clean build && \ + cp answer /usr/bin/answer && \ + cp configs/config.yaml /etc/config.yaml && \ + mkdir -p /tmp/cache && chmod 777 /tmp/cache && \ + mkdir -p /data/upfiles && chmod 777 /data/upfiles && cp -r i18n /data + +FROM debian:bullseye + +ENV TZ "Asia/Shanghai" +RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list \ + && sed -i 's/security.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list \ + && echo "Asia/Shanghai" > /etc/timezone \ + && apt -y update \ + && apt -y upgrade \ + && apt -y install ca-certificates openssl tzdata curl netcat dumb-init \ + && apt -y autoremove + +COPY --from=golang-builder /data /data +VOLUME /data + +COPY --from=golang-builder /etc/config.yaml /etc/answer.yaml +COPY --from=golang-builder /usr/bin/answer /usr/bin/answer + +EXPOSE 80 + +ENTRYPOINT ["dumb-init", "/usr/bin/answer", "-c", "/etc/answer.yaml"] diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 00000000..2b2f5c77 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1 @@ +# How to build and install diff --git a/INSTALL_CN.md b/INSTALL_CN.md new file mode 100644 index 00000000..1ef1e125 --- /dev/null +++ b/INSTALL_CN.md @@ -0,0 +1,9 @@ +# Answer 安装指引 + +## 前端安装 + +## 后端安装 + +## 编译镜像 + +## 常见问题 diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..ef922b2e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) since 2022 The Segmentfault Development Team. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..03f52ab3 --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +.PHONY: build clean ui + +VERSION=0.0.1 +BIN=answer +DIR_SRC=./cmd/answer +DOCKER_CMD=docker + +GO_ENV=CGO_ENABLED=0 +GO_FLAGS=-ldflags="-X main.Version=$(VERSION) -X 'main.Time=`date`' -extldflags -static" +GO=$(GO_ENV) $(shell which go) + +build: + @$(GO_ENV) $(GO) build $(GO_FLAGS) -o $(BIN) $(DIR_SRC) + +test: + @$(GO) test ./... + +# clean all build result +clean: + @$(GO) clean ./... + @rm -f $(BIN) + +install-ui-packages: + @corepack enable + @corepack prepare pnpm@v7.12.2 --activate + +ui: + @npm config set registry https://repo.huaweicloud.com/repository/npm/ + @cd ui && pnpm install && pnpm build && cd - + +all: clean build diff --git a/README.md b/README.md new file mode 100644 index 00000000..91a3d191 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# answer + +问答社区主项目代码 + +# Dependence + github.com/segmentfault/pacman + * config-file `viper` https://github.com/spf13/viper + * web `gin` https://gin-gonic.com/zh-cn/ + * log `zap` https://github.com/uber-go/zap + * orm `xorm` https://xorm.io/zh/ + * redis `go-redis` https://github.com/go-redis/redis + +# module + - email github.com/jordan-wright/email + - session github.com/gin-contrib/sessions + - Captcha github.com/mojocn/base64Captcha + +# Run +``` +cd cmd +export GOPRIVATE=git.backyard.segmentfault.com +go mod tidy +./dev.sh +``` + +# pprof + +``` + # Installation dependency + go get -u github.com/google/pprof + brew install graphviz +``` +``` +pprof -http :8082 http://XXX/debug/pprof/profile\?seconds\=10 +``` diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 00000000..8a6c11de --- /dev/null +++ b/README_CN.md @@ -0,0 +1,9 @@ +# Answer 问答社区 + +## 功能说明 + +## 安装 + +## 配置 + +## 常见问题 diff --git a/build/README.md b/build/README.md new file mode 100644 index 00000000..caac98d7 --- /dev/null +++ b/build/README.md @@ -0,0 +1,2 @@ +# /build +Packaging and Continuous Integration. diff --git a/charts/.idea/.gitignore b/charts/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/charts/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/charts/.idea/charts.iml b/charts/.idea/charts.iml new file mode 100644 index 00000000..5e764c4f --- /dev/null +++ b/charts/.idea/charts.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/charts/.idea/modules.xml b/charts/.idea/modules.xml new file mode 100644 index 00000000..442a908c --- /dev/null +++ b/charts/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/charts/.idea/vcs.xml b/charts/.idea/vcs.xml new file mode 100644 index 00000000..6c0b8635 --- /dev/null +++ b/charts/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/charts/Chart.yaml b/charts/Chart.yaml new file mode 100644 index 00000000..187c32fc --- /dev/null +++ b/charts/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: answer +description: a simple answer deployments for kubernetes +type: application +version: 0.1.0 +appVersion: "0.1.0" \ No newline at end of file diff --git a/charts/README.md b/charts/README.md new file mode 100644 index 00000000..e4d7d1cc --- /dev/null +++ b/charts/README.md @@ -0,0 +1,2 @@ +# Helm Charts for Answer project + diff --git a/charts/templates/_helpers.tpl b/charts/templates/_helpers.tpl new file mode 100644 index 00000000..cfdf84d7 --- /dev/null +++ b/charts/templates/_helpers.tpl @@ -0,0 +1,51 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "answer.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "answer.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "answer.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "answer.labels" -}} +helm.sh/chart: {{ .Release.Name }} +{{ include "answer.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "answer.selectorLabels" -}} +app.kubernetes.io/name: answer +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/charts/templates/config.yaml b/charts/templates/config.yaml new file mode 100644 index 00000000..24fd733e --- /dev/null +++ b/charts/templates/config.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: answer-config + namespace: {{ .Values.namespace | default "default" | quote }} +data: + default.yaml: |- + # diff --git a/charts/templates/service.yaml b/charts/templates/service.yaml new file mode 100644 index 00000000..b5db58f8 --- /dev/null +++ b/charts/templates/service.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "answer.fullname" . }} + labels: + {{- include "answer.labels" . | nindent 4 }} + namespace: {{ .Values.namespace | default "default" | quote }} +spec: + type: ClusterIP + ports: + - name: answer + port: 80 + targetPort: 80 + protocol: TCP + selector: + {{- include "answer.selectorLabels" . | nindent 4 }} diff --git a/charts/templates/statefulset.yaml b/charts/templates/statefulset.yaml new file mode 100644 index 00000000..ac6a6be8 --- /dev/null +++ b/charts/templates/statefulset.yaml @@ -0,0 +1,31 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: answer + namespace: {{ .Values.namespace | default "default" | quote }} +spec: + selector: + matchLabels: + {{- include "answer.labels" . | nindent 6 }} + serviceName: answer + replicas: 1 + template: + metadata: + labels: + {{- include "answer.labels" . | nindent 8 }} + spec: + containers: + - name: answer + image: nginx:stable + ports: + - containerPort: 80 + name: answer-ui + volumeMounts: + - name: config + mountPath: "/etc/answer.yaml" + subPath: default.yaml + volumes: + - name: config + configMap: + name: answer-config diff --git a/charts/values.yaml b/charts/values.yaml new file mode 100644 index 00000000..9065ca5b --- /dev/null +++ b/charts/values.yaml @@ -0,0 +1 @@ +namespace: default diff --git a/cmd/answer/main.go b/cmd/answer/main.go new file mode 100644 index 00000000..b90dd3a4 --- /dev/null +++ b/cmd/answer/main.go @@ -0,0 +1,70 @@ +package main + +import ( + "flag" + "os" + + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/conf" + "github.com/segmentfault/pacman" + "github.com/segmentfault/pacman/contrib/conf/viper" + "github.com/segmentfault/pacman/contrib/log/zap" + "github.com/segmentfault/pacman/contrib/server/http" + "github.com/segmentfault/pacman/log" +) + +// go build -ldflags "-X main.Version=x.y.z" +var ( + // Name is the name of the project + Name = "answer" + // Version is the version of the project + Version string + // confFlag is the config flag. + confFlag string + // log level + logLevel = os.Getenv("LOG_LEVEL") + // log path + logPath = os.Getenv("LOG_PATH") +) + +func init() { + flag.StringVar(&confFlag, "c", "../../configs/config.yaml", "config path, eg: -c config.yaml") +} + +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name Authorization +func main() { + flag.Parse() + + log.SetLogger(zap.NewLogger( + log.ParseLevel(logLevel), zap.WithName(Name), zap.WithPath(logPath), zap.WithCallerFullPath())) + + // init config + c := &conf.AllConfig{} + config, err := viper.NewWithPath(confFlag) + if err != nil { + panic(err) + } + if err = config.Parse(&c); err != nil { + panic(err) + } + + app, cleanup, err := initApplication( + c.Debug, c.Server, c.Data.Database, c.Data.Cache, c.I18n, c.Swaggerui, c.ServiceConfig, log.GetLogger()) + if err != nil { + panic(err) + } + defer cleanup() + if err := app.Run(); err != nil { + panic(err) + } +} + +func newApplication(serverConf *conf.Server, server *gin.Engine) *pacman.Application { + return pacman.NewApp( + pacman.WithName(Name), + pacman.WithVersion(Version), + pacman.WithServer(http.NewServer(server, serverConf.HTTP.Addr)), + ) +} diff --git a/cmd/answer/wire.go b/cmd/answer/wire.go new file mode 100644 index 00000000..d3a6a7d1 --- /dev/null +++ b/cmd/answer/wire.go @@ -0,0 +1,46 @@ +//go:build wireinject +// +build wireinject + +// The build tag makes sure the stub is not built in the final build. + +package main + +import ( + "github.com/google/wire" + "github.com/segmentfault/answer/internal/base/conf" + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/middleware" + "github.com/segmentfault/answer/internal/base/server" + "github.com/segmentfault/answer/internal/base/translator" + "github.com/segmentfault/answer/internal/controller" + "github.com/segmentfault/answer/internal/controller_backyard" + "github.com/segmentfault/answer/internal/repo" + "github.com/segmentfault/answer/internal/router" + "github.com/segmentfault/answer/internal/service" + "github.com/segmentfault/answer/internal/service/service_config" + "github.com/segmentfault/pacman" + "github.com/segmentfault/pacman/log" +) + +// initApplication init application. +func initApplication( + debug bool, + serverConf *conf.Server, + dbConf *data.Database, + cacheConf *data.CacheConf, + i18nConf *translator.I18n, + swaggerConf *router.SwaggerConfig, + serviceConf *service_config.ServiceConfig, + logConf log.Logger) (*pacman.Application, func(), error) { + panic(wire.Build( + server.ProviderSetServer, + router.ProviderSetRouter, + controller.ProviderSetController, + controller_backyard.ProviderSetController, + service.ProviderSetService, + repo.ProviderSetRepo, + translator.ProviderSet, + middleware.ProviderSetMiddleware, + newApplication, + )) +} diff --git a/cmd/answer/wire_gen.go b/cmd/answer/wire_gen.go new file mode 100644 index 00000000..30e260ad --- /dev/null +++ b/cmd/answer/wire_gen.go @@ -0,0 +1,180 @@ +// Code generated by Wire. DO NOT EDIT. + +//go:generate go run github.com/google/wire/cmd/wire +//go:build !wireinject +// +build !wireinject + +package main + +import ( + "github.com/segmentfault/answer/internal/base/conf" + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/middleware" + "github.com/segmentfault/answer/internal/base/server" + "github.com/segmentfault/answer/internal/base/translator" + "github.com/segmentfault/answer/internal/controller" + "github.com/segmentfault/answer/internal/controller_backyard" + "github.com/segmentfault/answer/internal/repo" + "github.com/segmentfault/answer/internal/repo/activity" + "github.com/segmentfault/answer/internal/repo/activity_common" + "github.com/segmentfault/answer/internal/repo/auth" + "github.com/segmentfault/answer/internal/repo/captcha" + "github.com/segmentfault/answer/internal/repo/collection" + "github.com/segmentfault/answer/internal/repo/comment" + "github.com/segmentfault/answer/internal/repo/common" + "github.com/segmentfault/answer/internal/repo/config" + "github.com/segmentfault/answer/internal/repo/export" + "github.com/segmentfault/answer/internal/repo/meta" + "github.com/segmentfault/answer/internal/repo/notification" + "github.com/segmentfault/answer/internal/repo/rank" + "github.com/segmentfault/answer/internal/repo/reason" + "github.com/segmentfault/answer/internal/repo/report" + "github.com/segmentfault/answer/internal/repo/revision" + "github.com/segmentfault/answer/internal/repo/tag" + "github.com/segmentfault/answer/internal/repo/unique" + "github.com/segmentfault/answer/internal/repo/user" + "github.com/segmentfault/answer/internal/router" + "github.com/segmentfault/answer/internal/service" + "github.com/segmentfault/answer/internal/service/action" + activity2 "github.com/segmentfault/answer/internal/service/activity" + "github.com/segmentfault/answer/internal/service/answer_common" + auth2 "github.com/segmentfault/answer/internal/service/auth" + "github.com/segmentfault/answer/internal/service/collection_common" + comment2 "github.com/segmentfault/answer/internal/service/comment" + export2 "github.com/segmentfault/answer/internal/service/export" + "github.com/segmentfault/answer/internal/service/follow" + meta2 "github.com/segmentfault/answer/internal/service/meta" + notification2 "github.com/segmentfault/answer/internal/service/notification" + "github.com/segmentfault/answer/internal/service/notification_common" + "github.com/segmentfault/answer/internal/service/object_info" + "github.com/segmentfault/answer/internal/service/question_common" + rank2 "github.com/segmentfault/answer/internal/service/rank" + reason2 "github.com/segmentfault/answer/internal/service/reason" + report2 "github.com/segmentfault/answer/internal/service/report" + "github.com/segmentfault/answer/internal/service/report_backyard" + "github.com/segmentfault/answer/internal/service/report_handle_backyard" + "github.com/segmentfault/answer/internal/service/revision_common" + "github.com/segmentfault/answer/internal/service/service_config" + tag2 "github.com/segmentfault/answer/internal/service/tag" + "github.com/segmentfault/answer/internal/service/tag_common" + "github.com/segmentfault/answer/internal/service/uploader" + "github.com/segmentfault/answer/internal/service/user_backyard" + "github.com/segmentfault/answer/internal/service/user_common" + "github.com/segmentfault/pacman" + "github.com/segmentfault/pacman/log" +) + +// Injectors from wire.go: + +// initApplication init application. +func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, cacheConf *data.CacheConf, i18nConf *translator.I18n, swaggerConf *router.SwaggerConfig, serviceConf *service_config.ServiceConfig, logConf log.Logger) (*pacman.Application, func(), error) { + staticRouter := router.NewStaticRouter(serviceConf) + i18nTranslator, err := translator.NewTranslator(i18nConf) + if err != nil { + return nil, nil, err + } + langController := controller.NewLangController(i18nTranslator) + engine := data.NewDB(debug, dbConf) + cache, cleanup, err := data.NewCache(cacheConf) + if err != nil { + return nil, nil, err + } + dataData, cleanup2, err := data.NewData(engine, cache) + if err != nil { + cleanup() + return nil, nil, err + } + authRepo := auth.NewAuthRepo(dataData) + authService := auth2.NewAuthService(authRepo) + configRepo := config.NewConfigRepo(dataData) + userRepo := user.NewUserRepo(dataData, configRepo) + uniqueIDRepo := unique.NewUniqueIDRepo(dataData) + activityRepo := repo.NewActivityRepo(dataData, uniqueIDRepo, configRepo) + userRankRepo := rank.NewUserRankRepo(dataData, configRepo) + userActiveActivityRepo := activity.NewUserActiveActivityRepo(dataData, activityRepo, userRankRepo, configRepo) + emailRepo := export.NewEmailRepo(dataData) + emailService := export2.NewEmailService(configRepo, emailRepo) + userService := service.NewUserService(userRepo, userActiveActivityRepo, emailService, authService, serviceConf) + captchaRepo := captcha.NewCaptchaRepo(dataData) + captchaService := action.NewCaptchaService(captchaRepo) + uploaderService := uploader.NewUploaderService(serviceConf) + userController := controller.NewUserController(authService, userService, captchaService, emailService, uploaderService) + commentRepo := comment.NewCommentRepo(dataData, uniqueIDRepo) + commentCommonRepo := comment.NewCommentCommonRepo(dataData, uniqueIDRepo) + answerRepo := repo.NewAnswerRepo(dataData, uniqueIDRepo, userRankRepo, activityRepo) + questionRepo := repo.NewQuestionRepo(dataData, uniqueIDRepo) + tagRepo := tag.NewTagRepo(dataData, uniqueIDRepo) + objService := object_info.NewObjService(answerRepo, questionRepo, commentCommonRepo, tagRepo) + voteRepo := activity_common.NewVoteRepo(dataData, activityRepo) + commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userRepo, objService, voteRepo) + rankService := rank2.NewRankService(userRepo, userRankRepo, objService, configRepo) + commentController := controller.NewCommentController(commentService, rankService) + reportRepo := report.NewReportRepo(dataData, uniqueIDRepo) + reportService := report2.NewReportService(reportRepo, objService) + reportController := controller.NewReportController(reportService, rankService) + serviceVoteRepo := activity.NewVoteRepo(dataData, uniqueIDRepo, configRepo, activityRepo, userRankRepo, voteRepo) + voteService := service.NewVoteService(serviceVoteRepo, uniqueIDRepo, configRepo, questionRepo, answerRepo, commentCommonRepo, objService) + voteController := controller.NewVoteController(voteService) + revisionRepo := revision.NewRevisionRepo(dataData, uniqueIDRepo) + revisionService := revision_common.NewRevisionService(revisionRepo, userRepo) + followRepo := activity_common.NewFollowRepo(dataData, uniqueIDRepo, activityRepo) + tagService := tag2.NewTagService(tagRepo, revisionService, followRepo) + tagController := controller.NewTagController(tagService, rankService) + followFollowRepo := activity.NewFollowRepo(dataData, uniqueIDRepo, activityRepo) + followService := follow.NewFollowService(followFollowRepo, followRepo, tagRepo) + followController := controller.NewFollowController(followService) + collectionRepo := collection.NewCollectionRepo(dataData, uniqueIDRepo) + collectionGroupRepo := collection.NewCollectionGroupRepo(dataData) + tagRelRepo := tag.NewTagListRepo(dataData) + tagCommonService := tagcommon.NewTagCommonService(tagRepo, tagRelRepo, revisionService) + userCommon := usercommon.NewUserCommon(userRepo) + collectionCommon := collectioncommon.NewCollectionCommon(collectionRepo) + answerCommon := answercommon.NewAnswerCommon(answerRepo) + metaRepo := meta.NewMetaRepo(dataData) + metaService := meta2.NewMetaService(metaRepo) + questionCommon := questioncommon.NewQuestionCommon(questionRepo, answerRepo, voteRepo, followRepo, tagCommonService, userCommon, collectionCommon, answerCommon, metaService, configRepo) + collectionService := service.NewCollectionService(collectionRepo, collectionGroupRepo, questionCommon) + collectionController := controller.NewCollectionController(collectionService) + answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo) + questionActivityRepo := activity.NewQuestionActivityRepo(dataData, activityRepo, userRankRepo) + answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, questionActivityRepo) + questionService := service.NewQuestionService(questionRepo, tagCommonService, questionCommon, userCommon, revisionService, metaService, collectionCommon, answerActivityService) + questionController := controller.NewQuestionController(questionService, rankService) + answerService := service.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo) + answerController := controller.NewAnswerController(answerService, rankService) + searchRepo := repo.NewSearchRepo(dataData, uniqueIDRepo, userRepo) + searchService := service.NewSearchService(searchRepo, tagRepo, userRepo, followRepo) + searchController := controller.NewSearchController(searchService) + serviceRevisionService := service.NewRevisionService(revisionRepo, userRepo, questionCommon, answerService) + revisionController := controller.NewRevisionController(serviceRevisionService) + rankController := controller.NewRankController(rankService) + commonRepo := common.NewCommonRepo(dataData, uniqueIDRepo) + reportHandle := report_handle_backyard.NewReportHandle(questionCommon, commentRepo, configRepo) + reportBackyardService := report_backyard.NewReportBackyardService(reportRepo, userCommon, commonRepo, answerRepo, questionRepo, commentCommonRepo, reportHandle, configRepo) + controller_backyardReportController := controller_backyard.NewReportController(reportBackyardService) + userBackyardRepo := user.NewUserBackyardRepo(dataData) + userBackyardService := user_backyard.NewUserBackyardService(userBackyardRepo) + userBackyardController := controller_backyard.NewUserBackyardController(userBackyardService) + reasonRepo := reason.NewReasonRepo(configRepo) + reasonService := reason2.NewReasonService(reasonRepo) + reasonController := controller.NewReasonController(reasonService) + themeController := controller_backyard.NewThemeController() + siteInfoRepo := repo.NewSiteInfo(dataData) + siteInfoService := service.NewSiteInfoService(siteInfoRepo) + siteInfoController := controller_backyard.NewSiteInfoController(siteInfoService) + siteinfoController := controller.NewSiteinfoController(siteInfoService) + notificationRepo := notification.NewNotificationRepo(dataData) + notificationCommon := notificationcommon.NewNotificationCommon(dataData, notificationRepo, userCommon, activityRepo, followRepo, objService) + notificationService := notification2.NewNotificationService(dataData, notificationRepo, notificationCommon) + notificationController := controller.NewNotificationController(notificationService) + answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, controller_backyardReportController, userBackyardController, reasonController, themeController, siteInfoController, siteinfoController, notificationController) + swaggerRouter := router.NewSwaggerRouter(swaggerConf) + viewRouter := router.NewViewRouter() + authUserMiddleware := middleware.NewAuthUserMiddleware(authService) + ginEngine := server.NewHTTPServer(debug, staticRouter, answerAPIRouter, swaggerRouter, viewRouter, authUserMiddleware) + application := newApplication(serverConf, ginEngine) + return application, func() { + cleanup2() + cleanup() + }, nil +} diff --git a/deployments/README.md b/deployments/README.md new file mode 100644 index 00000000..24810d0f --- /dev/null +++ b/deployments/README.md @@ -0,0 +1,3 @@ +# /deployments + +IaaS, PaaS, system and container orchestration deployment configurations and templates (docker-compose, kubernetes/helm, mesos, terraform, bosh). diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 00000000..d811ed2a --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,7 @@ +version: "3" +services: + answer: + build: + context: . + image: github.com/segmentfault/answer + restart: on-failure diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 00000000..e09a2ac8 --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,5676 @@ +// Package docs GENERATED BY SWAG; DO NOT EDIT +// This file was generated by swaggo/swag +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/answer/admin/api/answer/page": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Status:[available,deleted]", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "CmsSearchList", + "parameters": [ + { + "type": "integer", + "description": "page size", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size", + "name": "page_size", + "in": "query" + }, + { + "enum": [ + "available", + "deleted" + ], + "type": "string", + "description": "user status", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/answer/status": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Status:[available,deleted]", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "AdminSetAnswerStatus", + "parameters": [ + { + "description": "AdminSetAnswerStatusRequest", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.AdminSetAnswerStatusRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/language/options": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get language options", + "produces": [ + "application/json" + ], + "tags": [ + "Lang" + ], + "summary": "Get language options", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/question/page": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Status:[available,closed,deleted]", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "CmsSearchList", + "parameters": [ + { + "type": "integer", + "description": "page size", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size", + "name": "page_size", + "in": "query" + }, + { + "enum": [ + "available", + "closed", + "deleted" + ], + "type": "string", + "description": "user status", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/question/status": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Status:[available,closed,deleted]", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "AdminSetQuestionStatus", + "parameters": [ + { + "description": "AdminSetQuestionStatusRequest", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AdminSetQuestionStatusRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/reasons": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get reasons by object type and action", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "reason" + ], + "summary": "get reasons by object type and action", + "parameters": [ + { + "enum": [ + "question", + "answer", + "comment", + "user" + ], + "type": "string", + "description": "object_type", + "name": "object_type", + "in": "query", + "required": true + }, + { + "enum": [ + "status", + "close", + "flag", + "review" + ], + "type": "string", + "description": "action", + "name": "action", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/report/": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + }, + { + "ApiKeyAuth": [] + } + ], + "description": "handle flag", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "handle flag", + "parameters": [ + { + "description": "flag", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ReportHandleReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/reports/page": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + }, + { + "ApiKeyAuth": [] + } + ], + "description": "list report records", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "list report page", + "parameters": [ + { + "enum": [ + "pending", + "completed" + ], + "type": "string", + "description": "status", + "name": "status", + "in": "query", + "required": true + }, + { + "enum": [ + "all", + "question", + "answer", + "comment" + ], + "type": "string", + "description": "object_type", + "name": "object_type", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "page size", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/siteinfo/general": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get siteinfo general", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get siteinfo general", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteGeneralResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get siteinfo interface", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get siteinfo interface", + "parameters": [ + { + "description": "general", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SiteGeneralReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/siteinfo/interface": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get siteinfo interface", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get siteinfo interface", + "parameters": [ + { + "description": "general", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AddCommentReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteInterfaceResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get siteinfo interface", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get siteinfo interface", + "parameters": [ + { + "description": "general", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SiteInterfaceReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/theme/options": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get theme options", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get theme options", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/user/status": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "update user", + "parameters": [ + { + "description": "user", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UpdateUserStatusReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/users/page": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get user page", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "get user page", + "parameters": [ + { + "type": "integer", + "description": "page size", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size", + "name": "page_size", + "in": "query" + }, + { + "type": "string", + "description": "username", + "name": "username", + "in": "query" + }, + { + "type": "string", + "description": "email", + "name": "e_mail", + "in": "query" + }, + { + "enum": [ + "normal", + "suspended", + "deleted", + "inactive" + ], + "type": "string", + "description": "user status", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/definitions/pager.PageModel" + }, + { + "type": "object", + "properties": { + "records": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetUserPageResp" + } + } + } + } + ] + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/answer": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Update Answer", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-answer" + ], + "summary": "Update Answer", + "parameters": [ + { + "description": "AnswerUpdateReq", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AnswerUpdateReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Insert Answer", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-answer" + ], + "summary": "Insert Answer", + "parameters": [ + { + "description": "AnswerAddReq", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AnswerAddReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "delete answer", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-answer" + ], + "summary": "delete answer", + "parameters": [ + { + "description": "answer", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.RemoveAnswerReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/answer/acceptance": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Adopted", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-answer" + ], + "summary": "Adopted", + "parameters": [ + { + "description": "AnswerAdoptedReq", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AnswerAdoptedReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/answer/api/v1/answer/info": { + "get": { + "description": "Get Answer", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-answer" + ], + "summary": "Get Answer", + "parameters": [ + { + "type": "string", + "default": "1", + "description": "Answer TagID", + "name": "id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/answer/api/v1/answer/list": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "AnswerList \u003cbr\u003e \u003cb\u003eorder\u003c/b\u003e (default or updated)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-answer" + ], + "summary": "AnswerList", + "parameters": [ + { + "description": "AnswerList", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AnswerList" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/answer/api/v1/collection/switch": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "add collection", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Collection" + ], + "summary": "add collection", + "parameters": [ + { + "description": "collection", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.CollectionSwitchReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.CollectionSwitchResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/comment": { + "get": { + "description": "get comment by id", + "produces": [ + "application/json" + ], + "tags": [ + "Comment" + ], + "summary": "get comment by id", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/definitions/pager.PageModel" + }, + { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetCommentResp" + } + } + } + } + ] + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update comment", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Comment" + ], + "summary": "update comment", + "parameters": [ + { + "description": "comment", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UpdateCommentReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "add comment", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Comment" + ], + "summary": "add comment", + "parameters": [ + { + "description": "comment", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AddCommentReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.GetCommentResp" + } + } + } + ] + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "remove comment", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Comment" + ], + "summary": "remove comment", + "parameters": [ + { + "description": "comment", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.RemoveCommentReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/comment/page": { + "get": { + "description": "get comment page", + "produces": [ + "application/json" + ], + "tags": [ + "Comment" + ], + "summary": "get comment page", + "parameters": [ + { + "type": "integer", + "description": "page", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size", + "name": "page_size", + "in": "query" + }, + { + "type": "string", + "description": "object id", + "name": "object_id", + "in": "query", + "required": true + }, + { + "enum": [ + "vote" + ], + "type": "string", + "description": "query condition", + "name": "query_cond", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/definitions/pager.PageModel" + }, + { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetCommentResp" + } + } + } + } + ] + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/follow": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "follow object or cancel follow operation", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Activity" + ], + "summary": "follow object or cancel follow operation", + "parameters": [ + { + "description": "follow", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.FollowReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.FollowResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/follow/tags": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update user follow tags", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Activity" + ], + "summary": "update user follow tags", + "parameters": [ + { + "description": "follow", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UpdateFollowTagsReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/language/config": { + "get": { + "description": "get language config mapping", + "produces": [ + "application/json" + ], + "tags": [ + "Lang" + ], + "summary": "get language config mapping", + "parameters": [ + { + "type": "string", + "description": "Accept-Language", + "name": "Accept-Language", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/language/options": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get language options", + "produces": [ + "application/json" + ], + "tags": [ + "Lang" + ], + "summary": "Get language options", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/notification/page": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "GetRedDot", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Notification" + ], + "summary": "GetRedDot", + "parameters": [ + { + "type": "integer", + "description": "page size", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size", + "name": "page_size", + "in": "query" + }, + { + "enum": [ + "inbox", + "achievement" + ], + "type": "string", + "description": "type", + "name": "type", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/notification/read/state": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "ClearUnRead", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Notification" + ], + "summary": "ClearUnRead", + "parameters": [ + { + "description": "NotificationClearIDRequest", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.NotificationClearIDRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/notification/read/state/all": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "ClearUnRead", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Notification" + ], + "summary": "ClearUnRead", + "parameters": [ + { + "description": "NotificationClearRequest", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.NotificationClearRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/notification/status": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "GetRedDot", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Notification" + ], + "summary": "GetRedDot", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "DelRedDot", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Notification" + ], + "summary": "DelRedDot", + "parameters": [ + { + "description": "NotificationClearRequest", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.NotificationClearRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/personal/answer/page": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "UserAnswerList", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-answer" + ], + "summary": "UserAnswerList", + "parameters": [ + { + "type": "string", + "default": "string", + "description": "username", + "name": "username", + "in": "query", + "required": true + }, + { + "enum": [ + "newest", + "score" + ], + "type": "string", + "description": "order", + "name": "order", + "in": "query", + "required": true + }, + { + "type": "string", + "default": "0", + "description": "page", + "name": "page", + "in": "query", + "required": true + }, + { + "type": "string", + "default": "20", + "description": "pagesize", + "name": "pagesize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/personal/collection/page": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "UserCollectionList", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Collection" + ], + "summary": "UserCollectionList", + "parameters": [ + { + "type": "string", + "default": "0", + "description": "page", + "name": "page", + "in": "query", + "required": true + }, + { + "type": "string", + "default": "20", + "description": "pagesize", + "name": "pagesize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/personal/comment/page": { + "get": { + "description": "user personal comment list", + "produces": [ + "application/json" + ], + "tags": [ + "Comment" + ], + "summary": "user personal comment list", + "parameters": [ + { + "type": "integer", + "description": "page", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size", + "name": "page_size", + "in": "query" + }, + { + "type": "string", + "description": "username", + "name": "username", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/definitions/pager.PageModel" + }, + { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetCommentPersonalWithPageResp" + } + } + } + } + ] + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/personal/qa/top": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "UserTop", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "UserTop", + "parameters": [ + { + "type": "string", + "default": "string", + "description": "username", + "name": "username", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/personal/rank/page": { + "get": { + "description": "user personal rank list", + "produces": [ + "application/json" + ], + "tags": [ + "Rank" + ], + "summary": "user personal rank list", + "parameters": [ + { + "type": "integer", + "description": "page", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size", + "name": "page_size", + "in": "query" + }, + { + "type": "string", + "description": "username", + "name": "username", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/definitions/pager.PageModel" + }, + { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetRankPersonalWithPageResp" + } + } + } + } + ] + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/personal/user/info": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "GetOtherUserInfoByUsername", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "GetOtherUserInfoByUsername", + "parameters": [ + { + "type": "string", + "description": "username", + "name": "username", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.GetOtherUserInfoResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/personal/vote/page": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "user's vote", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Activity" + ], + "summary": "user's votes", + "parameters": [ + { + "type": "integer", + "description": "page size", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/definitions/pager.PageModel" + }, + { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetVoteWithPageResp" + } + } + } + } + ] + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/question": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update question", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "update question", + "parameters": [ + { + "description": "question", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.QuestionUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "add question", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "add question", + "parameters": [ + { + "description": "question", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.QuestionAdd" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "delete question", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "delete question", + "parameters": [ + { + "description": "question", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.RemoveQuestionReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/question/closemsglist": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "close question msg list", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "close question msg list", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/question/info": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "GetQuestion Question", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "GetQuestion Question", + "parameters": [ + { + "type": "string", + "default": "1", + "description": "Question TagID", + "name": "id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/answer/api/v1/question/page": { + "post": { + "description": "SearchQuestionList \u003cbr\u003e \"order\" Enums(newest, active,frequent,score,unanswered)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "SearchQuestionList", + "parameters": [ + { + "description": "QuestionSearch", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.QuestionSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/answer/api/v1/question/search": { + "post": { + "description": "SearchQuestionList", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "SearchQuestionList", + "parameters": [ + { + "description": "QuestionSearch", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.QuestionSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/answer/api/v1/question/similar": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "add question title like", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "add question title like", + "parameters": [ + { + "type": "string", + "default": "string", + "description": "title", + "name": "title", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/question/similar/tag": { + "get": { + "description": "Search Similar Question", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "Search Similar Question", + "parameters": [ + { + "type": "string", + "default": "", + "description": "question_id", + "name": "question_id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/answer/api/v1/question/status": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Close question", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "Close question", + "parameters": [ + { + "description": "question", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.CloseQuestionReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/question/tags": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get tag list", + "produces": [ + "application/json" + ], + "tags": [ + "Tag" + ], + "summary": "get tag list", + "parameters": [ + { + "type": "string", + "description": "tag", + "name": "tag", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetTagResp" + } + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/reasons": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get reasons by object type and action", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "reason" + ], + "summary": "get reasons by object type and action", + "parameters": [ + { + "enum": [ + "question", + "answer", + "comment", + "user" + ], + "type": "string", + "description": "object_type", + "name": "object_type", + "in": "query", + "required": true + }, + { + "enum": [ + "status", + "close", + "flag", + "review" + ], + "type": "string", + "description": "action", + "name": "action", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/report": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + }, + { + "ApiKeyAuth": [] + } + ], + "description": "add report \u003cbr\u003e source (question, answer, comment, user)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Report" + ], + "summary": "add report", + "parameters": [ + { + "description": "report", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AddReportReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/report/type/list": { + "get": { + "description": "get report type list", + "produces": [ + "application/json" + ], + "tags": [ + "Report" + ], + "summary": "get report type list", + "parameters": [ + { + "enum": [ + "question", + "answer", + "comment", + "user" + ], + "type": "string", + "description": "report source", + "name": "source", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetReportTypeResp" + } + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/revisions": { + "get": { + "description": "get revision list", + "produces": [ + "application/json" + ], + "tags": [ + "Revision" + ], + "summary": "get revision list", + "parameters": [ + { + "type": "string", + "description": "object id", + "name": "object_id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetRevisionResp" + } + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/search": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "search object", + "produces": [ + "application/json" + ], + "tags": [ + "Search" + ], + "summary": "search object", + "parameters": [ + { + "type": "string", + "description": "query string", + "name": "q", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SearchListResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/siteinfo": { + "get": { + "description": "Get siteinfo", + "produces": [ + "application/json" + ], + "tags": [ + "site" + ], + "summary": "Get siteinfo", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteGeneralResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/tag": { + "get": { + "description": "get tag one", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tag" + ], + "summary": "get tag one", + "parameters": [ + { + "type": "string", + "description": "tag id", + "name": "tag_id", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "tag name", + "name": "tag_name", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.GetTagResp" + } + } + } + ] + } + } + } + }, + "put": { + "description": "update tag", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tag" + ], + "summary": "update tag", + "parameters": [ + { + "description": "tag", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UpdateTagReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + }, + "delete": { + "description": "delete tag", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tag" + ], + "summary": "delete tag", + "parameters": [ + { + "description": "tag", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.RemoveTagReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/tag/synonym": { + "put": { + "description": "update tag", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tag" + ], + "summary": "update tag", + "parameters": [ + { + "description": "tag", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UpdateTagSynonymReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/tag/synonyms": { + "get": { + "description": "get tag synonyms", + "produces": [ + "application/json" + ], + "tags": [ + "Tag" + ], + "summary": "get tag synonyms", + "parameters": [ + { + "type": "integer", + "description": "tag id", + "name": "tag_id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetTagSynonymsResp" + } + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/tags/following": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get following tag list", + "produces": [ + "application/json" + ], + "tags": [ + "Tag" + ], + "summary": "get following tag list", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetFollowingTagsResp" + } + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/tags/page": { + "get": { + "description": "get tag page", + "produces": [ + "application/json" + ], + "tags": [ + "Tag" + ], + "summary": "get tag page", + "parameters": [ + { + "type": "integer", + "description": "page size", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size", + "name": "page_size", + "in": "query" + }, + { + "type": "string", + "description": "slug_name", + "name": "slug_name", + "in": "query" + }, + { + "enum": [ + "popular", + "name", + "newest" + ], + "type": "string", + "description": "query condition", + "name": "query_cond", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/definitions/pager.PageModel" + }, + { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetTagPageResp" + } + } + } + } + ] + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/user/action/record": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "ActionRecord", + "tags": [ + "User" + ], + "summary": "ActionRecord", + "parameters": [ + { + "enum": [ + "login", + "e_mail", + "find_pass" + ], + "type": "string", + "description": "action", + "name": "action", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ActionRecordResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/user/avatar/upload": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "UserUpdateInfo", + "consumes": [ + "multipart/form-data" + ], + "tags": [ + "User" + ], + "summary": "UserUpdateInfo", + "parameters": [ + { + "type": "file", + "description": "file", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/user/email": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "user change email verification", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "user change email verification", + "parameters": [ + { + "description": "UserChangeEmailVerifyReq", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserChangeEmailVerifyReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/user/email/change/code": { + "post": { + "description": "send email to the user email then change their email", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "send email to the user email then change their email", + "parameters": [ + { + "description": "UserChangeEmailSendCodeReq", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserChangeEmailSendCodeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/user/email/verification": { + "post": { + "description": "UserVerifyEmail", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "UserVerifyEmail", + "parameters": [ + { + "type": "string", + "default": "", + "description": "code", + "name": "code", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.GetUserResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/user/email/verification/send": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "UserVerifyEmailSend", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "UserVerifyEmailSend", + "parameters": [ + { + "type": "string", + "default": "", + "description": "captcha_id", + "name": "captcha_id", + "in": "query" + }, + { + "type": "string", + "default": "", + "description": "captcha_code", + "name": "captcha_code", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/answer/api/v1/user/info": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "GetUserInfoByUserID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "GetUserInfoByUserID", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.GetUserResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "UserUpdateInfo", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "UserUpdateInfo", + "parameters": [ + { + "type": "string", + "description": "access-token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "description": "UpdateInfoRequest", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UpdateInfoRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/user/login/email": { + "post": { + "description": "UserEmailLogin", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "UserEmailLogin", + "parameters": [ + { + "description": "UserEmailLogin", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserEmailLogin" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.GetUserResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/user/logout": { + "get": { + "description": "user logout", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "user logout", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/user/notice/set": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "UserNoticeSet", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "UserNoticeSet", + "parameters": [ + { + "description": "UserNoticeSetRequest", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserNoticeSetRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.UserNoticeSetResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/user/password": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "UserModifyPassWord", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "UserModifyPassWord", + "parameters": [ + { + "description": "UserModifyPassWordRequest", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserModifyPassWordRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/user/password/replacement": { + "post": { + "description": "UseRePassWord", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "UseRePassWord", + "parameters": [ + { + "description": "UserRePassWordRequest", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserRePassWordRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/answer/api/v1/user/password/reset": { + "post": { + "description": "RetrievePassWord", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "RetrievePassWord", + "parameters": [ + { + "description": "UserRetrievePassWordRequest", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserRetrievePassWordRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/answer/api/v1/user/post/file": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "upload user post file", + "consumes": [ + "multipart/form-data" + ], + "tags": [ + "User" + ], + "summary": "upload user post file", + "parameters": [ + { + "type": "file", + "description": "file", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/user/register/email": { + "post": { + "description": "UserRegisterByEmail", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "UserRegisterByEmail", + "parameters": [ + { + "description": "UserRegister", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserRegister" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.GetUserResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/vote/down": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "add vote", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Activity" + ], + "summary": "vote down", + "parameters": [ + { + "description": "vote", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.VoteReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.VoteResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/vote/up": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "add vote", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Activity" + ], + "summary": "vote up", + "parameters": [ + { + "description": "vote", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.VoteReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.VoteResp" + } + } + } + ] + } + } + } + } + }, + "/personal/question/page": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "UserList", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "UserList", + "parameters": [ + { + "type": "string", + "default": "string", + "description": "username", + "name": "username", + "in": "query", + "required": true + }, + { + "enum": [ + "newest", + "score" + ], + "type": "string", + "description": "order", + "name": "order", + "in": "query", + "required": true + }, + { + "type": "string", + "default": "0", + "description": "page", + "name": "page", + "in": "query", + "required": true + }, + { + "type": "string", + "default": "20", + "description": "pagesize", + "name": "pagesize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + } + }, + "definitions": { + "entity.AdminSetAnswerStatusRequest": { + "type": "object", + "properties": { + "answer_id": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "handler.RespBody": { + "type": "object", + "properties": { + "code": { + "description": "http code", + "type": "integer" + }, + "data": { + "description": "response data" + }, + "msg": { + "description": "response message", + "type": "string" + }, + "reason": { + "description": "reason key", + "type": "string" + } + } + }, + "pager.PageModel": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "list": {} + } + }, + "schema.ActionRecordResp": { + "type": "object", + "properties": { + "captcha_id": { + "type": "string" + }, + "captcha_img": { + "type": "string" + }, + "verify": { + "type": "boolean" + } + } + }, + "schema.AddCollectionGroupReq": { + "type": "object", + "required": [ + "create_time", + "default_group", + "name", + "update_time", + "user_id" + ], + "properties": { + "create_time": { + "type": "string" + }, + "default_group": { + "description": "mark this group is default, default 1", + "type": "integer" + }, + "name": { + "description": "the collection group name", + "type": "string", + "maxLength": 50 + }, + "update_time": { + "type": "string" + }, + "user_id": { + "type": "integer" + } + } + }, + "schema.AddCommentReq": { + "type": "object", + "required": [ + "object_id", + "original_text", + "parsed_text" + ], + "properties": { + "mention_username_list": { + "description": "@ user id list", + "type": "array", + "items": { + "type": "string" + } + }, + "object_id": { + "description": "object id", + "type": "string" + }, + "original_text": { + "description": "original comment content", + "type": "string" + }, + "parsed_text": { + "description": "parsed comment content", + "type": "string" + }, + "reply_comment_id": { + "description": "reply comment id", + "type": "string" + } + } + }, + "schema.AddNotificationReadReq": { + "type": "object", + "required": [ + "is_read", + "message_id", + "user_id" + ], + "properties": { + "is_read": { + "description": "read status(unread: 1; read 2)", + "type": "integer" + }, + "message_id": { + "description": "message id", + "type": "integer" + }, + "user_id": { + "description": "user id", + "type": "integer" + } + } + }, + "schema.AddReportReq": { + "type": "object", + "required": [ + "object_id", + "report_type" + ], + "properties": { + "content": { + "description": "report content", + "type": "string", + "maxLength": 500 + }, + "object_id": { + "description": "object id", + "type": "string", + "maxLength": 20 + }, + "report_type": { + "description": "report type", + "type": "integer" + } + } + }, + "schema.AddUserGroupReq": { + "type": "object" + }, + "schema.AdminSetQuestionStatusRequest": { + "type": "object", + "properties": { + "question_id": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "schema.AnswerAddReq": { + "type": "object", + "properties": { + "content": { + "description": "content", + "type": "string" + }, + "html": { + "description": "解析后的html", + "type": "string" + }, + "question_id": { + "description": "question_id", + "type": "string" + } + } + }, + "schema.AnswerAdoptedReq": { + "type": "object", + "properties": { + "answer_id": { + "type": "string" + }, + "question_id": { + "description": "question_id", + "type": "string" + } + } + }, + "schema.AnswerList": { + "type": "object", + "properties": { + "order": { + "description": "1 Default 2 time", + "type": "string" + }, + "page": { + "description": "Query number of pages", + "type": "integer" + }, + "page_size": { + "description": "Search page size", + "type": "integer" + }, + "question_id": { + "description": "question_id", + "type": "string" + } + } + }, + "schema.AnswerUpdateReq": { + "type": "object", + "properties": { + "content": { + "description": "content", + "type": "string" + }, + "edit_summary": { + "description": "edit_summary", + "type": "string" + }, + "html": { + "description": "解析后的html", + "type": "string" + }, + "id": { + "description": "id", + "type": "string" + }, + "question_id": { + "description": "question_id", + "type": "string" + }, + "title": { + "description": "title", + "type": "string" + } + } + }, + "schema.CloseQuestionReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "close_msg": { + "description": "close_type", + "type": "string" + }, + "close_type": { + "description": "close_type", + "type": "integer" + }, + "id": { + "type": "string" + } + } + }, + "schema.CollectionSwitchReq": { + "type": "object", + "required": [ + "group_id", + "object_id" + ], + "properties": { + "group_id": { + "description": "user collection group TagID", + "type": "string" + }, + "object_id": { + "description": "object TagID", + "type": "string" + } + } + }, + "schema.CollectionSwitchResp": { + "type": "object", + "properties": { + "object_collection_count": { + "type": "string" + }, + "object_id": { + "type": "string" + }, + "switch": { + "type": "boolean" + } + } + }, + "schema.FollowReq": { + "type": "object", + "required": [ + "object_id" + ], + "properties": { + "is_cancel": { + "description": "is cancel", + "type": "boolean" + }, + "object_id": { + "description": "object id", + "type": "string" + } + } + }, + "schema.FollowResp": { + "type": "object", + "properties": { + "follows": { + "description": "the followers of object", + "type": "integer" + }, + "is_followed": { + "description": "if user is followed object will be true,otherwise false", + "type": "boolean" + } + } + }, + "schema.GetCommentPersonalWithPageResp": { + "type": "object", + "properties": { + "answer_id": { + "description": "answer id", + "type": "string" + }, + "comment_id": { + "description": "comment id", + "type": "string" + }, + "content": { + "description": "content", + "type": "string" + }, + "created_at": { + "description": "create time", + "type": "integer" + }, + "object_id": { + "description": "object id", + "type": "string" + }, + "object_type": { + "description": "object type", + "type": "string", + "enum": [ + "question", + "answer", + "tag", + "comment" + ] + }, + "question_id": { + "description": "question id", + "type": "string" + }, + "title": { + "description": "title", + "type": "string" + } + } + }, + "schema.GetCommentResp": { + "type": "object", + "properties": { + "comment_id": { + "description": "comment id", + "type": "string" + }, + "created_at": { + "description": "create time", + "type": "integer" + }, + "is_vote": { + "description": "current user if already vote this comment", + "type": "boolean" + }, + "member_actions": { + "description": "MemberActions", + "type": "array", + "items": { + "$ref": "#/definitions/schema.PermissionMemberAction" + } + }, + "object_id": { + "description": "object id", + "type": "string" + }, + "original_text": { + "description": "original comment content", + "type": "string" + }, + "parsed_text": { + "description": "parsed comment content", + "type": "string" + }, + "reply_comment_id": { + "description": "reply comment id", + "type": "string" + }, + "reply_user_display_name": { + "description": "reply user display name", + "type": "string" + }, + "reply_user_id": { + "description": "reply user id", + "type": "string" + }, + "reply_username": { + "description": "reply user username", + "type": "string" + }, + "user_avatar": { + "description": "user avatar", + "type": "string" + }, + "user_display_name": { + "description": "user display name", + "type": "string" + }, + "user_id": { + "description": "user id", + "type": "string" + }, + "username": { + "description": "username", + "type": "string" + }, + "vote_count": { + "description": "user vote amount", + "type": "integer" + } + } + }, + "schema.GetFollowingTagsResp": { + "type": "object", + "properties": { + "display_name": { + "description": "display name", + "type": "string" + }, + "main_tag_slug_name": { + "description": "if main tag slug name is not empty, this tag is synonymous with the main tag", + "type": "string" + }, + "slug_name": { + "description": "slug name", + "type": "string" + }, + "tag_id": { + "description": "tag id", + "type": "string" + } + } + }, + "schema.GetNotificationReadResp": { + "type": "object", + "properties": { + "created_at": { + "description": "create time", + "type": "string" + }, + "id": { + "description": "id", + "type": "integer" + }, + "is_read": { + "description": "read status(unread: 1; read 2)", + "type": "integer" + }, + "message_id": { + "description": "message id", + "type": "integer" + }, + "updated_at": { + "description": "update time", + "type": "string" + }, + "user_id": { + "description": "user id", + "type": "integer" + } + } + }, + "schema.GetOtherUserInfoByUsernameResp": { + "type": "object", + "properties": { + "answer_count": { + "description": "answer count", + "type": "integer" + }, + "avatar": { + "description": "avatar", + "type": "string" + }, + "bio": { + "description": "bio markdown", + "type": "string" + }, + "bio_html": { + "description": "bio html", + "type": "string" + }, + "created_at": { + "description": "create time", + "type": "integer" + }, + "display_name": { + "description": "display name", + "type": "string" + }, + "follow_count": { + "description": "email\nfollow count", + "type": "integer" + }, + "id": { + "description": "user id", + "type": "string" + }, + "ip_info": { + "description": "ip info", + "type": "string" + }, + "is_admin": { + "description": "is admin", + "type": "boolean" + }, + "last_login_date": { + "description": "last login date", + "type": "integer" + }, + "location": { + "description": "location", + "type": "string" + }, + "mobile": { + "description": "mobile", + "type": "string" + }, + "question_count": { + "description": "question count", + "type": "integer" + }, + "rank": { + "description": "rank", + "type": "integer" + }, + "status": { + "type": "string" + }, + "status_msg": { + "type": "string" + }, + "username": { + "description": "username", + "type": "string" + }, + "website": { + "description": "website", + "type": "string" + } + } + }, + "schema.GetOtherUserInfoResp": { + "type": "object", + "properties": { + "has": { + "type": "boolean" + }, + "info": { + "$ref": "#/definitions/schema.GetOtherUserInfoByUsernameResp" + } + } + }, + "schema.GetRankPersonalWithPageResp": { + "type": "object", + "properties": { + "answer_id": { + "description": "answer id", + "type": "string" + }, + "content": { + "description": "content", + "type": "string" + }, + "created_at": { + "description": "create time", + "type": "integer" + }, + "object_id": { + "description": "object id", + "type": "string" + }, + "object_type": { + "description": "object type", + "type": "string", + "enum": [ + "question", + "answer", + "tag", + "comment" + ] + }, + "question_id": { + "description": "question id", + "type": "string" + }, + "rank_type": { + "description": "rank type", + "type": "string" + }, + "reputation": { + "description": "reputation", + "type": "integer" + }, + "title": { + "description": "title", + "type": "string" + } + } + }, + "schema.GetReportTypeResp": { + "type": "object", + "properties": { + "content_type": { + "description": "content type", + "type": "string" + }, + "description": { + "description": "report description", + "type": "string" + }, + "have_content": { + "description": "is have content", + "type": "boolean" + }, + "name": { + "description": "report name", + "type": "string" + }, + "source": { + "description": "report source", + "type": "string" + }, + "type": { + "description": "report type", + "type": "integer" + } + } + }, + "schema.GetRevisionResp": { + "type": "object", + "properties": { + "content": { + "description": "content parsed" + }, + "create_at": { + "type": "integer" + }, + "id": { + "description": "id", + "type": "string" + }, + "object_id": { + "description": "object id", + "type": "string" + }, + "reason": { + "type": "string" + }, + "status": { + "description": "revision status(normal: 1; delete 2)", + "type": "integer" + }, + "title": { + "description": "title", + "type": "string" + }, + "use_id": { + "description": "user id", + "type": "string" + }, + "user_info": { + "$ref": "#/definitions/schema.UserBasicInfo" + } + } + }, + "schema.GetTagPageResp": { + "type": "object", + "properties": { + "created_at": { + "description": "created time", + "type": "integer" + }, + "display_name": { + "description": "display_name", + "type": "string" + }, + "excerpt": { + "description": "excerpt", + "type": "string" + }, + "follow_count": { + "description": "follower amount", + "type": "integer" + }, + "is_follower": { + "description": "is follower", + "type": "boolean" + }, + "original_text": { + "description": "original text", + "type": "string" + }, + "parsed_text": { + "description": "parsed_text", + "type": "string" + }, + "question_count": { + "description": "question amount", + "type": "integer" + }, + "slug_name": { + "description": "slug_name", + "type": "string" + }, + "tag_id": { + "description": "tag_id", + "type": "string" + }, + "updated_at": { + "description": "updated time", + "type": "integer" + } + } + }, + "schema.GetTagResp": { + "type": "object", + "properties": { + "created_at": { + "description": "created time", + "type": "integer" + }, + "display_name": { + "description": "display name", + "type": "string" + }, + "excerpt": { + "description": "excerpt", + "type": "string" + }, + "follow_count": { + "description": "follower amount", + "type": "integer" + }, + "is_follower": { + "description": "is follower", + "type": "boolean" + }, + "main_tag_slug_name": { + "description": "if main tag slug name is not empty, this tag is synonymous with the main tag", + "type": "string" + }, + "member_actions": { + "description": "MemberActions", + "type": "array", + "items": { + "$ref": "#/definitions/schema.PermissionMemberAction" + } + }, + "original_text": { + "description": "original text", + "type": "string" + }, + "parsed_text": { + "description": "parsed text", + "type": "string" + }, + "question_count": { + "description": "question amount", + "type": "integer" + }, + "slug_name": { + "description": "slug name", + "type": "string" + }, + "tag_id": { + "description": "tag id", + "type": "string" + }, + "updated_at": { + "description": "updated time", + "type": "integer" + } + } + }, + "schema.GetTagSynonymsResp": { + "type": "object", + "properties": { + "display_name": { + "description": "display name", + "type": "string" + }, + "main_tag_slug_name": { + "description": "if main tag slug name is not empty, this tag is synonymous with the main tag", + "type": "string" + }, + "slug_name": { + "description": "slug name", + "type": "string" + }, + "tag_id": { + "description": "tag id", + "type": "string" + } + } + }, + "schema.GetUserGroupResp": { + "type": "object", + "properties": { + "id": { + "description": "user group id", + "type": "integer" + } + } + }, + "schema.GetUserInfoResp": { + "type": "object" + }, + "schema.GetUserPageResp": { + "type": "object", + "properties": { + "avatar": { + "description": "avatar", + "type": "string" + }, + "created_at": { + "description": "create time", + "type": "integer" + }, + "deleted_at": { + "description": "delete time", + "type": "integer" + }, + "display_name": { + "description": "display name", + "type": "string" + }, + "e_mail": { + "description": "email", + "type": "string" + }, + "rank": { + "description": "rank", + "type": "integer" + }, + "status": { + "description": "user status(normal,suspended,deleted,inactive)", + "type": "string" + }, + "suspended_at": { + "description": "suspended time", + "type": "integer" + }, + "user_id": { + "description": "user id", + "type": "string" + }, + "username": { + "description": "username", + "type": "string" + } + } + }, + "schema.GetUserResp": { + "type": "object", + "properties": { + "access_token": { + "description": "access token", + "type": "string" + }, + "answer_count": { + "description": "answer count", + "type": "integer" + }, + "authority_group": { + "description": "authority group", + "type": "integer" + }, + "avatar": { + "description": "avatar", + "type": "string" + }, + "bio": { + "description": "bio markdown", + "type": "string" + }, + "bio_html": { + "description": "bio html", + "type": "string" + }, + "created_at": { + "description": "create time", + "type": "integer" + }, + "display_name": { + "description": "display name", + "type": "string" + }, + "e_mail": { + "description": "email", + "type": "string" + }, + "follow_count": { + "description": "follow count", + "type": "integer" + }, + "id": { + "description": "user id", + "type": "string" + }, + "ip_info": { + "description": "ip info", + "type": "string" + }, + "is_admin": { + "description": "is admin", + "type": "boolean" + }, + "last_login_date": { + "description": "last login date", + "type": "integer" + }, + "location": { + "description": "location", + "type": "string" + }, + "mail_status": { + "description": "mail status(1 pass 2 to be verified)", + "type": "integer" + }, + "mobile": { + "description": "mobile", + "type": "string" + }, + "notice_status": { + "description": "notice status(1 on 2off)", + "type": "integer" + }, + "question_count": { + "description": "question count", + "type": "integer" + }, + "rank": { + "description": "rank", + "type": "integer" + }, + "status": { + "description": "user status", + "type": "string" + }, + "username": { + "description": "username", + "type": "string" + }, + "website": { + "description": "website", + "type": "string" + } + } + }, + "schema.GetVoteWithPageResp": { + "type": "object", + "properties": { + "answer_id": { + "description": "answer id", + "type": "string" + }, + "content": { + "description": "content", + "type": "string" + }, + "created_at": { + "description": "create time", + "type": "integer" + }, + "object_id": { + "description": "object id", + "type": "string" + }, + "object_type": { + "description": "object type", + "type": "string", + "enum": [ + "question", + "answer", + "tag", + "comment" + ] + }, + "question_id": { + "description": "question id", + "type": "string" + }, + "title": { + "description": "title", + "type": "string" + }, + "vote_type": { + "description": "vote type", + "type": "string" + } + } + }, + "schema.NotificationClearIDRequest": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + } + }, + "schema.NotificationClearRequest": { + "type": "object", + "properties": { + "type": { + "description": "inbox achievement", + "type": "string" + } + } + }, + "schema.PermissionMemberAction": { + "type": "object", + "properties": { + "action": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "schema.QuestionAdd": { + "type": "object", + "required": [ + "content", + "html", + "tags", + "title" + ], + "properties": { + "content": { + "description": "content", + "type": "string", + "maxLength": 65535, + "minLength": 6 + }, + "html": { + "description": "html", + "type": "string", + "maxLength": 65535, + "minLength": 6 + }, + "tags": { + "description": "tags", + "type": "array", + "items": { + "$ref": "#/definitions/schema.TagItem" + } + }, + "title": { + "description": "question title", + "type": "string", + "maxLength": 64, + "minLength": 6 + } + } + }, + "schema.QuestionSearch": { + "type": "object", + "properties": { + "order": { + "description": "Search order by", + "type": "string" + }, + "page": { + "description": "Query number of pages", + "type": "integer" + }, + "page_size": { + "description": "Search page size", + "type": "integer" + }, + "tags": { + "description": "Search tag", + "type": "array", + "items": { + "type": "string" + } + }, + "username": { + "description": "Search username", + "type": "string" + } + } + }, + "schema.QuestionUpdate": { + "type": "object", + "required": [ + "content", + "html", + "id", + "tags", + "title" + ], + "properties": { + "content": { + "description": "content", + "type": "string", + "maxLength": 65535, + "minLength": 6 + }, + "edit_summary": { + "description": "edit summary", + "type": "string" + }, + "html": { + "description": "html", + "type": "string", + "maxLength": 65535, + "minLength": 6 + }, + "id": { + "description": "question id", + "type": "string" + }, + "tags": { + "description": "tags", + "type": "array", + "items": { + "$ref": "#/definitions/schema.TagItem" + } + }, + "title": { + "description": "question title", + "type": "string", + "maxLength": 64, + "minLength": 6 + } + } + }, + "schema.RemoveAnswerReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "description": "answer id", + "type": "string" + } + } + }, + "schema.RemoveCommentReq": { + "type": "object", + "required": [ + "comment_id" + ], + "properties": { + "comment_id": { + "description": "comment id", + "type": "string" + } + } + }, + "schema.RemoveNotificationReadReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "description": "id", + "type": "integer" + } + } + }, + "schema.RemoveQuestionReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "description": "question id", + "type": "string" + } + } + }, + "schema.RemoveTagReq": { + "type": "object", + "required": [ + "tag_id" + ], + "properties": { + "tag_id": { + "description": "tag_id", + "type": "string" + } + } + }, + "schema.RemoveUserGroupReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "description": "user group id", + "type": "integer" + } + } + }, + "schema.ReportHandleReq": { + "type": "object", + "required": [ + "flaged_type", + "id" + ], + "properties": { + "flaged_content": { + "type": "string" + }, + "flaged_type": { + "type": "integer" + }, + "id": { + "type": "string" + } + } + }, + "schema.SearchListResp": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "extra": { + "description": "extra fields" + }, + "list": { + "description": "search response", + "type": "array", + "items": { + "$ref": "#/definitions/schema.SearchResp" + } + } + } + }, + "schema.SearchObject": { + "type": "object", + "properties": { + "accepted": { + "type": "boolean" + }, + "answer_count": { + "type": "integer" + }, + "created_at": { + "type": "integer" + }, + "excerpt": { + "type": "string" + }, + "id": { + "type": "string" + }, + "tags": { + "description": "tags", + "type": "array", + "items": { + "$ref": "#/definitions/schema.TagResp" + } + }, + "title": { + "type": "string" + }, + "user_info": { + "description": "user info", + "$ref": "#/definitions/schema.UserBasicInfo" + }, + "vote_count": { + "type": "integer" + } + } + }, + "schema.SearchResp": { + "type": "object", + "properties": { + "object": { + "description": "this object", + "$ref": "#/definitions/schema.SearchObject" + }, + "object_type": { + "description": "object_type", + "type": "string" + } + } + }, + "schema.SiteGeneralReq": { + "type": "object", + "required": [ + "description", + "name", + "short_description" + ], + "properties": { + "description": { + "type": "string", + "maxLength": 2000 + }, + "name": { + "type": "string", + "maxLength": 128 + }, + "short_description": { + "type": "string", + "maxLength": 255 + } + } + }, + "schema.SiteGeneralResp": { + "type": "object", + "required": [ + "description", + "name", + "short_description" + ], + "properties": { + "description": { + "type": "string", + "maxLength": 2000 + }, + "name": { + "type": "string", + "maxLength": 128 + }, + "short_description": { + "type": "string", + "maxLength": 255 + } + } + }, + "schema.SiteInterfaceReq": { + "type": "object", + "required": [ + "language", + "theme" + ], + "properties": { + "language": { + "type": "string", + "maxLength": 128 + }, + "logo": { + "type": "string", + "maxLength": 256 + }, + "theme": { + "type": "string", + "maxLength": 128 + } + } + }, + "schema.SiteInterfaceResp": { + "type": "object", + "required": [ + "language", + "theme" + ], + "properties": { + "language": { + "type": "string", + "maxLength": 128 + }, + "logo": { + "type": "string", + "maxLength": 256 + }, + "theme": { + "type": "string", + "maxLength": 128 + } + } + }, + "schema.TagItem": { + "type": "object", + "properties": { + "display_name": { + "description": "display_name", + "type": "string", + "maxLength": 50 + }, + "original_text": { + "description": "original text", + "type": "string" + }, + "parsed_text": { + "description": "parsed text", + "type": "string" + }, + "slug_name": { + "description": "slug_name", + "type": "string", + "maxLength": 50 + } + } + }, + "schema.TagResp": { + "type": "object", + "properties": { + "display_name": { + "type": "string" + }, + "main_tag_slug_name": { + "description": "if main tag slug name is not empty, this tag is synonymous with the main tag", + "type": "string" + }, + "slug_name": { + "type": "string" + } + } + }, + "schema.UpdateCollectionGroupReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "create_time": { + "type": "string" + }, + "default_group": { + "description": "mark this group is default, default 1", + "type": "integer" + }, + "id": { + "type": "integer" + }, + "name": { + "description": "the collection group name", + "type": "string", + "maxLength": 50 + }, + "update_time": { + "type": "string" + }, + "user_id": { + "type": "integer" + } + } + }, + "schema.UpdateCommentReq": { + "type": "object", + "required": [ + "comment_id" + ], + "properties": { + "comment_id": { + "description": "comment id", + "type": "string" + }, + "original_text": { + "description": "original comment content", + "type": "string" + }, + "parsed_text": { + "description": "parsed comment content", + "type": "string" + } + } + }, + "schema.UpdateFollowTagsReq": { + "type": "object", + "properties": { + "slug_name_list": { + "description": "tag slug name list", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "schema.UpdateInfoRequest": { + "type": "object", + "properties": { + "avatar": { + "description": "avatar", + "type": "string" + }, + "bio": { + "type": "string" + }, + "bio_html": { + "type": "string" + }, + "display_name": { + "description": "display_name", + "type": "string" + }, + "location": { + "description": "location", + "type": "string" + }, + "username": { + "description": "name", + "type": "string" + }, + "website": { + "description": "website", + "type": "string" + } + } + }, + "schema.UpdateNotificationReadReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "description": "id", + "type": "integer" + }, + "is_read": { + "description": "read status(unread: 1; read 2)", + "type": "integer" + }, + "message_id": { + "description": "message id", + "type": "integer" + }, + "user_id": { + "description": "user id", + "type": "integer" + } + } + }, + "schema.UpdateTagReq": { + "type": "object", + "required": [ + "tag_id" + ], + "properties": { + "display_name": { + "description": "display_name", + "type": "string", + "maxLength": 50 + }, + "edit_summary": { + "description": "edit summary", + "type": "string" + }, + "original_text": { + "description": "original text", + "type": "string" + }, + "parsed_text": { + "description": "parsed text", + "type": "string" + }, + "slug_name": { + "description": "slug_name", + "type": "string", + "maxLength": 50 + }, + "tag_id": { + "description": "tag_id", + "type": "string" + } + } + }, + "schema.UpdateTagSynonymReq": { + "type": "object", + "required": [ + "synonym_tag_list", + "tag_id" + ], + "properties": { + "synonym_tag_list": { + "description": "synonym tag list", + "type": "array", + "items": { + "$ref": "#/definitions/schema.TagItem" + } + }, + "tag_id": { + "description": "tag_id", + "type": "string" + } + } + }, + "schema.UpdateUserGroupReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "description": "user group id", + "type": "integer" + } + } + }, + "schema.UpdateUserStatusReq": { + "type": "object", + "required": [ + "status", + "user_id" + ], + "properties": { + "status": { + "description": "user status", + "type": "string", + "enum": [ + "normal", + "suspended", + "deleted", + "inactive" + ] + }, + "user_id": { + "description": "user id", + "type": "string" + } + } + }, + "schema.UserBasicInfo": { + "type": "object", + "properties": { + "avatar": { + "description": "avatar", + "type": "string" + }, + "display_name": { + "description": "display_name", + "type": "string" + }, + "ip_info": { + "description": "ip info", + "type": "string" + }, + "location": { + "description": "location", + "type": "string" + }, + "rank": { + "description": "rank", + "type": "integer" + }, + "status": { + "description": "status", + "type": "integer" + }, + "username": { + "description": "name", + "type": "string" + }, + "website": { + "description": "website", + "type": "string" + } + } + }, + "schema.UserChangeEmailSendCodeReq": { + "type": "object", + "required": [ + "e_mail" + ], + "properties": { + "e_mail": { + "type": "string", + "maxLength": 500 + } + } + }, + "schema.UserChangeEmailVerifyReq": { + "type": "object", + "required": [ + "code" + ], + "properties": { + "code": { + "type": "string", + "maxLength": 500 + } + } + }, + "schema.UserEmailLogin": { + "type": "object", + "properties": { + "captcha_code": { + "description": "captcha_code", + "type": "string" + }, + "captcha_id": { + "description": "captcha_id", + "type": "string" + }, + "e_mail": { + "description": "e_mail", + "type": "string" + }, + "pass": { + "description": "password", + "type": "string" + } + } + }, + "schema.UserModifyPassWordRequest": { + "type": "object", + "properties": { + "old_pass": { + "description": "old password", + "type": "string" + }, + "pass": { + "description": "password", + "type": "string" + } + } + }, + "schema.UserNoticeSetRequest": { + "type": "object", + "properties": { + "notice_switch": { + "type": "boolean" + } + } + }, + "schema.UserNoticeSetResp": { + "type": "object", + "properties": { + "notice_switch": { + "type": "boolean" + } + } + }, + "schema.UserRePassWordRequest": { + "type": "object", + "required": [ + "code", + "pass" + ], + "properties": { + "code": { + "description": "code", + "type": "string", + "maxLength": 100 + }, + "pass": { + "description": "Password", + "type": "string", + "maxLength": 32 + } + } + }, + "schema.UserRegister": { + "type": "object", + "required": [ + "e_mail", + "name", + "pass" + ], + "properties": { + "e_mail": { + "description": "email", + "type": "string", + "maxLength": 500 + }, + "name": { + "description": "name", + "type": "string", + "maxLength": 50 + }, + "pass": { + "description": "password", + "type": "string", + "maxLength": 32, + "minLength": 8 + } + } + }, + "schema.UserRetrievePassWordRequest": { + "type": "object", + "required": [ + "e_mail" + ], + "properties": { + "captcha_code": { + "description": "captcha_code", + "type": "string" + }, + "captcha_id": { + "description": "captcha_id", + "type": "string" + }, + "e_mail": { + "description": "e_mail", + "type": "string", + "maxLength": 500 + } + } + }, + "schema.VoteReq": { + "type": "object", + "required": [ + "object_id" + ], + "properties": { + "is_cancel": { + "description": "is cancel", + "type": "boolean" + }, + "object_id": { + "description": "id", + "type": "string" + } + } + }, + "schema.VoteResp": { + "type": "object", + "properties": { + "down_votes": { + "type": "integer" + }, + "up_votes": { + "type": "integer" + }, + "vote_status": { + "type": "string" + }, + "votes": { + "type": "integer" + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "", + Description: "", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 00000000..9feff4b0 --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,5648 @@ +{ + "swagger": "2.0", + "info": { + "contact": {} + }, + "paths": { + "/answer/admin/api/answer/page": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Status:[available,deleted]", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "CmsSearchList", + "parameters": [ + { + "type": "integer", + "description": "page size", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size", + "name": "page_size", + "in": "query" + }, + { + "enum": [ + "available", + "deleted" + ], + "type": "string", + "description": "user status", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/answer/status": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Status:[available,deleted]", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "AdminSetAnswerStatus", + "parameters": [ + { + "description": "AdminSetAnswerStatusRequest", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/entity.AdminSetAnswerStatusRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/language/options": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get language options", + "produces": [ + "application/json" + ], + "tags": [ + "Lang" + ], + "summary": "Get language options", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/question/page": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Status:[available,closed,deleted]", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "CmsSearchList", + "parameters": [ + { + "type": "integer", + "description": "page size", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size", + "name": "page_size", + "in": "query" + }, + { + "enum": [ + "available", + "closed", + "deleted" + ], + "type": "string", + "description": "user status", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/question/status": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Status:[available,closed,deleted]", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "AdminSetQuestionStatus", + "parameters": [ + { + "description": "AdminSetQuestionStatusRequest", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AdminSetQuestionStatusRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/reasons": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get reasons by object type and action", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "reason" + ], + "summary": "get reasons by object type and action", + "parameters": [ + { + "enum": [ + "question", + "answer", + "comment", + "user" + ], + "type": "string", + "description": "object_type", + "name": "object_type", + "in": "query", + "required": true + }, + { + "enum": [ + "status", + "close", + "flag", + "review" + ], + "type": "string", + "description": "action", + "name": "action", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/report/": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + }, + { + "ApiKeyAuth": [] + } + ], + "description": "handle flag", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "handle flag", + "parameters": [ + { + "description": "flag", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.ReportHandleReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/reports/page": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + }, + { + "ApiKeyAuth": [] + } + ], + "description": "list report records", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "list report page", + "parameters": [ + { + "enum": [ + "pending", + "completed" + ], + "type": "string", + "description": "status", + "name": "status", + "in": "query", + "required": true + }, + { + "enum": [ + "all", + "question", + "answer", + "comment" + ], + "type": "string", + "description": "object_type", + "name": "object_type", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "page size", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/siteinfo/general": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get siteinfo general", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get siteinfo general", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteGeneralResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get siteinfo interface", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get siteinfo interface", + "parameters": [ + { + "description": "general", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SiteGeneralReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/siteinfo/interface": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get siteinfo interface", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get siteinfo interface", + "parameters": [ + { + "description": "general", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AddCommentReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteInterfaceResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get siteinfo interface", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get siteinfo interface", + "parameters": [ + { + "description": "general", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SiteInterfaceReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/theme/options": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get theme options", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get theme options", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/user/status": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "update user", + "parameters": [ + { + "description": "user", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UpdateUserStatusReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/admin/api/users/page": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get user page", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "get user page", + "parameters": [ + { + "type": "integer", + "description": "page size", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size", + "name": "page_size", + "in": "query" + }, + { + "type": "string", + "description": "username", + "name": "username", + "in": "query" + }, + { + "type": "string", + "description": "email", + "name": "e_mail", + "in": "query" + }, + { + "enum": [ + "normal", + "suspended", + "deleted", + "inactive" + ], + "type": "string", + "description": "user status", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/definitions/pager.PageModel" + }, + { + "type": "object", + "properties": { + "records": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetUserPageResp" + } + } + } + } + ] + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/answer": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Update Answer", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-answer" + ], + "summary": "Update Answer", + "parameters": [ + { + "description": "AnswerUpdateReq", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AnswerUpdateReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Insert Answer", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-answer" + ], + "summary": "Insert Answer", + "parameters": [ + { + "description": "AnswerAddReq", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AnswerAddReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "delete answer", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-answer" + ], + "summary": "delete answer", + "parameters": [ + { + "description": "answer", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.RemoveAnswerReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/answer/acceptance": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Adopted", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-answer" + ], + "summary": "Adopted", + "parameters": [ + { + "description": "AnswerAdoptedReq", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AnswerAdoptedReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/answer/api/v1/answer/info": { + "get": { + "description": "Get Answer", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-answer" + ], + "summary": "Get Answer", + "parameters": [ + { + "type": "string", + "default": "1", + "description": "Answer TagID", + "name": "id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/answer/api/v1/answer/list": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "AnswerList \u003cbr\u003e \u003cb\u003eorder\u003c/b\u003e (default or updated)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-answer" + ], + "summary": "AnswerList", + "parameters": [ + { + "description": "AnswerList", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AnswerList" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/answer/api/v1/collection/switch": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "add collection", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Collection" + ], + "summary": "add collection", + "parameters": [ + { + "description": "collection", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.CollectionSwitchReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.CollectionSwitchResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/comment": { + "get": { + "description": "get comment by id", + "produces": [ + "application/json" + ], + "tags": [ + "Comment" + ], + "summary": "get comment by id", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/definitions/pager.PageModel" + }, + { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetCommentResp" + } + } + } + } + ] + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update comment", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Comment" + ], + "summary": "update comment", + "parameters": [ + { + "description": "comment", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UpdateCommentReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "add comment", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Comment" + ], + "summary": "add comment", + "parameters": [ + { + "description": "comment", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AddCommentReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.GetCommentResp" + } + } + } + ] + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "remove comment", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Comment" + ], + "summary": "remove comment", + "parameters": [ + { + "description": "comment", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.RemoveCommentReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/comment/page": { + "get": { + "description": "get comment page", + "produces": [ + "application/json" + ], + "tags": [ + "Comment" + ], + "summary": "get comment page", + "parameters": [ + { + "type": "integer", + "description": "page", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size", + "name": "page_size", + "in": "query" + }, + { + "type": "string", + "description": "object id", + "name": "object_id", + "in": "query", + "required": true + }, + { + "enum": [ + "vote" + ], + "type": "string", + "description": "query condition", + "name": "query_cond", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/definitions/pager.PageModel" + }, + { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetCommentResp" + } + } + } + } + ] + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/follow": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "follow object or cancel follow operation", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Activity" + ], + "summary": "follow object or cancel follow operation", + "parameters": [ + { + "description": "follow", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.FollowReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.FollowResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/follow/tags": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update user follow tags", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Activity" + ], + "summary": "update user follow tags", + "parameters": [ + { + "description": "follow", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UpdateFollowTagsReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/language/config": { + "get": { + "description": "get language config mapping", + "produces": [ + "application/json" + ], + "tags": [ + "Lang" + ], + "summary": "get language config mapping", + "parameters": [ + { + "type": "string", + "description": "Accept-Language", + "name": "Accept-Language", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/language/options": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get language options", + "produces": [ + "application/json" + ], + "tags": [ + "Lang" + ], + "summary": "Get language options", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/notification/page": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "GetRedDot", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Notification" + ], + "summary": "GetRedDot", + "parameters": [ + { + "type": "integer", + "description": "page size", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size", + "name": "page_size", + "in": "query" + }, + { + "enum": [ + "inbox", + "achievement" + ], + "type": "string", + "description": "type", + "name": "type", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/notification/read/state": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "ClearUnRead", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Notification" + ], + "summary": "ClearUnRead", + "parameters": [ + { + "description": "NotificationClearIDRequest", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.NotificationClearIDRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/notification/read/state/all": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "ClearUnRead", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Notification" + ], + "summary": "ClearUnRead", + "parameters": [ + { + "description": "NotificationClearRequest", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.NotificationClearRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/notification/status": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "GetRedDot", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Notification" + ], + "summary": "GetRedDot", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "DelRedDot", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Notification" + ], + "summary": "DelRedDot", + "parameters": [ + { + "description": "NotificationClearRequest", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.NotificationClearRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/personal/answer/page": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "UserAnswerList", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-answer" + ], + "summary": "UserAnswerList", + "parameters": [ + { + "type": "string", + "default": "string", + "description": "username", + "name": "username", + "in": "query", + "required": true + }, + { + "enum": [ + "newest", + "score" + ], + "type": "string", + "description": "order", + "name": "order", + "in": "query", + "required": true + }, + { + "type": "string", + "default": "0", + "description": "page", + "name": "page", + "in": "query", + "required": true + }, + { + "type": "string", + "default": "20", + "description": "pagesize", + "name": "pagesize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/personal/collection/page": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "UserCollectionList", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Collection" + ], + "summary": "UserCollectionList", + "parameters": [ + { + "type": "string", + "default": "0", + "description": "page", + "name": "page", + "in": "query", + "required": true + }, + { + "type": "string", + "default": "20", + "description": "pagesize", + "name": "pagesize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/personal/comment/page": { + "get": { + "description": "user personal comment list", + "produces": [ + "application/json" + ], + "tags": [ + "Comment" + ], + "summary": "user personal comment list", + "parameters": [ + { + "type": "integer", + "description": "page", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size", + "name": "page_size", + "in": "query" + }, + { + "type": "string", + "description": "username", + "name": "username", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/definitions/pager.PageModel" + }, + { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetCommentPersonalWithPageResp" + } + } + } + } + ] + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/personal/qa/top": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "UserTop", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "UserTop", + "parameters": [ + { + "type": "string", + "default": "string", + "description": "username", + "name": "username", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/personal/rank/page": { + "get": { + "description": "user personal rank list", + "produces": [ + "application/json" + ], + "tags": [ + "Rank" + ], + "summary": "user personal rank list", + "parameters": [ + { + "type": "integer", + "description": "page", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size", + "name": "page_size", + "in": "query" + }, + { + "type": "string", + "description": "username", + "name": "username", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/definitions/pager.PageModel" + }, + { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetRankPersonalWithPageResp" + } + } + } + } + ] + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/personal/user/info": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "GetOtherUserInfoByUsername", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "GetOtherUserInfoByUsername", + "parameters": [ + { + "type": "string", + "description": "username", + "name": "username", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.GetOtherUserInfoResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/personal/vote/page": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "user's vote", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Activity" + ], + "summary": "user's votes", + "parameters": [ + { + "type": "integer", + "description": "page size", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/definitions/pager.PageModel" + }, + { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetVoteWithPageResp" + } + } + } + } + ] + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/question": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update question", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "update question", + "parameters": [ + { + "description": "question", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.QuestionUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "add question", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "add question", + "parameters": [ + { + "description": "question", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.QuestionAdd" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "delete question", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "delete question", + "parameters": [ + { + "description": "question", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.RemoveQuestionReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/question/closemsglist": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "close question msg list", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "close question msg list", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/question/info": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "GetQuestion Question", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "GetQuestion Question", + "parameters": [ + { + "type": "string", + "default": "1", + "description": "Question TagID", + "name": "id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/answer/api/v1/question/page": { + "post": { + "description": "SearchQuestionList \u003cbr\u003e \"order\" Enums(newest, active,frequent,score,unanswered)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "SearchQuestionList", + "parameters": [ + { + "description": "QuestionSearch", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.QuestionSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/answer/api/v1/question/search": { + "post": { + "description": "SearchQuestionList", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "SearchQuestionList", + "parameters": [ + { + "description": "QuestionSearch", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.QuestionSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/answer/api/v1/question/similar": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "add question title like", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "add question title like", + "parameters": [ + { + "type": "string", + "default": "string", + "description": "title", + "name": "title", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/question/similar/tag": { + "get": { + "description": "Search Similar Question", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "Search Similar Question", + "parameters": [ + { + "type": "string", + "default": "", + "description": "question_id", + "name": "question_id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/answer/api/v1/question/status": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Close question", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "Close question", + "parameters": [ + { + "description": "question", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.CloseQuestionReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/question/tags": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get tag list", + "produces": [ + "application/json" + ], + "tags": [ + "Tag" + ], + "summary": "get tag list", + "parameters": [ + { + "type": "string", + "description": "tag", + "name": "tag", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetTagResp" + } + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/reasons": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get reasons by object type and action", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "reason" + ], + "summary": "get reasons by object type and action", + "parameters": [ + { + "enum": [ + "question", + "answer", + "comment", + "user" + ], + "type": "string", + "description": "object_type", + "name": "object_type", + "in": "query", + "required": true + }, + { + "enum": [ + "status", + "close", + "flag", + "review" + ], + "type": "string", + "description": "action", + "name": "action", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/report": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + }, + { + "ApiKeyAuth": [] + } + ], + "description": "add report \u003cbr\u003e source (question, answer, comment, user)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Report" + ], + "summary": "add report", + "parameters": [ + { + "description": "report", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.AddReportReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/report/type/list": { + "get": { + "description": "get report type list", + "produces": [ + "application/json" + ], + "tags": [ + "Report" + ], + "summary": "get report type list", + "parameters": [ + { + "enum": [ + "question", + "answer", + "comment", + "user" + ], + "type": "string", + "description": "report source", + "name": "source", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetReportTypeResp" + } + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/revisions": { + "get": { + "description": "get revision list", + "produces": [ + "application/json" + ], + "tags": [ + "Revision" + ], + "summary": "get revision list", + "parameters": [ + { + "type": "string", + "description": "object id", + "name": "object_id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetRevisionResp" + } + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/search": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "search object", + "produces": [ + "application/json" + ], + "tags": [ + "Search" + ], + "summary": "search object", + "parameters": [ + { + "type": "string", + "description": "query string", + "name": "q", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SearchListResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/siteinfo": { + "get": { + "description": "Get siteinfo", + "produces": [ + "application/json" + ], + "tags": [ + "site" + ], + "summary": "Get siteinfo", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteGeneralResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/tag": { + "get": { + "description": "get tag one", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tag" + ], + "summary": "get tag one", + "parameters": [ + { + "type": "string", + "description": "tag id", + "name": "tag_id", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "tag name", + "name": "tag_name", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.GetTagResp" + } + } + } + ] + } + } + } + }, + "put": { + "description": "update tag", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tag" + ], + "summary": "update tag", + "parameters": [ + { + "description": "tag", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UpdateTagReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + }, + "delete": { + "description": "delete tag", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tag" + ], + "summary": "delete tag", + "parameters": [ + { + "description": "tag", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.RemoveTagReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/tag/synonym": { + "put": { + "description": "update tag", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Tag" + ], + "summary": "update tag", + "parameters": [ + { + "description": "tag", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UpdateTagSynonymReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/tag/synonyms": { + "get": { + "description": "get tag synonyms", + "produces": [ + "application/json" + ], + "tags": [ + "Tag" + ], + "summary": "get tag synonyms", + "parameters": [ + { + "type": "integer", + "description": "tag id", + "name": "tag_id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetTagSynonymsResp" + } + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/tags/following": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get following tag list", + "produces": [ + "application/json" + ], + "tags": [ + "Tag" + ], + "summary": "get following tag list", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetFollowingTagsResp" + } + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/tags/page": { + "get": { + "description": "get tag page", + "produces": [ + "application/json" + ], + "tags": [ + "Tag" + ], + "summary": "get tag page", + "parameters": [ + { + "type": "integer", + "description": "page size", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size", + "name": "page_size", + "in": "query" + }, + { + "type": "string", + "description": "slug_name", + "name": "slug_name", + "in": "query" + }, + { + "enum": [ + "popular", + "name", + "newest" + ], + "type": "string", + "description": "query condition", + "name": "query_cond", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/definitions/pager.PageModel" + }, + { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetTagPageResp" + } + } + } + } + ] + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/user/action/record": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "ActionRecord", + "tags": [ + "User" + ], + "summary": "ActionRecord", + "parameters": [ + { + "enum": [ + "login", + "e_mail", + "find_pass" + ], + "type": "string", + "description": "action", + "name": "action", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.ActionRecordResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/user/avatar/upload": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "UserUpdateInfo", + "consumes": [ + "multipart/form-data" + ], + "tags": [ + "User" + ], + "summary": "UserUpdateInfo", + "parameters": [ + { + "type": "file", + "description": "file", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/user/email": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "user change email verification", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "user change email verification", + "parameters": [ + { + "description": "UserChangeEmailVerifyReq", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserChangeEmailVerifyReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/user/email/change/code": { + "post": { + "description": "send email to the user email then change their email", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "send email to the user email then change their email", + "parameters": [ + { + "description": "UserChangeEmailSendCodeReq", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserChangeEmailSendCodeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/user/email/verification": { + "post": { + "description": "UserVerifyEmail", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "UserVerifyEmail", + "parameters": [ + { + "type": "string", + "default": "", + "description": "code", + "name": "code", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.GetUserResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/user/email/verification/send": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "UserVerifyEmailSend", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "UserVerifyEmailSend", + "parameters": [ + { + "type": "string", + "default": "", + "description": "captcha_id", + "name": "captcha_id", + "in": "query" + }, + { + "type": "string", + "default": "", + "description": "captcha_code", + "name": "captcha_code", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/answer/api/v1/user/info": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "GetUserInfoByUserID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "GetUserInfoByUserID", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.GetUserResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "UserUpdateInfo", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "UserUpdateInfo", + "parameters": [ + { + "type": "string", + "description": "access-token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "description": "UpdateInfoRequest", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UpdateInfoRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/user/login/email": { + "post": { + "description": "UserEmailLogin", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "UserEmailLogin", + "parameters": [ + { + "description": "UserEmailLogin", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserEmailLogin" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.GetUserResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/user/logout": { + "get": { + "description": "user logout", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "user logout", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/user/notice/set": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "UserNoticeSet", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "UserNoticeSet", + "parameters": [ + { + "description": "UserNoticeSetRequest", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserNoticeSetRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.UserNoticeSetResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/user/password": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "UserModifyPassWord", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "UserModifyPassWord", + "parameters": [ + { + "description": "UserModifyPassWordRequest", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserModifyPassWordRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, + "/answer/api/v1/user/password/replacement": { + "post": { + "description": "UseRePassWord", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "UseRePassWord", + "parameters": [ + { + "description": "UserRePassWordRequest", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserRePassWordRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/answer/api/v1/user/password/reset": { + "post": { + "description": "RetrievePassWord", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "RetrievePassWord", + "parameters": [ + { + "description": "UserRetrievePassWordRequest", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserRetrievePassWordRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/answer/api/v1/user/post/file": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "upload user post file", + "consumes": [ + "multipart/form-data" + ], + "tags": [ + "User" + ], + "summary": "upload user post file", + "parameters": [ + { + "type": "file", + "description": "file", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/user/register/email": { + "post": { + "description": "UserRegisterByEmail", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "UserRegisterByEmail", + "parameters": [ + { + "description": "UserRegister", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.UserRegister" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.GetUserResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/vote/down": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "add vote", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Activity" + ], + "summary": "vote down", + "parameters": [ + { + "description": "vote", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.VoteReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.VoteResp" + } + } + } + ] + } + } + } + } + }, + "/answer/api/v1/vote/up": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "add vote", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Activity" + ], + "summary": "vote up", + "parameters": [ + { + "description": "vote", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.VoteReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.VoteResp" + } + } + } + ] + } + } + } + } + }, + "/personal/question/page": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "UserList", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-question" + ], + "summary": "UserList", + "parameters": [ + { + "type": "string", + "default": "string", + "description": "username", + "name": "username", + "in": "query", + "required": true + }, + { + "enum": [ + "newest", + "score" + ], + "type": "string", + "description": "order", + "name": "order", + "in": "query", + "required": true + }, + { + "type": "string", + "default": "0", + "description": "page", + "name": "page", + "in": "query", + "required": true + }, + { + "type": "string", + "default": "20", + "description": "pagesize", + "name": "pagesize", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + } + }, + "definitions": { + "entity.AdminSetAnswerStatusRequest": { + "type": "object", + "properties": { + "answer_id": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "handler.RespBody": { + "type": "object", + "properties": { + "code": { + "description": "http code", + "type": "integer" + }, + "data": { + "description": "response data" + }, + "msg": { + "description": "response message", + "type": "string" + }, + "reason": { + "description": "reason key", + "type": "string" + } + } + }, + "pager.PageModel": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "list": {} + } + }, + "schema.ActionRecordResp": { + "type": "object", + "properties": { + "captcha_id": { + "type": "string" + }, + "captcha_img": { + "type": "string" + }, + "verify": { + "type": "boolean" + } + } + }, + "schema.AddCollectionGroupReq": { + "type": "object", + "required": [ + "create_time", + "default_group", + "name", + "update_time", + "user_id" + ], + "properties": { + "create_time": { + "type": "string" + }, + "default_group": { + "description": "mark this group is default, default 1", + "type": "integer" + }, + "name": { + "description": "the collection group name", + "type": "string", + "maxLength": 50 + }, + "update_time": { + "type": "string" + }, + "user_id": { + "type": "integer" + } + } + }, + "schema.AddCommentReq": { + "type": "object", + "required": [ + "object_id", + "original_text", + "parsed_text" + ], + "properties": { + "mention_username_list": { + "description": "@ user id list", + "type": "array", + "items": { + "type": "string" + } + }, + "object_id": { + "description": "object id", + "type": "string" + }, + "original_text": { + "description": "original comment content", + "type": "string" + }, + "parsed_text": { + "description": "parsed comment content", + "type": "string" + }, + "reply_comment_id": { + "description": "reply comment id", + "type": "string" + } + } + }, + "schema.AddNotificationReadReq": { + "type": "object", + "required": [ + "is_read", + "message_id", + "user_id" + ], + "properties": { + "is_read": { + "description": "read status(unread: 1; read 2)", + "type": "integer" + }, + "message_id": { + "description": "message id", + "type": "integer" + }, + "user_id": { + "description": "user id", + "type": "integer" + } + } + }, + "schema.AddReportReq": { + "type": "object", + "required": [ + "object_id", + "report_type" + ], + "properties": { + "content": { + "description": "report content", + "type": "string", + "maxLength": 500 + }, + "object_id": { + "description": "object id", + "type": "string", + "maxLength": 20 + }, + "report_type": { + "description": "report type", + "type": "integer" + } + } + }, + "schema.AddUserGroupReq": { + "type": "object" + }, + "schema.AdminSetQuestionStatusRequest": { + "type": "object", + "properties": { + "question_id": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "schema.AnswerAddReq": { + "type": "object", + "properties": { + "content": { + "description": "content", + "type": "string" + }, + "html": { + "description": "解析后的html", + "type": "string" + }, + "question_id": { + "description": "question_id", + "type": "string" + } + } + }, + "schema.AnswerAdoptedReq": { + "type": "object", + "properties": { + "answer_id": { + "type": "string" + }, + "question_id": { + "description": "question_id", + "type": "string" + } + } + }, + "schema.AnswerList": { + "type": "object", + "properties": { + "order": { + "description": "1 Default 2 time", + "type": "string" + }, + "page": { + "description": "Query number of pages", + "type": "integer" + }, + "page_size": { + "description": "Search page size", + "type": "integer" + }, + "question_id": { + "description": "question_id", + "type": "string" + } + } + }, + "schema.AnswerUpdateReq": { + "type": "object", + "properties": { + "content": { + "description": "content", + "type": "string" + }, + "edit_summary": { + "description": "edit_summary", + "type": "string" + }, + "html": { + "description": "解析后的html", + "type": "string" + }, + "id": { + "description": "id", + "type": "string" + }, + "question_id": { + "description": "question_id", + "type": "string" + }, + "title": { + "description": "title", + "type": "string" + } + } + }, + "schema.CloseQuestionReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "close_msg": { + "description": "close_type", + "type": "string" + }, + "close_type": { + "description": "close_type", + "type": "integer" + }, + "id": { + "type": "string" + } + } + }, + "schema.CollectionSwitchReq": { + "type": "object", + "required": [ + "group_id", + "object_id" + ], + "properties": { + "group_id": { + "description": "user collection group TagID", + "type": "string" + }, + "object_id": { + "description": "object TagID", + "type": "string" + } + } + }, + "schema.CollectionSwitchResp": { + "type": "object", + "properties": { + "object_collection_count": { + "type": "string" + }, + "object_id": { + "type": "string" + }, + "switch": { + "type": "boolean" + } + } + }, + "schema.FollowReq": { + "type": "object", + "required": [ + "object_id" + ], + "properties": { + "is_cancel": { + "description": "is cancel", + "type": "boolean" + }, + "object_id": { + "description": "object id", + "type": "string" + } + } + }, + "schema.FollowResp": { + "type": "object", + "properties": { + "follows": { + "description": "the followers of object", + "type": "integer" + }, + "is_followed": { + "description": "if user is followed object will be true,otherwise false", + "type": "boolean" + } + } + }, + "schema.GetCommentPersonalWithPageResp": { + "type": "object", + "properties": { + "answer_id": { + "description": "answer id", + "type": "string" + }, + "comment_id": { + "description": "comment id", + "type": "string" + }, + "content": { + "description": "content", + "type": "string" + }, + "created_at": { + "description": "create time", + "type": "integer" + }, + "object_id": { + "description": "object id", + "type": "string" + }, + "object_type": { + "description": "object type", + "type": "string", + "enum": [ + "question", + "answer", + "tag", + "comment" + ] + }, + "question_id": { + "description": "question id", + "type": "string" + }, + "title": { + "description": "title", + "type": "string" + } + } + }, + "schema.GetCommentResp": { + "type": "object", + "properties": { + "comment_id": { + "description": "comment id", + "type": "string" + }, + "created_at": { + "description": "create time", + "type": "integer" + }, + "is_vote": { + "description": "current user if already vote this comment", + "type": "boolean" + }, + "member_actions": { + "description": "MemberActions", + "type": "array", + "items": { + "$ref": "#/definitions/schema.PermissionMemberAction" + } + }, + "object_id": { + "description": "object id", + "type": "string" + }, + "original_text": { + "description": "original comment content", + "type": "string" + }, + "parsed_text": { + "description": "parsed comment content", + "type": "string" + }, + "reply_comment_id": { + "description": "reply comment id", + "type": "string" + }, + "reply_user_display_name": { + "description": "reply user display name", + "type": "string" + }, + "reply_user_id": { + "description": "reply user id", + "type": "string" + }, + "reply_username": { + "description": "reply user username", + "type": "string" + }, + "user_avatar": { + "description": "user avatar", + "type": "string" + }, + "user_display_name": { + "description": "user display name", + "type": "string" + }, + "user_id": { + "description": "user id", + "type": "string" + }, + "username": { + "description": "username", + "type": "string" + }, + "vote_count": { + "description": "user vote amount", + "type": "integer" + } + } + }, + "schema.GetFollowingTagsResp": { + "type": "object", + "properties": { + "display_name": { + "description": "display name", + "type": "string" + }, + "main_tag_slug_name": { + "description": "if main tag slug name is not empty, this tag is synonymous with the main tag", + "type": "string" + }, + "slug_name": { + "description": "slug name", + "type": "string" + }, + "tag_id": { + "description": "tag id", + "type": "string" + } + } + }, + "schema.GetNotificationReadResp": { + "type": "object", + "properties": { + "created_at": { + "description": "create time", + "type": "string" + }, + "id": { + "description": "id", + "type": "integer" + }, + "is_read": { + "description": "read status(unread: 1; read 2)", + "type": "integer" + }, + "message_id": { + "description": "message id", + "type": "integer" + }, + "updated_at": { + "description": "update time", + "type": "string" + }, + "user_id": { + "description": "user id", + "type": "integer" + } + } + }, + "schema.GetOtherUserInfoByUsernameResp": { + "type": "object", + "properties": { + "answer_count": { + "description": "answer count", + "type": "integer" + }, + "avatar": { + "description": "avatar", + "type": "string" + }, + "bio": { + "description": "bio markdown", + "type": "string" + }, + "bio_html": { + "description": "bio html", + "type": "string" + }, + "created_at": { + "description": "create time", + "type": "integer" + }, + "display_name": { + "description": "display name", + "type": "string" + }, + "follow_count": { + "description": "email\nfollow count", + "type": "integer" + }, + "id": { + "description": "user id", + "type": "string" + }, + "ip_info": { + "description": "ip info", + "type": "string" + }, + "is_admin": { + "description": "is admin", + "type": "boolean" + }, + "last_login_date": { + "description": "last login date", + "type": "integer" + }, + "location": { + "description": "location", + "type": "string" + }, + "mobile": { + "description": "mobile", + "type": "string" + }, + "question_count": { + "description": "question count", + "type": "integer" + }, + "rank": { + "description": "rank", + "type": "integer" + }, + "status": { + "type": "string" + }, + "status_msg": { + "type": "string" + }, + "username": { + "description": "username", + "type": "string" + }, + "website": { + "description": "website", + "type": "string" + } + } + }, + "schema.GetOtherUserInfoResp": { + "type": "object", + "properties": { + "has": { + "type": "boolean" + }, + "info": { + "$ref": "#/definitions/schema.GetOtherUserInfoByUsernameResp" + } + } + }, + "schema.GetRankPersonalWithPageResp": { + "type": "object", + "properties": { + "answer_id": { + "description": "answer id", + "type": "string" + }, + "content": { + "description": "content", + "type": "string" + }, + "created_at": { + "description": "create time", + "type": "integer" + }, + "object_id": { + "description": "object id", + "type": "string" + }, + "object_type": { + "description": "object type", + "type": "string", + "enum": [ + "question", + "answer", + "tag", + "comment" + ] + }, + "question_id": { + "description": "question id", + "type": "string" + }, + "rank_type": { + "description": "rank type", + "type": "string" + }, + "reputation": { + "description": "reputation", + "type": "integer" + }, + "title": { + "description": "title", + "type": "string" + } + } + }, + "schema.GetReportTypeResp": { + "type": "object", + "properties": { + "content_type": { + "description": "content type", + "type": "string" + }, + "description": { + "description": "report description", + "type": "string" + }, + "have_content": { + "description": "is have content", + "type": "boolean" + }, + "name": { + "description": "report name", + "type": "string" + }, + "source": { + "description": "report source", + "type": "string" + }, + "type": { + "description": "report type", + "type": "integer" + } + } + }, + "schema.GetRevisionResp": { + "type": "object", + "properties": { + "content": { + "description": "content parsed" + }, + "create_at": { + "type": "integer" + }, + "id": { + "description": "id", + "type": "string" + }, + "object_id": { + "description": "object id", + "type": "string" + }, + "reason": { + "type": "string" + }, + "status": { + "description": "revision status(normal: 1; delete 2)", + "type": "integer" + }, + "title": { + "description": "title", + "type": "string" + }, + "use_id": { + "description": "user id", + "type": "string" + }, + "user_info": { + "$ref": "#/definitions/schema.UserBasicInfo" + } + } + }, + "schema.GetTagPageResp": { + "type": "object", + "properties": { + "created_at": { + "description": "created time", + "type": "integer" + }, + "display_name": { + "description": "display_name", + "type": "string" + }, + "excerpt": { + "description": "excerpt", + "type": "string" + }, + "follow_count": { + "description": "follower amount", + "type": "integer" + }, + "is_follower": { + "description": "is follower", + "type": "boolean" + }, + "original_text": { + "description": "original text", + "type": "string" + }, + "parsed_text": { + "description": "parsed_text", + "type": "string" + }, + "question_count": { + "description": "question amount", + "type": "integer" + }, + "slug_name": { + "description": "slug_name", + "type": "string" + }, + "tag_id": { + "description": "tag_id", + "type": "string" + }, + "updated_at": { + "description": "updated time", + "type": "integer" + } + } + }, + "schema.GetTagResp": { + "type": "object", + "properties": { + "created_at": { + "description": "created time", + "type": "integer" + }, + "display_name": { + "description": "display name", + "type": "string" + }, + "excerpt": { + "description": "excerpt", + "type": "string" + }, + "follow_count": { + "description": "follower amount", + "type": "integer" + }, + "is_follower": { + "description": "is follower", + "type": "boolean" + }, + "main_tag_slug_name": { + "description": "if main tag slug name is not empty, this tag is synonymous with the main tag", + "type": "string" + }, + "member_actions": { + "description": "MemberActions", + "type": "array", + "items": { + "$ref": "#/definitions/schema.PermissionMemberAction" + } + }, + "original_text": { + "description": "original text", + "type": "string" + }, + "parsed_text": { + "description": "parsed text", + "type": "string" + }, + "question_count": { + "description": "question amount", + "type": "integer" + }, + "slug_name": { + "description": "slug name", + "type": "string" + }, + "tag_id": { + "description": "tag id", + "type": "string" + }, + "updated_at": { + "description": "updated time", + "type": "integer" + } + } + }, + "schema.GetTagSynonymsResp": { + "type": "object", + "properties": { + "display_name": { + "description": "display name", + "type": "string" + }, + "main_tag_slug_name": { + "description": "if main tag slug name is not empty, this tag is synonymous with the main tag", + "type": "string" + }, + "slug_name": { + "description": "slug name", + "type": "string" + }, + "tag_id": { + "description": "tag id", + "type": "string" + } + } + }, + "schema.GetUserGroupResp": { + "type": "object", + "properties": { + "id": { + "description": "user group id", + "type": "integer" + } + } + }, + "schema.GetUserInfoResp": { + "type": "object" + }, + "schema.GetUserPageResp": { + "type": "object", + "properties": { + "avatar": { + "description": "avatar", + "type": "string" + }, + "created_at": { + "description": "create time", + "type": "integer" + }, + "deleted_at": { + "description": "delete time", + "type": "integer" + }, + "display_name": { + "description": "display name", + "type": "string" + }, + "e_mail": { + "description": "email", + "type": "string" + }, + "rank": { + "description": "rank", + "type": "integer" + }, + "status": { + "description": "user status(normal,suspended,deleted,inactive)", + "type": "string" + }, + "suspended_at": { + "description": "suspended time", + "type": "integer" + }, + "user_id": { + "description": "user id", + "type": "string" + }, + "username": { + "description": "username", + "type": "string" + } + } + }, + "schema.GetUserResp": { + "type": "object", + "properties": { + "access_token": { + "description": "access token", + "type": "string" + }, + "answer_count": { + "description": "answer count", + "type": "integer" + }, + "authority_group": { + "description": "authority group", + "type": "integer" + }, + "avatar": { + "description": "avatar", + "type": "string" + }, + "bio": { + "description": "bio markdown", + "type": "string" + }, + "bio_html": { + "description": "bio html", + "type": "string" + }, + "created_at": { + "description": "create time", + "type": "integer" + }, + "display_name": { + "description": "display name", + "type": "string" + }, + "e_mail": { + "description": "email", + "type": "string" + }, + "follow_count": { + "description": "follow count", + "type": "integer" + }, + "id": { + "description": "user id", + "type": "string" + }, + "ip_info": { + "description": "ip info", + "type": "string" + }, + "is_admin": { + "description": "is admin", + "type": "boolean" + }, + "last_login_date": { + "description": "last login date", + "type": "integer" + }, + "location": { + "description": "location", + "type": "string" + }, + "mail_status": { + "description": "mail status(1 pass 2 to be verified)", + "type": "integer" + }, + "mobile": { + "description": "mobile", + "type": "string" + }, + "notice_status": { + "description": "notice status(1 on 2off)", + "type": "integer" + }, + "question_count": { + "description": "question count", + "type": "integer" + }, + "rank": { + "description": "rank", + "type": "integer" + }, + "status": { + "description": "user status", + "type": "string" + }, + "username": { + "description": "username", + "type": "string" + }, + "website": { + "description": "website", + "type": "string" + } + } + }, + "schema.GetVoteWithPageResp": { + "type": "object", + "properties": { + "answer_id": { + "description": "answer id", + "type": "string" + }, + "content": { + "description": "content", + "type": "string" + }, + "created_at": { + "description": "create time", + "type": "integer" + }, + "object_id": { + "description": "object id", + "type": "string" + }, + "object_type": { + "description": "object type", + "type": "string", + "enum": [ + "question", + "answer", + "tag", + "comment" + ] + }, + "question_id": { + "description": "question id", + "type": "string" + }, + "title": { + "description": "title", + "type": "string" + }, + "vote_type": { + "description": "vote type", + "type": "string" + } + } + }, + "schema.NotificationClearIDRequest": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + } + }, + "schema.NotificationClearRequest": { + "type": "object", + "properties": { + "type": { + "description": "inbox achievement", + "type": "string" + } + } + }, + "schema.PermissionMemberAction": { + "type": "object", + "properties": { + "action": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "schema.QuestionAdd": { + "type": "object", + "required": [ + "content", + "html", + "tags", + "title" + ], + "properties": { + "content": { + "description": "content", + "type": "string", + "maxLength": 65535, + "minLength": 6 + }, + "html": { + "description": "html", + "type": "string", + "maxLength": 65535, + "minLength": 6 + }, + "tags": { + "description": "tags", + "type": "array", + "items": { + "$ref": "#/definitions/schema.TagItem" + } + }, + "title": { + "description": "question title", + "type": "string", + "maxLength": 64, + "minLength": 6 + } + } + }, + "schema.QuestionSearch": { + "type": "object", + "properties": { + "order": { + "description": "Search order by", + "type": "string" + }, + "page": { + "description": "Query number of pages", + "type": "integer" + }, + "page_size": { + "description": "Search page size", + "type": "integer" + }, + "tags": { + "description": "Search tag", + "type": "array", + "items": { + "type": "string" + } + }, + "username": { + "description": "Search username", + "type": "string" + } + } + }, + "schema.QuestionUpdate": { + "type": "object", + "required": [ + "content", + "html", + "id", + "tags", + "title" + ], + "properties": { + "content": { + "description": "content", + "type": "string", + "maxLength": 65535, + "minLength": 6 + }, + "edit_summary": { + "description": "edit summary", + "type": "string" + }, + "html": { + "description": "html", + "type": "string", + "maxLength": 65535, + "minLength": 6 + }, + "id": { + "description": "question id", + "type": "string" + }, + "tags": { + "description": "tags", + "type": "array", + "items": { + "$ref": "#/definitions/schema.TagItem" + } + }, + "title": { + "description": "question title", + "type": "string", + "maxLength": 64, + "minLength": 6 + } + } + }, + "schema.RemoveAnswerReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "description": "answer id", + "type": "string" + } + } + }, + "schema.RemoveCommentReq": { + "type": "object", + "required": [ + "comment_id" + ], + "properties": { + "comment_id": { + "description": "comment id", + "type": "string" + } + } + }, + "schema.RemoveNotificationReadReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "description": "id", + "type": "integer" + } + } + }, + "schema.RemoveQuestionReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "description": "question id", + "type": "string" + } + } + }, + "schema.RemoveTagReq": { + "type": "object", + "required": [ + "tag_id" + ], + "properties": { + "tag_id": { + "description": "tag_id", + "type": "string" + } + } + }, + "schema.RemoveUserGroupReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "description": "user group id", + "type": "integer" + } + } + }, + "schema.ReportHandleReq": { + "type": "object", + "required": [ + "flaged_type", + "id" + ], + "properties": { + "flaged_content": { + "type": "string" + }, + "flaged_type": { + "type": "integer" + }, + "id": { + "type": "string" + } + } + }, + "schema.SearchListResp": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "extra": { + "description": "extra fields" + }, + "list": { + "description": "search response", + "type": "array", + "items": { + "$ref": "#/definitions/schema.SearchResp" + } + } + } + }, + "schema.SearchObject": { + "type": "object", + "properties": { + "accepted": { + "type": "boolean" + }, + "answer_count": { + "type": "integer" + }, + "created_at": { + "type": "integer" + }, + "excerpt": { + "type": "string" + }, + "id": { + "type": "string" + }, + "tags": { + "description": "tags", + "type": "array", + "items": { + "$ref": "#/definitions/schema.TagResp" + } + }, + "title": { + "type": "string" + }, + "user_info": { + "description": "user info", + "$ref": "#/definitions/schema.UserBasicInfo" + }, + "vote_count": { + "type": "integer" + } + } + }, + "schema.SearchResp": { + "type": "object", + "properties": { + "object": { + "description": "this object", + "$ref": "#/definitions/schema.SearchObject" + }, + "object_type": { + "description": "object_type", + "type": "string" + } + } + }, + "schema.SiteGeneralReq": { + "type": "object", + "required": [ + "description", + "name", + "short_description" + ], + "properties": { + "description": { + "type": "string", + "maxLength": 2000 + }, + "name": { + "type": "string", + "maxLength": 128 + }, + "short_description": { + "type": "string", + "maxLength": 255 + } + } + }, + "schema.SiteGeneralResp": { + "type": "object", + "required": [ + "description", + "name", + "short_description" + ], + "properties": { + "description": { + "type": "string", + "maxLength": 2000 + }, + "name": { + "type": "string", + "maxLength": 128 + }, + "short_description": { + "type": "string", + "maxLength": 255 + } + } + }, + "schema.SiteInterfaceReq": { + "type": "object", + "required": [ + "language", + "theme" + ], + "properties": { + "language": { + "type": "string", + "maxLength": 128 + }, + "logo": { + "type": "string", + "maxLength": 256 + }, + "theme": { + "type": "string", + "maxLength": 128 + } + } + }, + "schema.SiteInterfaceResp": { + "type": "object", + "required": [ + "language", + "theme" + ], + "properties": { + "language": { + "type": "string", + "maxLength": 128 + }, + "logo": { + "type": "string", + "maxLength": 256 + }, + "theme": { + "type": "string", + "maxLength": 128 + } + } + }, + "schema.TagItem": { + "type": "object", + "properties": { + "display_name": { + "description": "display_name", + "type": "string", + "maxLength": 50 + }, + "original_text": { + "description": "original text", + "type": "string" + }, + "parsed_text": { + "description": "parsed text", + "type": "string" + }, + "slug_name": { + "description": "slug_name", + "type": "string", + "maxLength": 50 + } + } + }, + "schema.TagResp": { + "type": "object", + "properties": { + "display_name": { + "type": "string" + }, + "main_tag_slug_name": { + "description": "if main tag slug name is not empty, this tag is synonymous with the main tag", + "type": "string" + }, + "slug_name": { + "type": "string" + } + } + }, + "schema.UpdateCollectionGroupReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "create_time": { + "type": "string" + }, + "default_group": { + "description": "mark this group is default, default 1", + "type": "integer" + }, + "id": { + "type": "integer" + }, + "name": { + "description": "the collection group name", + "type": "string", + "maxLength": 50 + }, + "update_time": { + "type": "string" + }, + "user_id": { + "type": "integer" + } + } + }, + "schema.UpdateCommentReq": { + "type": "object", + "required": [ + "comment_id" + ], + "properties": { + "comment_id": { + "description": "comment id", + "type": "string" + }, + "original_text": { + "description": "original comment content", + "type": "string" + }, + "parsed_text": { + "description": "parsed comment content", + "type": "string" + } + } + }, + "schema.UpdateFollowTagsReq": { + "type": "object", + "properties": { + "slug_name_list": { + "description": "tag slug name list", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "schema.UpdateInfoRequest": { + "type": "object", + "properties": { + "avatar": { + "description": "avatar", + "type": "string" + }, + "bio": { + "type": "string" + }, + "bio_html": { + "type": "string" + }, + "display_name": { + "description": "display_name", + "type": "string" + }, + "location": { + "description": "location", + "type": "string" + }, + "username": { + "description": "name", + "type": "string" + }, + "website": { + "description": "website", + "type": "string" + } + } + }, + "schema.UpdateNotificationReadReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "description": "id", + "type": "integer" + }, + "is_read": { + "description": "read status(unread: 1; read 2)", + "type": "integer" + }, + "message_id": { + "description": "message id", + "type": "integer" + }, + "user_id": { + "description": "user id", + "type": "integer" + } + } + }, + "schema.UpdateTagReq": { + "type": "object", + "required": [ + "tag_id" + ], + "properties": { + "display_name": { + "description": "display_name", + "type": "string", + "maxLength": 50 + }, + "edit_summary": { + "description": "edit summary", + "type": "string" + }, + "original_text": { + "description": "original text", + "type": "string" + }, + "parsed_text": { + "description": "parsed text", + "type": "string" + }, + "slug_name": { + "description": "slug_name", + "type": "string", + "maxLength": 50 + }, + "tag_id": { + "description": "tag_id", + "type": "string" + } + } + }, + "schema.UpdateTagSynonymReq": { + "type": "object", + "required": [ + "synonym_tag_list", + "tag_id" + ], + "properties": { + "synonym_tag_list": { + "description": "synonym tag list", + "type": "array", + "items": { + "$ref": "#/definitions/schema.TagItem" + } + }, + "tag_id": { + "description": "tag_id", + "type": "string" + } + } + }, + "schema.UpdateUserGroupReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "description": "user group id", + "type": "integer" + } + } + }, + "schema.UpdateUserStatusReq": { + "type": "object", + "required": [ + "status", + "user_id" + ], + "properties": { + "status": { + "description": "user status", + "type": "string", + "enum": [ + "normal", + "suspended", + "deleted", + "inactive" + ] + }, + "user_id": { + "description": "user id", + "type": "string" + } + } + }, + "schema.UserBasicInfo": { + "type": "object", + "properties": { + "avatar": { + "description": "avatar", + "type": "string" + }, + "display_name": { + "description": "display_name", + "type": "string" + }, + "ip_info": { + "description": "ip info", + "type": "string" + }, + "location": { + "description": "location", + "type": "string" + }, + "rank": { + "description": "rank", + "type": "integer" + }, + "status": { + "description": "status", + "type": "integer" + }, + "username": { + "description": "name", + "type": "string" + }, + "website": { + "description": "website", + "type": "string" + } + } + }, + "schema.UserChangeEmailSendCodeReq": { + "type": "object", + "required": [ + "e_mail" + ], + "properties": { + "e_mail": { + "type": "string", + "maxLength": 500 + } + } + }, + "schema.UserChangeEmailVerifyReq": { + "type": "object", + "required": [ + "code" + ], + "properties": { + "code": { + "type": "string", + "maxLength": 500 + } + } + }, + "schema.UserEmailLogin": { + "type": "object", + "properties": { + "captcha_code": { + "description": "captcha_code", + "type": "string" + }, + "captcha_id": { + "description": "captcha_id", + "type": "string" + }, + "e_mail": { + "description": "e_mail", + "type": "string" + }, + "pass": { + "description": "password", + "type": "string" + } + } + }, + "schema.UserModifyPassWordRequest": { + "type": "object", + "properties": { + "old_pass": { + "description": "old password", + "type": "string" + }, + "pass": { + "description": "password", + "type": "string" + } + } + }, + "schema.UserNoticeSetRequest": { + "type": "object", + "properties": { + "notice_switch": { + "type": "boolean" + } + } + }, + "schema.UserNoticeSetResp": { + "type": "object", + "properties": { + "notice_switch": { + "type": "boolean" + } + } + }, + "schema.UserRePassWordRequest": { + "type": "object", + "required": [ + "code", + "pass" + ], + "properties": { + "code": { + "description": "code", + "type": "string", + "maxLength": 100 + }, + "pass": { + "description": "Password", + "type": "string", + "maxLength": 32 + } + } + }, + "schema.UserRegister": { + "type": "object", + "required": [ + "e_mail", + "name", + "pass" + ], + "properties": { + "e_mail": { + "description": "email", + "type": "string", + "maxLength": 500 + }, + "name": { + "description": "name", + "type": "string", + "maxLength": 50 + }, + "pass": { + "description": "password", + "type": "string", + "maxLength": 32, + "minLength": 8 + } + } + }, + "schema.UserRetrievePassWordRequest": { + "type": "object", + "required": [ + "e_mail" + ], + "properties": { + "captcha_code": { + "description": "captcha_code", + "type": "string" + }, + "captcha_id": { + "description": "captcha_id", + "type": "string" + }, + "e_mail": { + "description": "e_mail", + "type": "string", + "maxLength": 500 + } + } + }, + "schema.VoteReq": { + "type": "object", + "required": [ + "object_id" + ], + "properties": { + "is_cancel": { + "description": "is cancel", + "type": "boolean" + }, + "object_id": { + "description": "id", + "type": "string" + } + } + }, + "schema.VoteResp": { + "type": "object", + "properties": { + "down_votes": { + "type": "integer" + }, + "up_votes": { + "type": "integer" + }, + "vote_status": { + "type": "string" + }, + "votes": { + "type": "integer" + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 00000000..ba4fed36 --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,3639 @@ +definitions: + entity.AdminSetAnswerStatusRequest: + properties: + answer_id: + type: string + status: + type: string + type: object + handler.RespBody: + properties: + code: + description: http code + type: integer + data: + description: response data + msg: + description: response message + type: string + reason: + description: reason key + type: string + type: object + pager.PageModel: + properties: + count: + type: integer + list: {} + type: object + schema.ActionRecordResp: + properties: + captcha_id: + type: string + captcha_img: + type: string + verify: + type: boolean + type: object + schema.AddCollectionGroupReq: + properties: + create_time: + type: string + default_group: + description: mark this group is default, default 1 + type: integer + name: + description: the collection group name + maxLength: 50 + type: string + update_time: + type: string + user_id: + type: integer + required: + - create_time + - default_group + - name + - update_time + - user_id + type: object + schema.AddCommentReq: + properties: + mention_username_list: + description: '@ user id list' + items: + type: string + type: array + object_id: + description: object id + type: string + original_text: + description: original comment content + type: string + parsed_text: + description: parsed comment content + type: string + reply_comment_id: + description: reply comment id + type: string + required: + - object_id + - original_text + - parsed_text + type: object + schema.AddNotificationReadReq: + properties: + is_read: + description: 'read status(unread: 1; read 2)' + type: integer + message_id: + description: message id + type: integer + user_id: + description: user id + type: integer + required: + - is_read + - message_id + - user_id + type: object + schema.AddReportReq: + properties: + content: + description: report content + maxLength: 500 + type: string + object_id: + description: object id + maxLength: 20 + type: string + report_type: + description: report type + type: integer + required: + - object_id + - report_type + type: object + schema.AddUserGroupReq: + type: object + schema.AdminSetQuestionStatusRequest: + properties: + question_id: + type: string + status: + type: string + type: object + schema.AnswerAddReq: + properties: + content: + description: content + type: string + html: + description: 解析后的html + type: string + question_id: + description: question_id + type: string + type: object + schema.AnswerAdoptedReq: + properties: + answer_id: + type: string + question_id: + description: question_id + type: string + type: object + schema.AnswerList: + properties: + order: + description: 1 Default 2 time + type: string + page: + description: Query number of pages + type: integer + page_size: + description: Search page size + type: integer + question_id: + description: question_id + type: string + type: object + schema.AnswerUpdateReq: + properties: + content: + description: content + type: string + edit_summary: + description: edit_summary + type: string + html: + description: 解析后的html + type: string + id: + description: id + type: string + question_id: + description: question_id + type: string + title: + description: title + type: string + type: object + schema.CloseQuestionReq: + properties: + close_msg: + description: close_type + type: string + close_type: + description: close_type + type: integer + id: + type: string + required: + - id + type: object + schema.CollectionSwitchReq: + properties: + group_id: + description: user collection group TagID + type: string + object_id: + description: object TagID + type: string + required: + - group_id + - object_id + type: object + schema.CollectionSwitchResp: + properties: + object_collection_count: + type: string + object_id: + type: string + switch: + type: boolean + type: object + schema.FollowReq: + properties: + is_cancel: + description: is cancel + type: boolean + object_id: + description: object id + type: string + required: + - object_id + type: object + schema.FollowResp: + properties: + follows: + description: the followers of object + type: integer + is_followed: + description: if user is followed object will be true,otherwise false + type: boolean + type: object + schema.GetCommentPersonalWithPageResp: + properties: + answer_id: + description: answer id + type: string + comment_id: + description: comment id + type: string + content: + description: content + type: string + created_at: + description: create time + type: integer + object_id: + description: object id + type: string + object_type: + description: object type + enum: + - question + - answer + - tag + - comment + type: string + question_id: + description: question id + type: string + title: + description: title + type: string + type: object + schema.GetCommentResp: + properties: + comment_id: + description: comment id + type: string + created_at: + description: create time + type: integer + is_vote: + description: current user if already vote this comment + type: boolean + member_actions: + description: MemberActions + items: + $ref: '#/definitions/schema.PermissionMemberAction' + type: array + object_id: + description: object id + type: string + original_text: + description: original comment content + type: string + parsed_text: + description: parsed comment content + type: string + reply_comment_id: + description: reply comment id + type: string + reply_user_display_name: + description: reply user display name + type: string + reply_user_id: + description: reply user id + type: string + reply_username: + description: reply user username + type: string + user_avatar: + description: user avatar + type: string + user_display_name: + description: user display name + type: string + user_id: + description: user id + type: string + username: + description: username + type: string + vote_count: + description: user vote amount + type: integer + type: object + schema.GetFollowingTagsResp: + properties: + display_name: + description: display name + type: string + main_tag_slug_name: + description: if main tag slug name is not empty, this tag is synonymous with + the main tag + type: string + slug_name: + description: slug name + type: string + tag_id: + description: tag id + type: string + type: object + schema.GetNotificationReadResp: + properties: + created_at: + description: create time + type: string + id: + description: id + type: integer + is_read: + description: 'read status(unread: 1; read 2)' + type: integer + message_id: + description: message id + type: integer + updated_at: + description: update time + type: string + user_id: + description: user id + type: integer + type: object + schema.GetOtherUserInfoByUsernameResp: + properties: + answer_count: + description: answer count + type: integer + avatar: + description: avatar + type: string + bio: + description: bio markdown + type: string + bio_html: + description: bio html + type: string + created_at: + description: create time + type: integer + display_name: + description: display name + type: string + follow_count: + description: |- + email + follow count + type: integer + id: + description: user id + type: string + ip_info: + description: ip info + type: string + is_admin: + description: is admin + type: boolean + last_login_date: + description: last login date + type: integer + location: + description: location + type: string + mobile: + description: mobile + type: string + question_count: + description: question count + type: integer + rank: + description: rank + type: integer + status: + type: string + status_msg: + type: string + username: + description: username + type: string + website: + description: website + type: string + type: object + schema.GetOtherUserInfoResp: + properties: + has: + type: boolean + info: + $ref: '#/definitions/schema.GetOtherUserInfoByUsernameResp' + type: object + schema.GetRankPersonalWithPageResp: + properties: + answer_id: + description: answer id + type: string + content: + description: content + type: string + created_at: + description: create time + type: integer + object_id: + description: object id + type: string + object_type: + description: object type + enum: + - question + - answer + - tag + - comment + type: string + question_id: + description: question id + type: string + rank_type: + description: rank type + type: string + reputation: + description: reputation + type: integer + title: + description: title + type: string + type: object + schema.GetReportTypeResp: + properties: + content_type: + description: content type + type: string + description: + description: report description + type: string + have_content: + description: is have content + type: boolean + name: + description: report name + type: string + source: + description: report source + type: string + type: + description: report type + type: integer + type: object + schema.GetRevisionResp: + properties: + content: + description: content parsed + create_at: + type: integer + id: + description: id + type: string + object_id: + description: object id + type: string + reason: + type: string + status: + description: 'revision status(normal: 1; delete 2)' + type: integer + title: + description: title + type: string + use_id: + description: user id + type: string + user_info: + $ref: '#/definitions/schema.UserBasicInfo' + type: object + schema.GetTagPageResp: + properties: + created_at: + description: created time + type: integer + display_name: + description: display_name + type: string + excerpt: + description: excerpt + type: string + follow_count: + description: follower amount + type: integer + is_follower: + description: is follower + type: boolean + original_text: + description: original text + type: string + parsed_text: + description: parsed_text + type: string + question_count: + description: question amount + type: integer + slug_name: + description: slug_name + type: string + tag_id: + description: tag_id + type: string + updated_at: + description: updated time + type: integer + type: object + schema.GetTagResp: + properties: + created_at: + description: created time + type: integer + display_name: + description: display name + type: string + excerpt: + description: excerpt + type: string + follow_count: + description: follower amount + type: integer + is_follower: + description: is follower + type: boolean + main_tag_slug_name: + description: if main tag slug name is not empty, this tag is synonymous with + the main tag + type: string + member_actions: + description: MemberActions + items: + $ref: '#/definitions/schema.PermissionMemberAction' + type: array + original_text: + description: original text + type: string + parsed_text: + description: parsed text + type: string + question_count: + description: question amount + type: integer + slug_name: + description: slug name + type: string + tag_id: + description: tag id + type: string + updated_at: + description: updated time + type: integer + type: object + schema.GetTagSynonymsResp: + properties: + display_name: + description: display name + type: string + main_tag_slug_name: + description: if main tag slug name is not empty, this tag is synonymous with + the main tag + type: string + slug_name: + description: slug name + type: string + tag_id: + description: tag id + type: string + type: object + schema.GetUserGroupResp: + properties: + id: + description: user group id + type: integer + type: object + schema.GetUserInfoResp: + type: object + schema.GetUserPageResp: + properties: + avatar: + description: avatar + type: string + created_at: + description: create time + type: integer + deleted_at: + description: delete time + type: integer + display_name: + description: display name + type: string + e_mail: + description: email + type: string + rank: + description: rank + type: integer + status: + description: user status(normal,suspended,deleted,inactive) + type: string + suspended_at: + description: suspended time + type: integer + user_id: + description: user id + type: string + username: + description: username + type: string + type: object + schema.GetUserResp: + properties: + access_token: + description: access token + type: string + answer_count: + description: answer count + type: integer + authority_group: + description: authority group + type: integer + avatar: + description: avatar + type: string + bio: + description: bio markdown + type: string + bio_html: + description: bio html + type: string + created_at: + description: create time + type: integer + display_name: + description: display name + type: string + e_mail: + description: email + type: string + follow_count: + description: follow count + type: integer + id: + description: user id + type: string + ip_info: + description: ip info + type: string + is_admin: + description: is admin + type: boolean + last_login_date: + description: last login date + type: integer + location: + description: location + type: string + mail_status: + description: mail status(1 pass 2 to be verified) + type: integer + mobile: + description: mobile + type: string + notice_status: + description: notice status(1 on 2off) + type: integer + question_count: + description: question count + type: integer + rank: + description: rank + type: integer + status: + description: user status + type: string + username: + description: username + type: string + website: + description: website + type: string + type: object + schema.GetVoteWithPageResp: + properties: + answer_id: + description: answer id + type: string + content: + description: content + type: string + created_at: + description: create time + type: integer + object_id: + description: object id + type: string + object_type: + description: object type + enum: + - question + - answer + - tag + - comment + type: string + question_id: + description: question id + type: string + title: + description: title + type: string + vote_type: + description: vote type + type: string + type: object + schema.NotificationClearIDRequest: + properties: + id: + type: string + type: object + schema.NotificationClearRequest: + properties: + type: + description: inbox achievement + type: string + type: object + schema.PermissionMemberAction: + properties: + action: + type: string + name: + type: string + type: + type: string + type: object + schema.QuestionAdd: + properties: + content: + description: content + maxLength: 65535 + minLength: 6 + type: string + html: + description: html + maxLength: 65535 + minLength: 6 + type: string + tags: + description: tags + items: + $ref: '#/definitions/schema.TagItem' + type: array + title: + description: question title + maxLength: 64 + minLength: 6 + type: string + required: + - content + - html + - tags + - title + type: object + schema.QuestionSearch: + properties: + order: + description: Search order by + type: string + page: + description: Query number of pages + type: integer + page_size: + description: Search page size + type: integer + tags: + description: Search tag + items: + type: string + type: array + username: + description: Search username + type: string + type: object + schema.QuestionUpdate: + properties: + content: + description: content + maxLength: 65535 + minLength: 6 + type: string + edit_summary: + description: edit summary + type: string + html: + description: html + maxLength: 65535 + minLength: 6 + type: string + id: + description: question id + type: string + tags: + description: tags + items: + $ref: '#/definitions/schema.TagItem' + type: array + title: + description: question title + maxLength: 64 + minLength: 6 + type: string + required: + - content + - html + - id + - tags + - title + type: object + schema.RemoveAnswerReq: + properties: + id: + description: answer id + type: string + required: + - id + type: object + schema.RemoveCommentReq: + properties: + comment_id: + description: comment id + type: string + required: + - comment_id + type: object + schema.RemoveNotificationReadReq: + properties: + id: + description: id + type: integer + required: + - id + type: object + schema.RemoveQuestionReq: + properties: + id: + description: question id + type: string + required: + - id + type: object + schema.RemoveTagReq: + properties: + tag_id: + description: tag_id + type: string + required: + - tag_id + type: object + schema.RemoveUserGroupReq: + properties: + id: + description: user group id + type: integer + required: + - id + type: object + schema.ReportHandleReq: + properties: + flaged_content: + type: string + flaged_type: + type: integer + id: + type: string + required: + - flaged_type + - id + type: object + schema.SearchListResp: + properties: + count: + type: integer + extra: + description: extra fields + list: + description: search response + items: + $ref: '#/definitions/schema.SearchResp' + type: array + type: object + schema.SearchObject: + properties: + accepted: + type: boolean + answer_count: + type: integer + created_at: + type: integer + excerpt: + type: string + id: + type: string + tags: + description: tags + items: + $ref: '#/definitions/schema.TagResp' + type: array + title: + type: string + user_info: + $ref: '#/definitions/schema.UserBasicInfo' + description: user info + vote_count: + type: integer + type: object + schema.SearchResp: + properties: + object: + $ref: '#/definitions/schema.SearchObject' + description: this object + object_type: + description: object_type + type: string + type: object + schema.SiteGeneralReq: + properties: + description: + maxLength: 2000 + type: string + name: + maxLength: 128 + type: string + short_description: + maxLength: 255 + type: string + required: + - description + - name + - short_description + type: object + schema.SiteGeneralResp: + properties: + description: + maxLength: 2000 + type: string + name: + maxLength: 128 + type: string + short_description: + maxLength: 255 + type: string + required: + - description + - name + - short_description + type: object + schema.SiteInterfaceReq: + properties: + language: + maxLength: 128 + type: string + logo: + maxLength: 256 + type: string + theme: + maxLength: 128 + type: string + required: + - language + - theme + type: object + schema.SiteInterfaceResp: + properties: + language: + maxLength: 128 + type: string + logo: + maxLength: 256 + type: string + theme: + maxLength: 128 + type: string + required: + - language + - theme + type: object + schema.TagItem: + properties: + display_name: + description: display_name + maxLength: 50 + type: string + original_text: + description: original text + type: string + parsed_text: + description: parsed text + type: string + slug_name: + description: slug_name + maxLength: 50 + type: string + type: object + schema.TagResp: + properties: + display_name: + type: string + main_tag_slug_name: + description: if main tag slug name is not empty, this tag is synonymous with + the main tag + type: string + slug_name: + type: string + type: object + schema.UpdateCollectionGroupReq: + properties: + create_time: + type: string + default_group: + description: mark this group is default, default 1 + type: integer + id: + type: integer + name: + description: the collection group name + maxLength: 50 + type: string + update_time: + type: string + user_id: + type: integer + required: + - id + type: object + schema.UpdateCommentReq: + properties: + comment_id: + description: comment id + type: string + original_text: + description: original comment content + type: string + parsed_text: + description: parsed comment content + type: string + required: + - comment_id + type: object + schema.UpdateFollowTagsReq: + properties: + slug_name_list: + description: tag slug name list + items: + type: string + type: array + type: object + schema.UpdateInfoRequest: + properties: + avatar: + description: avatar + type: string + bio: + type: string + bio_html: + type: string + display_name: + description: display_name + type: string + location: + description: location + type: string + username: + description: name + type: string + website: + description: website + type: string + type: object + schema.UpdateNotificationReadReq: + properties: + id: + description: id + type: integer + is_read: + description: 'read status(unread: 1; read 2)' + type: integer + message_id: + description: message id + type: integer + user_id: + description: user id + type: integer + required: + - id + type: object + schema.UpdateTagReq: + properties: + display_name: + description: display_name + maxLength: 50 + type: string + edit_summary: + description: edit summary + type: string + original_text: + description: original text + type: string + parsed_text: + description: parsed text + type: string + slug_name: + description: slug_name + maxLength: 50 + type: string + tag_id: + description: tag_id + type: string + required: + - tag_id + type: object + schema.UpdateTagSynonymReq: + properties: + synonym_tag_list: + description: synonym tag list + items: + $ref: '#/definitions/schema.TagItem' + type: array + tag_id: + description: tag_id + type: string + required: + - synonym_tag_list + - tag_id + type: object + schema.UpdateUserGroupReq: + properties: + id: + description: user group id + type: integer + required: + - id + type: object + schema.UpdateUserStatusReq: + properties: + status: + description: user status + enum: + - normal + - suspended + - deleted + - inactive + type: string + user_id: + description: user id + type: string + required: + - status + - user_id + type: object + schema.UserBasicInfo: + properties: + avatar: + description: avatar + type: string + display_name: + description: display_name + type: string + ip_info: + description: ip info + type: string + location: + description: location + type: string + rank: + description: rank + type: integer + status: + description: status + type: integer + username: + description: name + type: string + website: + description: website + type: string + type: object + schema.UserChangeEmailSendCodeReq: + properties: + e_mail: + maxLength: 500 + type: string + required: + - e_mail + type: object + schema.UserChangeEmailVerifyReq: + properties: + code: + maxLength: 500 + type: string + required: + - code + type: object + schema.UserEmailLogin: + properties: + captcha_code: + description: captcha_code + type: string + captcha_id: + description: captcha_id + type: string + e_mail: + description: e_mail + type: string + pass: + description: password + type: string + type: object + schema.UserModifyPassWordRequest: + properties: + old_pass: + description: old password + type: string + pass: + description: password + type: string + type: object + schema.UserNoticeSetRequest: + properties: + notice_switch: + type: boolean + type: object + schema.UserNoticeSetResp: + properties: + notice_switch: + type: boolean + type: object + schema.UserRePassWordRequest: + properties: + code: + description: code + maxLength: 100 + type: string + pass: + description: Password + maxLength: 32 + type: string + required: + - code + - pass + type: object + schema.UserRegister: + properties: + e_mail: + description: email + maxLength: 500 + type: string + name: + description: name + maxLength: 50 + type: string + pass: + description: password + maxLength: 32 + minLength: 8 + type: string + required: + - e_mail + - name + - pass + type: object + schema.UserRetrievePassWordRequest: + properties: + captcha_code: + description: captcha_code + type: string + captcha_id: + description: captcha_id + type: string + e_mail: + description: e_mail + maxLength: 500 + type: string + required: + - e_mail + type: object + schema.VoteReq: + properties: + is_cancel: + description: is cancel + type: boolean + object_id: + description: id + type: string + required: + - object_id + type: object + schema.VoteResp: + properties: + down_votes: + type: integer + up_votes: + type: integer + vote_status: + type: string + votes: + type: integer + type: object +info: + contact: {} +paths: + /answer/admin/api/answer/page: + get: + consumes: + - application/json + description: Status:[available,deleted] + parameters: + - description: page size + in: query + name: page + type: integer + - description: page size + in: query + name: page_size + type: integer + - description: user status + enum: + - available + - deleted + in: query + name: status + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: CmsSearchList + tags: + - admin + /answer/admin/api/answer/status: + put: + consumes: + - application/json + description: Status:[available,deleted] + parameters: + - description: AdminSetAnswerStatusRequest + in: body + name: data + required: true + schema: + $ref: '#/definitions/entity.AdminSetAnswerStatusRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: AdminSetAnswerStatus + tags: + - admin + /answer/admin/api/language/options: + get: + description: Get language options + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: Get language options + tags: + - Lang + /answer/admin/api/question/page: + get: + consumes: + - application/json + description: Status:[available,closed,deleted] + parameters: + - description: page size + in: query + name: page + type: integer + - description: page size + in: query + name: page_size + type: integer + - description: user status + enum: + - available + - closed + - deleted + in: query + name: status + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: CmsSearchList + tags: + - admin + /answer/admin/api/question/status: + put: + consumes: + - application/json + description: Status:[available,closed,deleted] + parameters: + - description: AdminSetQuestionStatusRequest + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.AdminSetQuestionStatusRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: AdminSetQuestionStatus + tags: + - admin + /answer/admin/api/reasons: + get: + consumes: + - application/json + description: get reasons by object type and action + parameters: + - description: object_type + enum: + - question + - answer + - comment + - user + in: query + name: object_type + required: true + type: string + - description: action + enum: + - status + - close + - flag + - review + in: query + name: action + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: get reasons by object type and action + tags: + - reason + /answer/admin/api/report/: + put: + consumes: + - application/json + description: handle flag + parameters: + - description: flag + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.ReportHandleReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: [] + summary: handle flag + tags: + - admin + /answer/admin/api/reports/page: + get: + consumes: + - application/json + description: list report records + parameters: + - description: status + enum: + - pending + - completed + in: query + name: status + required: true + type: string + - description: object_type + enum: + - all + - question + - answer + - comment + in: query + name: object_type + required: true + type: string + - description: page size + in: query + name: page + type: integer + - description: page size + in: query + name: page_size + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: [] + summary: list report page + tags: + - admin + /answer/admin/api/siteinfo/general: + get: + description: Get siteinfo general + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.SiteGeneralResp' + type: object + security: + - ApiKeyAuth: [] + summary: Get siteinfo general + tags: + - admin + put: + description: Get siteinfo interface + parameters: + - description: general + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.SiteGeneralReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: Get siteinfo interface + tags: + - admin + /answer/admin/api/siteinfo/interface: + get: + description: Get siteinfo interface + parameters: + - description: general + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.AddCommentReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.SiteInterfaceResp' + type: object + security: + - ApiKeyAuth: [] + summary: Get siteinfo interface + tags: + - admin + put: + description: Get siteinfo interface + parameters: + - description: general + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.SiteInterfaceReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: Get siteinfo interface + tags: + - admin + /answer/admin/api/theme/options: + get: + description: Get theme options + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: Get theme options + tags: + - admin + /answer/admin/api/user/status: + put: + consumes: + - application/json + description: update user + parameters: + - description: user + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.UpdateUserStatusReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: update user + tags: + - admin + /answer/admin/api/users/page: + get: + description: get user page + parameters: + - description: page size + in: query + name: page + type: integer + - description: page size + in: query + name: page_size + type: integer + - description: username + in: query + name: username + type: string + - description: email + in: query + name: e_mail + type: string + - description: user status + enum: + - normal + - suspended + - deleted + - inactive + in: query + name: status + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + allOf: + - $ref: '#/definitions/pager.PageModel' + - properties: + records: + items: + $ref: '#/definitions/schema.GetUserPageResp' + type: array + type: object + type: object + security: + - ApiKeyAuth: [] + summary: get user page + tags: + - admin + /answer/api/v1/answer: + delete: + consumes: + - application/json + description: delete answer + parameters: + - description: answer + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.RemoveAnswerReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: delete answer + tags: + - api-answer + post: + consumes: + - application/json + description: Insert Answer + parameters: + - description: AnswerAddReq + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.AnswerAddReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + type: string + security: + - ApiKeyAuth: [] + summary: Insert Answer + tags: + - api-answer + put: + consumes: + - application/json + description: Update Answer + parameters: + - description: AnswerUpdateReq + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.AnswerUpdateReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + type: string + security: + - ApiKeyAuth: [] + summary: Update Answer + tags: + - api-answer + /answer/api/v1/answer/acceptance: + post: + consumes: + - application/json + description: Adopted + parameters: + - description: AnswerAdoptedReq + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.AnswerAdoptedReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + type: string + security: + - ApiKeyAuth: [] + summary: Adopted + tags: + - api-answer + /answer/api/v1/answer/info: + get: + consumes: + - application/json + description: Get Answer + parameters: + - default: "1" + description: Answer TagID + in: query + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + type: string + summary: Get Answer + tags: + - api-answer + /answer/api/v1/answer/list: + post: + consumes: + - application/json + description: AnswerList
order (default or updated) + parameters: + - description: AnswerList + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.AnswerList' + produces: + - application/json + responses: + "200": + description: OK + schema: + type: string + security: + - ApiKeyAuth: [] + summary: AnswerList + tags: + - api-answer + /answer/api/v1/collection/switch: + post: + consumes: + - application/json + description: add collection + parameters: + - description: collection + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.CollectionSwitchReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.CollectionSwitchResp' + type: object + security: + - ApiKeyAuth: [] + summary: add collection + tags: + - Collection + /answer/api/v1/comment: + delete: + consumes: + - application/json + description: remove comment + parameters: + - description: comment + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.RemoveCommentReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: remove comment + tags: + - Comment + get: + description: get comment by id + parameters: + - description: id + in: query + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + allOf: + - $ref: '#/definitions/pager.PageModel' + - properties: + list: + items: + $ref: '#/definitions/schema.GetCommentResp' + type: array + type: object + type: object + summary: get comment by id + tags: + - Comment + post: + consumes: + - application/json + description: add comment + parameters: + - description: comment + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.AddCommentReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.GetCommentResp' + type: object + security: + - ApiKeyAuth: [] + summary: add comment + tags: + - Comment + put: + consumes: + - application/json + description: update comment + parameters: + - description: comment + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.UpdateCommentReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: update comment + tags: + - Comment + /answer/api/v1/comment/page: + get: + description: get comment page + parameters: + - description: page + in: query + name: page + type: integer + - description: page size + in: query + name: page_size + type: integer + - description: object id + in: query + name: object_id + required: true + type: string + - description: query condition + enum: + - vote + in: query + name: query_cond + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + allOf: + - $ref: '#/definitions/pager.PageModel' + - properties: + list: + items: + $ref: '#/definitions/schema.GetCommentResp' + type: array + type: object + type: object + summary: get comment page + tags: + - Comment + /answer/api/v1/follow: + post: + consumes: + - application/json + description: follow object or cancel follow operation + parameters: + - description: follow + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.FollowReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.FollowResp' + type: object + security: + - ApiKeyAuth: [] + summary: follow object or cancel follow operation + tags: + - Activity + /answer/api/v1/follow/tags: + put: + consumes: + - application/json + description: update user follow tags + parameters: + - description: follow + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.UpdateFollowTagsReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: update user follow tags + tags: + - Activity + /answer/api/v1/language/config: + get: + description: get language config mapping + parameters: + - description: Accept-Language + in: header + name: Accept-Language + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + summary: get language config mapping + tags: + - Lang + /answer/api/v1/language/options: + get: + description: Get language options + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: Get language options + tags: + - Lang + /answer/api/v1/notification/page: + get: + consumes: + - application/json + description: GetRedDot + parameters: + - description: page size + in: query + name: page + type: integer + - description: page size + in: query + name: page_size + type: integer + - description: type + enum: + - inbox + - achievement + in: query + name: type + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: GetRedDot + tags: + - Notification + /answer/api/v1/notification/read/state: + put: + consumes: + - application/json + description: ClearUnRead + parameters: + - description: NotificationClearIDRequest + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.NotificationClearIDRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: ClearUnRead + tags: + - Notification + /answer/api/v1/notification/read/state/all: + put: + consumes: + - application/json + description: ClearUnRead + parameters: + - description: NotificationClearRequest + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.NotificationClearRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: ClearUnRead + tags: + - Notification + /answer/api/v1/notification/status: + get: + consumes: + - application/json + description: GetRedDot + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: GetRedDot + tags: + - Notification + put: + consumes: + - application/json + description: DelRedDot + parameters: + - description: NotificationClearRequest + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.NotificationClearRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: DelRedDot + tags: + - Notification + /answer/api/v1/personal/answer/page: + get: + consumes: + - application/json + description: UserAnswerList + parameters: + - default: string + description: username + in: query + name: username + required: true + type: string + - description: order + enum: + - newest + - score + in: query + name: order + required: true + type: string + - default: "0" + description: page + in: query + name: page + required: true + type: string + - default: "20" + description: pagesize + in: query + name: pagesize + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: UserAnswerList + tags: + - api-answer + /answer/api/v1/personal/collection/page: + get: + consumes: + - application/json + description: UserCollectionList + parameters: + - default: "0" + description: page + in: query + name: page + required: true + type: string + - default: "20" + description: pagesize + in: query + name: pagesize + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: UserCollectionList + tags: + - Collection + /answer/api/v1/personal/comment/page: + get: + description: user personal comment list + parameters: + - description: page + in: query + name: page + type: integer + - description: page size + in: query + name: page_size + type: integer + - description: username + in: query + name: username + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + allOf: + - $ref: '#/definitions/pager.PageModel' + - properties: + list: + items: + $ref: '#/definitions/schema.GetCommentPersonalWithPageResp' + type: array + type: object + type: object + summary: user personal comment list + tags: + - Comment + /answer/api/v1/personal/qa/top: + get: + consumes: + - application/json + description: UserTop + parameters: + - default: string + description: username + in: query + name: username + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: UserTop + tags: + - api-question + /answer/api/v1/personal/rank/page: + get: + description: user personal rank list + parameters: + - description: page + in: query + name: page + type: integer + - description: page size + in: query + name: page_size + type: integer + - description: username + in: query + name: username + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + allOf: + - $ref: '#/definitions/pager.PageModel' + - properties: + list: + items: + $ref: '#/definitions/schema.GetRankPersonalWithPageResp' + type: array + type: object + type: object + summary: user personal rank list + tags: + - Rank + /answer/api/v1/personal/user/info: + get: + consumes: + - application/json + description: GetOtherUserInfoByUsername + parameters: + - description: username + in: query + name: username + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.GetOtherUserInfoResp' + type: object + security: + - ApiKeyAuth: [] + summary: GetOtherUserInfoByUsername + tags: + - User + /answer/api/v1/personal/vote/page: + get: + consumes: + - application/json + description: user's vote + parameters: + - description: page size + in: query + name: page + type: integer + - description: page size + in: query + name: page_size + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + allOf: + - $ref: '#/definitions/pager.PageModel' + - properties: + list: + items: + $ref: '#/definitions/schema.GetVoteWithPageResp' + type: array + type: object + type: object + security: + - ApiKeyAuth: [] + summary: user's votes + tags: + - Activity + /answer/api/v1/question: + delete: + consumes: + - application/json + description: delete question + parameters: + - description: question + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.RemoveQuestionReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: delete question + tags: + - api-question + post: + consumes: + - application/json + description: add question + parameters: + - description: question + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.QuestionAdd' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: add question + tags: + - api-question + put: + consumes: + - application/json + description: update question + parameters: + - description: question + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.QuestionUpdate' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: update question + tags: + - api-question + /answer/api/v1/question/closemsglist: + get: + consumes: + - application/json + description: close question msg list + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: close question msg list + tags: + - api-question + /answer/api/v1/question/info: + get: + consumes: + - application/json + description: GetQuestion Question + parameters: + - default: "1" + description: Question TagID + in: query + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + type: string + security: + - ApiKeyAuth: [] + summary: GetQuestion Question + tags: + - api-question + /answer/api/v1/question/page: + post: + consumes: + - application/json + description: SearchQuestionList
"order" Enums(newest, active,frequent,score,unanswered) + parameters: + - description: QuestionSearch + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.QuestionSearch' + produces: + - application/json + responses: + "200": + description: OK + schema: + type: string + summary: SearchQuestionList + tags: + - api-question + /answer/api/v1/question/search: + post: + consumes: + - application/json + description: SearchQuestionList + parameters: + - description: QuestionSearch + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.QuestionSearch' + produces: + - application/json + responses: + "200": + description: OK + schema: + type: string + summary: SearchQuestionList + tags: + - api-question + /answer/api/v1/question/similar: + get: + consumes: + - application/json + description: add question title like + parameters: + - default: string + description: title + in: query + name: title + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: add question title like + tags: + - api-question + /answer/api/v1/question/similar/tag: + get: + consumes: + - application/json + description: Search Similar Question + parameters: + - default: "" + description: question_id + in: query + name: question_id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + type: string + summary: Search Similar Question + tags: + - api-question + /answer/api/v1/question/status: + put: + consumes: + - application/json + description: Close question + parameters: + - description: question + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.CloseQuestionReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: Close question + tags: + - api-question + /answer/api/v1/question/tags: + get: + description: get tag list + parameters: + - description: tag + in: query + name: tag + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + items: + $ref: '#/definitions/schema.GetTagResp' + type: array + type: object + security: + - ApiKeyAuth: [] + summary: get tag list + tags: + - Tag + /answer/api/v1/reasons: + get: + consumes: + - application/json + description: get reasons by object type and action + parameters: + - description: object_type + enum: + - question + - answer + - comment + - user + in: query + name: object_type + required: true + type: string + - description: action + enum: + - status + - close + - flag + - review + in: query + name: action + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: get reasons by object type and action + tags: + - reason + /answer/api/v1/report: + post: + consumes: + - application/json + description: add report
source (question, answer, comment, user) + parameters: + - description: report + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.AddReportReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + - ApiKeyAuth: [] + summary: add report + tags: + - Report + /answer/api/v1/report/type/list: + get: + description: get report type list + parameters: + - description: report source + enum: + - question + - answer + - comment + - user + in: query + name: source + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + items: + $ref: '#/definitions/schema.GetReportTypeResp' + type: array + type: object + summary: get report type list + tags: + - Report + /answer/api/v1/revisions: + get: + description: get revision list + parameters: + - description: object id + in: query + name: object_id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + items: + $ref: '#/definitions/schema.GetRevisionResp' + type: array + type: object + summary: get revision list + tags: + - Revision + /answer/api/v1/search: + get: + description: search object + parameters: + - description: query string + in: query + name: q + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.SearchListResp' + type: object + security: + - ApiKeyAuth: [] + summary: search object + tags: + - Search + /answer/api/v1/siteinfo: + get: + description: Get siteinfo + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.SiteGeneralResp' + type: object + summary: Get siteinfo + tags: + - site + /answer/api/v1/tag: + delete: + consumes: + - application/json + description: delete tag + parameters: + - description: tag + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.RemoveTagReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + summary: delete tag + tags: + - Tag + get: + consumes: + - application/json + description: get tag one + parameters: + - description: tag id + in: query + name: tag_id + required: true + type: string + - description: tag name + in: query + name: tag_name + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.GetTagResp' + type: object + summary: get tag one + tags: + - Tag + put: + consumes: + - application/json + description: update tag + parameters: + - description: tag + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.UpdateTagReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + summary: update tag + tags: + - Tag + /answer/api/v1/tag/synonym: + put: + consumes: + - application/json + description: update tag + parameters: + - description: tag + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.UpdateTagSynonymReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + summary: update tag + tags: + - Tag + /answer/api/v1/tag/synonyms: + get: + description: get tag synonyms + parameters: + - description: tag id + in: query + name: tag_id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + items: + $ref: '#/definitions/schema.GetTagSynonymsResp' + type: array + type: object + summary: get tag synonyms + tags: + - Tag + /answer/api/v1/tags/following: + get: + description: get following tag list + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + items: + $ref: '#/definitions/schema.GetFollowingTagsResp' + type: array + type: object + security: + - ApiKeyAuth: [] + summary: get following tag list + tags: + - Tag + /answer/api/v1/tags/page: + get: + description: get tag page + parameters: + - description: page size + in: query + name: page + type: integer + - description: page size + in: query + name: page_size + type: integer + - description: slug_name + in: query + name: slug_name + type: string + - description: query condition + enum: + - popular + - name + - newest + in: query + name: query_cond + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + allOf: + - $ref: '#/definitions/pager.PageModel' + - properties: + list: + items: + $ref: '#/definitions/schema.GetTagPageResp' + type: array + type: object + type: object + summary: get tag page + tags: + - Tag + /answer/api/v1/user/action/record: + get: + description: ActionRecord + parameters: + - description: action + enum: + - login + - e_mail + - find_pass + in: query + name: action + required: true + type: string + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.ActionRecordResp' + type: object + security: + - ApiKeyAuth: [] + summary: ActionRecord + tags: + - User + /answer/api/v1/user/avatar/upload: + post: + consumes: + - multipart/form-data + description: UserUpdateInfo + parameters: + - description: file + in: formData + name: file + required: true + type: file + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: UserUpdateInfo + tags: + - User + /answer/api/v1/user/email: + put: + consumes: + - application/json + description: user change email verification + parameters: + - description: UserChangeEmailVerifyReq + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.UserChangeEmailVerifyReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: user change email verification + tags: + - User + /answer/api/v1/user/email/change/code: + post: + consumes: + - application/json + description: send email to the user email then change their email + parameters: + - description: UserChangeEmailSendCodeReq + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.UserChangeEmailSendCodeReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + summary: send email to the user email then change their email + tags: + - User + /answer/api/v1/user/email/verification: + post: + consumes: + - application/json + description: UserVerifyEmail + parameters: + - default: "" + description: code + in: query + name: code + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.GetUserResp' + type: object + summary: UserVerifyEmail + tags: + - User + /answer/api/v1/user/email/verification/send: + post: + consumes: + - application/json + description: UserVerifyEmailSend + parameters: + - default: "" + description: captcha_id + in: query + name: captcha_id + type: string + - default: "" + description: captcha_code + in: query + name: captcha_code + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + type: string + security: + - ApiKeyAuth: [] + summary: UserVerifyEmailSend + tags: + - User + /answer/api/v1/user/info: + get: + consumes: + - application/json + description: GetUserInfoByUserID + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.GetUserResp' + type: object + security: + - ApiKeyAuth: [] + summary: GetUserInfoByUserID + tags: + - User + put: + consumes: + - application/json + description: UserUpdateInfo + parameters: + - description: access-token + in: header + name: Authorization + required: true + type: string + - description: UpdateInfoRequest + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.UpdateInfoRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: UserUpdateInfo + tags: + - User + /answer/api/v1/user/login/email: + post: + consumes: + - application/json + description: UserEmailLogin + parameters: + - description: UserEmailLogin + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.UserEmailLogin' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.GetUserResp' + type: object + summary: UserEmailLogin + tags: + - User + /answer/api/v1/user/logout: + get: + consumes: + - application/json + description: user logout + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + summary: user logout + tags: + - User + /answer/api/v1/user/notice/set: + post: + consumes: + - application/json + description: UserNoticeSet + parameters: + - description: UserNoticeSetRequest + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.UserNoticeSetRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.UserNoticeSetResp' + type: object + security: + - ApiKeyAuth: [] + summary: UserNoticeSet + tags: + - User + /answer/api/v1/user/password: + put: + consumes: + - application/json + description: UserModifyPassWord + parameters: + - description: UserModifyPassWordRequest + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.UserModifyPassWordRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: UserModifyPassWord + tags: + - User + /answer/api/v1/user/password/replacement: + post: + consumes: + - application/json + description: UseRePassWord + parameters: + - description: UserRePassWordRequest + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.UserRePassWordRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + type: string + summary: UseRePassWord + tags: + - User + /answer/api/v1/user/password/reset: + post: + consumes: + - application/json + description: RetrievePassWord + parameters: + - description: UserRetrievePassWordRequest + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.UserRetrievePassWordRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + type: string + summary: RetrievePassWord + tags: + - User + /answer/api/v1/user/post/file: + post: + consumes: + - multipart/form-data + description: upload user post file + parameters: + - description: file + in: formData + name: file + required: true + type: file + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: upload user post file + tags: + - User + /answer/api/v1/user/register/email: + post: + consumes: + - application/json + description: UserRegisterByEmail + parameters: + - description: UserRegister + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.UserRegister' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.GetUserResp' + type: object + summary: UserRegisterByEmail + tags: + - User + /answer/api/v1/vote/down: + post: + consumes: + - application/json + description: add vote + parameters: + - description: vote + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.VoteReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.VoteResp' + type: object + security: + - ApiKeyAuth: [] + summary: vote down + tags: + - Activity + /answer/api/v1/vote/up: + post: + consumes: + - application/json + description: add vote + parameters: + - description: vote + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.VoteReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.VoteResp' + type: object + security: + - ApiKeyAuth: [] + summary: vote up + tags: + - Activity + /personal/question/page: + get: + consumes: + - application/json + description: UserList + parameters: + - default: string + description: username + in: query + name: username + required: true + type: string + - description: order + enum: + - newest + - score + in: query + name: order + required: true + type: string + - default: "0" + description: page + in: query + name: page + required: true + type: string + - default: "20" + description: pagesize + in: query + name: pagesize + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: UserList + tags: + - api-question +securityDefinitions: + ApiKeyAuth: + in: header + name: Authorization + type: apiKey +swagger: "2.0" diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..08c2a30c --- /dev/null +++ b/go.mod @@ -0,0 +1,87 @@ +module github.com/segmentfault/answer + +go 1.18 + +require ( + github.com/Chain-Zhang/pinyin v0.1.3 + github.com/bwmarrin/snowflake v0.3.0 + github.com/davecgh/go-spew v1.1.1 + github.com/gin-gonic/gin v1.8.1 + github.com/go-playground/locales v0.14.0 + github.com/go-playground/universal-translator v0.18.0 + github.com/go-playground/validator/v10 v10.11.1 + github.com/go-sql-driver/mysql v1.6.0 + github.com/goccy/go-json v0.9.11 + github.com/google/uuid v1.3.0 + github.com/google/wire v0.5.0 + github.com/jinzhu/copier v0.3.5 + github.com/jinzhu/now v1.1.5 + github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible + github.com/mojocn/base64Captcha v1.3.5 + github.com/segmentfault/pacman v1.0.1 + github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20220926035018-18f894415e5b + github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20220926035018-18f894415e5b + github.com/segmentfault/pacman/contrib/i18n v0.0.0-20220926035018-18f894415e5b + github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20220926035018-18f894415e5b + github.com/segmentfault/pacman/contrib/server/http v0.0.0-20220926035018-18f894415e5b + github.com/stretchr/testify v1.8.0 + github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a + github.com/swaggo/gin-swagger v1.5.3 + github.com/swaggo/swag v1.8.6 + golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be + golang.org/x/net v0.0.0-20220926192436-02166a98028e + xorm.io/builder v0.3.12 + xorm.io/core v0.7.3 + xorm.io/xorm v1.3.2 +) + +require ( + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/spec v0.20.7 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible // indirect + github.com/lestrrat-go/strftime v1.0.6 // indirect + github.com/magiconair/properties v1.8.6 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect + github.com/mitchellh/mapstructure v1.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/nicksnyder/go-i18n/v2 v2.2.0 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // 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 + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.13.0 // indirect + github.com/subosito/gotenv v1.4.1 // indirect + github.com/syndtr/goleveldb v1.0.0 // indirect + github.com/ugorji/go/codec v1.2.7 // indirect + go.uber.org/atomic v1.10.0 // indirect + go.uber.org/multierr v1.8.0 // indirect + go.uber.org/zap v1.23.0 // indirect + golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 // indirect + golang.org/x/sys v0.0.0-20220926163933-8cfa568d3c25 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/tools v0.1.12 // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..902e19fb --- /dev/null +++ b/go.sum @@ -0,0 +1,1167 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +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/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= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Chain-Zhang/pinyin v0.1.3 h1:RzErNyNwVa8z2sOLCuXSOtVdY/AsARb8mBzI2p2qtnE= +github.com/Chain-Zhang/pinyin v0.1.3/go.mod h1:5iHpt9p4znrnaP59/hfPMnAojajkDxQaP9io+tRMPho= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +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= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= +github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= +github.com/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/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/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/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= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/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/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/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/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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= +github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +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.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= +github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/spec v0.20.7 h1:1Rlu/ZrOCCob0n+JKKJAWhNWMPW8bOZRg8FJaY+0SKI= +github.com/go-openapi/spec v0.20.7/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= +github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/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= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= +github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +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/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk= +github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.8.1/go.mod h1:JV6m6b6jhjdmzchES0drzCcYcAHS1OPD5xu3OZ/lE2g= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0= +github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po= +github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ= +github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE= +github.com/jackc/pgtype v1.8.0/go.mod h1:PqDKcEBtllAtk/2p6z6SHdXW5UB+MhE75tUol2OKexE= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA= +github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o= +github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= +github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc= +github.com/jackc/pgx/v4 v4.12.0/go.mod h1:fE547h6VulLPA3kySjfnSG/e2D861g/50JlVUa/ub60= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= +github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg= +github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA= +github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= +github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4= +github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA= +github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ= +github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= +github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +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/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= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +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/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= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nicksnyder/go-i18n/v2 v2.2.0 h1:MNXbyPvd141JJqlU6gJKrczThxJy+kdCNivxZpBQFkw= +github.com/nicksnyder/go-i18n/v2 v2.2.0/go.mod h1:4OtLfzqyAxsscyCb//3gfqSvBc81gImX91LrZzczN1o= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +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= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +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/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= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= +github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/segmentfault/pacman v1.0.1 h1:GFdvPtNxvVVjnDM4ty02D/+4unHwG9PmjcOZSc2wRXE= +github.com/segmentfault/pacman v1.0.1/go.mod h1:5lNp5REd8QMThmBUvR3Fi9Y3AsOB4GRq7soCB4QLqOs= +github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20220926035018-18f894415e5b h1:jSnRy3z3KVtVuGM2YTZihXwc4zEhW+TvyyJbBm8rjh4= +github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20220926035018-18f894415e5b/go.mod h1:rmf1TCwz67dyM+AmTwSd1BxTo2AOYHj262lP93bOZbs= +github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20220926035018-18f894415e5b h1:Gx3Brm+VMAyBJn4aBsxgKl+EIhFHc/YH5cLGeFHAW4g= +github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20220926035018-18f894415e5b/go.mod h1:prPjFam7MyZ5b3S9dcDOt2tMPz6kf7C9c243s9zSwPY= +github.com/segmentfault/pacman/contrib/i18n v0.0.0-20220926035018-18f894415e5b h1:uQmSgcV2w4OVXU6l3bQb9O+cSAVuzDQ9adJArQyFBa4= +github.com/segmentfault/pacman/contrib/i18n v0.0.0-20220926035018-18f894415e5b/go.mod h1:5Afm+OQdau/HQqSOp/ALlSUp0vZsMMMbv//kJhxuoi8= +github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20220926035018-18f894415e5b h1:TaOBmAglooq+qKdnNTK2sy11t26ud7psHFB7/AV7l5U= +github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20220926035018-18f894415e5b/go.mod h1:L4GqtXLoR73obTYqUQIzfkm8NG8pvZafxFb6KZFSSHk= +github.com/segmentfault/pacman/contrib/server/http v0.0.0-20220926035018-18f894415e5b h1:n5n5VPeYGuZCmVppKPgWR/CaINHnL+ipEp9iE1XkcQc= +github.com/segmentfault/pacman/contrib/server/http v0.0.0-20220926035018-18f894415e5b/go.mod h1:UjNiOFYv1uGCq1ZCcONaKq4eE7MW3nbgpLqgl8f9N40= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +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/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= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +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.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= +github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY= +github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= +github.com/swaggo/gin-swagger v1.5.3 h1:8mWmHLolIbrhJJTflsaFoZzRBYVmEE7JZGIq08EiC0Q= +github.com/swaggo/gin-swagger v1.5.3/go.mod h1:3XJKSfHjDMB5dBo/0rrTXidPmgLeqsX89Yp4uA50HpI= +github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= +github.com/swaggo/swag v1.8.6 h1:2rgOaLbonWu1PLP6G+/rYjSvPg0jQE0HtrEKuE380eg= +github.com/swaggo/swag v1.8.6/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg= +github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/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/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +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/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= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= +go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220924013350-4ba4fb4dd9e7 h1:WJywXQVIb56P2kAvXeMGTIgQ1ZHQxR60+F9dLsodECc= +golang.org/x/crypto v0.0.0-20220924013350-4ba4fb4dd9e7/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A= +golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +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= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY= +golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220923203811-8be639271d50 h1:vKyz8L3zkd+xrMeIaBsQ/MNVPVFSffdaU3ZyYlBGFnI= +golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20220926192436-02166a98028e h1:I51lVG9ykW5AQeTE50sJ0+gJCAF0J78Hf1+1VUCGxDI= +golang.org/x/net v0.0.0-20220926192436-02166a98028e/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +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= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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-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-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= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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-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= +golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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-20211007075335-d3039528d8ac/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-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc= +golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220926163933-8cfa568d3c25 h1:nwzwVf0l2Y/lkov/+IYgMMbFyI+QypZDds9RxlSmsFQ= +golang.org/x/sys v0.0.0-20220926163933-8cfa568d3c25/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= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +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= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +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-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= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +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= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +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.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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= +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= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.18 h1:rMZhRcWrba0y3nVmdiQ7kxAgOOSq2m2f2VzjHLgEs6U= +modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60= +modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw= +modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI= +modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag= +modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw= +modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ= +modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c= +modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo= +modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg= +modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I= +modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs= +modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8= +modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE= +modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk= +modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w= +modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE= +modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8= +modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc= +modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU= +modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE= +modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk= +modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI= +modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE= +modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg= +modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74= +modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU= +modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU= +modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc= +modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM= +modernc.org/ccgo/v3 v3.12.65/go.mod h1:D6hQtKxPNZiY6wDBtehSGKFKmyXn53F8nGTpH+POmS4= +modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ= +modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84= +modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ= +modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY= +modernc.org/ccgo/v3 v3.12.82 h1:wudcnJyjLj1aQQCXF3IM9Gz2X6UNjw+afIghzdtn0v8= +modernc.org/ccgo/v3 v3.12.82/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w= +modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= +modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q= +modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg= +modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M= +modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU= +modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE= +modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso= +modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8= +modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8= +modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I= +modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk= +modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY= +modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE= +modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg= +modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM= +modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg= +modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo= +modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8= +modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ= +modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA= +modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM= +modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg= +modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE= +modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM= +modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU= +modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw= +modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M= +modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18= +modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8= +modernc.org/libc v1.11.70/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw= +modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw= +modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0= +modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI= +modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE= +modernc.org/libc v1.11.87 h1:PzIzOqtlzMDDcCzJ5cUP6h/Ku6Fa9iyflP2ccTY64aE= +modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY= +modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8= +modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= +modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14= +modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM= +modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.14.2 h1:ohsW2+e+Qe2To1W6GNezzKGwjXwSax6R+CrhRxVaFbE= +modernc.org/sqlite v1.14.2/go.mod h1:yqfn85u8wVOE6ub5UT8VI9JjhrwBUUCNyTACN0h6Sx8= +modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= +modernc.org/tcl v1.8.13/go.mod h1:V+q/Ef0IJaNUSECieLU4o+8IScapxnMyFV6i/7uQlAY= +modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= +xorm.io/builder v0.3.12 h1:ASZYX7fQmy+o8UJdhlLHSW57JDOkM8DNhcAF5d0LiJM= +xorm.io/builder v0.3.12/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= +xorm.io/core v0.7.3 h1:W8ws1PlrnkS1CZU1YWaYLMQcQilwAmQXU0BJDJon+H0= +xorm.io/core v0.7.3/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= +xorm.io/xorm v1.3.2 h1:uTRRKF2jYzbZ5nsofXVUx6ncMaek+SHjWYtCXyZo1oM= +xorm.io/xorm v1.3.2/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw= diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml new file mode 100644 index 00000000..6e4a1e8d --- /dev/null +++ b/i18n/en_US.yaml @@ -0,0 +1,160 @@ +base: + success: + other: "success" + unknown: + other: "unknown error" + request_format_error: + other: "request format is not valid" + unauthorized_error: + other: "unauthorized" + database_error: + other: "data server error" + +email: + other: "email" +password: + other: "password" + +email_or_password_wrong_error: &email_or_password_wrong + other: "email or password wrong" + +error: + admin: + email_or_password_wrong: *email_or_password_wrong + answer: + not_found: + other: "answer not found" + comment: + edit_without_permission: + other: "comment not allowed to edit" + not_found: + other: "comment not found" + email: + duplicate: + other: "email already exists" + need_to_be_verified: + other: "email should be verified" + verify_url_expired: + other: "email verified url is expired, please resend the email" + lang: + not_found: + other: "language not found" + object: + captcha_verification_failed: + other: "captcha wrong" + disallow_follow: + other: "You are not allowed to follow" + disallow_vote: + other: "You are not allowed to vote" + disallow_vote_your_self: + other: "You can't vote for your own post!" + not_found: + other: "object not found" + question: + not_found: + other: "question not found" + rank: + fail_to_meet_the_condition: + other: "rank fail to meet the condition" + report: + handle_failed: + other: "report handle failed" + not_found: + other: "report not found" + tag: + not_found: + other: "tag not found" + theme: + not_found: + other: "theme not found" + user: + email_or_password_wrong: + other: *email_or_password_wrong + not_found: + other: "user not found" + suspended: + other: "user is suspended" + + + +report: + spam: + name: + other: "spam" + description: + other: "This post is an advertisement,or vandalism.It is not useful or relevant to the current topic." + rude: + name: + other: "rude or abusive" + description: + other: "A reasonable person would find this content inappropriate for respectful discourse." + duplicate: + name: + other: "a duplicate" + description: + other: "This question has been asked before and already has an answer." + not_answer: + name: + other: "not an answer" + description: + other: "This was posted as an answer,but it does not attempt to answer the question. It should possibly be an edit,a comment,another question,or deleted altogether." + not_need: + name: + other: "no longer needed" + description: + other: "This comment is outdated,conversational or not relevant to this post." + other: + name: + other: "something else" + description: + other: "This post requires staff attention for another reason not listed above." + +question: + close: + duplicate: + name: + other: "spam" + description: + other: "This question has been asked before and already has an answer." + guideline: + name: + other: "a community-specific reason" + description: + other: "This question doesn't meet a community guideline." + multiple: + name: + other: "needs details or clarity" + description: + other: "This question currently includes multiple questions in one. It should focus on one problem only." + other: + name: + other: "something else" + description: + other: "This post requires another reason not listed above." + +notification: + action: + update_question: + other: "update question" + answer_the_question: + other: "answer the question" + update_answer: + other: "update answer" + adopt_answer: + other: "adopt answer" + comment_question: + other: "comment question" + comment_answer: + other: "comment answer" + reply_to_you: + other: "reply to you" + mention_you: + other: "mention you" + your_question_is_closed: + other: "your question is closed" + your_question_was_deleted: + other: "your question was deleted" + your_answer_was_deleted: + other: "your answer was deleted" + your_comment_was_deleted: + other: "your comment was deleted" diff --git a/i18n/zh_CN.yaml b/i18n/zh_CN.yaml new file mode 100644 index 00000000..30117fd9 --- /dev/null +++ b/i18n/zh_CN.yaml @@ -0,0 +1,25 @@ +base: + success: + other: "成功" + unknown: + other: "未知错误" + request_format_error: + other: "请求格式错误" + unauthorized_error: + other: "未登录" + database_error: + other: "数据服务异常" + +email: + other: "邮箱" +password: + other: "密码" + +username_or_password_wrong_error: &username_or_password_wrong + other: "用户名或密码错误" + +error: + user: + username_or_password_wrong: *username_or_password_wrong + admin: + username_or_password_wrong: *username_or_password_wrong diff --git a/internal/base/conf/conf.go b/internal/base/conf/conf.go new file mode 100644 index 00000000..70eb85ef --- /dev/null +++ b/internal/base/conf/conf.go @@ -0,0 +1,71 @@ +package conf + +import ( + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/server" + "github.com/segmentfault/answer/internal/base/translator" + "github.com/segmentfault/answer/internal/router" + "github.com/segmentfault/answer/internal/service/service_config" +) + +// AllConfig all config +type AllConfig struct { + Debug bool `json:"debug" mapstructure:"debug"` + Data *Data `json:"data" mapstructure:"data"` + Server *Server `json:"server" mapstructure:"server"` + I18n *translator.I18n `json:"i18n" mapstructure:"i18n"` + Swaggerui *router.SwaggerConfig `json:"swaggerui" mapstructure:"swaggerui"` + ServiceConfig *service_config.ServiceConfig `json:"service_config" mapstructure:"service_config"` +} + +// Server server config +type Server struct { + HTTP *server.HTTP `json:"http" mapstructure:"http"` +} + +// Data data config +type Data struct { + Database *data.Database `json:"database" mapstructure:"database"` + Cache *data.CacheConf `json:"cache" mapstructure:"cache"` +} + +// ------------------ remove + +// log . +type log struct { + Dir string `json:"dir"` + Name string `json:"name"` + Access bool `json:"access"` + Level string `json:"level"` + MaxSize int `json:"max_size"` + MaxBackups int `json:"max_backups"` + MaxAge int `json:"max_age"` +} + +// Local . +type Local struct { + Address string `json:"address"` + Debug bool `json:"debug"` + log log `json:"log"` +} + +// // SwaggerConfig . +// type SwaggerConfig struct { +// Show bool `json:"show"` +// Protocol string `json:"protocol"` +// Host string `json:"host"` +// Address string `json:"address"` +// } + +// Answer . +type Answer struct { + MaxIdle int `json:"max_idle"` + MaxOpen int `json:"max_open"` + IsDebug bool `json:"is_debug"` + Datasource string `json:"datasource"` +} + +// Mysql . +type Mysql struct { + Answer Answer `json:"answer"` +} diff --git a/internal/base/constant/constant.go b/internal/base/constant/constant.go new file mode 100644 index 00000000..e8dfc000 --- /dev/null +++ b/internal/base/constant/constant.go @@ -0,0 +1,51 @@ +package constant + +import "time" + +const ( + Default_PageSize = 20 //Default number of pages + Key_UserID = "_UserID" //session userid + LoginUserID = "login_user_id" + LoginUserVerify = "login_user_verify" + UserStatusChangedCacheKey = "answer:user:status:" + UserStatusChangedCacheTime = 7 * 24 * time.Hour + UserTokenCacheKey = "answer:user:token:" + UserTokenCacheTime = 7 * 24 * time.Hour + AdminTokenCacheKey = "answer:admin:token:" + AdminTokenCacheTime = 7 * 24 * time.Hour +) + +const ( + QuestionObjectType = "question" + AnswerObjectType = "answer" + TagObjectType = "tag" + UserObjectType = "user" + CollectionObjectType = "collection" + CommentObjectType = "comment" + ReportObjectType = "report" +) + +// ObjectTypeStrMapping key => value +// object TagID AnswerList +// key equal database's table name +var ( + ObjectTypeStrMapping = map[string]int{ + QuestionObjectType: 1, + AnswerObjectType: 2, + TagObjectType: 3, + UserObjectType: 4, + CollectionObjectType: 6, + CommentObjectType: 7, + ReportObjectType: 8, + } + + ObjectTypeNumberMapping = map[int]string{ + 1: QuestionObjectType, + 2: AnswerObjectType, + 3: TagObjectType, + 4: UserObjectType, + 6: CollectionObjectType, + 7: CommentObjectType, + 8: ReportObjectType, + } +) diff --git a/internal/base/constant/notification.go b/internal/base/constant/notification.go new file mode 100644 index 00000000..fe9fbf41 --- /dev/null +++ b/internal/base/constant/notification.go @@ -0,0 +1,28 @@ +package constant + +const ( + // UpdateQuestion update question + UpdateQuestion = "notification.action.update_question" + // AnswerTheQuestion answer the question + AnswerTheQuestion = "notification.action.answer_the_question" + // UpdateAnswer update answer + UpdateAnswer = "notification.action.update_answer" + // AdoptAnswer adopt answer + AdoptAnswer = "notification.action.adopt_answer" + // CommentQuestion comment question + CommentQuestion = "notification.action.comment_question" + // CommentAnswer comment answer + CommentAnswer = "notification.action.comment_answer" + // ReplyToYou reply to you + ReplyToYou = "notification.action.reply_to_you" + // MentionYou mention you + MentionYou = "notification.action.mention_you" + // YourQuestionIsClosed your question is closed + YourQuestionIsClosed = "notification.action.your_question_is_closed" + // YourQuestionWasDeleted your question was deleted + YourQuestionWasDeleted = "notification.action.your_question_was_deleted" + // YourAnswerWasDeleted your answer was deleted + YourAnswerWasDeleted = "notification.action.your_answer_was_deleted" + // YourCommentWasDeleted your comment was deleted + YourCommentWasDeleted = "notification.action.your_comment_was_deleted" +) diff --git a/internal/base/constant/report.go b/internal/base/constant/report.go new file mode 100644 index 00000000..4bcca92f --- /dev/null +++ b/internal/base/constant/report.go @@ -0,0 +1,34 @@ +package constant + +const ( + ReportSpamName = "report.spam.name" + ReportSpamDescription = "report.spam.description" + ReportRudeName = "report.rude.name" + ReportRudeDescription = "report.rude.description" + ReportDuplicateName = "report.duplicate.name" + ReportDuplicateDescription = "report.duplicate.description" + ReportOtherName = "report.other.name" + ReportOtherDescription = "report.other.description" + ReportNotAnswerName = "report.not_answer.name" + ReportNotAnswerDescription = "report.not_answer.description" + ReportNotNeedName = "report.not_need.name" + ReportNotNeedDescription = "report.not_need.description" + //question close + QuestionCloseDuplicateName = "question.close.duplicate.name" + QuestionCloseDuplicateDescription = "question.close.duplicate.description" + QuestionCloseGuidelineName = "question.close.guideline.name" + QuestionCloseGuidelineDescription = "question.close.guideline.description" + QuestionCloseMultipleName = "question.close.multiple.name" + QuestionCloseMultipleDescription = "question.close.multiple.description" + QuestionCloseOtherName = "question.close.other.name" + QuestionCloseOtherDescription = "question.close.other.description" +) + +const ( + // TODO put this in database + // TODO need reason controller to resolve + QuestionCloseJson = `[{"name":"question.close.duplicate.name","description":"question.close.duplicate.description","source":"question","type":1,"have_content":false,"content_type":""},{"name":"question.close.guideline.name","description":"question.close.guideline.description","source":"question","type":2,"have_content":false,"content_type":""},{"name":"question.close.multiple.name","description":"question.close.multiple.description","source":"question","type":3,"have_content":true,"content_type":"text"},{"name":"question.close.other.name","description":"question.close.other.description","source":"question","type":4,"have_content":true,"content_type":"textarea"}]` + QuestionReportJson = `[{"name":"report.spam.name","description":"report.spam.description","source":"question","type":1,"have_content":false,"content_type":""},{"name":"report.rude.name","description":"report.rude.description","source":"question","type":2,"have_content":false,"content_type":""},{"name":"report.duplicate.name","description":"report.duplicate.description","source":"question","type":3,"have_content":true,"content_type":"text"},{"name":"report.other.name","description":"report.other.description","source":"question","type":4,"have_content":true,"content_type":"textarea"}]` + AnswerReportJson = `[{"name":"report.spam.name","description":"report.spam.description","source":"answer","type":1,"have_content":false,"content_type":""},{"name":"report.rude.name","description":"report.rude.description","source":"answer","type":2,"have_content":false,"content_type":""},{"name":"report.not_answer.name","description":"report.not_answer.description","source":"answer","type":3,"have_content":false,"content_type":""},{"name":"report.other.name","description":"report.other.description","source":"answer","type":4,"have_content":true,"content_type":"textarea"}]` + CommentReportJson = `[{"name":"report.spam.name","description":"report.spam.description","source":"comment","type":1,"have_content":false,"content_type":""},{"name":"report.rude.name","description":"report.rude.description","source":"comment","type":2,"have_content":false,"content_type":""},{"name":"report.not_need.name","description":"report.not_need.description","source":"comment","type":3,"have_content":true,"content_type":"text"},{"name":"report.other.name","description":"report.other.description","source":"comment","type":4,"have_content":true,"content_type":"textarea"}]` +) diff --git a/internal/base/data/config.go b/internal/base/data/config.go new file mode 100644 index 00000000..d7268ab0 --- /dev/null +++ b/internal/base/data/config.go @@ -0,0 +1,14 @@ +package data + +// Database database config +type Database struct { + Connection string `json:"connection" mapstructure:"connection"` + ConnMaxLifeTime int `json:"conn_max_life_time" mapstructure:"conn_max_life_time"` + MaxOpenConn int `json:"max_open_conn" mapstructure:"max_open_conn"` + MaxIdleConn int `json:"max_idle_conn" mapstructure:"max_idle_conn"` +} + +// CacheConf cache +type CacheConf struct { + FilePath string `json:"file_path" mapstructure:"file_path"` +} diff --git a/internal/base/data/data.go b/internal/base/data/data.go new file mode 100644 index 00000000..40fb7cf1 --- /dev/null +++ b/internal/base/data/data.go @@ -0,0 +1,83 @@ +package data + +import ( + "time" + + _ "github.com/go-sql-driver/mysql" + "github.com/segmentfault/pacman/cache" + "github.com/segmentfault/pacman/contrib/cache/memory" + "github.com/segmentfault/pacman/log" + "xorm.io/core" + "xorm.io/xorm" +) + +// Data data +type Data struct { + DB *xorm.Engine + Cache cache.Cache +} + +// NewData new data instance +func NewData(db *xorm.Engine, cache cache.Cache) (*Data, func(), error) { + cleanup := func() { + log.Info("closing the data resources") + db.Close() + } + return &Data{DB: db, Cache: cache}, cleanup, nil +} + +// NewDB new database instance +func NewDB(debug bool, dataConf *Database) *xorm.Engine { + engine, err := xorm.NewEngine("mysql", dataConf.Connection) + if err != nil { + panic(err) + } + + if err = engine.Ping(); err != nil { + panic(err) + } + + if debug { + engine.ShowSQL(true) + } + + if dataConf.MaxIdleConn > 0 { + engine.SetMaxIdleConns(dataConf.MaxIdleConn) + } + if dataConf.MaxOpenConn > 0 { + engine.SetMaxOpenConns(dataConf.MaxOpenConn) + } + if dataConf.ConnMaxLifeTime > 0 { + engine.SetConnMaxLifetime(time.Duration(dataConf.ConnMaxLifeTime) * time.Second) + } + engine.SetColumnMapper(core.GonicMapper{}) + return engine +} + +// NewCache new cache instance +func NewCache(c *CacheConf) (cache.Cache, func(), error) { + // TODO What cache type should be initialized according to the configuration file + memCache := memory.NewCache() + + if len(c.FilePath) > 0 { + log.Infof("try to load cache file from %s", c.FilePath) + if err := memory.Load(memCache, c.FilePath); err != nil { + log.Warn(err) + } + go func() { + ticker := time.Tick(time.Minute) + for range ticker { + if err := memory.Save(memCache, c.FilePath); err != nil { + log.Warn(err) + } + } + }() + } + cleanup := func() { + log.Infof("try to save cache file to %s", c.FilePath) + if err := memory.Save(memCache, c.FilePath); err != nil { + log.Warn(err) + } + } + return memCache, cleanup, nil +} diff --git a/internal/base/handler/handler.go b/internal/base/handler/handler.go new file mode 100644 index 00000000..774db9ab --- /dev/null +++ b/internal/base/handler/handler.go @@ -0,0 +1,59 @@ +package handler + +import ( + "errors" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/base/validator" + myErrors "github.com/segmentfault/pacman/errors" + "github.com/segmentfault/pacman/log" +) + +// HandleResponse Handle response body +func HandleResponse(ctx *gin.Context, err error, data interface{}) { + lang := GetLang(ctx) + // no error + if err == nil { + ctx.JSON(http.StatusOK, NewRespBodyData(http.StatusOK, reason.Success, data).TrMsg(lang)) + return + } + + var myErr *myErrors.Error + // unknown error + if !errors.As(err, &myErr) { + log.Error(err, "\n", myErrors.LogStack(2, 5)) + ctx.JSON(http.StatusInternalServerError, NewRespBody( + http.StatusInternalServerError, reason.UnknownError).TrMsg(lang)) + return + } + + // log internal server error + if myErrors.IsInternalServer(myErr) { + log.Error(myErr) + } + + respBody := NewRespBodyFromError(myErr).TrMsg(lang) + if data != nil { + respBody.Data = data + } + ctx.JSON(myErr.Code, respBody) + return +} + +// BindAndCheck bind request and check +func BindAndCheck(ctx *gin.Context, data interface{}) bool { + if err := ctx.ShouldBind(data); err != nil { + log.Errorf("http_handle BindAndCheck fail, %s", err.Error()) + HandleResponse(ctx, myErrors.New(http.StatusBadRequest, reason.RequestFormatError), nil) + return true + } + + errField, err := validator.GetValidatorByLang(GetLang(ctx).Abbr()).Check(data) + if err != nil { + HandleResponse(ctx, myErrors.New(http.StatusBadRequest, reason.RequestFormatError).WithMsg(err.Error()), errField) + return true + } + return false +} diff --git a/internal/base/handler/lang.go b/internal/base/handler/lang.go new file mode 100644 index 00000000..38bbccf1 --- /dev/null +++ b/internal/base/handler/lang.go @@ -0,0 +1,19 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/segmentfault/pacman/i18n" +) + +// GetLang get language from header +func GetLang(ctx *gin.Context) i18n.Language { + acceptLanguage := ctx.GetHeader("Accept-Language") + switch i18n.Language(acceptLanguage) { + case i18n.LanguageChinese: + return i18n.LanguageChinese + case i18n.LanguageEnglish: + return i18n.LanguageEnglish + default: + return i18n.DefaultLang + } +} diff --git a/internal/base/handler/response.go b/internal/base/handler/response.go new file mode 100644 index 00000000..e79fbce7 --- /dev/null +++ b/internal/base/handler/response.go @@ -0,0 +1,53 @@ +package handler + +import ( + "github.com/segmentfault/answer/internal/base/translator" + "github.com/segmentfault/pacman/errors" + "github.com/segmentfault/pacman/i18n" +) + +// RespBody response body. +type RespBody struct { + // http code + Code int `json:"code"` + // reason key + Reason string `json:"reason"` + // response message + Message string `json:"msg"` + // response data + Data interface{} `json:"data"` +} + +// TrMsg translate the reason cause as a message +func (r *RespBody) TrMsg(lang i18n.Language) *RespBody { + if len(r.Message) == 0 { + r.Message = translator.GlobalTrans.Tr(lang, r.Reason) + } + return r +} + +// NewRespBody new response body +func NewRespBody(code int, reason string) *RespBody { + return &RespBody{ + Code: code, + Reason: reason, + } +} + +// NewRespBodyFromError new response body from error +func NewRespBodyFromError(e *errors.Error) *RespBody { + return &RespBody{ + Code: e.Code, + Reason: e.Reason, + Message: e.Message, + } +} + +// NewRespBodyData new response body with data +func NewRespBodyData(code int, reason string, data interface{}) *RespBody { + return &RespBody{ + Code: code, + Reason: reason, + Data: data, + } +} diff --git a/internal/base/middleware/auth.go b/internal/base/middleware/auth.go new file mode 100644 index 00000000..c3fd6dc0 --- /dev/null +++ b/internal/base/middleware/auth.go @@ -0,0 +1,177 @@ +package middleware + +import ( + "strings" + + "github.com/davecgh/go-spew/spew" + "github.com/segmentfault/answer/internal/schema" + + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/service/auth" + "github.com/segmentfault/answer/pkg/converter" + "github.com/segmentfault/pacman/errors" +) + +var ( + ctxUuidKey = "ctxUuidKey" +) + +// AuthUserMiddleware auth user middleware +type AuthUserMiddleware struct { + authService *auth.AuthService +} + +// NewAuthUserMiddleware new auth user middleware +func NewAuthUserMiddleware(authService *auth.AuthService) *AuthUserMiddleware { + return &AuthUserMiddleware{ + authService: authService, + } +} + +// Auth get token and auth user, set user info to context if user is already login +func (am *AuthUserMiddleware) Auth() gin.HandlerFunc { + return func(ctx *gin.Context) { + token := ExtractToken(ctx) + if len(token) == 0 { + ctx.Next() + return + } + if token == "888" { + userInfo := &entity.UserCacheInfo{} + userInfo.UserID = "2" + spew.Dump("开发环境 Auth", userInfo) + ctx.Set(ctxUuidKey, userInfo) + } else { + userInfo, err := am.authService.GetUserCacheInfo(ctx, token) + if err != nil { + ctx.Next() + return + } + if userInfo != nil { + ctx.Set(ctxUuidKey, userInfo) + } + } + ctx.Next() + } +} + +// MustAuth auth user info. If the user does not log in, an unauthenticated error is displayed +func (am *AuthUserMiddleware) MustAuth() gin.HandlerFunc { + return func(ctx *gin.Context) { + token := ExtractToken(ctx) + if len(token) == 0 { + handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil) + ctx.Abort() + return + } + if token == "888" { + userInfo := &entity.UserCacheInfo{} + userInfo.UserID = "2" + spew.Dump("开发环境 MustAuth", userInfo) + ctx.Set(ctxUuidKey, userInfo) + } else { + userInfo, err := am.authService.GetUserCacheInfo(ctx, token) + spew.Dump(userInfo, err) + if err != nil || userInfo == nil { + handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil) + ctx.Abort() + return + } + if userInfo.EmailStatus != entity.EmailStatusAvailable { + handler.HandleResponse(ctx, errors.Forbidden(reason.EmailNeedToBeVerified), + &schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeInactive}) + ctx.Abort() + return + } + if userInfo.UserStatus == entity.UserStatusSuspended { + handler.HandleResponse(ctx, errors.Forbidden(reason.UserSuspended), + &schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeUserSuspended}) + ctx.Abort() + return + } + if userInfo.UserStatus == entity.UserStatusDeleted { + handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil) + ctx.Abort() + return + } + ctx.Set(ctxUuidKey, userInfo) + } + ctx.Next() + } +} + +func (am *AuthUserMiddleware) CmsAuth() gin.HandlerFunc { + return func(ctx *gin.Context) { + token := ExtractToken(ctx) + if len(token) == 0 { + handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil) + ctx.Abort() + return + } + if token == "888" { + userInfo := &entity.UserCacheInfo{} + userInfo.UserID = "2" + spew.Dump("开发环境 CmsAuth", userInfo) + ctx.Set(ctxUuidKey, userInfo) + } else { + userInfo, err := am.authService.GetCmsUserCacheInfo(ctx, token) + if err != nil { + handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil) + ctx.Abort() + return + } + if userInfo != nil { + if userInfo.UserStatus == entity.UserStatusDeleted { + handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil) + ctx.Abort() + return + } + ctx.Set(ctxUuidKey, userInfo) + } + } + ctx.Next() + } +} + +// GetLoginUserIDFromContext get user id from context +func GetLoginUserIDFromContext(ctx *gin.Context) (userID string) { + userInfo, exist := ctx.Get(ctxUuidKey) + if !exist { + return "" + } + u, ok := userInfo.(*entity.UserCacheInfo) + if !ok { + return "" + } + return u.UserID +} + +// GetUserInfoFromContext get user info from context +func GetUserInfoFromContext(ctx *gin.Context) (u *entity.UserCacheInfo) { + userInfo, exist := ctx.Get(ctxUuidKey) + if !exist { + return nil + } + u, ok := userInfo.(*entity.UserCacheInfo) + if !ok { + return nil + } + return u +} + +func GetLoginUserIDInt64FromContext(ctx *gin.Context) (userID int64) { + userIDStr := GetLoginUserIDFromContext(ctx) + return converter.StringToInt64(userIDStr) +} + +// ExtractToken extract token from context +func ExtractToken(ctx *gin.Context) (token string) { + token = ctx.GetHeader("Authorization") + if len(token) == 0 { + token = ctx.Query("Authorization") + } + return strings.TrimPrefix(token, "Bearer ") +} diff --git a/internal/base/middleware/provider.go b/internal/base/middleware/provider.go new file mode 100644 index 00000000..a07318d9 --- /dev/null +++ b/internal/base/middleware/provider.go @@ -0,0 +1,10 @@ +package middleware + +import ( + "github.com/google/wire" +) + +// ProviderSetMiddleware is providers. +var ProviderSetMiddleware = wire.NewSet( + NewAuthUserMiddleware, +) diff --git a/internal/base/pager/pager.go b/internal/base/pager/pager.go new file mode 100644 index 00000000..d3f57d7d --- /dev/null +++ b/internal/base/pager/pager.go @@ -0,0 +1,21 @@ +package pager + +import ( + "errors" + "reflect" + + "xorm.io/xorm" +) + +// Help xorm page helper +func Help(page, pageSize int, rowsSlicePtr interface{}, rowElement interface{}, session *xorm.Session) (total int64, err error) { + page, pageSize = ValPageAndPageSize(page, pageSize) + + sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) + if sliceValue.Kind() != reflect.Slice { + return 0, errors.New("not a slice") + } + + startNum := (page - 1) * pageSize + return session.Limit(pageSize, startNum).FindAndCount(rowsSlicePtr, rowElement) +} diff --git a/internal/base/pager/pagination.go b/internal/base/pager/pagination.go new file mode 100644 index 00000000..239c2945 --- /dev/null +++ b/internal/base/pager/pagination.go @@ -0,0 +1,45 @@ +package pager + +import ( + "reflect" +) + +// PageModel page model +type PageModel struct { + Count int64 `json:"count"` + List interface{} `json:"list"` +} + +// PageCond page condition +type PageCond struct { + Page int + PageSize int +} + +// NewPageModel new page model +func NewPageModel(page, pageSize int, totalRecords int64, records interface{}) *PageModel { + sliceValue := reflect.Indirect(reflect.ValueOf(records)) + if sliceValue.Kind() != reflect.Slice { + panic("not a slice") + } + + if totalRecords < 0 { + totalRecords = 0 + } + + return &PageModel{ + Count: totalRecords, + List: records, + } +} + +// ValPageAndPageSize validate page pageSize +func ValPageAndPageSize(page, pageSize int) (int, int) { + if page <= 0 { + page = 1 + } + if pageSize <= 0 { + pageSize = 10 + } + return page, pageSize +} diff --git a/internal/base/reason/reason.go b/internal/base/reason/reason.go new file mode 100644 index 00000000..b3e0c8b5 --- /dev/null +++ b/internal/base/reason/reason.go @@ -0,0 +1,38 @@ +package reason + +const ( + // Success . + Success = "base.success" + // UnknownError unknown error + UnknownError = "base.unknown" + // RequestFormatError request format error + RequestFormatError = "base.request_format_error" + // UnauthorizedError unauthorized error + UnauthorizedError = "base.unauthorized_error" + // DatabaseError database error + DatabaseError = "base.database_error" +) + +const ( + EmailOrPasswordWrong = "error.user.email_or_password_wrong" + CommentNotFound = "error.comment.not_found" + QuestionNotFound = "error.question.not_found" + AnswerNotFound = "error.answer.not_found" + CommentEditWithoutPermission = "error.comment.edit_without_permission" + DisallowVote = "error.object.disallow_vote" + DisallowFollow = "error.object.disallow_follow" + DisallowVoteYourSelf = "error.object.disallow_vote_your_self" + CaptchaVerificationFailed = "error.object.captcha_verification_failed" + UserNotFound = "error.user.not_found" + EmailDuplicate = "error.email.duplicate" + EmailVerifyUrlExpired = "error.email.verify_url_expired" + EmailNeedToBeVerified = "error.email.need_to_be_verified" + UserSuspended = "error.user.suspended" + ObjectNotFound = "error.object.not_found" + TagNotFound = "error.tag.not_found" + RankFailToMeetTheCondition = "error.rank.fail_to_meet_the_condition" + ThemeNotFound = "error.theme.not_found" + LangNotFound = "error.lang.not_found" + ReportHandleFailed = "error.report.handle_failed" + ReportNotFound = "error.report.not_found" +) diff --git a/internal/base/server/config.go b/internal/base/server/config.go new file mode 100644 index 00000000..54cfbf9b --- /dev/null +++ b/internal/base/server/config.go @@ -0,0 +1,6 @@ +package server + +// HTTP http config +type HTTP struct { + Addr string `json:"addr" mapstructure:"addr"` +} diff --git a/internal/base/server/http.go b/internal/base/server/http.go new file mode 100644 index 00000000..b021fe4a --- /dev/null +++ b/internal/base/server/http.go @@ -0,0 +1,46 @@ +package server + +import ( + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/middleware" + "github.com/segmentfault/answer/internal/router" +) + +// NewHTTPServer new http server. +func NewHTTPServer(debug bool, + staticRouter *router.StaticRouter, + answerRouter *router.AnswerAPIRouter, + swaggerRouter *router.SwaggerRouter, + viewRouter *router.ViewRouter, + authUserMiddleware *middleware.AuthUserMiddleware) *gin.Engine { + + if debug { + gin.SetMode(gin.DebugMode) + } else { + gin.SetMode(gin.ReleaseMode) + } + r := gin.New() + r.GET("/healthz", func(ctx *gin.Context) { ctx.String(200, "OK") }) + + viewRouter.RegisterViewRouter(r) + + rootGroup := r.Group("") + swaggerRouter.Register(rootGroup) + staticRouter.RegisterStaticRouter(rootGroup) + + // register api that no need to login + unAuthV1 := r.Group("/answer/api/v1") + unAuthV1.Use(authUserMiddleware.Auth()) + answerRouter.RegisterUnAuthAnswerAPIRouter(unAuthV1) + + // register api that must be authenticated + authV1 := r.Group("/answer/api/v1") + authV1.Use(authUserMiddleware.MustAuth()) + answerRouter.RegisterAnswerAPIRouter(authV1) + + cmsauthV1 := r.Group("/answer/admin/api") + cmsauthV1.Use(authUserMiddleware.CmsAuth()) + answerRouter.RegisterAnswerCmsAPIRouter(cmsauthV1) + + return r +} diff --git a/internal/base/server/provider.go b/internal/base/server/provider.go new file mode 100644 index 00000000..96f42a3b --- /dev/null +++ b/internal/base/server/provider.go @@ -0,0 +1,6 @@ +package server + +import "github.com/google/wire" + +// ProviderSetServer is providers. +var ProviderSetServer = wire.NewSet(NewHTTPServer) diff --git a/internal/base/translator/config.go b/internal/base/translator/config.go new file mode 100644 index 00000000..a00b5218 --- /dev/null +++ b/internal/base/translator/config.go @@ -0,0 +1,6 @@ +package translator + +// I18n i18n config +type I18n struct { + BundleDir string `json:"bundle_dir" mapstructure:"bundle_dir"` +} diff --git a/internal/base/translator/provider.go b/internal/base/translator/provider.go new file mode 100644 index 00000000..0c0bcac6 --- /dev/null +++ b/internal/base/translator/provider.go @@ -0,0 +1,17 @@ +package translator + +import ( + "github.com/google/wire" + myTran "github.com/segmentfault/pacman/contrib/i18n" + "github.com/segmentfault/pacman/i18n" +) + +// ProviderSet is providers. +var ProviderSet = wire.NewSet(NewTranslator) +var GlobalTrans i18n.Translator + +// NewTranslator new a translator +func NewTranslator(c *I18n) (tr i18n.Translator, err error) { + GlobalTrans, err = myTran.NewTranslator(c.BundleDir) + return GlobalTrans, err +} diff --git a/internal/base/validator/validator.go b/internal/base/validator/validator.go new file mode 100644 index 00000000..94a515e5 --- /dev/null +++ b/internal/base/validator/validator.go @@ -0,0 +1,117 @@ +package validator + +import ( + "errors" + "reflect" + + "github.com/go-playground/locales" + english "github.com/go-playground/locales/en" + zhongwen "github.com/go-playground/locales/zh" + "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" + "github.com/go-playground/validator/v10/translations/en" + "github.com/go-playground/validator/v10/translations/zh" + "github.com/segmentfault/answer/internal/base/translator" + "github.com/segmentfault/pacman/i18n" +) + +// MyValidator my validator +type MyValidator struct { + Validate *validator.Validate + Tran ut.Translator + Lang i18n.Language +} + +// ErrorField error field +type ErrorField struct { + Key string `json:"key"` + Value string `json:"value"` +} + +var ( + // GlobalValidatorMapping is a mapping from validator to translator used + GlobalValidatorMapping = make(map[string]*MyValidator, 0) +) + +func init() { + zhTran, zhVal := getTran(zhongwen.New(), i18n.LanguageChinese.Abbr()), createDefaultValidator(i18n.LanguageChinese) + if err := zh.RegisterDefaultTranslations(zhVal, zhTran); err != nil { + panic(err) + } + GlobalValidatorMapping[i18n.LanguageChinese.Abbr()] = &MyValidator{Validate: zhVal, Tran: zhTran, Lang: i18n.LanguageChinese} + + enTran, enVal := getTran(english.New(), i18n.LanguageEnglish.Abbr()), createDefaultValidator(i18n.LanguageEnglish) + if err := en.RegisterDefaultTranslations(enVal, enTran); err != nil { + panic(err) + } + GlobalValidatorMapping[i18n.LanguageEnglish.Abbr()] = &MyValidator{Validate: enVal, Tran: enTran, Lang: i18n.LanguageEnglish} +} + +func getTran(lo locales.Translator, la string) ut.Translator { + tran, ok := ut.New(lo, lo).GetTranslator(la) + if !ok { + panic(ok) + } + return tran +} + +func createDefaultValidator(la i18n.Language) *validator.Validate { + validate := validator.New() + validate.RegisterTagNameFunc(func(fld reflect.StructField) (res string) { + defer func() { + if len(res) > 0 { + res = translator.GlobalTrans.Tr(la, res) + } + }() + if jsonTag := fld.Tag.Get("json"); len(jsonTag) > 0 { + if jsonTag == "-" { + return "" + } + return jsonTag + } + if formTag := fld.Tag.Get("form"); len(formTag) > 0 { + return formTag + } + return fld.Name + }) + return validate +} + +func GetValidatorByLang(la string) *MyValidator { + if GlobalValidatorMapping[la] != nil { + return GlobalValidatorMapping[la] + } + return GlobalValidatorMapping[i18n.DefaultLang.Abbr()] +} + +// Check / +func (m *MyValidator) Check(value interface{}) (errField *ErrorField, err error) { + err = m.Validate.Struct(value) + if err != nil { + var valErrors validator.ValidationErrors + if !errors.As(err, &valErrors) { + return nil, errors.New("validate check exception") + } + + for _, fieldError := range valErrors { + errField = &ErrorField{ + Key: translator.GlobalTrans.Tr(m.Lang, fieldError.Field()), + Value: fieldError.Translate(m.Tran), + } + return errField, errors.New(fieldError.Translate(m.Tran)) + } + } + + if v, ok := value.(Checker); ok { + errField, err = v.Check() + if err != nil { + return errField, err + } + } + return nil, nil +} + +// Checker . +type Checker interface { + Check() (errField *ErrorField, err error) +} diff --git a/internal/controller/answer_controller.go b/internal/controller/answer_controller.go new file mode 100644 index 00000000..1389b402 --- /dev/null +++ b/internal/controller/answer_controller.go @@ -0,0 +1,244 @@ +package controller + +import ( + "context" + "fmt" + + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/base/middleware" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service" + "github.com/segmentfault/answer/internal/service/rank" + "github.com/segmentfault/pacman/errors" +) + +// AnswerController answer controller +type AnswerController struct { + answerService *service.AnswerService + rankService *rank.RankService +} + +// NewAnswerController new controller +func NewAnswerController(answerService *service.AnswerService, rankService *rank.RankService) *AnswerController { + return &AnswerController{answerService: answerService, rankService: rankService} +} + +// RemoveAnswer delete answer +// @Summary delete answer +// @Description delete answer +// @Tags api-answer +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.RemoveAnswerReq true "answer" +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/answer [delete] +func (ac *AnswerController) RemoveAnswer(ctx *gin.Context) { + req := &schema.RemoveAnswerReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + if can, err := ac.rankService.CheckRankPermission(ctx, req.UserID, rank.AnswerDeleteRank); err != nil || !can { + handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition)) + return + } + + err := ac.answerService.RemoveAnswer(ctx, req.ID) + handler.HandleResponse(ctx, err, nil) +} + +// Get godoc +// @Summary Get Answer +// @Description Get Answer +// @Tags api-answer +// @Accept json +// @Produce json +// @Param id query string true "Answer TagID" default(1) +// @Router /answer/api/v1/answer/info [get] +// @Success 200 {string} string "" +func (ac *AnswerController) Get(ctx *gin.Context) { + id := ctx.Query("id") + userId := middleware.GetLoginUserIDFromContext(ctx) + + info, questionInfo, has, err := ac.answerService.Get(ctx, id, userId) + if err != nil { + handler.HandleResponse(ctx, err, gin.H{}) + return + } + if !has { + handler.HandleResponse(ctx, fmt.Errorf(""), gin.H{}) + return + } + handler.HandleResponse(ctx, err, gin.H{ + "info": info, + "question": questionInfo, + }) +} + +// Add godoc +// @Summary Insert Answer +// @Description Insert Answer +// @Tags api-answer +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.AnswerAddReq true "AnswerAddReq" +// @Success 200 {string} string "" +// @Router /answer/api/v1/answer [post] +func (ac *AnswerController) Add(ctx *gin.Context) { + req := &schema.AnswerAddReq{} + if handler.BindAndCheck(ctx, req) { + return + } + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + + if can, err := ac.rankService.CheckRankPermission(ctx, req.UserID, rank.AnswerAddRank); err != nil || !can { + handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition)) + return + } + + answerId, err := ac.answerService.Insert(ctx, req) + if err != nil { + handler.HandleResponse(ctx, err, nil) + return + } + info, questionInfo, has, err := ac.answerService.Get(ctx, answerId, req.UserID) + if err != nil { + handler.HandleResponse(ctx, err, nil) + return + } + if !has { + //todo !has + handler.HandleResponse(ctx, nil, nil) + return + } + handler.HandleResponse(ctx, nil, gin.H{ + "info": info, + "question": questionInfo, + }) + +} + +// Update godoc +// @Summary Update Answer +// @Description Update Answer +// @Tags api-answer +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.AnswerUpdateReq true "AnswerUpdateReq" +// @Success 200 {string} string "" +// @Router /answer/api/v1/answer [put] +func (ac *AnswerController) Update(ctx *gin.Context) { + req := &schema.AnswerUpdateReq{} + if handler.BindAndCheck(ctx, req) { + return + } + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + + if can, err := ac.rankService.CheckRankPermission(ctx, req.UserID, rank.AnswerEditRank); err != nil || !can { + handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition)) + return + } + + _, err := ac.answerService.Update(ctx, req) + if err != nil { + handler.HandleResponse(ctx, err, nil) + return + } + info, questionInfo, has, err := ac.answerService.Get(ctx, req.ID, req.UserID) + if err != nil { + handler.HandleResponse(ctx, err, nil) + return + } + if !has { + //todo !has + handler.HandleResponse(ctx, nil, nil) + return + } + handler.HandleResponse(ctx, nil, gin.H{ + "info": info, + "question": questionInfo, + }) +} + +// AnswerList godoc +// @Summary AnswerList +// @Description AnswerList
order (default or updated) +// @Tags api-answer +// @Security ApiKeyAuth +// @Accept json +// @Produce json +// @Param data body schema.AnswerList true "AnswerList" +// @Success 200 {string} string "" +// @Router /answer/api/v1/answer/list [post] +func (ac *AnswerController) AnswerList(c *gin.Context) { + input := new(schema.AnswerList) + err := c.BindJSON(input) + if err != nil { + handler.HandleResponse(c, err, nil) + return + } + ctx := context.Background() + userId := middleware.GetLoginUserIDFromContext(c) + input.LoginUserID = userId + list, count, err := ac.answerService.SearchList(ctx, input) + if err != nil { + handler.HandleResponse(c, err, nil) + return + } + handler.HandleResponse(c, nil, gin.H{ + "list": list, + "count": count, + }) +} + +// Adopted godoc +// @Summary Adopted +// @Description Adopted +// @Tags api-answer +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.AnswerAdoptedReq true "AnswerAdoptedReq" +// @Success 200 {string} string "" +// @Router /answer/api/v1/answer/acceptance [post] +func (ac *AnswerController) Adopted(ctx *gin.Context) { + req := &schema.AnswerAdoptedReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + if can, err := ac.rankService.CheckRankPermission(ctx, req.UserID, rank.AnswerAcceptRank); err != nil || !can { + handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition)) + return + } + + err := ac.answerService.UpdateAdopted(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + +// AdminSetAnswerStatus godoc +// @Summary AdminSetAnswerStatus +// @Description Status:[available,deleted] +// @Tags admin +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body entity.AdminSetAnswerStatusRequest true "AdminSetAnswerStatusRequest" +// @Router /answer/admin/api/answer/status [put] +// @Success 200 {object} handler.RespBody +func (ac *AnswerController) AdminSetAnswerStatus(ctx *gin.Context) { + req := &entity.AdminSetAnswerStatusRequest{} + if handler.BindAndCheck(ctx, req) { + return + } + err := ac.answerService.AdminSetAnswerStatus(ctx, req.AnswerID, req.StatusStr) + handler.HandleResponse(ctx, err, gin.H{}) +} diff --git a/internal/controller/collection_controller.go b/internal/controller/collection_controller.go new file mode 100644 index 00000000..a85e9324 --- /dev/null +++ b/internal/controller/collection_controller.go @@ -0,0 +1,56 @@ +package controller + +import ( + "github.com/gin-gonic/gin" + "github.com/jinzhu/copier" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/base/middleware" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service" + "github.com/segmentfault/answer/pkg/converter" + "github.com/segmentfault/pacman/errors" +) + +// CollectionController collection controller +type CollectionController struct { + collectionService *service.CollectionService +} + +// NewCollectionController new controller +func NewCollectionController(collectionService *service.CollectionService) *CollectionController { + return &CollectionController{collectionService: collectionService} +} + +// CollectionSwitch add collection +// @Summary add collection +// @Description add collection +// @Tags Collection +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.CollectionSwitchReq true "collection" +// @Success 200 {object} handler.RespBody{data=schema.CollectionSwitchResp} +// @Router /answer/api/v1/collection/switch [post] +func (cc *CollectionController) CollectionSwitch(ctx *gin.Context) { + req := &schema.CollectionSwitchReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + dto := &schema.CollectionSwitchDTO{} + _ = copier.Copy(dto, req) + + dto.UserID = middleware.GetLoginUserIDFromContext(ctx) + + if converter.StringToInt64(req.ObjectID) < 1 { + return + } + if converter.StringToInt64(dto.UserID) < 1 { + handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil) + return + } + + resp, err := cc.collectionService.CollectionSwitch(ctx, dto) + handler.HandleResponse(ctx, err, resp) +} diff --git a/internal/controller/comment_controller.go b/internal/controller/comment_controller.go new file mode 100644 index 00000000..53df3f2f --- /dev/null +++ b/internal/controller/comment_controller.go @@ -0,0 +1,168 @@ +package controller + +import ( + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/base/middleware" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/comment" + "github.com/segmentfault/answer/internal/service/rank" + "github.com/segmentfault/pacman/errors" +) + +// CommentController comment controller +type CommentController struct { + commentService *comment.CommentService + rankService *rank.RankService +} + +// NewCommentController new controller +func NewCommentController( + commentService *comment.CommentService, + rankService *rank.RankService) *CommentController { + return &CommentController{commentService: commentService, rankService: rankService} +} + +// AddComment add comment +// @Summary add comment +// @Description add comment +// @Tags Comment +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.AddCommentReq true "comment" +// @Success 200 {object} handler.RespBody{data=schema.GetCommentResp} +// @Router /answer/api/v1/comment [post] +func (cc *CommentController) AddComment(ctx *gin.Context) { + req := &schema.AddCommentReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + if can, err := cc.rankService.CheckRankPermission(ctx, req.UserID, rank.CommentAddRank); err != nil || !can { + handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition)) + return + } + + resp, err := cc.commentService.AddComment(ctx, req) + handler.HandleResponse(ctx, err, resp) +} + +// RemoveComment remove comment +// @Summary remove comment +// @Description remove comment +// @Tags Comment +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.RemoveCommentReq true "comment" +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/comment [delete] +func (cc *CommentController) RemoveComment(ctx *gin.Context) { + req := &schema.RemoveCommentReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + if can, err := cc.rankService.CheckRankPermission(ctx, req.UserID, rank.CommentDeleteRank); err != nil || !can { + handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition)) + return + } + + err := cc.commentService.RemoveComment(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + +// UpdateComment update comment +// @Summary update comment +// @Description update comment +// @Tags Comment +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.UpdateCommentReq true "comment" +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/comment [put] +func (cc *CommentController) UpdateComment(ctx *gin.Context) { + req := &schema.UpdateCommentReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + if can, err := cc.rankService.CheckRankPermission(ctx, req.UserID, rank.CommentEditRank); err != nil || !can { + handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition)) + return + } + + err := cc.commentService.UpdateComment(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + +// GetCommentWithPage get comment page +// @Summary get comment page +// @Description get comment page +// @Tags Comment +// @Produce json +// @Param page query int false "page" +// @Param page_size query int false "page size" +// @Param object_id query string true "object id" +// @Param query_cond query string false "query condition" Enums(vote) +// @Success 200 {object} handler.RespBody{data=pager.PageModel{list=[]schema.GetCommentResp}} +// @Router /answer/api/v1/comment/page [get] +func (cc *CommentController) GetCommentWithPage(ctx *gin.Context) { + req := &schema.GetCommentWithPageReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + + resp, err := cc.commentService.GetCommentWithPage(ctx, req) + handler.HandleResponse(ctx, err, resp) +} + +// GetCommentPersonalWithPage user personal comment list +// @Summary user personal comment list +// @Description user personal comment list +// @Tags Comment +// @Produce json +// @Param page query int false "page" +// @Param page_size query int false "page size" +// @Param username query string false "username" +// @Success 200 {object} handler.RespBody{data=pager.PageModel{list=[]schema.GetCommentPersonalWithPageResp}} +// @Router /answer/api/v1/personal/comment/page [get] +func (cc *CommentController) GetCommentPersonalWithPage(ctx *gin.Context) { + req := &schema.GetCommentPersonalWithPageReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + + resp, err := cc.commentService.GetCommentPersonalWithPage(ctx, req) + handler.HandleResponse(ctx, err, resp) +} + +// GetComment godoc +// @Summary get comment by id +// @Description get comment by id +// @Tags Comment +// @Produce json +// @Param id query string true "id" +// @Success 200 {object} handler.RespBody{data=pager.PageModel{list=[]schema.GetCommentResp}} +// @Router /answer/api/v1/comment [get] +func (cc *CommentController) GetComment(ctx *gin.Context) { + req := &schema.GetCommentReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + + resp, err := cc.commentService.GetComment(ctx, req) + handler.HandleResponse(ctx, err, resp) +} diff --git a/internal/controller/controller.go b/internal/controller/controller.go new file mode 100644 index 00000000..65776043 --- /dev/null +++ b/internal/controller/controller.go @@ -0,0 +1,23 @@ +package controller + +import "github.com/google/wire" + +// ProviderSetController is controller providers. +var ProviderSetController = wire.NewSet( + NewLangController, + NewCommentController, + NewReportController, + NewVoteController, + NewTagController, + NewFollowController, + NewCollectionController, + NewUserController, + NewQuestionController, + NewAnswerController, + NewSearchController, + NewRevisionController, + NewRankController, + NewReasonController, + NewNotificationController, + NewSiteinfoController, +) diff --git a/internal/controller/follow_controller.go b/internal/controller/follow_controller.go new file mode 100644 index 00000000..f9a9bf2e --- /dev/null +++ b/internal/controller/follow_controller.go @@ -0,0 +1,70 @@ +package controller + +import ( + "github.com/gin-gonic/gin" + "github.com/jinzhu/copier" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/base/middleware" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/follow" +) + +// FollowController activity controller +type FollowController struct { + followService *follow.FollowService +} + +// NewFollowController new controller +func NewFollowController(followService *follow.FollowService) *FollowController { + return &FollowController{followService: followService} +} + +// Follow godoc +// @Summary follow object or cancel follow operation +// @Description follow object or cancel follow operation +// @Tags Activity +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.FollowReq true "follow" +// @Success 200 {object} handler.RespBody{data=schema.FollowResp} +// @Router /answer/api/v1/follow [post] +func (fc *FollowController) Follow(ctx *gin.Context) { + req := &schema.FollowReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + dto := &schema.FollowDTO{} + _ = copier.Copy(dto, req) + dto.UserID = middleware.GetLoginUserIDFromContext(ctx) + + resp, err := fc.followService.Follow(ctx, dto) + if err != nil { + handler.HandleResponse(ctx, err, schema.ErrTypeToast) + } else { + handler.HandleResponse(ctx, err, resp) + } +} + +// UpdateFollowTags update user follow tags +// @Summary update user follow tags +// @Description update user follow tags +// @Tags Activity +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.UpdateFollowTagsReq true "follow" +// @Success 200 {object} handler.RespBody{} +// @Router /answer/api/v1/follow/tags [put] +func (fc *FollowController) UpdateFollowTags(ctx *gin.Context) { + req := &schema.UpdateFollowTagsReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + + err := fc.followService.UpdateFollowTags(ctx, req) + handler.HandleResponse(ctx, err, nil) +} diff --git a/internal/controller/lang_controller.go b/internal/controller/lang_controller.go new file mode 100644 index 00000000..36bc37d7 --- /dev/null +++ b/internal/controller/lang_controller.go @@ -0,0 +1,47 @@ +package controller + +import ( + "encoding/json" + + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/pacman/i18n" +) + +type LangController struct { + translator i18n.Translator +} + +// NewLangController new language controller. +func NewLangController(tr i18n.Translator) *LangController { + return &LangController{translator: tr} +} + +// GetLangMapping get language config mapping +// @Summary get language config mapping +// @Description get language config mapping +// @Tags Lang +// @Param Accept-Language header string true "Accept-Language" +// @Produce json +// @Success 200 {object} handler.RespBody{} +// @Router /answer/api/v1/language/config [get] +func (u *LangController) GetLangMapping(ctx *gin.Context) { + data, _ := u.translator.Dump(handler.GetLang(ctx)) + var resp map[string]any + _ = json.Unmarshal(data, &resp) + handler.HandleResponse(ctx, nil, resp) +} + +// GetLangOptions Get language options +// @Summary Get language options +// @Description Get language options +// @Security ApiKeyAuth +// @Tags Lang +// @Produce json +// @Success 200 {object} handler.RespBody{} +// @Router /answer/api/v1/language/options [get] +// @Router /answer/admin/api/language/options [get] +func (u *LangController) GetLangOptions(ctx *gin.Context) { + handler.HandleResponse(ctx, nil, schema.GetLangOptions) +} diff --git a/internal/controller/notification_controller.go b/internal/controller/notification_controller.go new file mode 100644 index 00000000..31770c7e --- /dev/null +++ b/internal/controller/notification_controller.go @@ -0,0 +1,119 @@ +package controller + +import ( + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/base/middleware" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/notification" +) + +// NotificationController notification controller +type NotificationController struct { + notificationService *notification.NotificationService +} + +// NewNotificationController new controller +func NewNotificationController(notificationService *notification.NotificationService) *NotificationController { + return &NotificationController{notificationService: notificationService} +} + +// GetRedDot +// @Summary GetRedDot +// @Description GetRedDot +// @Tags Notification +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/notification/status [get] +func (nc *NotificationController) GetRedDot(ctx *gin.Context) { + userID := middleware.GetLoginUserIDFromContext(ctx) + RedDot, err := nc.notificationService.GetRedDot(ctx, userID) + handler.HandleResponse(ctx, err, RedDot) +} + +// ClearRedDot +// @Summary DelRedDot +// @Description DelRedDot +// @Tags Notification +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.NotificationClearRequest true "NotificationClearRequest" +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/notification/status [put] +func (nc *NotificationController) ClearRedDot(ctx *gin.Context) { + req := &schema.NotificationClearRequest{} + if handler.BindAndCheck(ctx, req) { + return + } + userID := middleware.GetLoginUserIDFromContext(ctx) + RedDot, err := nc.notificationService.ClearRedDot(ctx, userID, req.TypeStr) + handler.HandleResponse(ctx, err, RedDot) +} + +// ClearUnRead +// @Summary ClearUnRead +// @Description ClearUnRead +// @Tags Notification +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.NotificationClearRequest true "NotificationClearRequest" +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/notification/read/state/all [put] +func (nc *NotificationController) ClearUnRead(ctx *gin.Context) { + req := &schema.NotificationClearRequest{} + if handler.BindAndCheck(ctx, req) { + return + } + userID := middleware.GetLoginUserIDFromContext(ctx) + err := nc.notificationService.ClearUnRead(ctx, userID, req.TypeStr) + handler.HandleResponse(ctx, err, gin.H{}) +} + +// ClearIDUnRead +// @Summary ClearUnRead +// @Description ClearUnRead +// @Tags Notification +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.NotificationClearIDRequest true "NotificationClearIDRequest" +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/notification/read/state [put] +func (nc *NotificationController) ClearIDUnRead(ctx *gin.Context) { + req := &schema.NotificationClearIDRequest{} + if handler.BindAndCheck(ctx, req) { + return + } + userID := middleware.GetLoginUserIDFromContext(ctx) + err := nc.notificationService.ClearIDUnRead(ctx, userID, req.ID) + handler.HandleResponse(ctx, err, gin.H{}) +} + +// GetList +// @Summary GetRedDot +// @Description GetRedDot +// @Tags Notification +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param page query int false "page size" +// @Param page_size query int false "page size" +// @Param type query string false "type" Enums(inbox,achievement) +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/notification/page [get] +func (nc *NotificationController) GetList(ctx *gin.Context) { + req := &schema.NotificationSearch{} + if handler.BindAndCheck(ctx, req) { + return + } + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + list, count, err := nc.notificationService.GetList(ctx, req) + handler.HandleResponse(ctx, err, gin.H{ + "list": list, + "count": count, + }) +} diff --git a/internal/controller/question_controller.go b/internal/controller/question_controller.go new file mode 100644 index 00000000..748710e0 --- /dev/null +++ b/internal/controller/question_controller.go @@ -0,0 +1,425 @@ +package controller + +import ( + "context" + + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/base/middleware" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service" + "github.com/segmentfault/answer/internal/service/rank" + "github.com/segmentfault/answer/pkg/converter" + "github.com/segmentfault/pacman/errors" +) + +// QuestionController question controller +type QuestionController struct { + questionService *service.QuestionService + rankService *rank.RankService +} + +// NewQuestionController new controller +func NewQuestionController(questionService *service.QuestionService, rankService *rank.RankService) *QuestionController { + return &QuestionController{questionService: questionService, rankService: rankService} +} + +// RemoveQuestion delete question +// @Summary delete question +// @Description delete question +// @Tags api-question +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.RemoveQuestionReq true "question" +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/question [delete] +func (qc *QuestionController) RemoveQuestion(ctx *gin.Context) { + req := &schema.RemoveQuestionReq{} + if handler.BindAndCheck(ctx, req) { + return + } + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + if can, err := qc.rankService.CheckRankPermission(ctx, req.UserID, rank.QuestionDeleteRank); err != nil || !can { + handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition)) + return + } + + err := qc.questionService.RemoveQuestion(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + +// CloseQuestion Close question +// @Summary Close question +// @Description Close question +// @Tags api-question +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.CloseQuestionReq true "question" +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/question/status [put] +func (qc *QuestionController) CloseQuestion(ctx *gin.Context) { + req := &schema.CloseQuestionReq{} + if handler.BindAndCheck(ctx, req) { + return + } + req.UserId = middleware.GetLoginUserIDFromContext(ctx) + err := qc.questionService.CloseQuestion(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + +// GetQuestion godoc +// @Summary GetQuestion Question +// @Description GetQuestion Question +// @Tags api-question +// @Security ApiKeyAuth +// @Accept json +// @Produce json +// @Param id query string true "Question TagID" default(1) +// @Success 200 {string} string "" +// @Router /answer/api/v1/question/info [get] +func (qc *QuestionController) GetQuestion(c *gin.Context) { + id := c.Query("id") + ctx := context.Background() + userID := middleware.GetLoginUserIDFromContext(c) + info, err := qc.questionService.GetQuestion(ctx, id, userID) + if err != nil { + handler.HandleResponse(c, err, nil) + return + } + handler.HandleResponse(c, nil, info) +} + +// SimilarQuestion godoc +// @Summary Search Similar Question +// @Description Search Similar Question +// @Tags api-question +// @Accept json +// @Produce json +// @Param question_id query string true "question_id" default() +// @Success 200 {string} string "" +// @Router /answer/api/v1/question/similar/tag [get] +func (qc *QuestionController) SimilarQuestion(ctx *gin.Context) { + questionID := ctx.Query("question_id") + userID := middleware.GetLoginUserIDFromContext(ctx) + list, count, err := qc.questionService.SimilarQuestion(ctx, questionID, userID) + if err != nil { + handler.HandleResponse(ctx, err, nil) + return + } + handler.HandleResponse(ctx, nil, gin.H{ + "list": list, + "count": count, + }) + +} + +// Index godoc +// @Summary SearchQuestionList +// @Description SearchQuestionList
"order" Enums(newest, active,frequent,score,unanswered) +// @Tags api-question +// @Accept json +// @Produce json +// @Param data body schema.QuestionSearch true "QuestionSearch" +// @Success 200 {string} string "" +// @Router /answer/api/v1/question/page [post] +func (qc *QuestionController) Index(ctx *gin.Context) { + req := &schema.QuestionSearch{} + if handler.BindAndCheck(ctx, req) { + return + } + userID := middleware.GetLoginUserIDFromContext(ctx) + list, count, err := qc.questionService.SearchList(ctx, req, userID) + if err != nil { + handler.HandleResponse(ctx, err, nil) + return + } + handler.HandleResponse(ctx, nil, gin.H{ + "list": list, + "count": count, + }) +} + +// SearchList godoc +// @Summary SearchQuestionList +// @Description SearchQuestionList +// @Tags api-question +// @Accept json +// @Produce json +// @Param data body schema.QuestionSearch true "QuestionSearch" +// @Router /answer/api/v1/question/search [post] +// @Success 200 {string} string "" +func (qc *QuestionController) SearchList(c *gin.Context) { + Request := new(schema.QuestionSearch) + err := c.BindJSON(Request) + if err != nil { + handler.HandleResponse(c, err, nil) + return + } + ctx := context.Background() + userID := middleware.GetLoginUserIDFromContext(c) + list, count, err := qc.questionService.SearchList(ctx, Request, userID) + if err != nil { + handler.HandleResponse(c, err, nil) + return + } + handler.HandleResponse(c, nil, gin.H{ + "list": list, + "count": count, + }) +} + +// AddQuestion add question +// @Summary add question +// @Description add question +// @Tags api-question +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.QuestionAdd true "question" +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/question [post] +func (qc *QuestionController) AddQuestion(ctx *gin.Context) { + req := &schema.QuestionAdd{} + if handler.BindAndCheck(ctx, req) { + return + } + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + + if can, err := qc.rankService.CheckRankPermission(ctx, req.UserID, rank.QuestionAddRank); err != nil || !can { + handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition)) + return + } + + resp, err := qc.questionService.AddQuestion(ctx, req) + handler.HandleResponse(ctx, err, resp) +} + +// UpdateQuestion update question +// @Summary update question +// @Description update question +// @Tags api-question +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.QuestionUpdate true "question" +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/question [put] +func (qc *QuestionController) UpdateQuestion(ctx *gin.Context) { + req := &schema.QuestionUpdate{} + if handler.BindAndCheck(ctx, req) { + return + } + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + + if can, err := qc.rankService.CheckRankPermission(ctx, req.UserID, rank.QuestionEditRank); err != nil || !can { + handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition)) + return + } + + resp, err := qc.questionService.UpdateQuestion(ctx, req) + handler.HandleResponse(ctx, err, resp) +} + +// CloseMsgList close question msg list +// @Summary close question msg list +// @Description close question msg list +// @Tags api-question +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/question/closemsglist [get] +func (qc *QuestionController) CloseMsgList(ctx *gin.Context) { + resp, err := qc.questionService.CloseMsgList(ctx, handler.GetLang(ctx)) + handler.HandleResponse(ctx, err, resp) +} + +// SearchByTitleLike add question title like +// @Summary add question title like +// @Description add question title like +// @Tags api-question +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @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) { + title := ctx.Query("title") + userID := middleware.GetLoginUserIDFromContext(ctx) + resp, err := qc.questionService.SearchByTitleLike(ctx, title, userID) + handler.HandleResponse(ctx, err, resp) +} + +// UserTop godoc +// @Summary UserTop +// @Description UserTop +// @Tags api-question +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param username query string true "username" default(string) +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/personal/qa/top [get] +func (qc *QuestionController) UserTop(ctx *gin.Context) { + userName := ctx.Query("username") + userID := middleware.GetLoginUserIDFromContext(ctx) + questionList, answerList, err := qc.questionService.SearchUserTopList(ctx, userName, userID) + handler.HandleResponse(ctx, err, gin.H{ + "question": questionList, + "answer": answerList, + }) +} + +// UserList godoc +// @Summary UserList +// @Description UserList +// @Tags api-question +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param username query string true "username" default(string) +// @Param order query string true "order" Enums(newest,score) +// @Param page query string true "page" default(0) +// @Param pagesize query string true "pagesize" default(20) +// @Success 200 {object} handler.RespBody +// @Router /personal/question/page [get] +func (qc *QuestionController) UserList(ctx *gin.Context) { + userName := ctx.Query("username") + order := ctx.Query("order") + pageStr := ctx.Query("page") + pageSizeStr := ctx.Query("pagesize") + page := converter.StringToInt(pageStr) + pageSize := converter.StringToInt(pageSizeStr) + userID := middleware.GetLoginUserIDFromContext(ctx) + questionList, count, err := qc.questionService.SearchUserList(ctx, userName, order, page, pageSize, userID) + handler.HandleResponse(ctx, err, gin.H{ + "list": questionList, + "count": count, + }) +} + +// UserAnswerList godoc +// @Summary UserAnswerList +// @Description UserAnswerList +// @Tags api-answer +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param username query string true "username" default(string) +// @Param order query string true "order" Enums(newest,score) +// @Param page query string true "page" default(0) +// @Param pagesize query string true "pagesize" default(20) +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/personal/answer/page [get] +func (qc *QuestionController) UserAnswerList(ctx *gin.Context) { + userName := ctx.Query("username") + order := ctx.Query("order") + pageStr := ctx.Query("page") + pageSizeStr := ctx.Query("pagesize") + page := converter.StringToInt(pageStr) + pageSize := converter.StringToInt(pageSizeStr) + userID := middleware.GetLoginUserIDFromContext(ctx) + questionList, count, err := qc.questionService.SearchUserAnswerList(ctx, userName, order, page, pageSize, userID) + handler.HandleResponse(ctx, err, gin.H{ + "list": questionList, + "count": count, + }) +} + +// UserCollectionList godoc +// @Summary UserCollectionList +// @Description UserCollectionList +// @Tags Collection +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param page query string true "page" default(0) +// @Param pagesize query string true "pagesize" default(20) +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/personal/collection/page [get] +func (qc *QuestionController) UserCollectionList(ctx *gin.Context) { + pageStr := ctx.Query("page") + pageSizeStr := ctx.Query("pagesize") + page := converter.StringToInt(pageStr) + pageSize := converter.StringToInt(pageSizeStr) + userID := middleware.GetLoginUserIDFromContext(ctx) + questionList, count, err := qc.questionService.SearchUserCollectionList(ctx, page, pageSize, userID) + handler.HandleResponse(ctx, err, gin.H{ + "list": questionList, + "count": count, + }) +} + +// CmsSearchList godoc +// @Summary CmsSearchList +// @Description Status:[available,closed,deleted] +// @Tags admin +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param page query int false "page size" +// @Param page_size query int false "page size" +// @Param status query string false "user status" Enums(available, closed, deleted) +// @Success 200 {object} handler.RespBody +// @Router /answer/admin/api/question/page [get] +func (qc *QuestionController) CmsSearchList(ctx *gin.Context) { + req := &schema.CmsQuestionSearch{} + if handler.BindAndCheck(ctx, req) { + return + } + userID := middleware.GetLoginUserIDFromContext(ctx) + questionList, count, err := qc.questionService.CmsSearchList(ctx, req, userID) + handler.HandleResponse(ctx, err, gin.H{ + "list": questionList, + "count": count, + }) +} + +// CmsSearchAnswerList godoc +// @Summary CmsSearchList +// @Description Status:[available,deleted] +// @Tags admin +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param page query int false "page size" +// @Param page_size query int false "page size" +// @Param status query string false "user status" Enums(available,deleted) +// @Success 200 {object} handler.RespBody +// @Router /answer/admin/api/answer/page [get] +func (qc *QuestionController) CmsSearchAnswerList(ctx *gin.Context) { + req := &entity.CmsAnswerSearch{} + if handler.BindAndCheck(ctx, req) { + return + } + userID := middleware.GetLoginUserIDFromContext(ctx) + questionList, count, err := qc.questionService.CmsSearchAnswerList(ctx, req, userID) + handler.HandleResponse(ctx, err, gin.H{ + "list": questionList, + "count": count, + }) +} + +// AdminSetQuestionStatus godoc +// @Summary AdminSetQuestionStatus +// @Description Status:[available,closed,deleted] +// @Tags admin +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.AdminSetQuestionStatusRequest true "AdminSetQuestionStatusRequest" +// @Router /answer/admin/api/question/status [put] +// @Success 200 {object} handler.RespBody +func (qc *QuestionController) AdminSetQuestionStatus(ctx *gin.Context) { + req := &schema.AdminSetQuestionStatusRequest{} + if handler.BindAndCheck(ctx, req) { + return + } + err := qc.questionService.AdminSetQuestionStatus(ctx, req.QuestionID, req.StatusStr) + handler.HandleResponse(ctx, err, gin.H{}) +} diff --git a/internal/controller/rank_controller.go b/internal/controller/rank_controller.go new file mode 100644 index 00000000..d50528c7 --- /dev/null +++ b/internal/controller/rank_controller.go @@ -0,0 +1,42 @@ +package controller + +import ( + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/base/middleware" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/rank" +) + +// RankController rank controller +type RankController struct { + rankService *rank.RankService +} + +// NewRankController new controller +func NewRankController( + rankService *rank.RankService) *RankController { + return &RankController{rankService: rankService} +} + +// GetRankPersonalWithPage user personal rank list +// @Summary user personal rank list +// @Description user personal rank list +// @Tags Rank +// @Produce json +// @Param page query int false "page" +// @Param page_size query int false "page size" +// @Param username query string false "username" +// @Success 200 {object} handler.RespBody{data=pager.PageModel{list=[]schema.GetRankPersonalWithPageResp}} +// @Router /answer/api/v1/personal/rank/page [get] +func (cc *RankController) GetRankPersonalWithPage(ctx *gin.Context) { + req := &schema.GetRankPersonalWithPageReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + + resp, err := cc.rankService.GetRankPersonalWithPage(ctx, req) + handler.HandleResponse(ctx, err, resp) +} diff --git a/internal/controller/reason_controller.go b/internal/controller/reason_controller.go new file mode 100644 index 00000000..a4a39003 --- /dev/null +++ b/internal/controller/reason_controller.go @@ -0,0 +1,42 @@ +package controller + +import ( + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/reason" +) + +// ReasonController answer controller +type ReasonController struct { + reasonService *reason.ReasonService +} + +// NewReasonController new controller +func NewReasonController(answerService *reason.ReasonService) *ReasonController { + return &ReasonController{reasonService: answerService} +} + +// Reasons godoc +// @Summary get reasons by object type and action +// @Description get reasons by object type and action +// @Tags reason +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param object_type query string true "object_type" Enums(question, answer, comment, user) +// @Param action query string true "action" Enums(status, close, flag, review) +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/reasons [get] +// @Router /answer/admin/api/reasons [get] +func (rc *ReasonController) Reasons(ctx *gin.Context) { + req := &schema.ReasonReq{} + if handler.BindAndCheck(ctx, req) { + return + } + reasons, err := rc.reasonService.GetReasons(ctx, *req) + if err != nil { + err = nil + } + handler.HandleResponse(ctx, err, reasons) +} diff --git a/internal/controller/report_controller.go b/internal/controller/report_controller.go new file mode 100644 index 00000000..37cda067 --- /dev/null +++ b/internal/controller/report_controller.go @@ -0,0 +1,68 @@ +package controller + +import ( + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/base/middleware" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/rank" + "github.com/segmentfault/answer/internal/service/report" + "github.com/segmentfault/pacman/errors" +) + +// ReportController report controller +type ReportController struct { + reportService *report.ReportService + rankService *rank.RankService +} + +// NewReportController new controller +func NewReportController(reportService *report.ReportService, rankService *rank.RankService) *ReportController { + return &ReportController{reportService: reportService, rankService: rankService} +} + +// AddReport add report +// @Summary add report +// @Description add report
source (question, answer, comment, user) +// @Security ApiKeyAuth +// @Tags Report +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.AddReportReq true "report" +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/report [post] +func (rc *ReportController) AddReport(ctx *gin.Context) { + req := &schema.AddReportReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + if can, err := rc.rankService.CheckRankPermission(ctx, req.UserID, rank.ReportAddRank); err != nil || !can { + handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition)) + return + } + + err := rc.reportService.AddReport(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + +// GetReportTypeList get report type list +// @Summary get report type list +// @Description get report type list +// @Tags Report +// @Produce json +// @Param source query string true "report source" Enums(question, answer, comment, user) +// @Success 200 {object} handler.RespBody{data=[]schema.GetReportTypeResp} +// @Router /answer/api/v1/report/type/list [get] +func (rc *ReportController) GetReportTypeList(ctx *gin.Context) { + req := &schema.GetReportListReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + resp, err := rc.reportService.GetReportTypeList(ctx, handler.GetLang(ctx), req) + handler.HandleResponse(ctx, err, resp) +} diff --git a/internal/controller/revision_controller.go b/internal/controller/revision_controller.go new file mode 100644 index 00000000..2756763f --- /dev/null +++ b/internal/controller/revision_controller.go @@ -0,0 +1,63 @@ +package controller + +import ( + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service" + "github.com/segmentfault/pacman/errors" +) + +// RevisionController revision controller +type RevisionController struct { + revisionListService *service.RevisionService +} + +// NewRevisionController new controller +func NewRevisionController(revisionListService *service.RevisionService) *RevisionController { + return &RevisionController{revisionListService: revisionListService} +} + +// GetRevision get revision one +// @Summary get revision one +// @Description get revision one +// @Tags Revision +// @Accept json +// @Produce json +// @Param id path int true "revisionid" +// @Success 200 {object} handler.RespBody{data=schema.GetRevisionResp} +// Router /revision/{id} [get] +func (rc *RevisionController) GetRevision(ctx *gin.Context) { + id := ctx.Param("id") + if id == "0" { + handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil) + return + } + + resp, err := rc.revisionListService.GetRevision(ctx, id) + handler.HandleResponse(ctx, err, resp) +} + +// GetRevisionList godoc +// @Summary get revision list +// @Description get revision list +// @Tags Revision +// @Produce json +// @Param object_id query string true "object id" +// @Success 200 {object} handler.RespBody{data=[]schema.GetRevisionResp} +// @Router /answer/api/v1/revisions [get] +func (rc *RevisionController) GetRevisionList(ctx *gin.Context) { + objectID := ctx.Query("object_id") + if objectID == "0" || objectID == "" { + handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil) + return + } + + req := &schema.GetRevisionListReq{ + ObjectID: objectID, + } + + resp, err := rc.revisionListService.GetRevisionList(ctx, req) + handler.HandleResponse(ctx, err, resp) +} diff --git a/internal/controller/search_controller.go b/internal/controller/search_controller.go new file mode 100644 index 00000000..5835008d --- /dev/null +++ b/internal/controller/search_controller.go @@ -0,0 +1,69 @@ +package controller + +import ( + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/base/middleware" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service" + "github.com/segmentfault/answer/pkg/converter" + "github.com/segmentfault/pacman/errors" +) + +// SearchController tag controller +type SearchController struct { + searchService *service.SearchService +} + +// NewSearchController new controller +func NewSearchController(searchService *service.SearchService) *SearchController { + return &SearchController{searchService: searchService} +} + +// Search godoc +// @Summary search object +// @Description search object +// @Tags Search +// @Produce json +// @Security ApiKeyAuth +// @Param q query string true "query string" +// @Success 200 {object} handler.RespBody{data=schema.SearchListResp} +// @Router /answer/api/v1/search [get] +func (sc *SearchController) Search(ctx *gin.Context) { + var ( + q string + page string + size string + ok bool + dto schema.SearchDTO + ) + q, ok = ctx.GetQuery("q") + if len(q) == 0 || !ok { + handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), q) + return + } + page, ok = ctx.GetQuery("page") + if !ok { + page = "1" + } + size, ok = ctx.GetQuery("size") + if !ok { + size = "30" + } + + dto = schema.SearchDTO{ + Query: q, + Page: converter.StringToInt(page), + Size: converter.StringToInt(size), + UserID: middleware.GetLoginUserIDFromContext(ctx), + } + + resp, total, extra, err := sc.searchService.Search(ctx, &dto) + + handler.HandleResponse(ctx, err, schema.SearchListResp{ + Total: total, + SearchResp: resp, + Extra: extra, + }) +} diff --git a/internal/controller/siteinfo_controller.go b/internal/controller/siteinfo_controller.go new file mode 100644 index 00000000..3fb0a8f0 --- /dev/null +++ b/internal/controller/siteinfo_controller.go @@ -0,0 +1,47 @@ +package controller + +import ( + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service" +) + +type SiteinfoController struct { + siteInfoService *service.SiteInfoService +} + +// NewSiteinfoController new siteinfo controller. +func NewSiteinfoController(siteInfoService *service.SiteInfoService) *SiteinfoController { + return &SiteinfoController{ + siteInfoService: siteInfoService, + } +} + +// GetInfo godoc +// @Summary Get siteinfo +// @Description Get siteinfo +// @Tags site +// @Produce json +// @Success 200 {object} handler.RespBody{data=schema.SiteGeneralResp} +// @Router /answer/api/v1/siteinfo [get] +func (sc *SiteinfoController) GetInfo(ctx *gin.Context) { + var ( + resp = &schema.SiteInfoResp{} + general schema.SiteGeneralResp + face schema.SiteInterfaceResp + err error + ) + + general, err = sc.siteInfoService.GetSiteGeneral(ctx) + resp.General = &general + if err != nil { + handler.HandleResponse(ctx, err, resp) + return + } + + face, err = sc.siteInfoService.GetSiteInterface(ctx) + resp.Face = &face + + handler.HandleResponse(ctx, err, resp) +} diff --git a/internal/controller/tag_controller.go b/internal/controller/tag_controller.go new file mode 100644 index 00000000..01146a79 --- /dev/null +++ b/internal/controller/tag_controller.go @@ -0,0 +1,194 @@ +package controller + +import ( + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/base/middleware" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/rank" + "github.com/segmentfault/answer/internal/service/tag" + "github.com/segmentfault/pacman/errors" +) + +// TagController tag controller +type TagController struct { + tagService *tag.TagService + rankService *rank.RankService +} + +// NewTagController new controller +func NewTagController(tagService *tag.TagService, rankService *rank.RankService) *TagController { + return &TagController{tagService: tagService, rankService: rankService} +} + +// SearchTagLike get tag list +// @Summary get tag list +// @Description get tag list +// @Tags Tag +// @Produce json +// @Security ApiKeyAuth +// @Param tag query string false "tag" +// @Success 200 {object} handler.RespBody{data=[]schema.GetTagResp} +// @Router /answer/api/v1/question/tags [get] +func (tc *TagController) SearchTagLike(ctx *gin.Context) { + req := &schema.SearchTagLikeReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + resp, err := tc.tagService.SearchTagLike(ctx, req) + handler.HandleResponse(ctx, err, resp) +} + +// RemoveTag delete tag +// @Summary delete tag +// @Description delete tag +// @Tags Tag +// @Accept json +// @Produce json +// @Param data body schema.RemoveTagReq true "tag" +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/tag [delete] +func (tc *TagController) RemoveTag(ctx *gin.Context) { + req := &schema.RemoveTagReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + if can, err := tc.rankService.CheckRankPermission(ctx, req.UserID, rank.TagDeleteRank); err != nil || !can { + handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition)) + return + } + + err := tc.tagService.RemoveTag(ctx, req.TagID) + handler.HandleResponse(ctx, err, nil) +} + +// UpdateTag update tag +// @Summary update tag +// @Description update tag +// @Tags Tag +// @Accept json +// @Produce json +// @Param data body schema.UpdateTagReq true "tag" +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/tag [put] +func (tc *TagController) UpdateTag(ctx *gin.Context) { + req := &schema.UpdateTagReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + if can, err := tc.rankService.CheckRankPermission(ctx, req.UserID, rank.TagEditRank); err != nil || !can { + handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition)) + return + } + + err := tc.tagService.UpdateTag(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + +// GetTagInfo get tag one +// @Summary get tag one +// @Description get tag one +// @Tags Tag +// @Accept json +// @Produce json +// @Param tag_id query string true "tag id" +// @Param tag_name query string true "tag name" +// @Success 200 {object} handler.RespBody{data=schema.GetTagResp} +// @Router /answer/api/v1/tag [get] +func (tc *TagController) GetTagInfo(ctx *gin.Context) { + req := &schema.GetTagInfoReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + + resp, err := tc.tagService.GetTagInfo(ctx, req) + handler.HandleResponse(ctx, err, resp) +} + +// GetTagWithPage get tag page +// @Summary get tag page +// @Description get tag page +// @Tags Tag +// @Produce json +// @Param page query int false "page size" +// @Param page_size query int false "page size" +// @Param slug_name query string false "slug_name" +// @Param query_cond query string false "query condition" Enums(popular, name, newest) +// @Success 200 {object} handler.RespBody{data=pager.PageModel{list=[]schema.GetTagPageResp}} +// @Router /answer/api/v1/tags/page [get] +func (tc *TagController) GetTagWithPage(ctx *gin.Context) { + req := &schema.GetTagWithPageReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + + resp, err := tc.tagService.GetTagWithPage(ctx, req) + handler.HandleResponse(ctx, err, resp) +} + +// GetFollowingTags get following tag list +// @Summary get following tag list +// @Description get following tag list +// @Security ApiKeyAuth +// @Tags Tag +// @Produce json +// @Success 200 {object} handler.RespBody{data=[]schema.GetFollowingTagsResp} +// @Router /answer/api/v1/tags/following [get] +func (tc *TagController) GetFollowingTags(ctx *gin.Context) { + userID := middleware.GetLoginUserIDFromContext(ctx) + resp, err := tc.tagService.GetFollowingTags(ctx, userID) + handler.HandleResponse(ctx, err, resp) +} + +// GetTagSynonyms get tag synonyms +// @Summary get tag synonyms +// @Description get tag synonyms +// @Tags Tag +// @Produce json +// @Param tag_id query int true "tag id" +// @Success 200 {object} handler.RespBody{data=[]schema.GetTagSynonymsResp} +// @Router /answer/api/v1/tag/synonyms [get] +func (tc *TagController) GetTagSynonyms(ctx *gin.Context) { + req := &schema.GetTagSynonymsReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + resp, err := tc.tagService.GetTagSynonyms(ctx, req) + handler.HandleResponse(ctx, err, resp) +} + +// UpdateTagSynonym update tag +// @Summary update tag +// @Description update tag +// @Tags Tag +// @Accept json +// @Produce json +// @Param data body schema.UpdateTagSynonymReq true "tag" +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/tag/synonym [put] +func (tc *TagController) UpdateTagSynonym(ctx *gin.Context) { + req := &schema.UpdateTagSynonymReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + if can, err := tc.rankService.CheckRankPermission(ctx, req.UserID, rank.TagSynonymRank); err != nil || !can { + handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition)) + return + } + + err := tc.tagService.UpdateTagSynonym(ctx, req) + handler.HandleResponse(ctx, err, nil) +} diff --git a/internal/controller/useless/collection_group_controller.go b/internal/controller/useless/collection_group_controller.go new file mode 100644 index 00000000..7da94ff7 --- /dev/null +++ b/internal/controller/useless/collection_group_controller.go @@ -0,0 +1,58 @@ +package useless + +import ( + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service" + "github.com/segmentfault/pacman/log" +) + +// CollectionGroupController collectionGroup controller +type CollectionGroupController struct { + log log.log + collectionGroupService *service.CollectionGroupService +} + +// NewCollectionGroupController new controller +func NewCollectionGroupController(collectionGroupService *service.CollectionGroupService) *CollectionGroupController { + return &CollectionGroupController{collectionGroupService: collectionGroupService} +} + +// AddCollectionGroup add collection group +// @Summary add collection group +// @Description add collection group +// @Tags CollectionGroup +// @Accept json +// @Produce json +// @Param data body schema.AddCollectionGroupReq true "collection group" +// @Success 200 {object} handler.RespBody +// Router /collection-group [post] +func (cc *CollectionGroupController) AddCollectionGroup(ctx *gin.Context) { + req := &schema.AddCollectionGroupReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + err := cc.collectionGroupService.AddCollectionGroup(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + +// UpdateCollectionGroup update collection group +// @Summary update collection group +// @Description update collection group +// @Tags CollectionGroup +// @Accept json +// @Produce json +// @Param data body schema.UpdateCollectionGroupReq true "collection group" +// @Success 200 {object} handler.RespBody +// Router /collection-group [put] +func (cc *CollectionGroupController) UpdateCollectionGroup(ctx *gin.Context) { + req := &schema.UpdateCollectionGroupReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + err := cc.collectionGroupService.UpdateCollectionGroup(ctx, req, []string{}) + handler.HandleResponse(ctx, err, nil) +} diff --git a/internal/controller/useless/notification_read_controller.go b/internal/controller/useless/notification_read_controller.go new file mode 100644 index 00000000..22136383 --- /dev/null +++ b/internal/controller/useless/notification_read_controller.go @@ -0,0 +1,143 @@ +package useless + +import ( + "strconv" + + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/notification" + "github.com/segmentfault/pacman/errors" + "github.com/segmentfault/pacman/log" +) + +// NotificationReadController notificationRead controller +type NotificationReadController struct { + log log.log + notificationReadService *notification.NotificationReadService +} + +// NewNotificationReadController new controller +func NewNotificationReadController(notificationReadService *notification.NotificationReadService) *NotificationReadController { + return &NotificationReadController{notificationReadService: notificationReadService} +} + +// AddNotificationRead add notification read record +// @Summary add notification read record +// @Description add notification read record +// @Tags NotificationRead +// @Accept json +// @Produce json +// @Param data body schema.AddNotificationReadReq true "notification read record" +// @Success 200 {object} handler.RespBody +// Router /notification-read [post] +func (nc *NotificationReadController) AddNotificationRead(ctx *gin.Context) { + req := &schema.AddNotificationReadReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + err := nc.notificationReadService.AddNotificationRead(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + +// RemoveNotificationRead delete notification read record +// @Summary delete notification read record +// @Description delete notification read record +// @Tags NotificationRead +// @Accept json +// @Produce json +// @Param data body schema.RemoveNotificationReadReq true "notification read record" +// @Success 200 {object} handler.RespBody +// Router /notification-read [delete] +func (nc *NotificationReadController) RemoveNotificationRead(ctx *gin.Context) { + req := &schema.RemoveNotificationReadReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + err := nc.notificationReadService.RemoveNotificationRead(ctx, req.ID) + handler.HandleResponse(ctx, err, nil) +} + +// UpdateNotificationRead update notification read record +// @Summary update notification read record +// @Description update notification read record +// @Tags NotificationRead +// @Accept json +// @Produce json +// @Param data body schema.UpdateNotificationReadReq true "notification read record" +// @Success 200 {object} handler.RespBody +// Router /notification-read [put] +func (nc *NotificationReadController) UpdateNotificationRead(ctx *gin.Context) { + req := &schema.UpdateNotificationReadReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + err := nc.notificationReadService.UpdateNotificationRead(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + +// GetNotificationRead get notification read record one +// @Summary get notification read record one +// @Description get notification read record one +// @Tags NotificationRead +// @Accept json +// @Produce json +// @Param id path int true "notification read recordid" +// @Success 200 {object} handler.RespBody{data=schema.GetNotificationReadResp} +// Router /notification-read/{id} [get] +func (nc *NotificationReadController) GetNotificationRead(ctx *gin.Context) { + id, _ := strconv.Atoi(ctx.Param("id")) + if id == 0 { + handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil) + return + } + + resp, err := nc.notificationReadService.GetNotificationRead(ctx, id) + handler.HandleResponse(ctx, err, resp) +} + +// GetNotificationReadList get notification read record list +// @Summary get notification read record list +// @Description get notification read record list +// @Tags NotificationRead +// @Produce json +// @Param user_id query string false "user id" +// @Param message_id query string false "message id" +// @Param is_read query string false "read status(unread: 1; read 2)" +// @Success 200 {object} handler.RespBody{data=[]schema.GetNotificationReadResp} +// Router /notification-reads [get] +func (nc *NotificationReadController) GetNotificationReadList(ctx *gin.Context) { + req := &schema.GetNotificationReadListReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + resp, err := nc.notificationReadService.GetNotificationReadList(ctx, req) + handler.HandleResponse(ctx, err, resp) +} + +// GetNotificationReadWithPage get notification read record page +// @Summary get notification read record page +// @Description get notification read record page +// @Tags NotificationRead +// @Produce json +// @Param page query int false "page size" +// @Param page_size query int false "page size" +// @Param user_id query string false "user id" +// @Param message_id query string false "message id" +// @Param is_read query string false "read status(unread: 1; read 2)" +// @Success 200 {object} handler.RespBody{data=pager.PageModel{list=[]schema.GetNotificationReadResp}} +// Router /notification-reads/page [get] +func (nc *NotificationReadController) GetNotificationReadWithPage(ctx *gin.Context) { + req := &schema.GetNotificationReadWithPageReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + resp, err := nc.notificationReadService.GetNotificationReadWithPage(ctx, req) + handler.HandleResponse(ctx, err, resp) +} diff --git a/internal/controller/useless/user_group_controller.go b/internal/controller/useless/user_group_controller.go new file mode 100644 index 00000000..6f6a000e --- /dev/null +++ b/internal/controller/useless/user_group_controller.go @@ -0,0 +1,137 @@ +package useless + +import ( + "strconv" + + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service" + "github.com/segmentfault/pacman/errors" + "github.com/segmentfault/pacman/log" +) + +// UserGroupController userGroup controller +type UserGroupController struct { + log log.log + userGroupService *service.UserGroupService +} + +// NewUserGroupController new controller +func NewUserGroupController(userGroupService *service.UserGroupService) *UserGroupController { + return &UserGroupController{userGroupService: userGroupService} +} + +// AddUserGroup add user group +// @Summary add user group +// @Description add user group +// @Tags UserGroup +// @Accept json +// @Produce json +// @Param data body schema.AddUserGroupReq true "user group" +// @Success 200 {object} handler.RespBody +// Router /user-group [post] +func (uc *UserGroupController) AddUserGroup(ctx *gin.Context) { + req := &schema.AddUserGroupReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + err := uc.userGroupService.AddUserGroup(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + +// RemoveUserGroup delete user group +// @Summary delete user group +// @Description delete user group +// @Tags UserGroup +// @Accept json +// @Produce json +// @Param data body schema.RemoveUserGroupReq true "user group" +// @Success 200 {object} handler.RespBody +// Router /user-group [delete] +func (uc *UserGroupController) RemoveUserGroup(ctx *gin.Context) { + req := &schema.RemoveUserGroupReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + err := uc.userGroupService.RemoveUserGroup(ctx, int(req.ID)) + handler.HandleResponse(ctx, err, nil) +} + +// UpdateUserGroup update user group +// @Summary update user group +// @Description update user group +// @Tags UserGroup +// @Accept json +// @Produce json +// @Param data body schema.UpdateUserGroupReq true "user group" +// @Success 200 {object} handler.RespBody +// Router /user-group [put] +func (uc *UserGroupController) UpdateUserGroup(ctx *gin.Context) { + req := &schema.UpdateUserGroupReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + err := uc.userGroupService.UpdateUserGroup(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + +// GetUserGroup get user group one +// @Summary get user group one +// @Description get user group one +// @Tags UserGroup +// @Accept json +// @Produce json +// @Param id path int true "user groupid" +// @Success 200 {object} handler.RespBody{data=schema.GetUserGroupResp} +// Router /user-group/{id} [get] +func (uc *UserGroupController) GetUserGroup(ctx *gin.Context) { + id, _ := strconv.Atoi(ctx.Param("id")) + if id == 0 { + handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil) + return + } + + resp, err := uc.userGroupService.GetUserGroup(ctx, id) + handler.HandleResponse(ctx, err, resp) +} + +// GetUserGroupList get user group list +// @Summary get user group list +// @Description get user group list +// @Tags UserGroup +// @Produce json +// @Success 200 {object} handler.RespBody{data=[]schema.GetUserGroupResp} +// Router /user-groups [get] +func (uc *UserGroupController) GetUserGroupList(ctx *gin.Context) { + req := &schema.GetUserGroupListReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + resp, err := uc.userGroupService.GetUserGroupList(ctx, req) + handler.HandleResponse(ctx, err, resp) +} + +// GetUserGroupWithPage get user group page +// @Summary get user group page +// @Description get user group page +// @Tags UserGroup +// @Produce json +// @Param page query int false "page size" +// @Param page_size query int false "page size" +// @Success 200 {object} handler.RespBody{data=pager.PageModel{list=[]schema.GetUserGroupResp}} +// Router /user-groups/page [get] +func (uc *UserGroupController) GetUserGroupWithPage(ctx *gin.Context) { + req := &schema.GetUserGroupWithPageReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + resp, err := uc.userGroupService.GetUserGroupWithPage(ctx, req) + handler.HandleResponse(ctx, err, resp) +} diff --git a/internal/controller/user_controller.go b/internal/controller/user_controller.go new file mode 100644 index 00000000..8c8f6e45 --- /dev/null +++ b/internal/controller/user_controller.go @@ -0,0 +1,479 @@ +package controller + +import ( + "net/http" + "path" + "strings" + + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/base/middleware" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service" + "github.com/segmentfault/answer/internal/service/action" + "github.com/segmentfault/answer/internal/service/auth" + "github.com/segmentfault/answer/internal/service/export" + "github.com/segmentfault/answer/internal/service/uploader" + "github.com/segmentfault/pacman/errors" + "github.com/segmentfault/pacman/log" +) + +// UserController user controller +type UserController struct { + userService *service.UserService + authService *auth.AuthService + actionService *action.CaptchaService + uploaderService *uploader.UploaderService + emailService *export.EmailService +} + +// NewUserController new controller +func NewUserController( + authService *auth.AuthService, + userService *service.UserService, + actionService *action.CaptchaService, + emailService *export.EmailService, + uploaderService *uploader.UploaderService) *UserController { + return &UserController{ + authService: authService, + userService: userService, + actionService: actionService, + uploaderService: uploaderService, + emailService: emailService, + } +} + +// GetUserInfoByUserID godoc +// @Summary GetUserInfoByUserID +// @Description GetUserInfoByUserID +// @Tags User +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Success 200 {object} handler.RespBody{data=schema.GetUserResp} +// @Router /answer/api/v1/user/info [get] +func (uc *UserController) GetUserInfoByUserID(ctx *gin.Context) { + userID := middleware.GetLoginUserIDFromContext(ctx) + token := middleware.ExtractToken(ctx) + resp, err := uc.userService.GetUserInfoByUserID(ctx, token, userID) + handler.HandleResponse(ctx, err, resp) +} + +// GetOtherUserInfoByUsername godoc +// @Summary GetOtherUserInfoByUsername +// @Description GetOtherUserInfoByUsername +// @Tags User +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param username query string true "username" +// @Success 200 {object} handler.RespBody{data=schema.GetOtherUserInfoResp} +// @Router /answer/api/v1/personal/user/info [get] +func (uc *UserController) GetOtherUserInfoByUsername(ctx *gin.Context) { + req := &schema.GetOtherUserInfoByUsernameReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + resp, err := uc.userService.GetOtherUserInfoByUsername(ctx, req.Username) + handler.HandleResponse(ctx, err, resp) +} + +// UserEmailLogin godoc +// @Summary UserEmailLogin +// @Description UserEmailLogin +// @Tags User +// @Accept json +// @Produce json +// @Param data body schema.UserEmailLogin true "UserEmailLogin" +// @Success 200 {object} handler.RespBody{data=schema.GetUserResp} +// @Router /answer/api/v1/user/login/email [post] +func (uc *UserController) UserEmailLogin(ctx *gin.Context) { + req := &schema.UserEmailLogin{} + if handler.BindAndCheck(ctx, req) { + return + } + + captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecord_Type_Login, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode) + if !captchaPass { + handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), gin.H{ + "key": "captcha_code", + "value": "verification failed", + }) + return + } + + resp, err := uc.userService.EmailLogin(ctx, req) + if err != nil { + _, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecord_Type_Login, ctx.ClientIP()) + handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), gin.H{ + "key": "e_mail", + "value": "Email or password incorrect", + }) + return + } + uc.actionService.ActionRecordDel(ctx, schema.ActionRecord_Type_Login, ctx.ClientIP()) + handler.HandleResponse(ctx, nil, resp) +} + +// RetrievePassWord godoc +// @Summary RetrievePassWord +// @Description RetrievePassWord +// @Tags User +// @Accept json +// @Produce json +// @Param data body schema.UserRetrievePassWordRequest true "UserRetrievePassWordRequest" +// @Success 200 {string} string "" +// @Router /answer/api/v1/user/password/reset [post] +func (uc *UserController) RetrievePassWord(ctx *gin.Context) { + req := &schema.UserRetrievePassWordRequest{} + if handler.BindAndCheck(ctx, req) { + return + } + captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecord_Type_Find_Pass, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode) + if !captchaPass { + handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), gin.H{ + "key": "captcha_code", + "value": "verification failed", + }) + return + } + _, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecord_Type_Find_Pass, ctx.ClientIP()) + code, err := uc.userService.RetrievePassWord(ctx, req) + handler.HandleResponse(ctx, err, code) +} + +// UseRePassWord godoc +// @Summary UseRePassWord +// @Description UseRePassWord +// @Tags User +// @Accept json +// @Produce json +// @Param data body schema.UserRePassWordRequest true "UserRePassWordRequest" +// @Success 200 {string} string "" +// @Router /answer/api/v1/user/password/replacement [post] +func (uc *UserController) UseRePassWord(ctx *gin.Context) { + req := &schema.UserRePassWordRequest{} + if handler.BindAndCheck(ctx, req) { + return + } + + req.Content = uc.emailService.VerifyUrlExpired(ctx, req.Code) + if len(req.Content) == 0 { + handler.HandleResponse(ctx, errors.Forbidden(reason.EmailVerifyUrlExpired), + &schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeUrlExpired}) + return + } + + resp, err := uc.userService.UseRePassWord(ctx, req) + uc.actionService.ActionRecordDel(ctx, schema.ActionRecord_Type_Find_Pass, ctx.ClientIP()) + handler.HandleResponse(ctx, err, resp) +} + +// UserLogout user logout +// @Summary user logout +// @Description user logout +// @Tags User +// @Accept json +// @Produce json +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/user/logout [get] +func (uc *UserController) UserLogout(ctx *gin.Context) { + accessToken := middleware.ExtractToken(ctx) + _ = uc.authService.RemoveUserCacheInfo(ctx, accessToken) + handler.HandleResponse(ctx, nil, nil) +} + +// UserRegisterByEmail godoc +// @Summary UserRegisterByEmail +// @Description UserRegisterByEmail +// @Tags User +// @Accept json +// @Produce json +// @Param data body schema.UserRegister true "UserRegister" +// @Success 200 {object} handler.RespBody{data=schema.GetUserResp} +// @Router /answer/api/v1/user/register/email [post] +func (uc *UserController) UserRegisterByEmail(ctx *gin.Context) { + req := &schema.UserRegister{} + if handler.BindAndCheck(ctx, req) { + return + } + req.IP = ctx.ClientIP() + + resp, err := uc.userService.UserRegisterByEmail(ctx, req) + handler.HandleResponse(ctx, err, resp) +} + +// UserVerifyEmail godoc +// @Summary UserVerifyEmail +// @Description UserVerifyEmail +// @Tags User +// @Accept json +// @Produce json +// @Param code query string true "code" default() +// @Success 200 {object} handler.RespBody{data=schema.GetUserResp} +// @Router /answer/api/v1/user/email/verification [post] +func (uc *UserController) UserVerifyEmail(ctx *gin.Context) { + req := &schema.UserVerifyEmailReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + req.Content = uc.emailService.VerifyUrlExpired(ctx, req.Code) + if len(req.Content) == 0 { + handler.HandleResponse(ctx, errors.Forbidden(reason.EmailVerifyUrlExpired), + &schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeUrlExpired}) + return + } + + resp, err := uc.userService.UserVerifyEmail(ctx, req) + if err != nil { + handler.HandleResponse(ctx, err, nil) + return + } + + uc.actionService.ActionRecordDel(ctx, schema.ActionRecord_Type_Email, ctx.ClientIP()) + handler.HandleResponse(ctx, err, resp) +} + +// UserVerifyEmailSend godoc +// @Summary UserVerifyEmailSend +// @Description UserVerifyEmailSend +// @Tags User +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param captcha_id query string false "captcha_id" default() +// @Param captcha_code query string false "captcha_code" default() +// @Success 200 {string} string "" +// @Router /answer/api/v1/user/email/verification/send [post] +func (uc *UserController) UserVerifyEmailSend(ctx *gin.Context) { + req := &schema.UserVerifyEmailSendReq{} + if handler.BindAndCheck(ctx, req) { + return + } + userInfo := middleware.GetUserInfoFromContext(ctx) + if userInfo == nil { + handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil) + return + } + + captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecord_Type_Email, ctx.ClientIP(), + req.CaptchaID, req.CaptchaCode) + if !captchaPass { + handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), gin.H{ + "key": "captcha_code", + "value": "verification failed", + }) + return + } + uc.actionService.ActionRecordAdd(ctx, schema.ActionRecord_Type_Email, ctx.ClientIP()) + err := uc.userService.UserVerifyEmailSend(ctx, userInfo.UserID) + handler.HandleResponse(ctx, err, nil) +} + +// UserModifyPassWord godoc +// @Summary UserModifyPassWord +// @Description UserModifyPassWord +// @Tags User +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.UserModifyPassWordRequest true "UserModifyPassWordRequest" +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/user/password [put] +func (uc *UserController) UserModifyPassWord(ctx *gin.Context) { + req := &schema.UserModifyPassWordRequest{} + if handler.BindAndCheck(ctx, req) { + return + } + req.UserId = middleware.GetLoginUserIDFromContext(ctx) + + oldPassVerification, err := uc.userService.UserModifyPassWordVerification(ctx, req) + if err != nil { + handler.HandleResponse(ctx, err, nil) + return + } + if !oldPassVerification { + handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), gin.H{ + "key": "old_pass", + "value": "the old password verification failed", + }) + return + } + if req.OldPass == req.Pass { + handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), gin.H{ + "key": "pass", + "value": "The new password is the same as the previous setting", + }) + return + } + err = uc.userService.UserModifyPassWord(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + +// UserUpdateInfo godoc +// @Summary UserUpdateInfo +// @Description UserUpdateInfo +// @Tags User +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param Authorization header string true "access-token" +// @Param data body schema.UpdateInfoRequest true "UpdateInfoRequest" +// @Success 200 {object} handler.RespBody +// @Router /answer/api/v1/user/info [put] +func (uc *UserController) UserUpdateInfo(ctx *gin.Context) { + req := &schema.UpdateInfoRequest{} + if handler.BindAndCheck(ctx, req) { + return + } + req.UserId = middleware.GetLoginUserIDFromContext(ctx) + err := uc.userService.UpdateInfo(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + +// UploadUserAvatar godoc +// @Summary UserUpdateInfo +// @Description UserUpdateInfo +// @Tags User +// @Accept multipart/form-data +// @Security ApiKeyAuth +// @Param file formData file true "file" +// @Success 200 {object} handler.RespBody{data=string} +// @Router /answer/api/v1/user/avatar/upload [post] +func (uc *UserController) UploadUserAvatar(ctx *gin.Context) { + // max size + ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 10*1024*1024) + _, header, err := ctx.Request.FormFile("file") + if err != nil { + log.Error(err.Error()) + handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil) + return + } + fileExt := strings.ToLower(path.Ext(header.Filename)) + if fileExt != ".jpg" && fileExt != ".png" && fileExt != ".jpeg" { + log.Errorf("upload file format is not supported: %s", fileExt) + handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil) + return + } + + url, err := uc.uploaderService.UploadAvatarFile(ctx, header, fileExt) + handler.HandleResponse(ctx, err, url) +} + +// UploadUserPostFile godoc +// @Summary upload user post file +// @Description upload user post file +// @Tags User +// @Accept multipart/form-data +// @Security ApiKeyAuth +// @Param file formData file true "file" +// @Success 200 {object} handler.RespBody{data=string} +// @Router /answer/api/v1/user/post/file [post] +func (uc *UserController) UploadUserPostFile(ctx *gin.Context) { + // max size + ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 10*1024*1024) + _, header, err := ctx.Request.FormFile("file") + if err != nil { + log.Error(err.Error()) + handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil) + return + } + fileExt := strings.ToLower(path.Ext(header.Filename)) + if fileExt != ".jpg" && fileExt != ".png" && fileExt != ".jpeg" { + log.Errorf("upload file format is not supported: %s", fileExt) + handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil) + return + } + + url, err := uc.uploaderService.UploadPostFile(ctx, header, fileExt) + handler.HandleResponse(ctx, err, url) +} + +// ActionRecord godoc +// @Summary ActionRecord +// @Description ActionRecord +// @Tags User +// @Param action query string true "action" Enums(login, e_mail, find_pass) +// @Security ApiKeyAuth +// @Success 200 {object} handler.RespBody{data=schema.ActionRecordResp} +// @Router /answer/api/v1/user/action/record [get] +func (uc *UserController) ActionRecord(ctx *gin.Context) { + req := &schema.ActionRecordReq{} + if handler.BindAndCheck(ctx, req) { + return + } + req.Ip = ctx.ClientIP() + + resp, err := uc.actionService.ActionRecord(ctx, req) + handler.HandleResponse(ctx, err, resp) +} + +// UserNoticeSet godoc +// @Summary UserNoticeSet +// @Description UserNoticeSet +// @Tags User +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.UserNoticeSetRequest true "UserNoticeSetRequest" +// @Success 200 {object} handler.RespBody{data=schema.UserNoticeSetResp} +// @Router /answer/api/v1/user/notice/set [post] +func (uc *UserController) UserNoticeSet(ctx *gin.Context) { + req := &schema.UserNoticeSetRequest{} + if handler.BindAndCheck(ctx, req) { + return + } + + req.UserId = middleware.GetLoginUserIDFromContext(ctx) + resp, err := uc.userService.UserNoticeSet(ctx, req.UserId, req.NoticeSwitch) + handler.HandleResponse(ctx, err, resp) +} + +// UserChangeEmailSendCode send email to the user email then change their email +// @Summary send email to the user email then change their email +// @Description send email to the user email then change their email +// @Tags User +// @Accept json +// @Produce json +// @Param data body schema.UserChangeEmailSendCodeReq true "UserChangeEmailSendCodeReq" +// @Success 200 {object} handler.RespBody{} +// @Router /answer/api/v1/user/email/change/code [post] +func (uc *UserController) UserChangeEmailSendCode(ctx *gin.Context) { + req := &schema.UserChangeEmailSendCodeReq{} + if handler.BindAndCheck(ctx, req) { + return + } + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + + err := uc.userService.UserChangeEmailSendCode(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + +// UserChangeEmailVerify user change email verification +// @Summary user change email verification +// @Description user change email verification +// @Tags User +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.UserChangeEmailVerifyReq true "UserChangeEmailVerifyReq" +// @Success 200 {object} handler.RespBody{} +// @Router /answer/api/v1/user/email [put] +func (uc *UserController) UserChangeEmailVerify(ctx *gin.Context) { + req := &schema.UserChangeEmailVerifyReq{} + if handler.BindAndCheck(ctx, req) { + return + } + req.Content = uc.emailService.VerifyUrlExpired(ctx, req.Code) + if len(req.Content) == 0 { + handler.HandleResponse(ctx, errors.Forbidden(reason.EmailVerifyUrlExpired), + &schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeUrlExpired}) + return + } + + err := uc.userService.UserChangeEmailVerify(ctx, req.Content) + handler.HandleResponse(ctx, err, nil) +} diff --git a/internal/controller/vote_controller.go b/internal/controller/vote_controller.go new file mode 100644 index 00000000..794e8d98 --- /dev/null +++ b/internal/controller/vote_controller.go @@ -0,0 +1,105 @@ +package controller + +import ( + "github.com/gin-gonic/gin" + "github.com/jinzhu/copier" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/base/middleware" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service" +) + +// VoteController activity controller +type VoteController struct { + VoteService *service.VoteService +} + +// NewVoteController new controller +func NewVoteController(voteService *service.VoteService) *VoteController { + return &VoteController{VoteService: voteService} +} + +// VoteUp godoc +// @Summary vote up +// @Description add vote +// @Tags Activity +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.VoteReq true "vote" +// @Success 200 {object} handler.RespBody{data=schema.VoteResp} +// @Router /answer/api/v1/vote/up [post] +func (vc *VoteController) VoteUp(ctx *gin.Context) { + req := &schema.VoteReq{} + if handler.BindAndCheck(ctx, req) { + return + } + dto := &schema.VoteDTO{} + _ = copier.Copy(dto, req) + dto.UserID = middleware.GetLoginUserIDFromContext(ctx) + resp, err := vc.VoteService.VoteUp(ctx, dto) + if err != nil { + handler.HandleResponse(ctx, err, schema.ErrTypeToast) + } else { + handler.HandleResponse(ctx, err, resp) + } +} + +// VoteDown godoc +// @Summary vote down +// @Description add vote +// @Tags Activity +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.VoteReq true "vote" +// @Success 200 {object} handler.RespBody{data=schema.VoteResp} +// @Router /answer/api/v1/vote/down [post] +func (vc *VoteController) VoteDown(ctx *gin.Context) { + req := &schema.VoteReq{} + if handler.BindAndCheck(ctx, req) { + return + } + dto := &schema.VoteDTO{} + _ = copier.Copy(dto, req) + + dto.UserID = middleware.GetLoginUserIDFromContext(ctx) + resp, err := vc.VoteService.VoteDown(ctx, dto) + if err != nil { + handler.HandleResponse(ctx, err, schema.ErrTypeToast) + } else { + handler.HandleResponse(ctx, err, resp) + } +} + +// UserVotes godoc +// @Summary user's votes +// @Description user's vote +// @Tags Activity +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param page query int false "page size" +// @Param page_size query int false "page size" +// @Success 200 {object} handler.RespBody{data=pager.PageModel{list=[]schema.GetVoteWithPageResp}} +// @Router /answer/api/v1/personal/vote/page [get] +func (vc *VoteController) UserVotes(ctx *gin.Context) { + req := schema.GetVoteWithPageReq{} + req.UserID = middleware.GetLoginUserIDFromContext(ctx) + if handler.BindAndCheck(ctx, &req) { + return + } + if req.Page == 0 { + req.Page = 1 + } + if req.PageSize == 0 { + req.PageSize = 30 + } + + resp, err := vc.VoteService.ListUserVotes(ctx, req) + if err != nil { + handler.HandleResponse(ctx, err, schema.ErrTypeModal) + } else { + handler.HandleResponse(ctx, err, resp) + } +} diff --git a/internal/controller_backyard/controller.go b/internal/controller_backyard/controller.go new file mode 100644 index 00000000..4c541f36 --- /dev/null +++ b/internal/controller_backyard/controller.go @@ -0,0 +1,11 @@ +package controller_backyard + +import "github.com/google/wire" + +// ProviderSetController is controller providers. +var ProviderSetController = wire.NewSet( + NewReportController, + NewUserBackyardController, + NewThemeController, + NewSiteInfoController, +) diff --git a/internal/controller_backyard/report_controller.go b/internal/controller_backyard/report_controller.go new file mode 100644 index 00000000..6f567b80 --- /dev/null +++ b/internal/controller_backyard/report_controller.go @@ -0,0 +1,77 @@ +package controller_backyard + +import ( + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/report_backyard" + "github.com/segmentfault/answer/pkg/converter" +) + +// ReportController report controller +type ReportController struct { + reportService *report_backyard.ReportBackyardService +} + +// NewReportController new controller +func NewReportController(reportService *report_backyard.ReportBackyardService) *ReportController { + return &ReportController{reportService: reportService} +} + +// ListReportPage godoc +// @Summary list report page +// @Description list report records +// @Security ApiKeyAuth +// @Tags admin +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param status query string true "status" Enums(pending, completed) +// @Param object_type query string true "object_type" Enums(all, question,answer,comment) +// @Param page query int false "page size" +// @Param page_size query int false "page size" +// @Success 200 {object} handler.RespBody +// @Router /answer/admin/api/reports/page [get] +func (rc *ReportController) ListReportPage(ctx *gin.Context) { + var ( + objectType = ctx.Query("object_type") + status = ctx.Query("status") + page = converter.StringToInt(ctx.DefaultQuery("page", "1")) + pageSize = converter.StringToInt(ctx.DefaultQuery("page_size", "20")) + ) + + dto := schema.GetReportListPageDTO{ + ObjectType: objectType, + Status: status, + Page: page, + PageSize: pageSize, + } + + resp, err := rc.reportService.ListReportPage(ctx, dto) + if err != nil { + handler.HandleResponse(ctx, err, schema.ErrTypeModal) + } else { + handler.HandleResponse(ctx, err, resp) + } +} + +// Handle godoc +// @Summary handle flag +// @Description handle flag +// @Security ApiKeyAuth +// @Tags admin +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param data body schema.ReportHandleReq true "flag" +// @Success 200 {object} handler.RespBody +// @Router /answer/admin/api/report/ [put] +func (rc *ReportController) Handle(ctx *gin.Context) { + req := schema.ReportHandleReq{} + if handler.BindAndCheck(ctx, &req) { + return + } + + err := rc.reportService.HandleReported(ctx, req) + handler.HandleResponse(ctx, err, nil) +} diff --git a/internal/controller_backyard/siteinfo_controller.go b/internal/controller_backyard/siteinfo_controller.go new file mode 100644 index 00000000..e3a11105 --- /dev/null +++ b/internal/controller_backyard/siteinfo_controller.go @@ -0,0 +1,78 @@ +package controller_backyard + +import ( + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service" +) + +type SiteInfoController struct { + siteInfoService *service.SiteInfoService +} + +// NewSiteInfoController new siteinfo controller. +func NewSiteInfoController(siteInfoService *service.SiteInfoService) *SiteInfoController { + return &SiteInfoController{ + siteInfoService: siteInfoService, + } +} + +// GetGeneral godoc +// @Summary Get siteinfo general +// @Description Get siteinfo general +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Success 200 {object} handler.RespBody{data=schema.SiteGeneralResp} +// @Router /answer/admin/api/siteinfo/general [get] +func (sc *SiteInfoController) GetGeneral(ctx *gin.Context) { + resp, err := sc.siteInfoService.GetSiteGeneral(ctx) + handler.HandleResponse(ctx, err, resp) +} + +// GetInterface godoc +// @Summary Get siteinfo interface +// @Description Get siteinfo interface +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Success 200 {object} handler.RespBody{data=schema.SiteInterfaceResp} +// @Router /answer/admin/api/siteinfo/interface [get] +// @Param data body schema.AddCommentReq true "general" +func (sc *SiteInfoController) GetInterface(ctx *gin.Context) { + resp, err := sc.siteInfoService.GetSiteInterface(ctx) + handler.HandleResponse(ctx, err, resp) +} + +// UpdateGeneral godoc +// @Summary Get siteinfo interface +// @Description Get siteinfo interface +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Param data body schema.SiteGeneralReq true "general" +// @Success 200 {object} handler.RespBody{} +// @Router /answer/admin/api/siteinfo/general [put] +func (sc *SiteInfoController) UpdateGeneral(ctx *gin.Context) { + req := schema.SiteGeneralReq{} + handler.BindAndCheck(ctx, &req) + err := sc.siteInfoService.SaveSiteGeneral(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + +// UpdateInterface godoc +// @Summary Get siteinfo interface +// @Description Get siteinfo interface +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Param data body schema.SiteInterfaceReq true "general" +// @Success 200 {object} handler.RespBody{} +// @Router /answer/admin/api/siteinfo/interface [put] +func (sc *SiteInfoController) UpdateInterface(ctx *gin.Context) { + req := schema.SiteInterfaceReq{} + handler.BindAndCheck(ctx, &req) + err := sc.siteInfoService.SaveSiteInterface(ctx, req) + handler.HandleResponse(ctx, err, nil) +} diff --git a/internal/controller_backyard/theme_controller.go b/internal/controller_backyard/theme_controller.go new file mode 100644 index 00000000..6bc512b9 --- /dev/null +++ b/internal/controller_backyard/theme_controller.go @@ -0,0 +1,26 @@ +package controller_backyard + +import ( + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/schema" +) + +type ThemeController struct{} + +// NewThemeController new theme controller. +func NewThemeController() *ThemeController { + return &ThemeController{} +} + +// GetThemeOptions godoc +// @Summary Get theme options +// @Description Get theme options +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Success 200 {object} handler.RespBody{} +// @Router /answer/admin/api/theme/options [get] +func (t *ThemeController) GetThemeOptions(ctx *gin.Context) { + handler.HandleResponse(ctx, nil, schema.GetThemeOptions) +} diff --git a/internal/controller_backyard/user_backyard_controller.go b/internal/controller_backyard/user_backyard_controller.go new file mode 100644 index 00000000..e87b50c6 --- /dev/null +++ b/internal/controller_backyard/user_backyard_controller.go @@ -0,0 +1,84 @@ +package controller_backyard + +import ( + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/handler" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/user_backyard" + "github.com/segmentfault/pacman/errors" +) + +// UserBackyardController user controller +type UserBackyardController struct { + userService *user_backyard.UserBackyardService +} + +// NewUserBackyardController new controller +func NewUserBackyardController(userService *user_backyard.UserBackyardService) *UserBackyardController { + return &UserBackyardController{userService: userService} +} + +// UpdateUserStatus update user +// @Summary update user +// @Description update user +// @Security ApiKeyAuth +// @Tags admin +// @Accept json +// @Produce json +// @Param data body schema.UpdateUserStatusReq true "user" +// @Success 200 {object} handler.RespBody +// @Router /answer/admin/api/user/status [put] +func (uc *UserBackyardController) UpdateUserStatus(ctx *gin.Context) { + req := &schema.UpdateUserStatusReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + err := uc.userService.UpdateUserStatus(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + +// GetUserInfo get user one +// @Summary get user one +// @Description get user one +// @Security ApiKeyAuth +// @Tags admin +// @Accept json +// @Produce json +// @Param id path int true "userid" +// @Success 200 {object} handler.RespBody{data=schema.GetUserInfoResp} +// Router /user/{id} [get] +func (uc *UserBackyardController) GetUserInfo(ctx *gin.Context) { + userID := ctx.Query("user_id") + if len(userID) == 0 { + handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil) + return + } + + resp, err := uc.userService.GetUserInfo(ctx, userID) + handler.HandleResponse(ctx, err, resp) +} + +// GetUserPage get user page +// @Summary get user page +// @Description get user page +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Param page query int false "page size" +// @Param page_size query int false "page size" +// @Param username query string false "username" +// @Param e_mail query string false "email" +// @Param status query string false "user status" Enums(normal, suspended, deleted, inactive) +// @Success 200 {object} handler.RespBody{data=pager.PageModel{records=[]schema.GetUserPageResp}} +// @Router /answer/admin/api/users/page [get] +func (uc *UserBackyardController) GetUserPage(ctx *gin.Context) { + req := &schema.GetUserPageReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + resp, err := uc.userService.GetUserPage(ctx, req) + handler.HandleResponse(ctx, err, resp) +} diff --git a/internal/entity/activity_entity.go b/internal/entity/activity_entity.go new file mode 100644 index 00000000..a1edac85 --- /dev/null +++ b/internal/entity/activity_entity.go @@ -0,0 +1,31 @@ +package entity + +import "time" + +const ( + ActivityAvailable = 0 + ActivityCancelled = 1 +) + +// Activity activity +type Activity struct { + ID string `xorm:"not null pk autoincr comment('Activity TagID autoincrement') BIGINT(20) id"` + CreatedAt time.Time `xorm:"created comment('create time') TIMESTAMP created_at"` + UpdatedAt time.Time `xorm:"updated comment('update time') TIMESTAMP updated_at"` + UserID string `xorm:"not null comment('the user ID that generated the activity or affected by the activity') index BIGINT(20) user_id"` + TriggerUserID int64 `xorm:"not null default 0 comment('the trigger user TagID that generated the activity or affected by the activity') index BIGINT(20) trigger_user_id"` + ObjectID string `xorm:"not null default 0 comment('the object TagID that affected by the activity') index BIGINT(20) object_id"` + ActivityType int `xorm:"not null comment('activity type, correspond to config id') INT(11) activity_type"` + Cancelled int `xorm:"not null default 0 comment('mark this activity if cancelled or not,default 0(not cancelled)') TINYINT(4) cancelled"` + Rank int `xorm:"not null default 0 comment('rank of current operating user affected') INT(11) rank"` + HasRank int `xorm:"not null default 0 comment('this activity has rank or not') TINYINT(4) has_rank"` +} + +type ActivityRunkSum struct { + Rank int `xorm:"not null default 0 comment('rank of current operating user affected') INT(11) rank"` +} + +// TableName activity table name +func (Activity) TableName() string { + return "activity" +} diff --git a/internal/entity/answer_entity.go b/internal/entity/answer_entity.go new file mode 100644 index 00000000..82a85a74 --- /dev/null +++ b/internal/entity/answer_entity.go @@ -0,0 +1,57 @@ +package entity + +import "time" + +const ( + Answer_Search_OrderBy_Default = "default" + Answer_Search_OrderBy_Time = "updated" + Answer_Search_OrderBy_Vote = "vote" + + AnswerStatusAvailable = 1 + AnswerStatusDeleted = 10 +) + +var CmsAnswerSearchStatus = map[string]int{ + "available": AnswerStatusAvailable, + "deleted": AnswerStatusDeleted, +} + +// Answer answer +type Answer struct { + ID string `xorm:"not null pk autoincr comment('answer id') BIGINT(20) id"` + CreatedAt time.Time `xorm:"created not null default CURRENT_TIMESTAMP TIMESTAMP created_at"` + UpdatedAt time.Time `xorm:"updated not null default CURRENT_TIMESTAMP TIMESTAMP updated_at"` + QuestionID string `xorm:"not null default 0 comment('question id') BIGINT(20) question_id"` + UserID string `xorm:"not null default 0 comment('answer user id') BIGINT(20) user_id"` + OriginalText string `xorm:"not null comment('original content') MEDIUMTEXT original_text"` + ParsedText string `xorm:"not null comment('parsed content') MEDIUMTEXT parsed_text"` + Status int `xorm:"not null default 1 comment(' answer status(available: 1; deleted: 10)') INT(11) status"` + Adopted int `xorm:"not null default 1 comment('adopted (1 failed 2 adopted)') INT(11) adopted"` + CommentCount int `xorm:"not null default 0 comment('comment count') INT(11) comment_count"` + VoteCount int `xorm:"not null default 0 comment('vote count') INT(11) vote_count"` + RevisionID string `xorm:"not null default 0 BIGINT(20) revision_id"` +} + +type AnswerSearch struct { + Answer + Order string `json:"order_by" ` // default or updated + Page int `json:"page" form:"page"` //Query number of pages + PageSize int `json:"page_size" form:"page_size"` //Search page size +} + +type CmsAnswerSearch struct { + Page int `json:"page" form:"page"` //Query number of pages + PageSize int `json:"page_size" form:"page_size"` //Search page size + Status int `json:"-" form:"-"` + StatusStr string `json:"status" form:"status"` //Status 1 Available 2 closed 10 Deleted +} + +type AdminSetAnswerStatusRequest struct { + StatusStr string `json:"status" form:"status"` + AnswerID string `json:"answer_id" form:"answer_id"` +} + +// TableName answer table name +func (Answer) TableName() string { + return "answer" +} diff --git a/internal/entity/auth_user_entity.go b/internal/entity/auth_user_entity.go new file mode 100644 index 00000000..2e846833 --- /dev/null +++ b/internal/entity/auth_user_entity.go @@ -0,0 +1,8 @@ +package entity + +// UserCacheInfo 用户缓存信息 +type UserCacheInfo struct { + UserID string `json:"user_id"` + UserStatus int `json:"user_status"` + EmailStatus int `json:"email_status"` +} diff --git a/internal/entity/collection_entity.go b/internal/entity/collection_entity.go new file mode 100644 index 00000000..ccef2d43 --- /dev/null +++ b/internal/entity/collection_entity.go @@ -0,0 +1,26 @@ +package entity + +import "time" + +const () + +// Collection collection +type Collection struct { + ID string `xorm:"not null pk default 0 comment('collection id') BIGINT(20) id"` + UserID string `xorm:"not null default 0 comment('user id') BIGINT(20) user_id"` + ObjectID string `xorm:"not null default 0 comment('object id') BIGINT(20) object_id"` + UserCollectionGroupID string `xorm:"not null default 0 comment('user collection group id') BIGINT(20) user_collection_group_id"` + CreatedAt time.Time `xorm:"created not null default CURRENT_TIMESTAMP TIMESTAMP created_at"` + UpdatedAt time.Time `xorm:"updated not null default CURRENT_TIMESTAMP TIMESTAMP updated_at"` +} + +type CollectionSearch struct { + Collection + Page int `json:"page" form:"page"` //Query number of pages + PageSize int `json:"page_size" form:"page_size"` //Search page size +} + +// TableName collection table name +func (Collection) TableName() string { + return "collection" +} diff --git a/internal/entity/collection_group_entity.go b/internal/entity/collection_group_entity.go new file mode 100644 index 00000000..85fb3e3d --- /dev/null +++ b/internal/entity/collection_group_entity.go @@ -0,0 +1,18 @@ +package entity + +import "time" + +// CollectionGroup collection group +type CollectionGroup struct { + ID string `xorm:"not null pk autoincr BIGINT(20) id"` + UserID string `xorm:"not null default 0 BIGINT(20) user_id"` + Name string `xorm:"not null default '' comment('the collection group name') VARCHAR(50) name"` + DefaultGroup int `xorm:"not null default 1 comment('mark this group is default, default 1') INT(11) default_group"` + CreatedAt time.Time `xorm:"created not null default CURRENT_TIMESTAMP TIMESTAMP created_at"` + UpdatedAt time.Time `xorm:"updated not null default CURRENT_TIMESTAMP TIMESTAMP updated_at"` +} + +// TableName collection group table name +func (CollectionGroup) TableName() string { + return "collection_group" +} diff --git a/internal/entity/comment_entity.go b/internal/entity/comment_entity.go new file mode 100644 index 00000000..53f145ae --- /dev/null +++ b/internal/entity/comment_entity.go @@ -0,0 +1,69 @@ +package entity + +import ( + "database/sql" + "fmt" + "time" + + "github.com/segmentfault/answer/pkg/converter" +) + +const ( + CommentStatusAvailable = 1 + CommentStatusDeleted = 10 +) + +// Comment comment +type Comment struct { + ID string `xorm:"not null pk autoincr comment('comment id') BIGINT(20) id"` + CreatedAt time.Time `xorm:"created comment('create time') TIMESTAMP created_at"` + UpdatedAt time.Time `xorm:"updated comment('update time') TIMESTAMP updated_at"` + UserID string `xorm:"not null default 0 comment('user id') BIGINT(20) user_id"` + ReplyUserID sql.NullInt64 `xorm:"comment('reply user id') BIGINT(20) reply_user_id"` + ReplyCommentID sql.NullInt64 `xorm:"comment('reply comment id') BIGINT(20) reply_comment_id"` + ObjectID string `xorm:"not null default 0 comment('object id') BIGINT(20) object_id"` + QuestionID string `xorm:"not null default 0 comment('question id') BIGINT(20) question_id"` + VoteCount int `xorm:"not null default 0 comment('user vote amount') INT(11) vote_count"` + Status int `xorm:"not null default 0 comment('comment status(available: 1; deleted: 10)') TINYINT(4) status"` + OriginalText string `xorm:"not null comment('original comment content') MEDIUMTEXT original_text"` + ParsedText string `xorm:"not null comment('parsed comment content') MEDIUMTEXT parsed_text"` +} + +// TableName comment table name +func (c *Comment) TableName() string { + return "comment" +} + +// GetReplyUserID get reply user id +func (c *Comment) GetReplyUserID() string { + if c.ReplyUserID.Valid { + return fmt.Sprintf("%d", c.ReplyUserID.Int64) + } + return "" +} + +// GetReplyCommentID get reply comment id +func (c *Comment) GetReplyCommentID() string { + if c.ReplyCommentID.Valid { + return fmt.Sprintf("%d", c.ReplyCommentID.Int64) + } + return "" +} + +// SetReplyUserID set reply user id +func (c *Comment) SetReplyUserID(str string) { + if len(str) > 0 { + c.ReplyUserID = sql.NullInt64{Int64: converter.StringToInt64(str), Valid: true} + } else { + c.ReplyUserID = sql.NullInt64{Valid: false} + } +} + +// SetReplyCommentID set reply comment id +func (c *Comment) SetReplyCommentID(str string) { + if len(str) > 0 { + c.ReplyCommentID = sql.NullInt64{Int64: converter.StringToInt64(str), Valid: true} + } else { + c.ReplyCommentID = sql.NullInt64{Valid: false} + } +} diff --git a/internal/entity/config_entity.go b/internal/entity/config_entity.go new file mode 100644 index 00000000..87a27213 --- /dev/null +++ b/internal/entity/config_entity.go @@ -0,0 +1,13 @@ +package entity + +// Config config +type Config struct { + ID int `xorm:"not null pk autoincr comment('config id') INT(11) id"` + Key string `xorm:"comment('the config key') unique VARCHAR(32) key"` + Value string `xorm:"comment('the config value, custom data structures and types') VARCHAR(128) value"` +} + +// TableName config table name +func (Config) TableName() string { + return "config" +} diff --git a/internal/entity/meta_entity.go b/internal/entity/meta_entity.go new file mode 100644 index 00000000..341c1bff --- /dev/null +++ b/internal/entity/meta_entity.go @@ -0,0 +1,25 @@ +package entity + +import "time" + +const ( + QuestionEditSummaryKey = "question.edit.summary" + QuestionCloseReasonKey = "question.close.reason" + AnswerEditSummaryKey = "answer.edit.summary" + TagEditSummaryKey = "tag.edit.summary" +) + +// Meta meta +type Meta struct { + ID int `xorm:"not null pk autoincr comment('id') INT(10) id"` + CreatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP created comment('created time') TIMESTAMP created_at"` + UpdatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP updated comment('updated time') TIMESTAMP updated_at"` + ObjectID string `xorm:"not null default 0 comment('object id') BIGINT(20) object_id"` + Key string `xorm:"not null comment('key') VARCHAR(100) key"` + Value string `xorm:"not null comment('value') MEDIUMTEXT value"` +} + +// TableName meta table name +func (Meta) TableName() string { + return "meta" +} diff --git a/internal/entity/notification_entity.go b/internal/entity/notification_entity.go new file mode 100644 index 00000000..5f6b5e93 --- /dev/null +++ b/internal/entity/notification_entity.go @@ -0,0 +1,21 @@ +package entity + +import "time" + +// Notification notification +type Notification struct { + ID string `xorm:"not null pk autoincr comment('notification id') BIGINT(20) id"` + UserID string `xorm:"not null default 0 comment('user id') BIGINT(20) user_id"` + ObjectID string `xorm:"not null default 0 comment('object id') index BIGINT(20) object_id"` + Content string `xorm:"not null comment('notification content') TEXT content"` + Type int `xorm:"not null default 0 comment('notification type( 1 inbox 2 achievement)') INT(11) type"` + IsRead int `xorm:"not null default 1 comment('read status(unread: 1; read 2)') INT(11) is_read"` + Status int `xorm:"not null default 1 comment('notification status(normal: 1; delete 2)') INT(11) status"` + CreatedAt time.Time `xorm:"created comment('create time') TIMESTAMP created_at"` + UpdatedAt time.Time `xorm:"comment('update time') TIMESTAMP updated_at"` +} + +// TableName notification table name +func (Notification) TableName() string { + return "notification" +} diff --git a/internal/entity/notification_read_entity.go b/internal/entity/notification_read_entity.go new file mode 100644 index 00000000..5e25b018 --- /dev/null +++ b/internal/entity/notification_read_entity.go @@ -0,0 +1,18 @@ +package entity + +import "time" + +// NotificationRead notification read record +type NotificationRead struct { + ID int `xorm:"not null pk autoincr comment('id') INT(11) id"` + CreatedAt time.Time `xorm:"created comment('create time') TIMESTAMP created_at"` + UpdatedAt time.Time `xorm:"updated comment('update time') TIMESTAMP updated_at"` + UserID int64 `xorm:"not null default 0 comment('user id') BIGINT(20) user_id"` + MessageID int64 `xorm:"not null default 0 comment('message id') BIGINT(20) message_id"` + IsRead int `xorm:"not null default 1 comment('read status(unread: 1; read 2)') INT(11) is_read"` +} + +// TableName notification read record table name +func (NotificationRead) TableName() string { + return "notification_read" +} diff --git a/internal/entity/question_entity.go b/internal/entity/question_entity.go new file mode 100644 index 00000000..1de02453 --- /dev/null +++ b/internal/entity/question_entity.go @@ -0,0 +1,49 @@ +package entity + +import ( + "time" +) + +const ( + QuestionStatusAvailable = 1 + QuestionStatusclosed = 2 + QuestionStatusDeleted = 10 +) + +var CmsQuestionSearchStatus = map[string]int{ + "available": QuestionStatusAvailable, + "closed": QuestionStatusclosed, + "deleted": QuestionStatusDeleted, +} + +type QuestionTag struct { + Question `xorm:"extends"` + TagRel `xorm:"extends"` +} + +// Question question +type Question struct { + ID string `xorm:"not null pk comment('question id') BIGINT(20) id"` + UserID string `xorm:"not null default 0 comment('user id') BIGINT(20) user_id"` + Title string `xorm:"not null default '' comment('question title') VARCHAR(255) title"` + OriginalText string `xorm:"not null comment('original content') MEDIUMTEXT original_text"` + ParsedText string `xorm:"not null comment('parsed content') MEDIUMTEXT parsed_text"` + Status int `xorm:"not null default 1 comment(' question status(available: 1; deleted: 10)') INT(11) status"` + ViewCount int `xorm:"not null default 0 comment('view count') INT(11) view_count"` + UniqueViewCount int `xorm:"not null default 0 comment('unique view count') INT(11) unique_view_count"` + VoteCount int `xorm:"not null default 0 comment('vote count') INT(11) vote_count"` + AnswerCount int `xorm:"not null default 0 comment('answer count') INT(11) answer_count"` + CollectionCount int `xorm:"not null default 0 comment('collection count') INT(11) collection_count"` + FollowCount int `xorm:"not null default 0 comment('follow count') INT(11) follow_count"` + AcceptedAnswerID string `xorm:"not null default 0 comment('accepted answer id') BIGINT(20) accepted_answer_id"` + LastAnswerID string `xorm:"not null default 0 comment('last answer id') BIGINT(20) last_answer_id"` + CreatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP comment('create time') TIMESTAMP created_at"` + UpdatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP comment('update time') TIMESTAMP updated_at"` + PostUpdateTime time.Time `xorm:"default CURRENT_TIMESTAMP comment('answer the last update time') TIMESTAMP post_update_time"` + RevisionID string `xorm:"not null default 0 BIGINT(20) revision_id"` +} + +// TableName question table name +func (Question) TableName() string { + return "question" +} diff --git a/internal/entity/report_entity.go b/internal/entity/report_entity.go new file mode 100644 index 00000000..53a0d510 --- /dev/null +++ b/internal/entity/report_entity.go @@ -0,0 +1,38 @@ +package entity + +import "time" + +const ( + ReportStatusPending = 1 + ReportStatusCompleted = 2 + ReportStatusDeleted = 10 +) + +var ( + ReportStatus = map[string]int{ + "pending": ReportStatusPending, + "completed": ReportStatusCompleted, + "deleted": ReportStatusDeleted, + } +) + +// Report report +type Report struct { + ID string `xorm:"not null pk autoincr comment('id') BIGINT(20) id"` + CreatedAt time.Time `xorm:"created comment('create time') TIMESTAMP created_at"` + UpdatedAt time.Time `xorm:"updated comment('update time') TIMESTAMP updated_at"` + UserID string `xorm:"not null comment('reporter user id') BIGINT(20) user_id"` + ReportedUserID string `xorm:"not null comment('reported user id') BIGINT(20) reported_user_id"` + ObjectID string `xorm:"not null comment('object id') BIGINT(20) object_id"` + ObjectType int `xorm:"not null default 0 comment('revision type') INT(11) object_type"` + ReportType int `xorm:"not null default 0 comment('report type') INT(11) report_type"` + Content string `xorm:"not null comment('report content') TEXT content"` + FlagedType int `xorm:"not null default 0 comment('flaged type') INT(11) flaged_type"` + FlagedContent string `xorm:"not null comment('flaged content') TEXT flaged_content"` + Status int `xorm:"not null default 1 comment('status(normal: 1; delete 2)') INT(11) status"` +} + +// TableName report table name +func (Report) TableName() string { + return "report" +} diff --git a/internal/entity/revision_entity.go b/internal/entity/revision_entity.go new file mode 100644 index 00000000..35ea1b0c --- /dev/null +++ b/internal/entity/revision_entity.go @@ -0,0 +1,22 @@ +package entity + +import "time" + +// Revision revision +type Revision struct { + ID string `xorm:"not null pk autoincr comment('id') BIGINT(20) id"` + CreatedAt time.Time `xorm:"created comment('create time') TIMESTAMP created_at"` + UpdatedAt time.Time `xorm:"updated comment('update time') TIMESTAMP updated_at"` + UserID string `xorm:"not null default 0 comment('user id') BIGINT(20) user_id"` + ObjectType int `xorm:"not null default 0 comment('revision type(question: 1; answer 2; tag 3)') INT(11) object_type"` + ObjectID string `xorm:"not null default 0 comment('object id') BIGINT(20) object_id"` + Title string `xorm:"not null default '' comment('title') VARCHAR(255) title"` + Content string `xorm:"not null comment('content') TEXT content"` + Log string `xorm:"comment('log') VARCHAR(255) log"` + Status int `xorm:"not null default 1 comment('revision status(normal: 1; delete 2)') INT(11) status"` +} + +// TableName revision table name +func (Revision) TableName() string { + return "revision" +} diff --git a/internal/entity/site_info.go b/internal/entity/site_info.go new file mode 100644 index 00000000..8e01ee6d --- /dev/null +++ b/internal/entity/site_info.go @@ -0,0 +1,18 @@ +package entity + +import "time" + +// SiteInfo site information setting +type SiteInfo struct { + ID string `xorm:"not null pk autoincr comment('id') INT(11) id"` + CreatedAt time.Time `xorm:"created comment('create time') TIMESTAMP created_at"` + UpdatedAt time.Time `xorm:"updated comment('update time') TIMESTAMP updated_at"` + Type string `xorm:"not null comment('content') VARCHAR(64) type"` + Content string `xorm:"not null comment('content') MEDIUMTEXT content"` + Status int `xorm:"not null default 1 comment('site info status(available: 1; deleted: 10)') INT(11) status"` +} + +// TableName table name +func (*SiteInfo) TableName() string { + return "site_info" +} diff --git a/internal/entity/tag_entity.go b/internal/entity/tag_entity.go new file mode 100644 index 00000000..0a682e20 --- /dev/null +++ b/internal/entity/tag_entity.go @@ -0,0 +1,30 @@ +package entity + +import "time" + +const ( + TagStatusAvailable = 1 + TagStatusDeleted = 10 +) + +// Tag tag +type Tag struct { + ID string `xorm:"not null comment('tag_id') BIGINT(20) id"` + CreatedAt time.Time `xorm:"created comment('create time') TIMESTAMP created_at"` + UpdatedAt time.Time `xorm:"updated comment('update time') TIMESTAMP updated_at"` + MainTagID int64 `xorm:"not null default 0 comment('main tag id') BIGINT(20) main_tag_id"` + MainTagSlugName string `xorm:"default '' comment('main_tag_slug_name') VARCHAR(50) main_tag_slug_name"` + SlugName string `xorm:"default '' comment('slug_name') unique VARCHAR(50) slug_name"` + DisplayName string `xorm:"not null default '' comment('display_name') VARCHAR(50) display_name"` + OriginalText string `xorm:"not null comment('original comment content') MEDIUMTEXT original_text"` + ParsedText string `xorm:"not null comment('parsed comment content') MEDIUMTEXT parsed_text"` + FollowCount int `xorm:"not null default 0 comment('associated follow count') INT(11) follow_count"` + QuestionCount int `xorm:"not null default 0 comment('associated question count') INT(11) question_count"` + Status int `xorm:"not null default 1 comment('tag status(available: 1; deleted: 10)') INT(11) status"` + RevisionID string `xorm:"not null default 0 BIGINT(20) revision_id"` +} + +// TableName tag table name +func (Tag) TableName() string { + return "tag" +} diff --git a/internal/entity/tag_rel_entity.go b/internal/entity/tag_rel_entity.go new file mode 100644 index 00000000..ac778fe8 --- /dev/null +++ b/internal/entity/tag_rel_entity.go @@ -0,0 +1,23 @@ +package entity + +import "time" + +const ( + TagRelStatusAvailable = 1 + TagRelStatusDeleted = 10 +) + +// TagRel tag relation +type TagRel struct { + ID int64 `xorm:"not null pk autoincr comment('tag_list_id') BIGINT(20) id"` + CreatedAt time.Time `xorm:"created comment('create time') TIMESTAMP created_at"` + UpdatedAt time.Time `xorm:"updated comment('update time') TIMESTAMP updated_at"` + ObjectID string `xorm:"not null comment('object_id') index BIGINT(20) object_id"` + TagID string `xorm:"not null comment('tag_id') index BIGINT(20) tag_id"` + Status int `xorm:"not null default 1 comment('tag_list_status(available: 1; deleted: 10)') INT(11) status"` +} + +// TableName tag list table name +func (TagRel) TableName() string { + return "tag_rel" +} diff --git a/internal/entity/uniqid_entity.go b/internal/entity/uniqid_entity.go new file mode 100644 index 00000000..f186b377 --- /dev/null +++ b/internal/entity/uniqid_entity.go @@ -0,0 +1,12 @@ +package entity + +// Uniqid uniqid +type Uniqid struct { + ID int64 `xorm:"not null pk autoincr comment('uniqid_id') BIGINT(20) id"` + UniqidType int `xorm:"not null default 0 comment('uniqid_type') INT(11) uniqid_type"` +} + +// TableName uniqid table name +func (Uniqid) TableName() string { + return "uniqid" +} diff --git a/internal/entity/user_entity.go b/internal/entity/user_entity.go new file mode 100644 index 00000000..813d271c --- /dev/null +++ b/internal/entity/user_entity.go @@ -0,0 +1,59 @@ +package entity + +import "time" + +const ( + UserStatusAvailable = 1 + UserStatusSuspended = 9 + UserStatusDeleted = 10 +) + +const ( + EmailStatusAvailable = 1 + EmailStatusToBeVerified = 2 +) + +const ( + UserAdminFlag = 1 +) + +// User user +type User struct { + ID string `xorm:"not null pk autoincr comment('user id') BIGINT(20) id"` + CreatedAt time.Time `xorm:"created comment('create time') TIMESTAMP created_at"` + UpdatedAt time.Time `xorm:"updated comment('update time') TIMESTAMP updated_at"` + SuspendedAt time.Time `xorm:"comment('suspended time') TIMESTAMP suspended_at"` + DeletedAt time.Time `xorm:"comment('delete time') TIMESTAMP deleted_at"` + LastLoginDate time.Time `xorm:"comment('last_login_date') TIMESTAMP last_login_date"` + Username string `xorm:"not null default '' comment('username') VARCHAR(50) username"` + Pass string `xorm:"not null default '' comment('password') VARCHAR(255) pass"` + EMail string `xorm:"not null comment('email') VARCHAR(100) e_mail"` + MailStatus int `xorm:"not null default 2 comment('mail status(1 pass 2 to be verified)') TINYINT(4) mail_status"` + NoticeStatus int `xorm:"not null default 2 comment('notice status(1 on 2off)') INT(11) notice_status"` + FollowCount int `xorm:"not null default 0 comment('follow count') INT(11) follow_count"` + AnswerCount int `xorm:"not null default 0 comment('answer count') INT(11) answer_count"` + QuestionCount int `xorm:"not null default 0 comment('question count') INT(11) question_count"` + Rank int `xorm:"not null default 0 comment('rank') INT(11) rank"` + Status int `xorm:"not null default 1 comment('user status(available: 1; deleted: 10)') INT(11) status"` + AuthorityGroup int `xorm:"not null default 1 comment('authority group') INT(11) authority_group"` + DisplayName string `xorm:"not null default '' comment('display name') VARCHAR(50) display_name"` + Avatar string `xorm:"not null default '' comment('avatar') VARCHAR(255) avatar"` + Mobile string `xorm:"not null comment('mobile') VARCHAR(20) mobile"` + Bio string `xorm:"not null comment('bio markdown') TEXT bio"` + BioHtml string `xorm:"not null comment('bio html') TEXT bio_html"` + Website string `xorm:"not null default '' comment('website') VARCHAR(255) website"` + Location string `xorm:"not null default '' comment('location') VARCHAR(100) location"` + IPInfo string `xorm:"not null default '' comment('ip info') VARCHAR(255) ip_info"` + IsAdmin bool `xorm:"not null default 0 comment('admin flag') INT(11) is_admin"` +} + +// TableName user table name +func (User) TableName() string { + return "user" +} + +type UserSearch struct { + User + Page int `json:"page" form:"page"` //Query number of pages + PageSize int `json:"page_size" form:"page_size"` //Search page size +} diff --git a/internal/entity/user_group_entity.go b/internal/entity/user_group_entity.go new file mode 100644 index 00000000..d2bbbbee --- /dev/null +++ b/internal/entity/user_group_entity.go @@ -0,0 +1,11 @@ +package entity + +// UserGroup user group +type UserGroup struct { + ID int64 `xorm:"not null pk autoincr comment('user group id') unique BIGINT(20) id"` +} + +// TableName user group table name +func (UserGroup) TableName() string { + return "user_group" +} diff --git a/internal/repo/activity/answer_repo.go b/internal/repo/activity/answer_repo.go new file mode 100644 index 00000000..74a06770 --- /dev/null +++ b/internal/repo/activity/answer_repo.go @@ -0,0 +1,336 @@ +package activity + +import ( + "context" + + "github.com/segmentfault/answer/internal/base/constant" + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/activity" + "github.com/segmentfault/answer/internal/service/activity_common" + "github.com/segmentfault/answer/internal/service/notice_queue" + "github.com/segmentfault/answer/internal/service/rank" + "github.com/segmentfault/pacman/errors" + "github.com/segmentfault/pacman/log" + "xorm.io/xorm" +) + +// AnswerActivityRepo answer accepted +type AnswerActivityRepo struct { + data *data.Data + activityRepo activity_common.ActivityRepo + userRankRepo rank.UserRankRepo +} + +const ( + acceptAction = "accept" + acceptedAction = "accepted" + acceptCancelAction = "accept_cancel" + acceptedCancelAction = "accepted_cancel" +) + +var ( + acceptActionList = []string{acceptAction, acceptedAction} + acceptCancelActionList = []string{acceptCancelAction, acceptedCancelAction} +) + +// NewAnswerActivityRepo new repository +func NewAnswerActivityRepo( + data *data.Data, + activityRepo activity_common.ActivityRepo, + userRankRepo rank.UserRankRepo, +) activity.AnswerActivityRepo { + return &AnswerActivityRepo{ + + data: data, + activityRepo: activityRepo, + userRankRepo: userRankRepo, + } +} + +// NewQuestionActivityRepo new repository +func NewQuestionActivityRepo( + data *data.Data, + activityRepo activity_common.ActivityRepo, + userRankRepo rank.UserRankRepo, +) activity.QuestionActivityRepo { + return &AnswerActivityRepo{ + data: data, + activityRepo: activityRepo, + userRankRepo: userRankRepo, + } +} + +func (ar *AnswerActivityRepo) DeleteQuestion(ctx context.Context, questionID string) (err error) { + questionInfo := &entity.Question{} + exist, err := ar.data.DB.Where("id = ?", questionID).Get(questionInfo) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + if !exist { + return nil + } + + // get all this object activity + activityList := make([]*entity.Activity, 0) + session := ar.data.DB.Where("has_rank = 1") + session.Where("cancelled = ?", entity.ActivityAvailable) + err = session.Find(&activityList, &entity.Activity{ObjectID: questionID}) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + if len(activityList) == 0 { + return nil + } + + log.Infof("questionInfo %s deleted will rollback activity %d", questionID, len(activityList)) + + _, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) { + for _, act := range activityList { + log.Infof("user %s rollback rank %d", act.UserID, -act.Rank) + _, e := ar.userRankRepo.TriggerUserRank( + ctx, session, act.UserID, -act.Rank, act.ActivityType) + if e != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack() + } + + if _, e := session.Where("id = ?", act.ID).Cols("`cancelled`"). + Update(&entity.Activity{Cancelled: entity.ActivityCancelled}); e != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack() + } + } + return nil, nil + }) + if err != nil { + return err + } + + // get all answers + answerList := make([]*entity.Answer, 0) + err = ar.data.DB.Find(&answerList, &entity.Answer{QuestionID: questionID}) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + for _, answerInfo := range answerList { + err = ar.DeleteAnswer(ctx, answerInfo.ID) + if err != nil { + log.Error(err) + } + } + return +} + +// AcceptAnswer accept other answer +func (ar *AnswerActivityRepo) AcceptAnswer(ctx context.Context, + answerObjID, questionUserID, answerUserID string, isSelf bool) (err error) { + addActivityList := make([]*entity.Activity, 0) + for _, action := range acceptActionList { + // get accept answer need add rank amount + activityType, deltaRank, hasRank, e := ar.activityRepo.GetActivityTypeByObjID(ctx, answerObjID, action) + if e != nil { + return errors.InternalServer(reason.DatabaseError).WithError(e).WithStack() + } + addActivity := &entity.Activity{ + ObjectID: answerObjID, + ActivityType: activityType, + Rank: deltaRank, + HasRank: hasRank, + } + if action == acceptAction { + addActivity.UserID = questionUserID + } else { + addActivity.UserID = answerUserID + } + if isSelf { + addActivity.Rank = 0 + } + addActivityList = append(addActivityList, addActivity) + } + + _, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) { + for _, addActivity := range addActivityList { + existsActivity, exists, e := ar.activityRepo.GetActivity( + ctx, session, answerObjID, addActivity.UserID, addActivity.ActivityType) + if e != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack() + } + if exists && existsActivity.Cancelled == entity.ActivityAvailable { + continue + } + + reachStandard, e := ar.userRankRepo.TriggerUserRank( + ctx, session, addActivity.UserID, addActivity.Rank, addActivity.ActivityType) + if e != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack() + } + if reachStandard { + addActivity.Rank = 0 + } + + if exists { + if _, e := session.Where("id = ?", existsActivity.ID).Cols("`cancelled`"). + Update(&entity.Activity{Cancelled: entity.ActivityAvailable}); e != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack() + } + } else { + if _, e = session.Insert(addActivity); e != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack() + } + } + } + return nil, nil + }) + if err != nil { + return err + } + for _, act := range addActivityList { + msg := &schema.NotificationMsg{ + Type: schema.NotificationTypeAchievement, + ObjectID: act.ObjectID, + ReceiverUserID: act.UserID, + } + if act.UserID == questionUserID { + msg.TriggerUserID = answerUserID + msg.ObjectType = constant.QuestionObjectType + } else { + msg.TriggerUserID = questionUserID + msg.ObjectType = constant.AnswerObjectType + } + notice_queue.AddNotification(msg) + } + + for _, act := range addActivityList { + msg := &schema.NotificationMsg{ + ReceiverUserID: act.UserID, + Type: schema.NotificationTypeInbox, + ObjectID: act.ObjectID, + } + if act.UserID != questionUserID { + msg.TriggerUserID = questionUserID + msg.ObjectType = constant.AnswerObjectType + msg.NotificationAction = constant.AdoptAnswer + notice_queue.AddNotification(msg) + } + } + return err +} + +// CancelAcceptAnswer accept other answer +func (ar *AnswerActivityRepo) CancelAcceptAnswer(ctx context.Context, + answerObjID, questionUserID, answerUserID string) (err error) { + addActivityList := make([]*entity.Activity, 0) + for _, action := range acceptActionList { + // get accept answer need add rank amount + activityType, deltaRank, hasRank, e := ar.activityRepo.GetActivityTypeByObjID(ctx, answerObjID, action) + if e != nil { + return errors.InternalServer(reason.DatabaseError).WithError(e).WithStack() + } + addActivity := &entity.Activity{ + ObjectID: answerObjID, + ActivityType: activityType, + Rank: -deltaRank, + HasRank: hasRank, + } + if action == acceptAction { + addActivity.UserID = questionUserID + } else { + addActivity.UserID = answerUserID + } + addActivityList = append(addActivityList, addActivity) + } + + _, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) { + for _, addActivity := range addActivityList { + existsActivity, exists, e := ar.activityRepo.GetActivity( + ctx, session, answerObjID, addActivity.UserID, addActivity.ActivityType) + if e != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack() + } + if exists && existsActivity.Cancelled == entity.ActivityCancelled { + continue + } + if !exists { + continue + } + + _, e = ar.userRankRepo.TriggerUserRank( + ctx, session, addActivity.UserID, addActivity.Rank, addActivity.ActivityType) + if e != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack() + } + + if _, e := session.Where("id = ?", existsActivity.ID).Cols("`cancelled`"). + Update(&entity.Activity{Cancelled: entity.ActivityCancelled}); e != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack() + } + } + return nil, nil + }) + if err != nil { + return err + } + for _, act := range addActivityList { + msg := &schema.NotificationMsg{ + ReceiverUserID: act.UserID, + Type: schema.NotificationTypeAchievement, + ObjectID: act.ObjectID, + } + if act.UserID == questionUserID { + msg.TriggerUserID = answerUserID + msg.ObjectType = constant.QuestionObjectType + } else { + msg.TriggerUserID = questionUserID + msg.ObjectType = constant.AnswerObjectType + } + notice_queue.AddNotification(msg) + } + return err +} + +func (ar *AnswerActivityRepo) DeleteAnswer(ctx context.Context, answerID string) (err error) { + answerInfo := &entity.Answer{} + exist, err := ar.data.DB.Where("id = ?", answerID).Get(answerInfo) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + if !exist { + return nil + } + + // get all this object activity + activityList := make([]*entity.Activity, 0) + session := ar.data.DB.Where("has_rank = 1") + session.Where("cancelled = ?", entity.ActivityAvailable) + err = session.Find(&activityList, &entity.Activity{ObjectID: answerID}) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + if len(activityList) == 0 { + return nil + } + + log.Infof("answerInfo %s deleted will rollback activity %d", answerID, len(activityList)) + + _, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) { + for _, act := range activityList { + log.Infof("user %s rollback rank %d", act.UserID, -act.Rank) + _, e := ar.userRankRepo.TriggerUserRank( + ctx, session, act.UserID, -act.Rank, act.ActivityType) + if e != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack() + } + + if _, e := session.Where("id = ?", act.ID).Cols("`cancelled`"). + Update(&entity.Activity{Cancelled: entity.ActivityCancelled}); e != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack() + } + } + return nil, nil + }) + if err != nil { + return err + } + return +} diff --git a/internal/repo/activity/follow_repo.go b/internal/repo/activity/follow_repo.go new file mode 100644 index 00000000..69462b7a --- /dev/null +++ b/internal/repo/activity/follow_repo.go @@ -0,0 +1,152 @@ +package activity + +import ( + "context" + + "github.com/segmentfault/answer/internal/service/activity_common" + "github.com/segmentfault/answer/internal/service/follow" + "github.com/segmentfault/answer/pkg/obj" + "github.com/segmentfault/pacman/log" + "xorm.io/builder" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/service/unique" + "github.com/segmentfault/pacman/errors" + "xorm.io/xorm" +) + +// FollowRepo activity repository +type FollowRepo struct { + data *data.Data + uniqueIDRepo unique.UniqueIDRepo + activityRepo activity_common.ActivityRepo +} + +// NewFollowRepo new repository +func NewFollowRepo( + data *data.Data, + uniqueIDRepo unique.UniqueIDRepo, + activityRepo activity_common.ActivityRepo, +) follow.FollowRepo { + return &FollowRepo{ + data: data, + uniqueIDRepo: uniqueIDRepo, + activityRepo: activityRepo, + } +} + +func (ar *FollowRepo) Follow(ctx context.Context, objectId, userId string) error { + activityType, _, _, err := ar.activityRepo.GetActivityTypeByObjID(nil, objectId, "follow") + if err != nil { + return err + } + + _, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) { + var ( + existsActivity entity.Activity + has bool + ) + result = nil + + has, err = session.Where(builder.Eq{"activity_type": activityType}). + And(builder.Eq{"user_id": userId}). + And(builder.Eq{"object_id": objectId}). + Get(&existsActivity) + + if err != nil { + return + } + + if has && existsActivity.Cancelled == 0 { + return + } + + if has { + _, err = session.Where(builder.Eq{"id": existsActivity.ID}). + Cols(`cancelled`). + Update(&entity.Activity{ + Cancelled: 0, + }) + } else { + // update existing activity with new user id and u object id + _, err = session.Insert(&entity.Activity{ + UserID: userId, + ObjectID: objectId, + ActivityType: activityType, + Cancelled: 0, + Rank: 0, + HasRank: 0, + }) + } + + if err != nil { + log.Error(err) + return + } + + // start update followers when everything is fine + err = ar.updateFollows(ctx, session, objectId, 1) + if err != nil { + log.Error(err) + } + + return + }) + + return err +} + +func (ar *FollowRepo) FollowCancel(ctx context.Context, objectId, userId string) error { + activityType, _, _, err := ar.activityRepo.GetActivityTypeByObjID(nil, objectId, "follow") + if err != nil { + return err + } + + _, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) { + var ( + existsActivity entity.Activity + has bool + ) + result = nil + + has, err = session.Where(builder.Eq{"activity_type": activityType}). + And(builder.Eq{"user_id": userId}). + And(builder.Eq{"object_id": objectId}). + Get(&existsActivity) + + if err != nil || !has { + return + } + + if has && existsActivity.Cancelled == 1 { + return + } + if _, err = session.Where("id = ?", existsActivity.ID). + Cols("cancelled"). + Update(&entity.Activity{ + Cancelled: 1, + }); err != nil { + return + } + err = ar.updateFollows(ctx, session, objectId, -1) + return + }) + return err +} + +func (ar *FollowRepo) updateFollows(ctx context.Context, session *xorm.Session, objectId string, follows int) error { + objectType, err := obj.GetObjectTypeStrByObjectID(objectId) + switch objectType { + case "question": + _, err = session.Where("id = ?", objectId).Incr("follow_count", follows).Update(&entity.Question{}) + case "user": + _, err = session.Where("id = ?", objectId).Incr("follow_count", follows).Update(&entity.User{}) + case "tag": + _, err = session.Where("id = ?", objectId).Incr("follow_count", follows).Update(&entity.Tag{}) + default: + err = errors.InternalServer(reason.DisallowFollow).WithMsg("this object can't be followed") + } + return err +} diff --git a/internal/repo/activity/user_active_repo.go b/internal/repo/activity/user_active_repo.go new file mode 100644 index 00000000..3810b5a1 --- /dev/null +++ b/internal/repo/activity/user_active_repo.go @@ -0,0 +1,83 @@ +package activity + +import ( + "context" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/service/activity" + "github.com/segmentfault/answer/internal/service/activity_common" + "github.com/segmentfault/answer/internal/service/config" + "github.com/segmentfault/answer/internal/service/rank" + "github.com/segmentfault/pacman/errors" + "xorm.io/xorm" +) + +// UserActiveActivityRepo answer accepted +type UserActiveActivityRepo struct { + data *data.Data + activityRepo activity_common.ActivityRepo + userRankRepo rank.UserRankRepo + configRepo config.ConfigRepo +} + +const ( + UserActivated = "user.activated" +) + +// NewUserActiveActivityRepo new repository +func NewUserActiveActivityRepo( + data *data.Data, + activityRepo activity_common.ActivityRepo, + userRankRepo rank.UserRankRepo, + configRepo config.ConfigRepo, +) activity.UserActiveActivityRepo { + return &UserActiveActivityRepo{ + data: data, + activityRepo: activityRepo, + userRankRepo: userRankRepo, + configRepo: configRepo, + } +} + +// UserActive accept other answer +func (ar *UserActiveActivityRepo) UserActive(ctx context.Context, userID string) (err error) { + _, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) { + + activityType, err := ar.configRepo.GetConfigType(UserActivated) + if err != nil { + return nil, err + } + deltaRank, err := ar.configRepo.GetInt(UserActivated) + if err != nil { + return nil, err + } + + addActivity := &entity.Activity{ + UserID: userID, + ObjectID: "0", + ActivityType: activityType, + Rank: deltaRank, + HasRank: 1, + } + _, exists, err := ar.activityRepo.GetActivity(ctx, session, "0", addActivity.UserID, activityType) + if err != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + if exists { + return nil, nil + } + + _, err = ar.userRankRepo.TriggerUserRank(ctx, session, addActivity.UserID, addActivity.Rank, activityType) + if err != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + _, err = session.Insert(addActivity) + if err != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return nil, nil + }) + return err +} diff --git a/internal/repo/activity/vote_repo.go b/internal/repo/activity/vote_repo.go new file mode 100644 index 00000000..e96730e8 --- /dev/null +++ b/internal/repo/activity/vote_repo.go @@ -0,0 +1,434 @@ +package activity + +import ( + "context" + "github.com/segmentfault/answer/pkg/converter" + "strings" + + "github.com/segmentfault/answer/internal/base/pager" + "github.com/segmentfault/answer/internal/service/config" + "github.com/segmentfault/answer/internal/service/notice_queue" + "github.com/segmentfault/answer/internal/service/rank" + "github.com/segmentfault/answer/pkg/obj" + + "xorm.io/builder" + + "github.com/segmentfault/answer/internal/service/activity_common" + "github.com/segmentfault/answer/internal/service/unique" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service" + "github.com/segmentfault/pacman/errors" + "xorm.io/xorm" +) + +// VoteRepo activity repository +type VoteRepo struct { + data *data.Data + uniqueIDRepo unique.UniqueIDRepo + configRepo config.ConfigRepo + activityRepo activity_common.ActivityRepo + userRankRepo rank.UserRankRepo + voteCommon activity_common.VoteRepo +} + +// NewVoteRepo new repository +func NewVoteRepo( + data *data.Data, + uniqueIDRepo unique.UniqueIDRepo, + configRepo config.ConfigRepo, + activityRepo activity_common.ActivityRepo, + userRankRepo rank.UserRankRepo, + voteCommon activity_common.VoteRepo) service.VoteRepo { + return &VoteRepo{ + data: data, + uniqueIDRepo: uniqueIDRepo, + configRepo: configRepo, + activityRepo: activityRepo, + userRankRepo: userRankRepo, + voteCommon: voteCommon, + } +} + +var LimitUpActions = map[string][]string{ + "question": {"vote_up", "voted_up"}, + "answer": {"vote_up", "voted_up"}, + "comment": {"vote_up"}, +} + +var LimitDownActions = map[string][]string{ + "question": {"vote_down", "voted_down"}, + "answer": {"vote_down", "voted_down"}, + "comment": {"vote_down"}, +} + +func (vr *VoteRepo) vote(ctx context.Context, objectID string, userID, objectUserId string, actions []string) (resp *schema.VoteResp, err error) { + resp = &schema.VoteResp{} + _, err = vr.data.DB.Transaction(func(session *xorm.Session) (result any, err error) { + result = nil + for _, action := range actions { + var ( + existsActivity entity.Activity + insertActivity entity.Activity + has bool + triggerUserID, + activityUserId string + activityType, deltaRank, hasRank int + ) + + activityUserId, activityType, deltaRank, hasRank, err = vr.CheckRank(ctx, objectID, objectUserId, userID, action) + if err != nil { + return + } + + triggerUserID = userID + if userID == activityUserId { + triggerUserID = "0" + } + + // check is voted up + has, _ = session. + Where(builder.Eq{"object_id": objectID}). + And(builder.Eq{"user_id": activityUserId}). + And(builder.Eq{"trigger_user_id": triggerUserID}). + And(builder.Eq{"activity_type": activityType}). + Get(&existsActivity) + + // is is voted,return + if has && existsActivity.Cancelled == 0 { + return + } + + insertActivity = entity.Activity{ + ObjectID: objectID, + UserID: activityUserId, + TriggerUserID: converter.StringToInt64(triggerUserID), + ActivityType: activityType, + Rank: deltaRank, + HasRank: hasRank, + Cancelled: 0, + } + + // trigger user rank and send notification + if hasRank != 0 { + isReachStandard, err := vr.userRankRepo.TriggerUserRank(ctx, session, activityUserId, deltaRank, activityType) + if err != nil { + return nil, err + } + if isReachStandard { + insertActivity.Rank = 0 + } + + vr.sendNotification(ctx, activityUserId, objectUserId, objectID) + } + + if has { + if _, err = session.Where("id = ?", existsActivity.ID).Cols("`cancelled`"). + Update(&entity.Activity{ + Cancelled: 0, + }); err != nil { + return + } + } else { + _, err = session.Insert(&insertActivity) + if err != nil { + return nil, err + } + } + + // update votes + if action == "vote_down" || action == "vote_up" { + votes := 1 + if action == "vote_down" { + votes = -1 + } + err = vr.updateVotes(ctx, session, objectID, votes) + if err != nil { + return + } + } + } + return + }) + if err != nil { + return + } + + resp, err = vr.GetVoteResultByObjectId(ctx, objectID) + resp.VoteStatus = vr.voteCommon.GetVoteStatus(ctx, objectID, userID) + + return +} + +func (vr *VoteRepo) voteCancel(ctx context.Context, objectID string, userID, objectUserId string, actions []string) (resp *schema.VoteResp, err error) { + resp = &schema.VoteResp{} + _, err = vr.data.DB.Transaction(func(session *xorm.Session) (result any, err error) { + for _, action := range actions { + var ( + existsActivity entity.Activity + has bool + triggerUserID, + activityUserId string + activityType, + deltaRank, hasRank int + ) + result = nil + + activityUserId, activityType, deltaRank, hasRank, err = vr.CheckRank(ctx, objectID, objectUserId, userID, action) + if err != nil { + return + } + + triggerUserID = userID + if userID == activityUserId { + triggerUserID = "0" + } + + has, err = session. + Where(builder.Eq{"user_id": activityUserId}). + And(builder.Eq{"trigger_user_id": triggerUserID}). + And(builder.Eq{"activity_type": activityType}). + And(builder.Eq{"object_id": objectID}). + Get(&existsActivity) + + if !has { + return + } + + if existsActivity.Cancelled == 1 { + return + } + + if _, err = session.Where("id = ?", existsActivity.ID).Cols("`cancelled`"). + Update(&entity.Activity{ + Cancelled: 1, + }); err != nil { + return + } + + // trigger user rank and send notification + if hasRank != 0 { + _, err = vr.userRankRepo.TriggerUserRank(ctx, session, activityUserId, -deltaRank, activityType) + if err != nil { + return + } + + vr.sendNotification(ctx, activityUserId, objectUserId, objectID) + } + + // update votes + if action == "vote_down" || action == "vote_up" { + votes := -1 + if action == "vote_down" { + votes = 1 + } + err = vr.updateVotes(ctx, session, objectID, votes) + if err != nil { + return + } + } + } + + return + }) + if err != nil { + return + } + resp, err = vr.GetVoteResultByObjectId(ctx, objectID) + resp.VoteStatus = vr.voteCommon.GetVoteStatus(ctx, objectID, userID) + return +} + +func (vr *VoteRepo) VoteUp(ctx context.Context, objectID string, userID, objectUserId string) (resp *schema.VoteResp, err error) { + resp = &schema.VoteResp{} + objectType, err := obj.GetObjectTypeStrByObjectID(objectID) + if err != nil { + err = errors.BadRequest(reason.ObjectNotFound) + return + } + + actions, ok := LimitUpActions[objectType] + if !ok { + err = errors.BadRequest(reason.DisallowVote) + return + } + + _, _ = vr.VoteDownCancel(ctx, objectID, userID, objectUserId) + return vr.vote(ctx, objectID, userID, objectUserId, actions) +} + +func (vr *VoteRepo) VoteDown(ctx context.Context, objectID string, userID, objectUserId string) (resp *schema.VoteResp, err error) { + resp = &schema.VoteResp{} + objectType, err := obj.GetObjectTypeStrByObjectID(objectID) + if err != nil { + err = errors.BadRequest(reason.ObjectNotFound) + return + } + actions, ok := LimitDownActions[objectType] + if !ok { + err = errors.BadRequest(reason.DisallowVote) + return + } + + _, _ = vr.VoteUpCancel(ctx, objectID, userID, objectUserId) + return vr.vote(ctx, objectID, userID, objectUserId, actions) +} + +func (vr *VoteRepo) VoteUpCancel(ctx context.Context, objectID string, userID, objectUserId string) (resp *schema.VoteResp, err error) { + var ( + objectType string + ) + resp = &schema.VoteResp{} + + objectType, err = obj.GetObjectTypeStrByObjectID(objectID) + if err != nil { + err = errors.BadRequest(reason.ObjectNotFound) + return + } + actions, ok := LimitUpActions[objectType] + if !ok { + err = errors.BadRequest(reason.DisallowVote) + return + } + + return vr.voteCancel(ctx, objectID, userID, objectUserId, actions) +} + +func (vr *VoteRepo) VoteDownCancel(ctx context.Context, objectID string, userID, objectUserId string) (resp *schema.VoteResp, err error) { + var ( + objectType string + ) + resp = &schema.VoteResp{} + + objectType, err = obj.GetObjectTypeStrByObjectID(objectID) + if err != nil { + err = errors.BadRequest(reason.ObjectNotFound) + return + } + actions, ok := LimitDownActions[objectType] + if !ok { + err = errors.BadRequest(reason.DisallowVote) + return + } + + return vr.voteCancel(ctx, objectID, userID, objectUserId, actions) +} + +func (vr *VoteRepo) CheckRank(ctx context.Context, objectID, objectUserId, userID string, action string) (activityUserId string, activityType, rank, hasRank int, err error) { + activityType, rank, hasRank, err = vr.activityRepo.GetActivityTypeByObjID(ctx, objectID, action) + + if err != nil { + return + } + + activityUserId = userID + if strings.Contains(action, "voted") { + activityUserId = objectUserId + } + + return activityUserId, activityType, rank, hasRank, nil +} + +func (vr *VoteRepo) GetVoteResultByObjectId(ctx context.Context, objectID string) (resp *schema.VoteResp, err error) { + resp = &schema.VoteResp{} + for _, action := range []string{"vote_up", "vote_down"} { + var ( + activity entity.Activity + votes int64 + activityType int + ) + + activityType, _, _, err = vr.activityRepo.GetActivityTypeByObjID(ctx, objectID, action) + + votes, err = vr.data.DB.Where(builder.Eq{"object_id": objectID}). + And(builder.Eq{"activity_type": activityType}). + And(builder.Eq{"cancelled": 0}). + Count(&activity) + + if err != nil { + return + } + + if action == "vote_up" { + resp.UpVotes = int(votes) + } else { + resp.DownVotes = int(votes) + } + } + + resp.Votes = resp.UpVotes - resp.DownVotes + + return resp, nil +} + +func (vr *VoteRepo) ListUserVotes( + ctx context.Context, + userID string, + req schema.GetVoteWithPageReq, + activityTypes []int, +) (voteList []entity.Activity, total int64, err error) { + session := vr.data.DB.NewSession() + cond := builder. + And( + builder.Eq{"user_id": userID}, + builder.Eq{"cancelled": 0}, + builder.In("activity_type", activityTypes), + ) + + session.Where(cond).OrderBy("updated_at desc") + + total, err = pager.Help(req.Page, req.PageSize, &voteList, &entity.Activity{}, session) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// updateVotes +// if votes < 0 Decr object vote_count,otherwise Incr object vote_count +func (vr *VoteRepo) updateVotes(ctx context.Context, session *xorm.Session, objectID string, votes int) (err error) { + var ( + objectType string + e error + ) + + objectType, err = obj.GetObjectTypeStrByObjectID(objectID) + switch objectType { + case "question": + _, err = session.Where("id = ?", objectID).Incr("vote_count", votes).Update(&entity.Question{}) + case "answer": + _, err = session.Where("id = ?", objectID).Incr("vote_count", votes).Update(&entity.Answer{}) + case "comment": + _, err = session.Where("id = ?", objectID).Incr("vote_count", votes).Update(&entity.Comment{}) + default: + e = errors.BadRequest(reason.DisallowVote) + } + + if e != nil { + err = e + } else if err != nil { + err = errors.BadRequest(reason.DatabaseError).WithError(err).WithStack() + } + + return +} + +// sendNotification send rank triggered notification +func (vr *VoteRepo) sendNotification(ctx context.Context, activityUserId, objectUserId, objectID string) { + objectType, err := obj.GetObjectTypeStrByObjectID(objectID) + if err != nil { + return + } + + msg := &schema.NotificationMsg{ + ReceiverUserID: activityUserId, + TriggerUserID: objectUserId, + Type: schema.NotificationTypeAchievement, + ObjectID: objectID, + ObjectType: objectType, + } + notice_queue.AddNotification(msg) +} diff --git a/internal/repo/activity_common/follow.go b/internal/repo/activity_common/follow.go new file mode 100644 index 00000000..dda87e06 --- /dev/null +++ b/internal/repo/activity_common/follow.go @@ -0,0 +1,129 @@ +package activity_common + +import ( + "context" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/service/activity_common" + "github.com/segmentfault/answer/internal/service/unique" + "github.com/segmentfault/answer/pkg/obj" + "github.com/segmentfault/pacman/errors" +) + +// FollowRepo follow repository +type FollowRepo struct { + data *data.Data + uniqueIDRepo unique.UniqueIDRepo + activityRepo activity_common.ActivityRepo +} + +// NewFollowRepo new repository +func NewFollowRepo( + data *data.Data, + uniqueIDRepo unique.UniqueIDRepo, + activityRepo activity_common.ActivityRepo, +) activity_common.FollowRepo { + return &FollowRepo{ + data: data, + uniqueIDRepo: uniqueIDRepo, + activityRepo: activityRepo, + } +} + +// GetFollowAmount get object id's follows +func (ar *FollowRepo) GetFollowAmount(ctx context.Context, objectId string) (follows int, err error) { + objectType, err := obj.GetObjectTypeStrByObjectID(objectId) + if err != nil { + return 0, err + } + switch objectType { + case "question": + model := &entity.Question{} + _, err = ar.data.DB.Where("id = ?", objectId).Cols("`follow_count`").Get(model) + if err == nil { + follows = int(model.FollowCount) + } + case "user": + model := &entity.User{} + _, err = ar.data.DB.Where("id = ?", objectId).Cols("`follow_count`").Get(model) + if err == nil { + follows = int(model.FollowCount) + } + case "tag": + model := &entity.Tag{} + _, err = ar.data.DB.Where("id = ?", objectId).Cols("`follow_count`").Get(model) + if err == nil { + follows = int(model.FollowCount) + } + default: + err = errors.InternalServer(reason.DisallowFollow).WithMsg("this object can't be followed") + } + + if err != nil { + return 0, err + } + return follows, nil +} + +// GetFollowUserIDs get follow userID by objectID +func (ar *FollowRepo) GetFollowUserIDs(ctx context.Context, objectID string) (userIDs []string, err error) { + objectTypeStr, err := obj.GetObjectTypeStrByObjectID(objectID) + if err != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + activityType, err := ar.activityRepo.GetActivityTypeByObjKey(ctx, objectTypeStr, "follow") + if err != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + + userIDs = make([]string, 0) + session := ar.data.DB.Select("user_id") + session.Table(entity.Activity{}.TableName()) + session.Where("object_id = ?", objectID) + session.Where("activity_type = ?", activityType) + session.Where("cancelled = 0") + err = session.Find(&userIDs) + if err != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return userIDs, nil +} + +// GetFollowIDs get all follow id list +func (ar *FollowRepo) GetFollowIDs(ctx context.Context, userID, objectKey string) (followIDs []string, err error) { + followIDs = make([]string, 0) + activityType, err := ar.activityRepo.GetActivityTypeByObjKey(ctx, objectKey, "follow") + session := ar.data.DB.Select("object_id") + session.Table(entity.Activity{}.TableName()) + session.Where("user_id = ? AND activity_type = ?", userID, activityType) + session.Where("cancelled = 0") + err = session.Find(&followIDs) + if err != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return followIDs, nil +} + +// IsFollowed check user if follow object or not +func (ar *FollowRepo) IsFollowed(userId, objectId string) (bool, error) { + activityType, _, _, err := ar.activityRepo.GetActivityTypeByObjID(nil, objectId, "follow") + if err != nil { + return false, err + } + + at := &entity.Activity{} + has, err := ar.data.DB.Where("user_id = ? AND object_id = ? AND activity_type = ?", userId, objectId, activityType).Get(at) + if err != nil { + return false, err + } + if !has { + return false, nil + } + if at.Cancelled == 1 { + return false, nil + } else { + return true, nil + } +} diff --git a/internal/repo/activity_common/vote.go b/internal/repo/activity_common/vote.go new file mode 100644 index 00000000..31b3b7f7 --- /dev/null +++ b/internal/repo/activity_common/vote.go @@ -0,0 +1,40 @@ +package activity_common + +import ( + "context" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/service/activity_common" + "github.com/segmentfault/answer/internal/service/unique" +) + +// VoteRepo activity repository +type VoteRepo struct { + data *data.Data + uniqueIDRepo unique.UniqueIDRepo + activityRepo activity_common.ActivityRepo +} + +// NewVoteRepo new repository +func NewVoteRepo(data *data.Data, activityRepo activity_common.ActivityRepo) activity_common.VoteRepo { + return &VoteRepo{ + data: data, + activityRepo: activityRepo, + } +} + +func (vr *VoteRepo) GetVoteStatus(ctx context.Context, objectId, userId string) (status string) { + for _, action := range []string{"vote_up", "vote_down"} { + at := &entity.Activity{} + activityType, _, _, err := vr.activityRepo.GetActivityTypeByObjID(ctx, objectId, action) + has, err := vr.data.DB.Where("object_id =? AND cancelled=0 AND activity_type=? AND user_id=?", objectId, activityType, userId).Get(at) + if err != nil { + return "" + } + if has { + return action + } + } + return "" +} diff --git a/internal/repo/activity_repo.go b/internal/repo/activity_repo.go new file mode 100644 index 00000000..5893ad1c --- /dev/null +++ b/internal/repo/activity_repo.go @@ -0,0 +1,90 @@ +package repo + +import ( + "context" + "fmt" + + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/service/activity_common" + "github.com/segmentfault/answer/pkg/obj" + "xorm.io/builder" + "xorm.io/xorm" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/service/config" + "github.com/segmentfault/answer/internal/service/unique" + "github.com/segmentfault/pacman/errors" +) + +// ActivityRepo activity repository +type ActivityRepo struct { + data *data.Data + uniqueIDRepo unique.UniqueIDRepo + configRepo config.ConfigRepo +} + +// NewActivityRepo new repository +func NewActivityRepo( + data *data.Data, + uniqueIDRepo unique.UniqueIDRepo, + configRepo config.ConfigRepo, +) activity_common.ActivityRepo { + return &ActivityRepo{ + data: data, + uniqueIDRepo: uniqueIDRepo, + configRepo: configRepo, + } +} + +func (ar *ActivityRepo) GetActivityTypeByObjID(ctx context.Context, objectId string, action string) (activityType, rank, hasRank int, err error) { + objectKey, err := obj.GetObjectTypeStrByObjectID(objectId) + if err != nil { + return + } + + confKey := fmt.Sprintf("%s.%s", objectKey, action) + activityType, err = ar.configRepo.GetConfigType(confKey) + + rank, err = ar.configRepo.GetInt(confKey) + hasRank = 0 + if rank != 0 { + hasRank = 1 + } + return +} + +func (ar *ActivityRepo) GetActivityTypeByObjKey(ctx context.Context, objectKey, action string) (activityType int, err error) { + confKey := fmt.Sprintf("%s.%s", objectKey, action) + activityType, err = ar.configRepo.GetConfigType(confKey) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +func (ar *ActivityRepo) GetActivity(ctx context.Context, session *xorm.Session, + objectID, userID string, activityType int) (existsActivity *entity.Activity, exist bool, err error) { + existsActivity = &entity.Activity{} + exist, err = session. + Where(builder.Eq{"object_id": objectID}). + And(builder.Eq{"user_id": userID}). + And(builder.Eq{"activity_type": activityType}). + Get(existsActivity) + return +} + +func (ar *ActivityRepo) GetUserIDObjectIDActivitySum(ctx context.Context, userID, objectID string) (int, error) { + sum := &entity.ActivityRunkSum{} + _, err := ar.data.DB.Table(entity.Activity{}.TableName()). + Select("sum(rank) as rank"). + Where("user_id =?", userID). + And("object_id = ?", objectID). + And("cancelled =0"). + Get(sum) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return 0, err + } + return sum.Rank, nil +} diff --git a/internal/repo/answer_repo.go b/internal/repo/answer_repo.go new file mode 100644 index 00000000..9045a8fb --- /dev/null +++ b/internal/repo/answer_repo.go @@ -0,0 +1,226 @@ +package repo + +import ( + "context" + + "github.com/segmentfault/answer/internal/base/constant" + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/pager" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/activity_common" + answercommon "github.com/segmentfault/answer/internal/service/answer_common" + "github.com/segmentfault/answer/internal/service/rank" + "github.com/segmentfault/answer/internal/service/unique" + "github.com/segmentfault/pacman/errors" +) + +// answerRepo answer repository +type answerRepo struct { + data *data.Data + uniqueIDRepo unique.UniqueIDRepo + userRankRepo rank.UserRankRepo + activityRepo activity_common.ActivityRepo +} + +// NewAnswerRepo new repository +func NewAnswerRepo( + data *data.Data, + uniqueIDRepo unique.UniqueIDRepo, + userRankRepo rank.UserRankRepo, + activityRepo activity_common.ActivityRepo, +) answercommon.AnswerRepo { + return &answerRepo{ + data: data, + uniqueIDRepo: uniqueIDRepo, + userRankRepo: userRankRepo, + activityRepo: activityRepo, + } +} + +// AddAnswer add answer +func (ar *answerRepo) AddAnswer(ctx context.Context, answer *entity.Answer) (err error) { + ID, err := ar.uniqueIDRepo.GenUniqueIDStr(ctx, answer.TableName()) + if err != nil { + errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + answer.ID = ID + _, err = ar.data.DB.Insert(answer) + + if err != nil { + errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return nil +} + +// RemoveAnswer delete answer +func (ar *answerRepo) RemoveAnswer(ctx context.Context, id string) (err error) { + answer := &entity.Answer{ + ID: id, + Status: entity.AnswerStatusDeleted, + } + _, err = ar.data.DB.Where("id = ?", id).Cols("status").Update(answer) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return nil +} + +// UpdateAnswer update answer +func (ar *answerRepo) UpdateAnswer(ctx context.Context, answer *entity.Answer, Colar []string) (err error) { + _, err = ar.data.DB.ID(answer.ID).Cols(Colar...).Update(answer) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return err +} + +func (ar *answerRepo) UpdateAnswerStatus(ctx context.Context, answer *entity.Answer) (err error) { + _, err = ar.data.DB.Where("id =?", answer.ID).Cols("status").Update(answer) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetAnswer get answer one +func (ar *answerRepo) GetAnswer(ctx context.Context, id string) ( + answer *entity.Answer, exist bool, err error) { + answer = &entity.Answer{} + exist, err = ar.data.DB.ID(id).Get(answer) + if err != nil { + return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetAnswerList get answer list all +func (ar *answerRepo) GetAnswerList(ctx context.Context, answer *entity.Answer) (answerList []*entity.Answer, err error) { + answerList = make([]*entity.Answer, 0) + err = ar.data.DB.Find(answerList, answer) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetAnswerPage get answer page +func (ar *answerRepo) GetAnswerPage(ctx context.Context, page, pageSize int, answer *entity.Answer) (answerList []*entity.Answer, total int64, err error) { + answerList = make([]*entity.Answer, 0) + total, err = pager.Help(page, pageSize, answerList, answer, ar.data.DB.NewSession()) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// UpdateAdopted +// If no answer is selected, the answer id can be 0 +func (ar *answerRepo) UpdateAdopted(ctx context.Context, id string, questionId string) error { + if questionId == "" { + return nil + } + var data entity.Answer + data.ID = id + + data.Adopted = schema.Answer_Adopted_Failed + _, err := ar.data.DB.Where("question_id =?", questionId).Cols("adopted").Update(&data) + if err != nil { + return err + } + if id != "" { + data.Adopted = schema.Answer_Adopted_Enable + _, err = ar.data.DB.Where("id = ?", id).Cols("adopted").Update(&data) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + } + return nil +} + +// GetByID +func (ar *answerRepo) GetByID(ctx context.Context, id string) (*entity.Answer, bool, error) { + var resp entity.Answer + has, err := ar.data.DB.Where("id =? ", id).Get(&resp) + if err != nil { + return &resp, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return &resp, has, nil +} + +func (ar *answerRepo) GetByUserIdQuestionId(ctx context.Context, userId string, questionId string) (*entity.Answer, bool, error) { + var resp entity.Answer + has, err := ar.data.DB.Where("question_id =? and user_id = ?", questionId, userId).Get(&resp) + if err != nil { + return &resp, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return &resp, has, nil +} + +// SearchList +func (ar *answerRepo) SearchList(ctx context.Context, search *entity.AnswerSearch) ([]*entity.Answer, int64, error) { + var count int64 + var err error + rows := make([]*entity.Answer, 0) + if search.Page > 0 { + search.Page = search.Page - 1 + } else { + search.Page = 0 + } + if search.PageSize == 0 { + search.PageSize = constant.Default_PageSize + } + offset := search.Page * search.PageSize + session := ar.data.DB.Where("") + + if search.QuestionID != "" { + session = session.And("question_id = ?", search.QuestionID) + } + if len(search.UserID) > 0 { + session = session.And("user_id = ?", search.UserID) + } + if search.Order == entity.Answer_Search_OrderBy_Time { + session = session.OrderBy("updated_at desc") + } else if search.Order == entity.Answer_Search_OrderBy_Vote { + session = session.OrderBy("vote_count desc") + } else { + session = session.OrderBy("adopted desc,vote_count desc") + } + + session = session.And("status = ?", entity.AnswerStatusAvailable) + + session = session.Limit(search.PageSize, offset) + count, err = session.OrderBy("updated_at desc").FindAndCount(&rows) + if err != nil { + return rows, count, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return rows, count, nil +} + +func (ar *answerRepo) CmsSearchList(ctx context.Context, search *entity.CmsAnswerSearch) ([]*entity.Answer, int64, error) { + var count int64 + var err error + if search.Status == 0 { + search.Status = 1 + } + rows := make([]*entity.Answer, 0) + if search.Page > 0 { + search.Page = search.Page - 1 + } else { + search.Page = 0 + } + if search.PageSize == 0 { + search.PageSize = constant.Default_PageSize + } + offset := search.Page * search.PageSize + session := ar.data.DB.Where("") + session = session.And("status =?", search.Status) + session = session.OrderBy("created_at desc") + session = session.Limit(search.PageSize, offset) + count, err = session.FindAndCount(&rows) + if err != nil { + return rows, count, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return rows, count, nil +} diff --git a/internal/repo/auth/auth.go b/internal/repo/auth/auth.go new file mode 100644 index 00000000..c71d4d00 --- /dev/null +++ b/internal/repo/auth/auth.go @@ -0,0 +1,113 @@ +package auth + +import ( + "context" + "encoding/json" + + "github.com/segmentfault/answer/internal/base/constant" + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/service/auth" + "github.com/segmentfault/pacman/errors" +) + +// authRepo activity repository +type authRepo struct { + data *data.Data +} + +func (ar *authRepo) GetUserCacheInfo(ctx context.Context, accessToken string) (userInfo *entity.UserCacheInfo, err error) { + userInfoCache, err := ar.data.Cache.GetString(ctx, constant.UserTokenCacheKey+accessToken) + if err != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + userInfo = &entity.UserCacheInfo{} + err = json.Unmarshal([]byte(userInfoCache), userInfo) + if err != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return userInfo, nil +} + +func (ar *authRepo) GetUserStatus(ctx context.Context, userID string) (userInfo *entity.UserCacheInfo, err error) { + userInfoCache, err := ar.data.Cache.GetString(ctx, constant.UserStatusChangedCacheKey+userID) + if err != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + userInfo = &entity.UserCacheInfo{} + err = json.Unmarshal([]byte(userInfoCache), userInfo) + if err != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return userInfo, nil +} + +func (ar *authRepo) SetUserCacheInfo(ctx context.Context, accessToken string, userInfo *entity.UserCacheInfo) (err error) { + userInfoCache, err := json.Marshal(userInfo) + if err != nil { + return err + } + err = ar.data.Cache.SetString(ctx, constant.UserTokenCacheKey+accessToken, + string(userInfoCache), constant.UserTokenCacheTime) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return nil +} + +func (ar *authRepo) RemoveUserCacheInfo(ctx context.Context, accessToken string) (err error) { + err = ar.data.Cache.Del(ctx, constant.UserTokenCacheKey+accessToken) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return err + } + return nil +} + +func (ar *authRepo) GetCmsUserCacheInfo(ctx context.Context, accessToken string) (userInfo *entity.UserCacheInfo, err error) { + userInfoCache, err := ar.data.Cache.GetString(ctx, constant.AdminTokenCacheKey+accessToken) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return + } + userInfo = &entity.UserCacheInfo{} + err = json.Unmarshal([]byte(userInfoCache), userInfo) + if err != nil { + return nil, err + } + return userInfo, nil +} + +func (ar *authRepo) SetCmsUserCacheInfo(ctx context.Context, accessToken string, userInfo *entity.UserCacheInfo) (err error) { + userInfoCache, err := json.Marshal(userInfo) + if err != nil { + return err + } + + err = ar.data.Cache.SetString(ctx, constant.AdminTokenCacheKey+accessToken, string(userInfoCache), + constant.AdminTokenCacheTime) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return err + } + return nil +} + +func (ar *authRepo) RemoveCmsUserCacheInfo(ctx context.Context, accessToken string) (err error) { + err = ar.data.Cache.Del(ctx, constant.AdminTokenCacheKey+accessToken) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return err + } + return nil +} + +// NewAuthRepo new repository +func NewAuthRepo( + data *data.Data, +) auth.AuthRepo { + return &authRepo{ + data: data, + } +} diff --git a/internal/repo/captcha/captcha.go b/internal/repo/captcha/captcha.go new file mode 100644 index 00000000..b30e180a --- /dev/null +++ b/internal/repo/captcha/captcha.go @@ -0,0 +1,72 @@ +package captcha + +import ( + "context" + "fmt" + "time" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/service/action" + "github.com/segmentfault/pacman/errors" +) + +// captchaRepo captcha repository +type captchaRepo struct { + data *data.Data +} + +// NewCaptchaRepo new repository +func NewCaptchaRepo(data *data.Data) action.CaptchaRepo { + return &captchaRepo{ + data: data, + } +} + +func (cr *captchaRepo) SetActionType(ctx context.Context, ip, actionType string, amount int) (err error) { + cacheKey := fmt.Sprintf("ActionRecord:%s@%s", ip, actionType) + err = cr.data.Cache.SetInt64(ctx, cacheKey, int64(amount), 6*time.Minute) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +func (cr *captchaRepo) GetActionType(ctx context.Context, ip, actionType string) (amount int, err error) { + cacheKey := fmt.Sprintf("ActionRecord:%s@%s", ip, actionType) + res, err := cr.data.Cache.GetInt64(ctx, cacheKey) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + // TODO: cache reflect should return empty when key not found + return int(res), nil +} + +func (cr *captchaRepo) DelActionType(ctx context.Context, ip, actionType string) (err error) { + cacheKey := fmt.Sprintf("ActionRecord:%s@%s", ip, actionType) + err = cr.data.Cache.Del(ctx, cacheKey) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// SetCaptcha set captcha to cache +func (cr *captchaRepo) SetCaptcha(ctx context.Context, key, captcha string) (err error) { + // TODO make cache time to config + err = cr.data.Cache.SetString(ctx, key, captcha, 6*time.Minute) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetCaptcha get captcha from cache +func (cr *captchaRepo) GetCaptcha(ctx context.Context, key string) (captcha string, err error) { + captcha, err = cr.data.Cache.GetString(ctx, key) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + // TODO: cache reflect should return empty when key not found + return captcha, nil +} diff --git a/internal/repo/collection/collection_group_repo.go b/internal/repo/collection/collection_group_repo.go new file mode 100644 index 00000000..405cb6dd --- /dev/null +++ b/internal/repo/collection/collection_group_repo.go @@ -0,0 +1,95 @@ +package collection + +import ( + "context" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/pager" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service" + "github.com/segmentfault/pacman/errors" +) + +// collectionGroupRepo collectionGroup repository +type collectionGroupRepo struct { + data *data.Data +} + +// NewCollectionGroupRepo new repository +func NewCollectionGroupRepo(data *data.Data) service.CollectionGroupRepo { + return &collectionGroupRepo{ + data: data, + } +} + +// AddCollectionGroup add collection group +func (cr *collectionGroupRepo) AddCollectionGroup(ctx context.Context, collectionGroup *entity.CollectionGroup) (err error) { + _, err = cr.data.DB.Insert(collectionGroup) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// AddCollectionDefaultGroup add collection group +func (cr *collectionGroupRepo) AddCollectionDefaultGroup(ctx context.Context, userID string) (collectionGroup *entity.CollectionGroup, err error) { + defaultGroup := &entity.CollectionGroup{ + Name: "default", + DefaultGroup: schema.CG_DEFAULT, + UserID: userID, + } + + _, err = cr.data.DB.Insert(defaultGroup) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return + } + return +} + +// UpdateCollectionGroup update collection group +func (cr *collectionGroupRepo) UpdateCollectionGroup(ctx context.Context, collectionGroup *entity.CollectionGroup, cols []string) (err error) { + _, err = cr.data.DB.ID(collectionGroup.ID).Cols(cols...).Update(collectionGroup) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetCollectionGroup get collection group one +func (cr *collectionGroupRepo) GetCollectionGroup(ctx context.Context, id string) ( + collectionGroup *entity.CollectionGroup, exist bool, err error) { + collectionGroup = &entity.CollectionGroup{} + exist, err = cr.data.DB.ID(id).Get(collectionGroup) + if err != nil { + return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetCollectionGroupPage get collection group page +func (cr *collectionGroupRepo) GetCollectionGroupPage(ctx context.Context, page, pageSize int, collectionGroup *entity.CollectionGroup) (collectionGroupList []*entity.CollectionGroup, total int64, err error) { + collectionGroupList = make([]*entity.CollectionGroup, 0) + + session := cr.data.DB.NewSession() + if collectionGroup.UserID != "" && collectionGroup.UserID != "0" { + session = session.Where("user_id = ?", collectionGroup.UserID) + } + session = session.OrderBy("update_time desc") + + total, err = pager.Help(page, pageSize, collectionGroupList, collectionGroup, session) + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return +} + +func (cr *collectionGroupRepo) GetDefaultID(ctx context.Context, userId string) (collectionGroup *entity.CollectionGroup, has bool, err error) { + collectionGroup = &entity.CollectionGroup{} + has, err = cr.data.DB.Where("user_id =? and default_group = ?", userId, schema.CG_DEFAULT).Get(collectionGroup) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return + } + return +} diff --git a/internal/repo/collection/collection_repo.go b/internal/repo/collection/collection_repo.go new file mode 100644 index 00000000..16170e3a --- /dev/null +++ b/internal/repo/collection/collection_repo.go @@ -0,0 +1,166 @@ +package collection + +import ( + "context" + + "github.com/segmentfault/answer/internal/base/constant" + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/pager" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + collectioncommon "github.com/segmentfault/answer/internal/service/collection_common" + "github.com/segmentfault/answer/internal/service/unique" + "github.com/segmentfault/pacman/errors" +) + +// collectionRepo collection repository +type collectionRepo struct { + data *data.Data + uniqueIDRepo unique.UniqueIDRepo +} + +// NewCollectionRepo new repository +func NewCollectionRepo(data *data.Data, uniqueIDRepo unique.UniqueIDRepo) collectioncommon.CollectionRepo { + return &collectionRepo{ + data: data, + uniqueIDRepo: uniqueIDRepo, + } +} + +// AddCollection add collection +func (cr *collectionRepo) AddCollection(ctx context.Context, collection *entity.Collection) (err error) { + id, err := cr.uniqueIDRepo.GenUniqueIDStr(ctx, collection.TableName()) + if err == nil { + collection.ID = id + _, err = cr.data.DB.Insert(collection) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + } + return nil +} + +// RemoveCollection delete collection +func (cr *collectionRepo) RemoveCollection(ctx context.Context, id string) (err error) { + _, err = cr.data.DB.Where("id =?", id).Delete(&entity.Collection{}) + if err != nil { + errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return nil +} + +// UpdateCollection update collection +func (cr *collectionRepo) UpdateCollection(ctx context.Context, collection *entity.Collection, cols []string) (err error) { + _, err = cr.data.DB.ID(collection.ID).Cols(cols...).Update(collection) + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() +} + +// GetCollection get collection one +func (cr *collectionRepo) GetCollection(ctx context.Context, id int) (collection *entity.Collection, exist bool, err error) { + collection = &entity.Collection{} + exist, err = cr.data.DB.ID(id).Get(collection) + if err != nil { + return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetCollectionList get collection list all +func (cr *collectionRepo) GetCollectionList(ctx context.Context, collection *entity.Collection) (collectionList []*entity.Collection, err error) { + collectionList = make([]*entity.Collection, 0) + err = cr.data.DB.Find(collectionList, collection) + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return +} + +// GetOneByObjectIDAndUser get one by object TagID and user +func (cr *collectionRepo) GetOneByObjectIDAndUser(ctx context.Context, userId string, objectId string) (collection *entity.Collection, exist bool, err error) { + collection = &entity.Collection{} + exist, err = cr.data.DB.Where("user_id = ? and object_id = ?", userId, objectId).Get(collection) + if err != nil { + return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// SearchByObjectIDsAndUser search by object IDs and user +func (cr *collectionRepo) SearchByObjectIDsAndUser(ctx context.Context, userId string, objectIds []string) ([]*entity.Collection, error) { + collectionList := make([]*entity.Collection, 0) + err := cr.data.DB.Where("user_id = ?", userId).In("object_id", objectIds).Find(&collectionList) + if err != nil { + return collectionList, err + } + return collectionList, nil +} + +// CountByObjectID count by object TagID +func (cr *collectionRepo) CountByObjectID(ctx context.Context, objectId string) (total int64, err error) { + collection := &entity.Collection{} + total, err = cr.data.DB.Where("object_id = ?", objectId).Count(collection) + if err != nil { + return 0, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetCollectionPage get collection page +func (cr *collectionRepo) GetCollectionPage(ctx context.Context, page, pageSize int, collection *entity.Collection) (collectionList []*entity.Collection, total int64, err error) { + + collectionList = make([]*entity.Collection, 0) + + session := cr.data.DB.NewSession() + if collection.UserID != "" && collection.UserID != "0" { + session = session.Where("user_id = ?", collection.UserID) + } + + if collection.UserCollectionGroupID != "" && collection.UserCollectionGroupID != "0" { + session = session.Where("user_collection_group_id = ?", collection.UserCollectionGroupID) + } + session = session.OrderBy("update_time desc") + + total, err = pager.Help(page, pageSize, collectionList, collection, session) + 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) + list, err := cr.SearchByObjectIDsAndUser(ctx, userId, objectIds) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return collectedMap, err + } + for _, item := range list { + collectedMap[item.ObjectID] = true + } + return collectedMap, err +} + +// SearchList +func (cr *collectionRepo) SearchList(ctx context.Context, search *entity.CollectionSearch) ([]*entity.Collection, int64, error) { + var count int64 + var err error + rows := make([]*entity.Collection, 0) + if search.Page > 0 { + search.Page = search.Page - 1 + } else { + search.Page = 0 + } + if search.PageSize == 0 { + search.PageSize = constant.Default_PageSize + } + offset := search.Page * search.PageSize + session := cr.data.DB.Where("") + if len(search.UserID) > 0 { + session = session.And("user_id = ?", search.UserID) + } else { + return rows, count, nil + } + session = session.Limit(search.PageSize, offset) + count, err = session.OrderBy("updated_at desc").FindAndCount(&rows) + if err != nil { + return rows, count, err + } + return rows, count, nil +} diff --git a/internal/repo/comment/comment_repo.go b/internal/repo/comment/comment_repo.go new file mode 100644 index 00000000..c8ea40a9 --- /dev/null +++ b/internal/repo/comment/comment_repo.go @@ -0,0 +1,96 @@ +package comment + +import ( + "context" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/pager" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/service/comment" + "github.com/segmentfault/answer/internal/service/comment_common" + "github.com/segmentfault/answer/internal/service/unique" + "github.com/segmentfault/pacman/errors" +) + +// commentRepo comment repository +type commentRepo struct { + data *data.Data + uniqueIDRepo unique.UniqueIDRepo +} + +// NewCommentRepo new repository +func NewCommentRepo(data *data.Data, uniqueIDRepo unique.UniqueIDRepo) comment.CommentRepo { + return &commentRepo{ + data: data, + uniqueIDRepo: uniqueIDRepo, + } +} + +// NewCommentCommonRepo new repository +func NewCommentCommonRepo(data *data.Data, uniqueIDRepo unique.UniqueIDRepo) comment_common.CommentCommonRepo { + return &commentRepo{ + data: data, + uniqueIDRepo: uniqueIDRepo, + } +} + +// AddComment add comment +func (cr *commentRepo) AddComment(ctx context.Context, comment *entity.Comment) (err error) { + comment.ID, err = cr.uniqueIDRepo.GenUniqueIDStr(ctx, comment.TableName()) + if err != nil { + return err + } + _, err = cr.data.DB.Insert(comment) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// RemoveComment delete comment +func (cr *commentRepo) RemoveComment(ctx context.Context, commentID string) (err error) { + session := cr.data.DB.ID(commentID) + _, err = session.Update(&entity.Comment{Status: entity.CommentStatusDeleted}) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// UpdateComment update comment +func (cr *commentRepo) UpdateComment(ctx context.Context, comment *entity.Comment) (err error) { + _, err = cr.data.DB.ID(comment.ID).Where("user_id = ?", comment.UserID).Update(comment) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetComment get comment one +func (cr *commentRepo) GetComment(ctx context.Context, commentID string) ( + comment *entity.Comment, exist bool, err error) { + comment = &entity.Comment{} + exist, err = cr.data.DB.ID(commentID).Get(comment) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetCommentPage get comment page +func (cr *commentRepo) GetCommentPage(ctx context.Context, commentQuery *comment.CommentQuery) ( + commentList []*entity.Comment, total int64, err error) { + commentList = make([]*entity.Comment, 0) + + session := cr.data.DB.NewSession() + session.OrderBy(commentQuery.GetOrderBy()) + session.Where("status = ?", entity.CommentStatusAvailable) + + cond := &entity.Comment{ObjectID: commentQuery.ObjectID, UserID: commentQuery.UserID} + total, err = pager.Help(commentQuery.Page, commentQuery.PageSize, &commentList, cond, session) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} diff --git a/internal/repo/comment/comment_repo_test.go b/internal/repo/comment/comment_repo_test.go new file mode 100644 index 00000000..8a24e02c --- /dev/null +++ b/internal/repo/comment/comment_repo_test.go @@ -0,0 +1,134 @@ +package comment + +import ( + "context" + "os" + "reflect" + "testing" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/repo/unique" + unique2 "github.com/segmentfault/answer/internal/service/unique" +) + +var ( + dataSource *data.Data + log log.log +) + +func init() { + s, _ := os.LookupEnv("TESTDATA-DB-CONNECTION") + cache, _, _ := data.NewCache(log.Getlog(), &data.CacheConf{}) + dataSource, _, _ = data.NewData(log.Getlog(), data.NewDB(true, &data.Database{ + Connection: s, + }), cache) + log = log.Getlog() +} + +func Test_commentRepo_AddComment(t *testing.T) { + type fields struct { + log log.log + data *data.Data + uniqueIDRepo unique2.UniqueIDRepo + } + type args struct { + ctx context.Context + comment *entity.Comment + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "AddComment", + fields: fields{ + + data: dataSource, + uniqueIDRepo: unique.NewUniqueIDRepo(log, dataSource), + }, + args: args{ + ctx: nil, + comment: &entity.Comment{ + UserID: "123", + ObjectID: "555", + VoteCount: 0, + Status: 1, + OriginalText: "12312312", + ParsedText: "123123123", + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cr := &commentRepo{ + log: tt.fields.log, + data: tt.fields.data, + uniqueIDRepo: tt.fields.uniqueIDRepo, + } + if err := cr.AddComment(tt.args.ctx, tt.args.comment); (err != nil) != tt.wantErr { + t.Errorf("AddComment() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_commentRepo_GetComment(t *testing.T) { + type fields struct { + log log.log + data *data.Data + uniqueIDRepo unique2.UniqueIDRepo + } + type args struct { + ctx context.Context + commentID string + } + tests := []struct { + name string + fields fields + args args + wantComment *entity.Comment + wantExist bool + wantErr bool + }{ + { + name: "test", + fields: fields{ + + data: dataSource, + uniqueIDRepo: unique.NewUniqueIDRepo(log, dataSource), + }, + args: args{ + ctx: nil, + commentID: "10070000000000236", + }, + wantComment: nil, + wantExist: false, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cr := &commentRepo{ + log: tt.fields.log, + data: tt.fields.data, + uniqueIDRepo: tt.fields.uniqueIDRepo, + } + gotComment, gotExist, err := cr.GetComment(tt.args.ctx, tt.args.commentID) + if (err != nil) != tt.wantErr { + t.Errorf("GetComment() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotComment, tt.wantComment) { + t.Errorf("GetComment() gotComment = %v, want %v", gotComment, tt.wantComment) + } + if gotExist != tt.wantExist { + t.Errorf("GetComment() gotExist = %v, want %v", gotExist, tt.wantExist) + } + }) + } +} diff --git a/internal/repo/common/common.go b/internal/repo/common/common.go new file mode 100644 index 00000000..15773a6a --- /dev/null +++ b/internal/repo/common/common.go @@ -0,0 +1,95 @@ +package common + +import ( + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/service/unique" + "github.com/segmentfault/answer/pkg/obj" + "github.com/segmentfault/pacman/errors" + "github.com/segmentfault/pacman/log" +) + +type CommonRepo struct { + data *data.Data + uniqueIDRepo unique.UniqueIDRepo +} + +func NewCommonRepo(data *data.Data, uniqueIDRepo unique.UniqueIDRepo) *CommonRepo { + return &CommonRepo{ + data: data, + uniqueIDRepo: uniqueIDRepo, + } +} + +// GetRootObjectID get root object ID +func (cr *CommonRepo) GetRootObjectID(objectID string) (rootObjectID string, err error) { + var ( + exist bool + objectType string + answer = entity.Answer{} + comment = entity.Comment{} + ) + + objectType, err = obj.GetObjectTypeStrByObjectID(objectID) + switch objectType { + case "answer": + exist, err = cr.data.DB.ID(objectID).Get(&answer) + if !exist { + err = errors.BadRequest(reason.ObjectNotFound) + } else { + objectID = answer.QuestionID + } + case "comment": + exist, err = cr.data.DB.ID(objectID).Get(&comment) + if !exist { + err = errors.BadRequest(reason.ObjectNotFound) + } else { + objectID, err = cr.GetRootObjectID(comment.ObjectID) + } + default: + rootObjectID = objectID + } + return +} + +// GetObjectIDMap get object ID map from object id +func (cr *CommonRepo) GetObjectIDMap(objectID string) (objectIDMap map[string]string, err error) { + var ( + exist bool + ID, + objectType string + answer = entity.Answer{} + comment = entity.Comment{} + ) + + objectIDMap = map[string]string{} + // 10070000000000450 + objectType, err = obj.GetObjectTypeStrByObjectID(objectID) + if err != nil { + log.Error("get report object type:", objectID, ",err:", err) + return + } + switch objectType { + case "answer": + exist, err = cr.data.DB.ID(objectID).Get(&answer) + if !exist { + err = errors.BadRequest(reason.ObjectNotFound) + } else { + objectIDMap, err = cr.GetObjectIDMap(answer.QuestionID) + ID = answer.ID + } + case "comment": + exist, err = cr.data.DB.ID(objectID).Get(&comment) + if !exist { + err = errors.BadRequest(reason.ObjectNotFound) + } else { + objectIDMap, err = cr.GetObjectIDMap(comment.ObjectID) + ID = comment.ID + } + case "question": + ID = objectID + } + objectIDMap[objectType] = ID + return +} diff --git a/internal/repo/config/config_repo.go b/internal/repo/config/config_repo.go new file mode 100644 index 00000000..aa68387e --- /dev/null +++ b/internal/repo/config/config_repo.go @@ -0,0 +1,122 @@ +package config + +import ( + "encoding/json" + "fmt" + "sync" + + "github.com/segmentfault/answer/internal/service/config" + "github.com/segmentfault/answer/pkg/converter" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/pacman/errors" +) + +var ( + Key2ValueMapping = make(map[string]interface{}) + Key2IDMapping = make(map[string]int) + ID2KeyMapping = make(map[int]string) +) + +// configRepo config repository +type configRepo struct { + data *data.Data + mu sync.Mutex +} + +// NewConfigRepo new repository +func NewConfigRepo(data *data.Data) config.ConfigRepo { + repo := &configRepo{ + data: data, + } + repo.init() + return repo +} + +// init initializes the Key2ValueMapping map data structures +func (cr *configRepo) init() { + cr.mu.Lock() + defer cr.mu.Unlock() + rows := &[]entity.Config{} + err := cr.data.DB.Find(rows) + if err == nil { + for _, row := range *rows { + Key2ValueMapping[row.Key] = row.Value + Key2IDMapping[row.Key] = row.ID + ID2KeyMapping[row.ID] = row.Key + } + } +} + +// Get Base method for getting the config value +// Key string +func (cr *configRepo) Get(key string) (interface{}, error) { + value, ok := Key2ValueMapping[key] + if ok { + return value, nil + } else { + return value, errors.InternalServer(reason.DatabaseError).WithMsg(fmt.Sprintf("no such config key: %v", key)) + } +} + +// GetString method for getting the config value to string +// key string +func (cr *configRepo) GetString(key string) (string, error) { + value, err := cr.Get(key) + if value != nil { + return value.(string), err + } + return "", err +} + +// GetInt method for getting the config value to int64 +// key string +func (cr *configRepo) GetInt(key string) (int, error) { + value, err := cr.GetString(key) + if err != nil { + return 0, err + } else { + return converter.StringToInt(value), nil + } +} + +// GetArrayString method for getting the config value to string array +func (cr *configRepo) GetArrayString(key string) ([]string, error) { + arr := &[]string{} + value, err := cr.GetString(key) + if err != nil { + return nil, err + } + err = json.Unmarshal([]byte(value), arr) + return *arr, err +} + +// GetConfigType method for getting the config type +func (cr *configRepo) GetConfigType(key string) (int, error) { + value, ok := Key2IDMapping[key] + if ok { + return value, nil + } else { + return 0, errors.InternalServer(reason.DatabaseError).WithMsg(fmt.Sprintf("no such config type: %v", key)) + } +} + +// GetConfigById get config key from config id +func (cr *configRepo) GetConfigById(id int, value any) (err error) { + var ( + ok = true + key string + conf interface{} + ) + key, ok = ID2KeyMapping[id] + if !ok { + err = errors.InternalServer(reason.DatabaseError).WithMsg(fmt.Sprintf("no such config id: %v", id)) + return + } + + conf, err = cr.Get(key) + value = json.Unmarshal([]byte(conf.(string)), value) + return +} diff --git a/internal/repo/config/config_repo_test.go b/internal/repo/config/config_repo_test.go new file mode 100644 index 00000000..e765ff7e --- /dev/null +++ b/internal/repo/config/config_repo_test.go @@ -0,0 +1,35 @@ +package config + +import ( + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/config" + "github.com/segmentfault/pacman/log" + "github.com/stretchr/testify/assert" + "testing" +) + +var ( + dataSource *data.Data + repo config.ConfigRepo +) + +func init() { + s := "root:123456@tcp(10.0.10.200:3306)/answer_new?charset=utf8&interpolateParams=true&timeout=3s&readTimeout=3s&writeTimeout=3s" + cache, _, _ := data.NewCache(&data.CacheConf{}) + dataSource, _, _ = data.NewData(data.NewDB(true, &data.Database{ + Connection: s, + }), cache) + repo = NewConfigRepo(dataSource) +} + +func TestConfigRepo_GetConfigById(t *testing.T) { + var ( + id = 58 + value = schema.ReasonItem{} + err error + ) + err = repo.GetConfigById(id, &value) + assert.NoError(t, err) + log.Info(value) +} diff --git a/internal/repo/export/email_repo.go b/internal/repo/export/email_repo.go new file mode 100644 index 00000000..0a70a28a --- /dev/null +++ b/internal/repo/export/email_repo.go @@ -0,0 +1,39 @@ +package export + +import ( + "context" + "time" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/service/export" + "github.com/segmentfault/pacman/errors" +) + +// emailRepo email repository +type emailRepo struct { + data *data.Data +} + +// NewEmailRepo new repository +func NewEmailRepo(data *data.Data) export.EmailRepo { + return &emailRepo{ + data: data, + } +} + +func (e *emailRepo) SetCode(ctx context.Context, code, content string) error { + err := e.data.Cache.SetString(ctx, code, content, 10*time.Minute) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return nil +} + +func (e *emailRepo) VerifyCode(ctx context.Context, code string) (content string, err error) { + content, err = e.data.Cache.GetString(ctx, code) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} diff --git a/internal/repo/meta/meta_repo.go b/internal/repo/meta/meta_repo.go new file mode 100644 index 00000000..f23b455b --- /dev/null +++ b/internal/repo/meta/meta_repo.go @@ -0,0 +1,83 @@ +package meta + +import ( + "context" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/pager" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/service/meta" + "github.com/segmentfault/pacman/errors" + "xorm.io/builder" +) + +// metaRepo meta repository +type metaRepo struct { + data *data.Data +} + +// NewMetaRepo new repository +func NewMetaRepo(data *data.Data) meta.MetaRepo { + return &metaRepo{ + data: data, + } +} + +// AddMeta add meta +func (mr *metaRepo) AddMeta(ctx context.Context, meta *entity.Meta) (err error) { + _, err = mr.data.DB.Insert(meta) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// RemoveMeta delete meta +func (mr *metaRepo) RemoveMeta(ctx context.Context, id int) (err error) { + _, err = mr.data.DB.ID(id).Delete(&entity.Meta{}) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// UpdateMeta update meta +func (mr *metaRepo) UpdateMeta(ctx context.Context, meta *entity.Meta) (err error) { + _, err = mr.data.DB.ID(meta.ID).Update(meta) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetMetaByObjectIdAndKey get meta one +func (mr *metaRepo) GetMetaByObjectIdAndKey(ctx context.Context, objectID, key string) ( + meta *entity.Meta, exist bool, err error) { + meta = &entity.Meta{} + exist, err = mr.data.DB.Where(builder.Eq{"object_id": objectID}.And(builder.Eq{"`key`": key})).Desc("created_at").Get(meta) + if err != nil { + return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetMetaList get meta list all +func (mr *metaRepo) GetMetaList(ctx context.Context, meta *entity.Meta) (metaList []*entity.Meta, err error) { + metaList = make([]*entity.Meta, 0) + err = mr.data.DB.Find(metaList, meta) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetMetaPage get meta page +func (mr *metaRepo) GetMetaPage(ctx context.Context, page, pageSize int, meta *entity.Meta) (metaList []*entity.Meta, total int64, err error) { + metaList = make([]*entity.Meta, 0) + total, err = pager.Help(page, pageSize, metaList, meta, mr.data.DB.NewSession()) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} diff --git a/internal/repo/notification/notification_read_repo.go b/internal/repo/notification/notification_read_repo.go new file mode 100644 index 00000000..49d64a04 --- /dev/null +++ b/internal/repo/notification/notification_read_repo.go @@ -0,0 +1,69 @@ +package notification + +import ( + "context" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/pager" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/service/notification" + "github.com/segmentfault/pacman/errors" +) + +// notificationReadRepo notificationRead repository +type notificationReadRepo struct { + data *data.Data +} + +// NewNotificationReadRepo new repository +func NewNotificationReadRepo(data *data.Data) notification.NotificationReadRepo { + return ¬ificationReadRepo{ + data: data, + } +} + +// AddNotificationRead add notification read record +func (nr *notificationReadRepo) AddNotificationRead(ctx context.Context, notificationRead *entity.NotificationRead) (err error) { + _, err = nr.data.DB.Insert(notificationRead) + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() +} + +// RemoveNotificationRead delete notification read record +func (nr *notificationReadRepo) RemoveNotificationRead(ctx context.Context, id int) (err error) { + _, err = nr.data.DB.ID(id).Delete(&entity.NotificationRead{}) + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() +} + +// UpdateNotificationRead update notification read record +func (nr *notificationReadRepo) UpdateNotificationRead(ctx context.Context, notificationRead *entity.NotificationRead) (err error) { + _, err = nr.data.DB.ID(notificationRead.ID).Update(notificationRead) + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() +} + +// GetNotificationRead get notification read record one +func (nr *notificationReadRepo) GetNotificationRead(ctx context.Context, id int) ( + notificationRead *entity.NotificationRead, exist bool, err error) { + notificationRead = &entity.NotificationRead{} + exist, err = nr.data.DB.ID(id).Get(notificationRead) + if err != nil { + return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetNotificationReadList get notification read record list all +func (nr *notificationReadRepo) GetNotificationReadList(ctx context.Context, notificationRead *entity.NotificationRead) (notificationReadList []*entity.NotificationRead, err error) { + notificationReadList = make([]*entity.NotificationRead, 0) + err = nr.data.DB.Find(notificationReadList, notificationRead) + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return +} + +// GetNotificationReadPage get notification read record page +func (nr *notificationReadRepo) GetNotificationReadPage(ctx context.Context, page, pageSize int, notificationRead *entity.NotificationRead) (notificationReadList []*entity.NotificationRead, total int64, err error) { + notificationReadList = make([]*entity.NotificationRead, 0) + total, err = pager.Help(page, pageSize, notificationReadList, notificationRead, nr.data.DB.NewSession()) + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return +} diff --git a/internal/repo/notification/notification_repo.go b/internal/repo/notification/notification_repo.go new file mode 100644 index 00000000..b001fb6f --- /dev/null +++ b/internal/repo/notification/notification_repo.go @@ -0,0 +1,119 @@ +package notification + +import ( + "context" + "time" + + "github.com/segmentfault/answer/internal/base/constant" + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + notficationcommon "github.com/segmentfault/answer/internal/service/notification_common" + "github.com/segmentfault/pacman/errors" +) + +// notificationRepo notification repository +type notificationRepo struct { + data *data.Data +} + +// NewNotificationRepo new repository +func NewNotificationRepo(data *data.Data) notficationcommon.NotificationRepo { + return ¬ificationRepo{ + data: data, + } +} + +// AddNotification add notification +func (nr *notificationRepo) AddNotification(ctx context.Context, notification *entity.Notification) (err error) { + _, err = nr.data.DB.Insert(notification) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return + } + return +} + +func (nr *notificationRepo) UpdateNotificationContent(ctx context.Context, notification *entity.Notification) (err error) { + now := time.Now() + notification.UpdatedAt = now + _, err = nr.data.DB.Where("id =?", notification.ID).Cols("content", "updated_at").Update(notification) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return + } + return +} + +func (nr *notificationRepo) ClearUnRead(ctx context.Context, userID string, notificationType int) (err error) { + info := &entity.Notification{} + info.IsRead = schema.NotificationRead + _, err = nr.data.DB.Where("user_id =?", userID).And("type =?", notificationType).Cols("is_read").Update(info) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return + } + return +} + +func (nr *notificationRepo) ClearIDUnRead(ctx context.Context, userID string, id string) (err error) { + info := &entity.Notification{} + info.IsRead = schema.NotificationRead + _, err = nr.data.DB.Where("user_id =?", userID).And("id =?", id).Cols("is_read").Update(info) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return + } + return +} + +func (nr *notificationRepo) GetById(ctx context.Context, id string) (*entity.Notification, bool, error) { + info := &entity.Notification{} + exist, err := nr.data.DB.Where("id = ? ", id).Get(info) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return info, false, err + } + return info, exist, nil +} + +func (nr *notificationRepo) GetByUserIdObjectIdTypeId(ctx context.Context, userID, objectID string, notificationType int) (*entity.Notification, bool, error) { + info := &entity.Notification{} + exist, err := nr.data.DB.Where("user_id = ? ", userID).And("object_id = ?", objectID).And("type = ?", notificationType).Get(info) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return info, false, err + } + return info, exist, nil +} + +func (nr *notificationRepo) SearchList(ctx context.Context, search *schema.NotificationSearch) ([]*entity.Notification, int64, error) { + var count int64 + var err error + + rows := make([]*entity.Notification, 0) + if search.UserID == "" { + return rows, 0, nil + } + if search.Page > 0 { + search.Page = search.Page - 1 + } else { + search.Page = 0 + } + if search.PageSize == 0 { + search.PageSize = constant.Default_PageSize + } + offset := search.Page * search.PageSize + session := nr.data.DB.Where("") + session = session.And("user_id = ?", search.UserID) + session = session.And("type = ?", search.Type) + session = session.OrderBy("updated_at desc") + session = session.Limit(search.PageSize, offset) + count, err = session.FindAndCount(&rows) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return rows, count, err + } + return rows, count, nil +} diff --git a/internal/repo/provider.go b/internal/repo/provider.go new file mode 100644 index 00000000..5afd7455 --- /dev/null +++ b/internal/repo/provider.go @@ -0,0 +1,63 @@ +package repo + +import ( + "github.com/google/wire" + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/repo/activity" + "github.com/segmentfault/answer/internal/repo/activity_common" + "github.com/segmentfault/answer/internal/repo/auth" + "github.com/segmentfault/answer/internal/repo/captcha" + "github.com/segmentfault/answer/internal/repo/collection" + "github.com/segmentfault/answer/internal/repo/comment" + "github.com/segmentfault/answer/internal/repo/common" + "github.com/segmentfault/answer/internal/repo/config" + "github.com/segmentfault/answer/internal/repo/export" + "github.com/segmentfault/answer/internal/repo/meta" + "github.com/segmentfault/answer/internal/repo/notification" + "github.com/segmentfault/answer/internal/repo/rank" + "github.com/segmentfault/answer/internal/repo/reason" + "github.com/segmentfault/answer/internal/repo/report" + "github.com/segmentfault/answer/internal/repo/revision" + "github.com/segmentfault/answer/internal/repo/tag" + "github.com/segmentfault/answer/internal/repo/unique" + "github.com/segmentfault/answer/internal/repo/user" +) + +// ProviderSetRepo is data providers. +var ProviderSetRepo = wire.NewSet( + common.NewCommonRepo, + data.NewData, + data.NewDB, + data.NewCache, + comment.NewCommentRepo, + comment.NewCommentCommonRepo, + captcha.NewCaptchaRepo, + unique.NewUniqueIDRepo, + report.NewReportRepo, + activity_common.NewFollowRepo, + activity_common.NewVoteRepo, + config.NewConfigRepo, + user.NewUserRepo, + user.NewUserBackyardRepo, + rank.NewUserRankRepo, + NewQuestionRepo, + NewAnswerRepo, + NewActivityRepo, + activity.NewVoteRepo, + activity.NewFollowRepo, + activity.NewAnswerActivityRepo, + activity.NewQuestionActivityRepo, + activity.NewUserActiveActivityRepo, + tag.NewTagRepo, + tag.NewTagListRepo, + collection.NewCollectionRepo, + collection.NewCollectionGroupRepo, + auth.NewAuthRepo, + revision.NewRevisionRepo, + NewSearchRepo, + meta.NewMetaRepo, + export.NewEmailRepo, + reason.NewReasonRepo, + NewSiteInfo, + notification.NewNotificationRepo, +) diff --git a/internal/repo/question_repo.go b/internal/repo/question_repo.go new file mode 100644 index 00000000..3f503f58 --- /dev/null +++ b/internal/repo/question_repo.go @@ -0,0 +1,249 @@ +package repo + +import ( + "context" + + "github.com/segmentfault/answer/internal/base/constant" + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/pager" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + questioncommon "github.com/segmentfault/answer/internal/service/question_common" + "github.com/segmentfault/answer/internal/service/unique" + + "github.com/segmentfault/pacman/errors" +) + +// questionRepo question repository +type questionRepo struct { + data *data.Data + uniqueIDRepo unique.UniqueIDRepo +} + +// NewQuestionRepo new repository +func NewQuestionRepo( + data *data.Data, + uniqueIDRepo unique.UniqueIDRepo, +) questioncommon.QuestionRepo { + return &questionRepo{ + data: data, + uniqueIDRepo: uniqueIDRepo, + } +} + +// AddQuestion add question +func (qr *questionRepo) AddQuestion(ctx context.Context, question *entity.Question) (err error) { + question.ID, err = qr.uniqueIDRepo.GenUniqueIDStr(ctx, question.TableName()) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + _, err = qr.data.DB.Insert(question) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// RemoveQuestion delete question +func (qr *questionRepo) RemoveQuestion(ctx context.Context, id string) (err error) { + _, err = qr.data.DB.Where("id =?", id).Delete(&entity.Question{}) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// UpdateQuestion update question +func (qr *questionRepo) UpdateQuestion(ctx context.Context, question *entity.Question, Cols []string) (err error) { + _, err = qr.data.DB.Where("id =?", question.ID).Cols(Cols...).Update(question) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +func (qr *questionRepo) UpdatePvCount(ctx context.Context, questionId string) (err error) { + question := &entity.Question{} + qr.data.DB.ShowSQL() + _, err = qr.data.DB.Where("id =?", questionId).Incr("view_count", 1).Update(question) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return nil +} + +func (qr *questionRepo) UpdateAnswerCount(ctx context.Context, questionId string, num int) (err error) { + question := &entity.Question{} + _, err = qr.data.DB.Where("id =?", questionId).Incr("answer_count", num).Update(question) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return nil +} + +func (qr *questionRepo) UpdateCollectionCount(ctx context.Context, questionId string, num int) (err error) { + question := &entity.Question{} + _, err = qr.data.DB.Where("id =?", questionId).Incr("collection_count", num).Update(question) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return nil +} + +func (qr *questionRepo) UpdateQuestionStatus(ctx context.Context, question *entity.Question) (err error) { + _, err = qr.data.DB.Where("id =?", question.ID).Cols("status").Update(question) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return nil +} + +func (qr *questionRepo) UpdateAccepted(ctx context.Context, question *entity.Question) (err error) { + _, err = qr.data.DB.Where("id =?", question.ID).Cols("accepted_answer_id").Update(question) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return nil +} + +func (qr *questionRepo) UpdateLastAnswer(ctx context.Context, question *entity.Question) (err error) { + _, err = qr.data.DB.Where("id =?", question.ID).Cols("last_answer_id").Update(question) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return nil +} + +// GetQuestion get question one +func (qr *questionRepo) GetQuestion(ctx context.Context, id string) ( + question *entity.Question, exist bool, err error) { + question = &entity.Question{} + question.ID = id + exist, err = qr.data.DB.Where("id = ?", id).Get(question) + if err != nil { + return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetTagBySlugName get tag by slug name +func (qr *questionRepo) SearchByTitleLike(ctx context.Context, title string) (questionList []*entity.Question, err error) { + questionList = make([]*entity.Question, 0) + err = qr.data.DB.Table("question").Where("title like ?", "%"+title+"%").Limit(10, 0).Find(&questionList) + if err != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +func (qr *questionRepo) FindByID(ctx context.Context, id []string) (questionList []*entity.Question, err error) { + questionList = make([]*entity.Question, 0) + err = qr.data.DB.Table("question").In("id", id).Find(&questionList) + if err != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetQuestionList get question list all +func (qr *questionRepo) GetQuestionList(ctx context.Context, question *entity.Question) (questionList []*entity.Question, err error) { + questionList = make([]*entity.Question, 0) + err = qr.data.DB.Find(questionList, question) + if err != nil { + return questionList, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetQuestionPage get question page +func (qr *questionRepo) GetQuestionPage(ctx context.Context, page, pageSize int, question *entity.Question) (questionList []*entity.Question, total int64, err error) { + questionList = make([]*entity.Question, 0) + total, err = pager.Help(page, pageSize, questionList, question, qr.data.DB.NewSession()) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// SearchList +func (qr *questionRepo) SearchList(ctx context.Context, search *schema.QuestionSearch) ([]*entity.QuestionTag, int64, error) { + var count int64 + var err error + rows := make([]*entity.QuestionTag, 0) + if search.Page > 0 { + search.Page = search.Page - 1 + } else { + search.Page = 0 + } + if search.PageSize == 0 { + search.PageSize = constant.Default_PageSize + } + offset := search.Page * search.PageSize + session := qr.data.DB.Table("question") + + if len(search.TagIDs) > 0 { + session = session.Join("RIGHT", "tag_rel", "question.id = tag_rel.object_id") + session = session.And("tag_rel.tag_id =?", search.TagIDs[0]) + //session = session.In("tag_rel.tag_id ", search.TagIDs) + session = session.And("tag_rel.status =?", entity.TagRelStatusAvailable) + } + + if len(search.UserID) > 0 { + session = session.And("question.user_id = ?", search.UserID) + } + + session = session.In("question.status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusclosed}) + // if search.Status > 0 { + // session = session.And("question.status = ?", search.Status) + // } + //switch + //newest, active,frequent,score,unanswered + switch search.Order { + case "newest": + session = session.OrderBy("question.created_at desc") + case "active": + session = session.OrderBy("question.post_update_time desc,question.updated_at desc") + case "frequent": + session = session.OrderBy("question.view_count desc") + case "score": + session = session.OrderBy("question.vote_count desc,question.view_count desc") + case "unanswered": + session = session.And("question.last_answer_id = 0") + session = session.OrderBy("question.created_at desc") + } + session = session.Limit(search.PageSize, offset) + session = session.Select("question.id,question.user_id,question.title,question.original_text,question.parsed_text,question.status,question.view_count,question.unique_view_count,question.vote_count,question.answer_count,question.collection_count,question.follow_count,question.accepted_answer_id,question.last_answer_id,question.created_at,question.updated_at,question.post_update_time,question.revision_id") + count, err = session.FindAndCount(&rows) + //spew.Dump("search", err, count, rows) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return rows, count, err + } + return rows, count, nil +} + +func (qr *questionRepo) CmsSearchList(ctx context.Context, search *schema.CmsQuestionSearch) ([]*entity.Question, int64, error) { + var count int64 + var err error + rows := make([]*entity.Question, 0) + if search.Page > 0 { + search.Page = search.Page - 1 + } else { + search.Page = 0 + } + if search.PageSize == 0 { + search.PageSize = constant.Default_PageSize + } + offset := search.Page * search.PageSize + session := qr.data.DB.Table("question") + session = session.And("status =?", search.Status) + session = session.OrderBy("created_at desc") + session = session.Limit(search.PageSize, offset) + count, err = session.FindAndCount(&rows) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return rows, count, err + } + return rows, count, nil +} diff --git a/internal/repo/rank/user_rank_repo.go b/internal/repo/rank/user_rank_repo.go new file mode 100644 index 00000000..ea6d9ef0 --- /dev/null +++ b/internal/repo/rank/user_rank_repo.go @@ -0,0 +1,139 @@ +package rank + +import ( + "context" + + "github.com/jinzhu/now" + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/pager" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/service/config" + "github.com/segmentfault/answer/internal/service/rank" + "github.com/segmentfault/pacman/errors" + "github.com/segmentfault/pacman/log" + "xorm.io/builder" + "xorm.io/xorm" +) + +// UserRankRepo user rank repository +type UserRankRepo struct { + data *data.Data + configRepo config.ConfigRepo +} + +// NewUserRankRepo new repository +func NewUserRankRepo(data *data.Data, configRepo config.ConfigRepo) rank.UserRankRepo { + return &UserRankRepo{ + data: data, + configRepo: configRepo, + } +} + +// TriggerUserRank trigger user rank change +// session is need provider, it means this action must be success or failure +// if outer action is failed then this action is need rollback +func (ur *UserRankRepo) TriggerUserRank(ctx context.Context, + session *xorm.Session, userId string, deltaRank int, activityType int) (isReachStandard bool, err error) { + if deltaRank == 0 { + return false, nil + } + + if deltaRank < 0 { + // if user rank is lower than 1 after this action, then user rank will be set to 1 only. + isReachMin, err := ur.checkUserMinRank(ctx, session, userId, activityType) + if err != nil { + return false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + if isReachMin { + _, err = session.Where(builder.Eq{"id": userId}).Update(&entity.User{Rank: 1}) + if err != nil { + return false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return false, nil + } + } else { + isReachStandard, err = ur.checkUserTodayRank(ctx, session, userId, activityType) + if err != nil { + return false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + if isReachStandard { + return isReachStandard, nil + } + } + _, err = session.Where(builder.Eq{"id": userId}).Incr("`rank`", deltaRank).Update(&entity.User{}) + if err != nil { + return false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return false, nil +} + +func (ur *UserRankRepo) checkUserMinRank(ctx context.Context, session *xorm.Session, userID string, deltaRank int) ( + isReachStandard bool, err error) { + bean := &entity.User{ID: userID} + _, err = session.Select("rank").Get(bean) + if err != nil { + return false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + if bean.Rank+deltaRank < 1 { + log.Infof("user %s is rank %d out of range before rank operation", userID, deltaRank) + return true, nil + } + return +} + +func (ur *UserRankRepo) checkUserTodayRank(ctx context.Context, + session *xorm.Session, userID string, activityType int) (isReachStandard bool, err error) { + // exclude daily rank + exclude, err := ur.configRepo.GetArrayString("daily_rank_limit.exclude") + for _, item := range exclude { + excludeActivityType, err := ur.configRepo.GetInt(item) + if err != nil { + return false, err + } + if activityType == excludeActivityType { + return false, nil + } + } + + // get user + start, end := now.BeginningOfDay(), now.EndOfDay() + session.Where(builder.Eq{"user_id": userID}) + session.Where(builder.Eq{"cancelled": 0}) + session.Where(builder.Between{ + Col: "updated_at", + LessVal: start, + MoreVal: end, + }) + earned, err := session.Sum(&entity.Activity{}, "rank") + if err != nil { + return false, err + } + + // max rank + maxDailyRank, err := ur.configRepo.GetInt("daily_rank_limit") + if err != nil { + return false, err + } + + if int(earned) < maxDailyRank { + return false, nil + } + log.Infof("user %s today has rank %d is reach stand %d", userID, earned, maxDailyRank) + return true, nil +} + +func (ur *UserRankRepo) UserRankPage(ctx context.Context, userId string, page, pageSize int) ( + rankPage []*entity.Activity, total int64, err error) { + rankPage = make([]*entity.Activity, 0) + + session := ur.data.DB.Where(builder.Eq{"has_rank": 1}.And(builder.Eq{"cancelled": 0})) + session.Desc("created_at") + + cond := &entity.Activity{UserID: userId} + total, err = pager.Help(page, pageSize, &rankPage, cond, session) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} diff --git a/internal/repo/reason/reason_repo.go b/internal/repo/reason/reason_repo.go new file mode 100644 index 00000000..64cb5d50 --- /dev/null +++ b/internal/repo/reason/reason_repo.go @@ -0,0 +1,59 @@ +package reason + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/config" + "github.com/segmentfault/answer/internal/service/reason_common" +) + +type reasonRepo struct { + configRepo config.ConfigRepo +} + +func NewReasonRepo(configRepo config.ConfigRepo) reason_common.ReasonRepo { + return &reasonRepo{ + configRepo: configRepo, + } +} + +func (rr *reasonRepo) ListReasons(ctx context.Context, req schema.ReasonReq) (resp []schema.ReasonItem, err error) { + var ( + reasonAction = fmt.Sprintf("%s.%s.reasons", req.ObjectType, req.Action) + reasonKeys []string + cfgValue string + ) + resp = []schema.ReasonItem{} + + reasonKeys, err = rr.configRepo.GetArrayString(reasonAction) + if err != nil { + return + } + for _, reasonKey := range reasonKeys { + var ( + reasonType int + reason = schema.ReasonItem{} + ) + + cfgValue, err = rr.configRepo.GetString(reasonKey) + if err != nil { + continue + } + + err = json.Unmarshal([]byte(cfgValue), &reason) + if err != nil { + continue + } + reasonType, err = rr.configRepo.GetConfigType(reasonKey) + if err != nil { + continue + } + + reason.ReasonType = reasonType + resp = append(resp, reason) + } + return +} diff --git a/internal/repo/report/report_repo.go b/internal/repo/report/report_repo.go new file mode 100644 index 00000000..dcb911cc --- /dev/null +++ b/internal/repo/report/report_repo.go @@ -0,0 +1,95 @@ +package report + +import ( + "context" + + "github.com/segmentfault/answer/internal/base/constant" + "github.com/segmentfault/answer/internal/base/pager" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/report_common" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/service/unique" + "github.com/segmentfault/pacman/errors" +) + +// reportRepo report repository +type reportRepo struct { + data *data.Data + uniqueIDRepo unique.UniqueIDRepo +} + +// NewReportRepo new repository +func NewReportRepo(data *data.Data, uniqueIDRepo unique.UniqueIDRepo) report_common.ReportRepo { + return &reportRepo{ + data: data, + uniqueIDRepo: uniqueIDRepo, + } +} + +// AddReport add report +func (rr *reportRepo) AddReport(ctx context.Context, report *entity.Report) (err error) { + report.ID, err = rr.uniqueIDRepo.GenUniqueIDStr(ctx, report.TableName()) + if err != nil { + return err + } + _, err = rr.data.DB.Insert(report) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetReportListPage get report list page +func (rr *reportRepo) GetReportListPage(ctx context.Context, dto schema.GetReportListPageDTO) (reports []entity.Report, total int64, err error) { + var ( + ok bool + status int + objectType int + session = rr.data.DB.NewSession() + cond = entity.Report{} + ) + + // parse status + status, ok = entity.ReportStatus[dto.Status] + if !ok { + status = entity.ReportStatus["pending"] + } + cond.Status = status + + // parse object type + objectType, ok = constant.ObjectTypeStrMapping[dto.ObjectType] + if ok { + cond.ObjectType = objectType + } + + // order + session.OrderBy("created_at desc") + + total, err = pager.Help(dto.Page, dto.PageSize, &reports, cond, session) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetByID get report by ID +func (ar *reportRepo) GetByID(ctx context.Context, id string) (report entity.Report, exist bool, err error) { + report = entity.Report{} + exist, err = ar.data.DB.ID(id).Get(&report) + return +} + +// UpdateByID handle report by ID +func (ar *reportRepo) UpdateByID( + ctx context.Context, + id string, + handleData entity.Report) (err error) { + _, err = ar.data.DB.ID(id).Update(&handleData) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} diff --git a/internal/repo/revision/revision_repo.go b/internal/repo/revision/revision_repo.go new file mode 100644 index 00000000..34509eb4 --- /dev/null +++ b/internal/repo/revision/revision_repo.go @@ -0,0 +1,128 @@ +package revision + +import ( + "context" + + "github.com/segmentfault/answer/internal/base/constant" + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/service/revision" + "github.com/segmentfault/answer/internal/service/unique" + "github.com/segmentfault/answer/pkg/obj" + "github.com/segmentfault/pacman/errors" + "xorm.io/builder" + "xorm.io/xorm" +) + +// revisionRepo revision repository +type revisionRepo struct { + data *data.Data + uniqueIDRepo unique.UniqueIDRepo +} + +// NewRevisionRepo new repository +func NewRevisionRepo(data *data.Data, uniqueIDRepo unique.UniqueIDRepo) revision.RevisionRepo { + return &revisionRepo{ + data: data, + uniqueIDRepo: uniqueIDRepo, + } +} + +// AddRevision add revision +// autoUpdateRevisionID bool : if autoUpdateRevisionID is true , the object.revision_id will be updated, +// if not need auto update object.revision_id, it must be false. +// example: user can edit the object, but need audit, the revision_id will be updated when admin approved +func (rr *revisionRepo) AddRevision(ctx context.Context, revision *entity.Revision, autoUpdateRevisionID bool) (err error) { + objectTypeNumber, err := obj.GetObjectTypeNumberByObjectID(revision.ObjectID) + if err != nil { + return errors.BadRequest(reason.ObjectNotFound) + } + + revision.ObjectType = objectTypeNumber + if !rr.allowRecord(revision.ObjectType) { + return nil + } + _, err = rr.data.DB.Transaction(func(session *xorm.Session) (interface{}, error) { + _, err = session.Insert(revision) + if err != nil { + _ = session.Rollback() + return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + if autoUpdateRevisionID { + err = rr.UpdateObjectRevisionId(ctx, revision, session) + if err != nil { + _ = session.Rollback() + return nil, err + } + } + return nil, nil + }) + + return err +} + +// UpdateObjectRevisionId updates the object.revision_id field +func (rr *revisionRepo) UpdateObjectRevisionId(ctx context.Context, revision *entity.Revision, session *xorm.Session) (err error) { + tableName, err := obj.GetObjectTypeStrByObjectID(revision.ObjectID) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + _, err = session.Table(tableName).Where("id = ?", revision.ObjectID).Cols("`revision_id`").Update(struct { + RevisionID string `xorm:"revision_id"` + }{ + RevisionID: revision.ID, + }) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return nil +} + +// GetRevision get revision one +func (rr *revisionRepo) GetRevision(ctx context.Context, id string) ( + revision *entity.Revision, exist bool, err error) { + revision = &entity.Revision{} + exist, err = rr.data.DB.ID(id).Get(revision) + if err != nil { + return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetLastRevisionByObjectID get object's last revision by object TagID +func (rr *revisionRepo) GetLastRevisionByObjectID(ctx context.Context, objectID string) ( + revision *entity.Revision, exist bool, err error) { + revision = &entity.Revision{} + exist, err = rr.data.DB.Where("object_id = ?", objectID).OrderBy("create_time DESC").Get(revision) + if err != nil { + return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetRevisionList get revision list all +func (rr *revisionRepo) GetRevisionList(ctx context.Context, revision *entity.Revision) (revisionList []entity.Revision, err error) { + revisionList = []entity.Revision{} + err = rr.data.DB.Where(builder.Eq{ + "object_id": revision.ObjectID, + }).OrderBy("created_at DESC").Find(&revisionList) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// allowRecord check the object type can record revision or not +func (rr *revisionRepo) allowRecord(objectType int) (ok bool) { + switch objectType { + case constant.ObjectTypeStrMapping["question"]: + return true + case constant.ObjectTypeStrMapping["answer"]: + return true + case constant.ObjectTypeStrMapping["tag"]: + return true + default: + return false + } +} diff --git a/internal/repo/revision/revision_repo_test.go b/internal/repo/revision/revision_repo_test.go new file mode 100644 index 00000000..766107e1 --- /dev/null +++ b/internal/repo/revision/revision_repo_test.go @@ -0,0 +1,49 @@ +package revision + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/entity" + repo2 "github.com/segmentfault/answer/internal/repo" + "github.com/segmentfault/answer/internal/repo/unique" + "github.com/segmentfault/pacman/log" + "github.com/stretchr/testify/assert" +) + +var ( + dataSource *data.Data + log log.log +) + +func Init() { + s, _ := os.LookupEnv("TESTDATA-DB-CONNECTION") + fmt.Println(s) + cache, _, _ := data.NewCache(log.Getlog(), &data.CacheConf{}) + dataSource, _, _ = data.NewData(log.Getlog(), data.NewDB(true, &data.Database{ + Connection: s, + }), cache) + log = log.Getlog() +} + +func TestRevisionRepo_AddRevision(t *testing.T) { + Init() + ctx := context.Background() + uniqueIDRepo := unique.NewUniqueIDRepo(log, dataSource) + questionRepo := repo2.NewQuestionRepo(log, dataSource, uniqueIDRepo) + question, _, _ := questionRepo.GetQuestion(ctx, "10010000000000048") + repo := NewRevisionRepo(log, dataSource, uniqueIDRepo) + revision := &entity.Revision{ + UserID: question.UserID, + ObjectType: 0, + ObjectID: question.ID, + Title: question.Title, + Content: question.OriginalText, + Status: 1, + } + err := repo.AddRevision(ctx, revision, true) + assert.NoError(t, err) +} diff --git a/internal/repo/search_repo.go b/internal/repo/search_repo.go new file mode 100644 index 00000000..ff042da5 --- /dev/null +++ b/internal/repo/search_repo.go @@ -0,0 +1,296 @@ +package repo + +import ( + "context" + "strings" + "time" + + "github.com/jinzhu/copier" + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/search_common" + "github.com/segmentfault/answer/internal/service/unique" + usercommon "github.com/segmentfault/answer/internal/service/user_common" + "github.com/segmentfault/answer/pkg/converter" + "github.com/segmentfault/answer/pkg/obj" + "github.com/segmentfault/pacman/errors" + "xorm.io/builder" +) + +// searchRepo tag repository +type searchRepo struct { + data *data.Data + userRepo usercommon.UserRepo + uniqueIDRepo unique.UniqueIDRepo +} + +// NewSearchRepo new repository +func NewSearchRepo(data *data.Data, uniqueIDRepo unique.UniqueIDRepo, userRepo usercommon.UserRepo) search_common.SearchRepo { + return &searchRepo{ + data: data, + uniqueIDRepo: uniqueIDRepo, + userRepo: userRepo, + } +} + +func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagID, userID string, votes int, page, size int) (resp []schema.SearchResp, total int64, err error) { + var ( + b *builder.Builder + ub *builder.Builder + ) + b = builder.Select( + "`question`.`id`", + "`question`.`id` as `question_id`", + "`title`", + "`original_text`", + "`question`.`created_at`", + "`user_id`", + "`vote_count`", + "`answer_count`", + "0 as `accepted`", + ).From("`question`") + ub = builder.Select( + "`answer`.`id` as `id`", + "`question_id`", + "`question`.`title` as `title`", + "`answer`.`original_text` as `original_text`", + "`answer`.`created_at`", + "`answer`.`user_id` as `user_id`", + "`answer`.`vote_count` as `vote_count`", + "0 as `answer_count`", + "`adopted` as `accepted`", + ).From("`answer`"). + LeftJoin("`question`", "`question`.id = `answer`.question_id") + + for i, word := range words { + if i == 0 { + b.Where(builder.Like{"original_text", word}) + ub.Where(builder.Like{"`answer`.original_text", word}) + } else { + b.Or(builder.Like{"original_text", word}) + ub.Or(builder.Like{"`answer`.original_text", word}) + } + } + + // check tag + if tagID != "" { + b.Join("INNER", "tag_rel", "question.id = tag_rel.object_id"). + Where(builder.Eq{"tag_rel.tag_id": tagID}) + } + + // check user + if userID != "" { + b.Where(builder.Eq{"question.user_id": userID}) + ub.Where(builder.Eq{"answer.user_id": userID}) + } + + // check vote + if votes == 0 { + b.Where(builder.Eq{"question.vote_count": votes}) + ub.Where(builder.Eq{"answer.vote_count": votes}) + } else if votes > 0 { + b.Where(builder.Gte{"question.vote_count": votes}) + ub.Where(builder.Gte{"answer.vote_count": votes}) + } + + b = b.Union("all", ub) + _, _, err = b.ToSQL() + if err != nil { + return + } + + res, err := sr.data.DB.OrderBy("created_at DESC").Limit(size, page).Query(b) + + tr, err := sr.data.DB.Query(builder.Select("count(*) total").From(b, "c")) + if len(tr) != 0 { + total = converter.StringToInt64(string(tr[0]["total"])) + } + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return + } else { + resp, err = sr.parseResult(ctx, res) + return + } +} + +func (sr *searchRepo) SearchQuestions(ctx context.Context, words []string, limitNoAccepted bool, answers, page, size int) (resp []schema.SearchResp, total int64, err error) { + b := builder.Select( + "`id`", + "`id` as `question_id`", + "`title`", + "`original_text`", + "`created_at`", + "`user_id`", + "`vote_count`", + "`answer_count`", + "0 as `accepted`", + ).From("question") + + for i, word := range words { + if i == 0 { + b.Where(builder.Like{"original_text", word}) + } else { + b.Or(builder.Like{"original_text", word}) + } + } + + // check need filter has not accepted + if limitNoAccepted { + b.And(builder.Eq{"accepted_answer_id": 0}) + } + + if answers == 0 { + b.And(builder.Eq{"answer_count": 0}) + } else if answers > 0 { + b.And(builder.Gte{"answer_count": answers}) + } + + res, err := sr.data.DB.OrderBy("created_at DESC").Limit(size, page).Query(b) + + tr, err := sr.data.DB.Query(builder.Select("count(*) total").From(b, "c")) + if len(tr) != 0 { + total = converter.StringToInt64(string(tr[0]["total"])) + } + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return + } + resp, err = sr.parseResult(ctx, res) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +func (sr *searchRepo) SearchAnswers(ctx context.Context, words []string, limitAccepted bool, questionID string, page, size int) (resp []schema.SearchResp, total int64, err error) { + b := builder.Select( + "`answer`.`id` as `id`", + "`question_id`", + "`question`.`title` as `title`", + "`answer`.`original_text` as `original_text`", + "`answer`.`created_at`", + "`answer`.`user_id` as `user_id`", + "`answer`.`vote_count` as `vote_count`", + "0 as `answer_count`", + "`adopted` as `accepted`", + ).From("`answer`"). + LeftJoin("`question`", "`question`.id = `answer`.question_id") + + for i, word := range words { + if i == 0 { + b.Where(builder.Like{"`answer`.original_text", word}) + } else { + b.Or(builder.Like{"`answer`.original_text", word}) + } + } + + if limitAccepted { + b.Where(builder.Eq{"adopted": 2}) + } + + if questionID != "" { + b.Where(builder.Eq{"question_id": questionID}) + } + + res, err := sr.data.DB.OrderBy("created_at DESC").Limit(size, page).Query(b) + + tr, err := sr.data.DB.Query(builder.Select("count(*) total").From(b, "c")) + total = converter.StringToInt64(string(tr[0]["total"])) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return + } + resp, err = sr.parseResult(ctx, res) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +func (sr *searchRepo) parseResult(ctx context.Context, res []map[string][]byte) (resp []schema.SearchResp, err error) { + for _, r := range res { + var ( + objectKey string + uInfo *schema.UserBasicInfo + + tags []schema.TagResp + tagsEntity []entity.Tag + object schema.SearchObject + ) + 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, exist, e := sr.userRepo.GetByUserID(ctx, string(r["user_id"])) + if e != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(e).WithStack() + return + } + + if exist { + uInfo = sr.userBasicInfoFormat(ctx, userInfo) + } + + // get tags + err = sr.data.DB. + Select("`display_name`,`slug_name`,`main_tag_slug_name`"). + 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}). + Find(&tagsEntity) + + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return + } + _ = copier.Copy(&tags, tagsEntity) + + object = schema.SearchObject{ + ID: string(r["id"]), + Title: string(r["title"]), + Excerpt: cutOutParsedText(string(r["original_text"])), + CreatedAtParsed: tp.Unix(), + UserInfo: uInfo, + Tags: tags, + VoteCount: converter.StringToInt(string(r["vote_count"])), + Accepted: string(r["accepted"]) == "2", + AnswerCount: converter.StringToInt(string(r["answer_count"])), + } + resp = append(resp, schema.SearchResp{ + ObjectType: objectKey, + Object: object, + }) + } + return +} + +// userBasicInfoFormat +func (sr *searchRepo) userBasicInfoFormat(ctx context.Context, dbinfo *entity.User) *schema.UserBasicInfo { + return &schema.UserBasicInfo{ + UserId: dbinfo.ID, + UserName: dbinfo.Username, + Rank: dbinfo.Rank, + DisplayName: dbinfo.DisplayName, + Avatar: dbinfo.Avatar, + Website: dbinfo.Website, + Location: dbinfo.Location, + IpInfo: dbinfo.IPInfo, + } +} + +func cutOutParsedText(parsedText string) string { + parsedText = strings.TrimSpace(parsedText) + idx := strings.Index(parsedText, "\n") + if idx >= 0 { + parsedText = parsedText[0:idx] + } + return parsedText +} diff --git a/internal/repo/siteinfo_repo.go b/internal/repo/siteinfo_repo.go new file mode 100644 index 00000000..b15262bf --- /dev/null +++ b/internal/repo/siteinfo_repo.go @@ -0,0 +1,54 @@ +package repo + +import ( + "context" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/service/siteinfo_common" + "github.com/segmentfault/pacman/errors" + "xorm.io/builder" +) + +type siteInfoRepo struct { + data *data.Data +} + +func NewSiteInfo(data *data.Data) siteinfo_common.SiteInfoRepo { + return &siteInfoRepo{ + data: data, + } +} + +// SaveByType save site setting by type +func (sr *siteInfoRepo) SaveByType(ctx context.Context, siteType string, data *entity.SiteInfo) (err error) { + var ( + old = &entity.SiteInfo{} + exist bool + ) + exist, err = sr.data.DB.Where(builder.Eq{"type": siteType}).Get(old) + if exist { + _, err = sr.data.DB.ID(old.ID).Update(data) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return + } + + _, err = sr.data.DB.Insert(data) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetByType get site info by type +func (sr *siteInfoRepo) GetByType(ctx context.Context, siteType string) (siteInfo *entity.SiteInfo, exist bool, err error) { + siteInfo = &entity.SiteInfo{} + exist, err = sr.data.DB.Where(builder.Eq{"type": siteType}).Get(siteInfo) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} diff --git a/internal/repo/tag/tag_rel_repo.go b/internal/repo/tag/tag_rel_repo.go new file mode 100644 index 00000000..5ba7758e --- /dev/null +++ b/internal/repo/tag/tag_rel_repo.go @@ -0,0 +1,104 @@ +package tag + +import ( + "context" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + tagcommon "github.com/segmentfault/answer/internal/service/tag_common" + "github.com/segmentfault/pacman/errors" +) + +// tagListRepo tagList repository +type tagListRepo struct { + data *data.Data +} + +// NewTagListRepo new repository +func NewTagListRepo(data *data.Data) tagcommon.TagRelRepo { + return &tagListRepo{ + data: data, + } +} + +// AddTagRelList add tag list +func (tr *tagListRepo) AddTagRelList(ctx context.Context, tagList []*entity.TagRel) (err error) { + _, err = tr.data.DB.Insert(tagList) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// RemoveTagRelListByObjectID delete tag list +func (tr *tagListRepo) RemoveTagRelListByObjectID(ctx context.Context, objectId string) (err error) { + _, err = tr.data.DB.Where("object_id = ?", objectId).Update(&entity.TagRel{Status: entity.TagRelStatusDeleted}) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// RemoveTagRelListByIDs delete tag list +func (tr *tagListRepo) RemoveTagRelListByIDs(ctx context.Context, ids []int64) (err error) { + _, err = tr.data.DB.In("id", ids).Update(&entity.TagRel{Status: entity.TagRelStatusDeleted}) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetObjectTagRelWithoutStatus get object tag relation no matter status +func (tr *tagListRepo) GetObjectTagRelWithoutStatus(ctx context.Context, objectId, tagID string) ( + tagRel *entity.TagRel, exist bool, err error) { + tagRel = &entity.TagRel{} + session := tr.data.DB.Where("object_id = ?", objectId).And("tag_id = ?", tagID) + exist, err = session.Get(tagRel) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// EnableTagRelByIDs update tag status to available +func (tr *tagListRepo) EnableTagRelByIDs(ctx context.Context, ids []int64) (err error) { + _, err = tr.data.DB.In("id", ids).Update(&entity.TagRel{Status: entity.TagRelStatusAvailable}) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetObjectTagRelList get object tag relation list all +func (tr *tagListRepo) GetObjectTagRelList(ctx context.Context, objectId string) (tagListList []*entity.TagRel, err error) { + tagListList = make([]*entity.TagRel, 0) + session := tr.data.DB.Where("object_id = ?", objectId) + session.Where("status = ?", entity.TagRelStatusAvailable) + err = session.Find(&tagListList) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// BatchGetObjectTagRelList get object tag relation list all +func (tr *tagListRepo) BatchGetObjectTagRelList(ctx context.Context, objectIds []string) (tagListList []*entity.TagRel, err error) { + tagListList = make([]*entity.TagRel, 0) + session := tr.data.DB.In("object_id", objectIds) + session.Where("status = ?", entity.TagRelStatusAvailable) + err = session.Find(&tagListList) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// CountTagRelByTagID count tag relation +func (tr *tagListRepo) CountTagRelByTagID(ctx context.Context, tagID string) (count int64, err error) { + count, err = tr.data.DB.Count(&entity.TagRel{TagID: tagID, Status: entity.AnswerStatusAvailable}) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} diff --git a/internal/repo/tag/tag_repo.go b/internal/repo/tag/tag_repo.go new file mode 100644 index 00000000..4622a85b --- /dev/null +++ b/internal/repo/tag/tag_repo.go @@ -0,0 +1,191 @@ +package tag + +import ( + "context" + "fmt" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/pager" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + tagcommon "github.com/segmentfault/answer/internal/service/tag_common" + "github.com/segmentfault/answer/internal/service/unique" + "github.com/segmentfault/pacman/errors" + "xorm.io/builder" +) + +// tagRepo tag repository +type tagRepo struct { + data *data.Data + uniqueIDRepo unique.UniqueIDRepo +} + +// NewTagRepo new repository +func NewTagRepo( + data *data.Data, + uniqueIDRepo unique.UniqueIDRepo, +) tagcommon.TagRepo { + return &tagRepo{ + data: data, + uniqueIDRepo: uniqueIDRepo, + } +} + +// AddTagList add tag +func (tr *tagRepo) AddTagList(ctx context.Context, tagList []*entity.Tag) (err error) { + for _, item := range tagList { + ID, err := tr.uniqueIDRepo.GenUniqueID(ctx, item.TableName()) + if err != nil { + return err + } + item.RevisionID = "0" + item.ID = fmt.Sprintf("%d", ID) + } + _, err = tr.data.DB.Insert(tagList) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetTagListByIDs get tag list all +func (tr *tagRepo) GetTagListByIDs(ctx context.Context, ids []string) (tagList []*entity.Tag, err error) { + tagList = make([]*entity.Tag, 0) + session := tr.data.DB.In("id", ids) + session.Where(builder.Eq{"status": entity.TagStatusAvailable}) + err = session.Find(&tagList) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetTagBySlugName get tag by slug name +func (tr *tagRepo) GetTagBySlugName(ctx context.Context, slugName string) (tagInfo *entity.Tag, exist bool, err error) { + tagInfo = &entity.Tag{} + session := tr.data.DB.Where("slug_name = ?", slugName) + session.Where(builder.Eq{"status": entity.TagStatusAvailable}) + exist, err = session.Get(tagInfo) + if err != nil { + return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetTagListByName get tag list all like name +func (tr *tagRepo) GetTagListByName(ctx context.Context, name string) (tagList []*entity.Tag, err error) { + tagList = make([]*entity.Tag, 0) + session := tr.data.DB.Where(builder.Like{"slug_name", name}) + session.Where(builder.Eq{"status": entity.TagStatusAvailable}) + err = session.Find(&tagList) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetTagListByNames get tag list all like name +func (tr *tagRepo) GetTagListByNames(ctx context.Context, names []string) (tagList []*entity.Tag, err error) { + tagList = make([]*entity.Tag, 0) + session := tr.data.DB.In("slug_name", names) + session.Where(builder.Eq{"status": entity.TagStatusAvailable}) + err = session.Find(&tagList) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// RemoveTag delete tag +func (tr *tagRepo) RemoveTag(ctx context.Context, tagID string) (err error) { + session := tr.data.DB.Where(builder.Eq{"id": tagID}) + _, err = session.Update(&entity.Tag{Status: entity.TagStatusDeleted}) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// UpdateTag update tag +func (tr *tagRepo) UpdateTag(ctx context.Context, tag *entity.Tag) (err error) { + _, err = tr.data.DB.Where(builder.Eq{"id": tag.ID}).Update(tag) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// UpdateTagQuestionCount update tag question count +func (tr *tagRepo) UpdateTagQuestionCount(ctx context.Context, tagID string, questionCount int) (err error) { + cond := &entity.Tag{QuestionCount: questionCount} + _, err = tr.data.DB.Where(builder.Eq{"id": tagID}).MustCols("question_count").Update(cond) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// UpdateTagSynonym update synonym tag +func (tr *tagRepo) UpdateTagSynonym(ctx context.Context, tagSlugNameList []string, mainTagID int64, + mainTagSlugName string) (err error) { + bean := &entity.Tag{MainTagID: mainTagID, MainTagSlugName: mainTagSlugName} + session := tr.data.DB.In("slug_name", tagSlugNameList).MustCols("main_tag_id", "main_tag_slug_name") + _, err = session.Update(bean) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetTagByID get tag one +func (tr *tagRepo) GetTagByID(ctx context.Context, tagID string) ( + tag *entity.Tag, exist bool, err error) { + tag = &entity.Tag{} + session := tr.data.DB.Where(builder.Eq{"id": tagID}) + session.Where(builder.Eq{"status": entity.TagStatusAvailable}) + exist, err = session.Get(tag) + if err != nil { + return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetTagList get tag list all +func (tr *tagRepo) GetTagList(ctx context.Context, tag *entity.Tag) (tagList []*entity.Tag, err error) { + tagList = make([]*entity.Tag, 0) + session := tr.data.DB.Where(builder.Eq{"status": entity.TagStatusAvailable}) + err = session.Find(&tagList, tag) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetTagPage get tag page +func (tr *tagRepo) GetTagPage(ctx context.Context, page, pageSize int, tag *entity.Tag, queryCond string) ( + tagList []*entity.Tag, total int64, err error) { + tagList = make([]*entity.Tag, 0) + session := tr.data.DB.NewSession() + + if len(tag.SlugName) > 0 { + session.Where(builder.Or(builder.Like{"slug_name", tag.SlugName}, builder.Like{"display_name", tag.SlugName})) + tag.SlugName = "" + } + session.Where(builder.Eq{"status": entity.TagStatusAvailable}) + session.Where("main_tag_id = 0") // if this tag is synonym, exclude it + + switch queryCond { + case "popular": + session.Desc("question_count") + case "name": + session.Asc("slug_name") + case "newest": + session.Desc("created_at") + } + + total, err = pager.Help(page, pageSize, &tagList, tag, session) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} diff --git a/internal/repo/unique/uniqid_repo.go b/internal/repo/unique/uniqid_repo.go new file mode 100644 index 00000000..f216ca0f --- /dev/null +++ b/internal/repo/unique/uniqid_repo.go @@ -0,0 +1,51 @@ +package unique + +import ( + "context" + "fmt" + "strconv" + + "github.com/segmentfault/answer/internal/base/constant" + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/service/unique" + "github.com/segmentfault/pacman/errors" +) + +// uniqueIDRepo Unique id repository +type uniqueIDRepo struct { + data *data.Data +} + +// NewUniqueIDRepo new repository +func NewUniqueIDRepo(data *data.Data) unique.UniqueIDRepo { + return &uniqueIDRepo{ + data: data, + } +} + +// GenUniqueID generate unique id +// 1 + 00x(objectType) + 000000000000x(id) +func (ur *uniqueIDRepo) GenUniqueID(ctx context.Context, key string) (uniqueID int64, err error) { + idStr, err := ur.GenUniqueIDStr(ctx, key) + if err != nil { + return 0, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + uniqueID, err = strconv.ParseInt(idStr, 10, 64) + if err != nil { + return 0, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return uniqueID, nil +} + +// GenUniqueIDStr generate unique id string +func (ur *uniqueIDRepo) GenUniqueIDStr(ctx context.Context, key string) (uniqueID string, err error) { + objectType := constant.ObjectTypeStrMapping[key] + bean := &entity.Uniqid{UniqidType: objectType} + _, err = ur.data.DB.Insert(bean) + if err != nil { + return "", errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return fmt.Sprintf("1%03d%013d", objectType, bean.ID), nil +} diff --git a/internal/repo/user/user_backyard_repo.go b/internal/repo/user/user_backyard_repo.go new file mode 100644 index 00000000..feccf286 --- /dev/null +++ b/internal/repo/user/user_backyard_repo.go @@ -0,0 +1,86 @@ +package user + +import ( + "context" + "encoding/json" + "time" + + "github.com/segmentfault/answer/internal/base/constant" + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/pager" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/service/user_backyard" + "github.com/segmentfault/pacman/errors" + "github.com/segmentfault/pacman/log" +) + +// userBackyardRepo user repository +type userBackyardRepo struct { + data *data.Data +} + +// NewUserBackyardRepo new repository +func NewUserBackyardRepo(data *data.Data) user_backyard.UserBackyardRepo { + return &userBackyardRepo{ + data: data, + } +} + +// UpdateUserStatus update user status +func (ur *userBackyardRepo) UpdateUserStatus(ctx context.Context, userID string, userStatus, mailStatus int, + email string) (err error) { + cond := &entity.User{Status: userStatus, MailStatus: mailStatus, EMail: email} + switch userStatus { + case entity.UserStatusSuspended: + cond.SuspendedAt = time.Now() + case entity.UserStatusDeleted: + cond.DeletedAt = time.Now() + } + _, err = ur.data.DB.ID(userID).Update(cond) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + + userCacheInfo := &entity.UserCacheInfo{ + UserID: userID, + EmailStatus: mailStatus, + UserStatus: userStatus, + } + t, _ := json.Marshal(userCacheInfo) + log.Infof("user change status: %s", string(t)) + err = ur.data.Cache.SetString(ctx, constant.UserStatusChangedCacheKey+userID, string(t), + constant.UserStatusChangedCacheTime) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetUserInfo get user info +func (ur *userBackyardRepo) GetUserInfo(ctx context.Context, userID string) (user *entity.User, exist bool, err error) { + user = &entity.User{} + exist, err = ur.data.DB.ID(userID).Get(user) + if err != nil { + return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetUserPage get user page +func (ur *userBackyardRepo) GetUserPage(ctx context.Context, page, pageSize int, user *entity.User) (users []*entity.User, total int64, err error) { + users = make([]*entity.User, 0) + session := ur.data.DB.NewSession() + if user.Status == entity.UserStatusDeleted { + session.Desc("deleted_at") + } else if user.Status == entity.UserStatusSuspended { + session.Desc("suspended_at") + } else { + session.Desc("created_at") + } + total, err = pager.Help(page, pageSize, &users, user, session) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} diff --git a/internal/repo/user/user_repo.go b/internal/repo/user/user_repo.go new file mode 100644 index 00000000..c3ce2c98 --- /dev/null +++ b/internal/repo/user/user_repo.go @@ -0,0 +1,155 @@ +package user + +import ( + "context" + "fmt" + "time" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/service/config" + usercommon "github.com/segmentfault/answer/internal/service/user_common" + "github.com/segmentfault/pacman/errors" +) + +// userRepo user repository +type userRepo struct { + data *data.Data + configRepo config.ConfigRepo +} + +// NewUserRepo new repository +func NewUserRepo(data *data.Data, configRepo config.ConfigRepo) usercommon.UserRepo { + return &userRepo{ + data: data, + configRepo: configRepo, + } +} + +// AddUser add user +func (ur *userRepo) AddUser(ctx context.Context, user *entity.User) (err error) { + _, err = ur.data.DB.Insert(user) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// IncreaseAnswerCount increase answer count +func (ur *userRepo) IncreaseAnswerCount(ctx context.Context, userID string, amount int) (err error) { + user := &entity.User{} + _, err = ur.data.DB.Where("id = ?", userID).Incr("answer_count", amount).Update(user) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return nil +} + +// IncreaseQuestionCount increase question count +func (ur *userRepo) IncreaseQuestionCount(ctx context.Context, userID string, amount int) (err error) { + user := &entity.User{} + _, err = ur.data.DB.Where("id = ?", userID).Incr("question_count", amount).Update(user) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return nil +} + +// UpdateLastLoginDate update last login date +func (ur *userRepo) UpdateLastLoginDate(ctx context.Context, userID string) (err error) { + user := &entity.User{LastLoginDate: time.Now()} + _, err = ur.data.DB.Where("id = ?", userID).Cols("last_login_date").Update(user) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return nil +} + +// UpdateEmailStatus update email status +func (ur *userRepo) UpdateEmailStatus(ctx context.Context, userID string, emailStatus int) error { + cond := &entity.User{MailStatus: emailStatus} + _, err := ur.data.DB.Where("id = ?", userID).Cols("mail_status").Update(cond) + if err != nil { + return err + } + return nil +} + +// UpdateNoticeStatus update notice status +func (ur *userRepo) UpdateNoticeStatus(ctx context.Context, userID string, noticeStatus int) error { + cond := &entity.User{NoticeStatus: noticeStatus} + _, err := ur.data.DB.Where("id = ?", userID).Cols("notice_status").Update(cond) + if err != nil { + return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return nil +} + +func (ur *userRepo) UpdatePass(ctx context.Context, Data *entity.User) error { + if Data.ID == "" { + return fmt.Errorf("input error") + } + _, err := ur.data.DB.Where("id = ?", Data.ID).Cols("pass").Update(Data) + if err != nil { + return err + } + return nil +} + +func (ur *userRepo) UpdateEmail(ctx context.Context, userID, email string) (err error) { + _, err = ur.data.DB.Where("id = ?", userID).Update(&entity.User{EMail: email}) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// UpdateInfo update user info +func (ur *userRepo) UpdateInfo(ctx context.Context, userInfo *entity.User) (err error) { + _, err = ur.data.DB.Where("id = ?", userInfo.ID). + Cols("display_name", "avatar", "bio", "bio_html", "website", "location").Update(userInfo) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetByUserID get user info by user id +func (ur *userRepo) GetByUserID(ctx context.Context, userID string) (userInfo *entity.User, exist bool, err error) { + userInfo = &entity.User{} + exist, err = ur.data.DB.Where("id = ?", userID).Get(userInfo) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +func (ur *userRepo) BatchGetByID(ctx context.Context, ids []string) ([]*entity.User, error) { + list := make([]*entity.User, 0) + err := ur.data.DB.In("id", ids).Find(&list) + if err != nil { + return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return list, nil +} + +// GetByUsername get user by username +func (ur *userRepo) GetByUsername(ctx context.Context, username string) (userInfo *entity.User, exist bool, err error) { + userInfo = &entity.User{} + exist, err = ur.data.DB.Where("username = ?", username).Get(userInfo) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +// GetByEmail get user by email +func (ur *userRepo) GetByEmail(ctx context.Context, email string) (userInfo *entity.User, exist bool, err error) { + userInfo = &entity.User{} + exist, err = ur.data.DB.Where("e_mail = ?", email).Get(userInfo) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} diff --git a/internal/router/answer_api_router.go b/internal/router/answer_api_router.go new file mode 100644 index 00000000..fb171877 --- /dev/null +++ b/internal/router/answer_api_router.go @@ -0,0 +1,225 @@ +package router + +import ( + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/controller" + "github.com/segmentfault/answer/internal/controller_backyard" +) + +type AnswerAPIRouter struct { + langController *controller.LangController + userController *controller.UserController + commentController *controller.CommentController + reportController *controller.ReportController + voteController *controller.VoteController + tagController *controller.TagController + followController *controller.FollowController + collectionController *controller.CollectionController + questionController *controller.QuestionController + answerController *controller.AnswerController + searchController *controller.SearchController + revisionController *controller.RevisionController + rankController *controller.RankController + backyardReportController *controller_backyard.ReportController + backyardUserController *controller_backyard.UserBackyardController + reasonController *controller.ReasonController + themeController *controller_backyard.ThemeController + siteInfoController *controller_backyard.SiteInfoController + siteinfoController *controller.SiteinfoController + notificationController *controller.NotificationController +} + +func NewAnswerAPIRouter( + langController *controller.LangController, + userController *controller.UserController, + commentController *controller.CommentController, + reportController *controller.ReportController, + voteController *controller.VoteController, + tagController *controller.TagController, + followController *controller.FollowController, + collectionController *controller.CollectionController, + questionController *controller.QuestionController, + answerController *controller.AnswerController, + searchController *controller.SearchController, + revisionController *controller.RevisionController, + rankController *controller.RankController, + backyardReportController *controller_backyard.ReportController, + backyardUserController *controller_backyard.UserBackyardController, + reasonController *controller.ReasonController, + themeController *controller_backyard.ThemeController, + siteInfoController *controller_backyard.SiteInfoController, + siteinfoController *controller.SiteinfoController, + notificationController *controller.NotificationController, + +) *AnswerAPIRouter { + return &AnswerAPIRouter{ + langController: langController, + userController: userController, + commentController: commentController, + reportController: reportController, + voteController: voteController, + tagController: tagController, + followController: followController, + collectionController: collectionController, + questionController: questionController, + answerController: answerController, + searchController: searchController, + revisionController: revisionController, + rankController: rankController, + backyardReportController: backyardReportController, + backyardUserController: backyardUserController, + reasonController: reasonController, + themeController: themeController, + siteInfoController: siteInfoController, + notificationController: notificationController, + siteinfoController: siteinfoController, + } +} + +func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) { + // i18n + r.GET("/language/config", a.langController.GetLangMapping) + r.GET("/language/options", a.langController.GetLangOptions) + + // comment + r.GET("/comment/page", a.commentController.GetCommentWithPage) + r.GET("/personal/comment/page", a.commentController.GetCommentPersonalWithPage) + r.GET("/comment", a.commentController.GetComment) + + // user + r.GET("/user/action/record", a.userController.ActionRecord) + r.POST("/user/login/email", a.userController.UserEmailLogin) + r.POST("/user/register/email", a.userController.UserRegisterByEmail) + r.POST("/user/email/verification", a.userController.UserVerifyEmail) + r.POST("/user/password/reset", a.userController.RetrievePassWord) + r.POST("/user/password/replacement", a.userController.UseRePassWord) + r.GET("/personal/user/info", a.userController.GetOtherUserInfoByUsername) + r.POST("/user/email/verification/send", a.userController.UserVerifyEmailSend) + r.GET("/user/logout", a.userController.UserLogout) + r.PUT("/user/email", a.userController.UserChangeEmailVerify) + + //answer + r.GET("/answer/info", a.answerController.Get) + r.POST("/answer/list", a.answerController.AnswerList) + r.GET("/personal/answer/page", a.questionController.UserAnswerList) + + //question + r.GET("/question/info", a.questionController.GetQuestion) + r.POST("/question/search", a.questionController.SearchList) + r.GET("/question/page", a.questionController.Index) + r.GET("/question/similar/tag", a.questionController.SimilarQuestion) + r.GET("/personal/qa/top", a.questionController.UserTop) + r.GET("/personal/question/page", a.questionController.UserList) + + //revision + r.GET("/revisions", a.revisionController.GetRevisionList) + + // tag + r.GET("/tags/page", a.tagController.GetTagWithPage) + r.GET("/tags/following", a.tagController.GetFollowingTags) + r.GET("/tag", a.tagController.GetTagInfo) + r.GET("/tag/synonyms", a.tagController.GetTagSynonyms) + r.GET("/question/index", a.questionController.Index) + + //search + r.GET("/search", a.searchController.Search) + + //rank + r.GET("/personal/rank/page", a.rankController.GetRankPersonalWithPage) + + //siteinfo + r.GET("/siteinfo", a.siteinfoController.GetInfo) +} + +func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) { + // comment + r.POST("/comment", a.commentController.AddComment) + r.DELETE("/comment", a.commentController.RemoveComment) + r.PUT("/comment", a.commentController.UpdateComment) + + // report + r.POST("/report", a.reportController.AddReport) + + // vote + r.POST("/vote/up", a.voteController.VoteUp) + r.POST("/vote/down", a.voteController.VoteDown) + + // follow + r.POST("/follow", a.followController.Follow) + r.PUT("/follow/tags", a.followController.UpdateFollowTags) + + // tag + r.GET("/question/tags", a.tagController.SearchTagLike) + r.PUT("/tag", a.tagController.UpdateTag) + r.DELETE("/tag", a.tagController.RemoveTag) + r.PUT("/tag/synonym", a.tagController.UpdateTagSynonym) + + // collection + r.POST("/collection/switch", a.collectionController.CollectionSwitch) + r.GET("/personal/collection/page", a.questionController.UserCollectionList) + + // question + r.POST("/question", a.questionController.AddQuestion) + r.PUT("/question", a.questionController.UpdateQuestion) + r.DELETE("/question", a.questionController.RemoveQuestion) + r.PUT("/question/status", a.questionController.CloseQuestion) + r.GET("/question/similar", a.questionController.SearchByTitleLike) + + // answer + r.POST("/answer", a.answerController.Add) + r.PUT("/answer", a.answerController.Update) + r.POST("/answer/acceptance", a.answerController.Adopted) + r.DELETE("/answer", a.answerController.RemoveAnswer) + + // user + r.GET("/user/info", a.userController.GetUserInfoByUserID) + r.PUT("/user/password", a.userController.UserModifyPassWord) + r.PUT("/user/info", a.userController.UserUpdateInfo) + r.POST("/user/avatar/upload", a.userController.UploadUserAvatar) + r.POST("/user/post/file", a.userController.UploadUserPostFile) + r.POST("/user/notice/set", a.userController.UserNoticeSet) + r.POST("/user/email/change/code", a.userController.UserChangeEmailSendCode) + + // vote + r.GET("/personal/vote/page", a.voteController.UserVotes) + + // reason + r.GET("/reasons", a.reasonController.Reasons) + + // notification + r.GET("/notification/status", a.notificationController.GetRedDot) + r.PUT("/notification/status", a.notificationController.ClearRedDot) + r.GET("/notification/page", a.notificationController.GetList) + r.PUT("/notification/read/state/all", a.notificationController.ClearUnRead) + r.PUT("/notification/read/state", a.notificationController.ClearIDUnRead) +} + +func (a *AnswerAPIRouter) RegisterAnswerCmsAPIRouter(r *gin.RouterGroup) { + r.GET("/question/page", a.questionController.CmsSearchList) + r.PUT("/question/status", a.questionController.AdminSetQuestionStatus) + r.GET("/answer/page", a.questionController.CmsSearchAnswerList) + r.PUT("/answer/status", a.answerController.AdminSetAnswerStatus) + + // report + r.GET("/reports/page", a.backyardReportController.ListReportPage) + r.PUT("/report", a.backyardReportController.Handle) + + // user + r.GET("/users/page", a.backyardUserController.GetUserPage) + r.PUT("/user/status", a.backyardUserController.UpdateUserStatus) + + // reason + r.GET("/reasons", a.reasonController.Reasons) + + // language + r.GET("/language/options", a.langController.GetLangOptions) + + // theme + r.GET("/theme/options", a.themeController.GetThemeOptions) + + // siteinfo + r.GET("/siteinfo/general", a.siteInfoController.GetGeneral) + r.GET("/siteinfo/interface", a.siteInfoController.GetInterface) + r.PUT("/siteinfo/general", a.siteInfoController.UpdateGeneral) + r.PUT("/siteinfo/interface", a.siteInfoController.UpdateInterface) +} diff --git a/internal/router/config.go b/internal/router/config.go new file mode 100644 index 00000000..742c9c8c --- /dev/null +++ b/internal/router/config.go @@ -0,0 +1,9 @@ +package router + +// SwaggerConfig struct describes configure for the Swagger API endpoint +type SwaggerConfig struct { + Show bool `json:"show"` + Protocol string `json:"protocol"` + Host string `json:"host"` + Address string `json:"address"` +} diff --git a/internal/router/provider.go b/internal/router/provider.go new file mode 100644 index 00000000..52679ef7 --- /dev/null +++ b/internal/router/provider.go @@ -0,0 +1,6 @@ +package router + +import "github.com/google/wire" + +// ProviderSetRouter is providers. +var ProviderSetRouter = wire.NewSet(NewAnswerAPIRouter, NewSwaggerRouter, NewStaticRouter, NewViewRouter) diff --git a/internal/router/static_router.go b/internal/router/static_router.go new file mode 100644 index 00000000..76e6b336 --- /dev/null +++ b/internal/router/static_router.go @@ -0,0 +1,23 @@ +package router + +import ( + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/service/service_config" +) + +// StaticRouter static api router +type StaticRouter struct { + serviceConfig *service_config.ServiceConfig +} + +// NewStaticRouter new static api router +func NewStaticRouter(serviceConfig *service_config.ServiceConfig) *StaticRouter { + return &StaticRouter{ + serviceConfig: serviceConfig, + } +} + +// RegisterStaticRouter register static api router +func (a *StaticRouter) RegisterStaticRouter(r *gin.RouterGroup) { + r.Static("/uploads", a.serviceConfig.UploadPath) +} diff --git a/internal/router/swagger_router.go b/internal/router/swagger_router.go new file mode 100644 index 00000000..c6ee462b --- /dev/null +++ b/internal/router/swagger_router.go @@ -0,0 +1,40 @@ +package router + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/docs" + swaggerfiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" +) + +// SwaggerRouter swagger api router +type SwaggerRouter struct { + config *SwaggerConfig +} + +// NewSwaggerRouter new swagger api router +func NewSwaggerRouter(config *SwaggerConfig) *SwaggerRouter { + return &SwaggerRouter{ + config: config, + } +} + +// Register register swagger api router +func (a *SwaggerRouter) Register(r *gin.RouterGroup) { + if a.config.Show { + a.InitSwaggerDocs() + gofmt := fmt.Sprintf("%s://%s%s/swagger/doc.json", a.config.Protocol, a.config.Host, a.config.Address) + r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler, ginSwagger.URL(gofmt))) + } +} + +// InitSwaggerDocs init swagger docs +func (a *SwaggerRouter) InitSwaggerDocs() { + docs.SwaggerInfo.Title = "answer" + docs.SwaggerInfo.Description = "answer api" + docs.SwaggerInfo.Version = "v0.0.1" + docs.SwaggerInfo.Host = fmt.Sprintf("%s%s", a.config.Host, a.config.Address) + docs.SwaggerInfo.BasePath = "/" +} diff --git a/internal/router/view_router.go b/internal/router/view_router.go new file mode 100644 index 00000000..36876932 --- /dev/null +++ b/internal/router/view_router.go @@ -0,0 +1,67 @@ +package router + +import ( + "embed" + "errors" + "io/fs" + "net/http" + "os" + "path" + "path/filepath" + "strings" + + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/web" +) + +// RegisterViewRouter +type ViewRouter struct { +} + +// NewRegisterViewRouter +func NewViewRouter() *ViewRouter { + return &ViewRouter{} +} + +type Resource struct { + fs embed.FS + path string +} + +func NewResource() *Resource { + return &Resource{ + fs: web.Static, + path: "html", + } +} + +func (r *Resource) Open(name string) (fs.File, error) { + if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) { + return nil, errors.New("http: invalid character in file path") + } + fullName := filepath.Join(r.path, filepath.FromSlash(path.Clean("/static/"+name))) + file, err := r.fs.Open(fullName) + return file, err +} + +func (a *ViewRouter) RegisterViewRouter(r *gin.Engine) { + //export answer_html_static_path="../../web/static" + //export answer_html_page_path="../../web" + static := os.Getenv("answer_html_static_path") + index := os.Getenv("answer_html_page_path") + if len(static) > 0 && len(index) > 0 { + r.LoadHTMLGlob(index + "/*.html") + r.Static("/static", static) + r.NoRoute(func(c *gin.Context) { + c.HTML(http.StatusOK, "index.html", gin.H{}) + }) + return + } else { + r.StaticFS("/static", http.FS(NewResource())) + r.NoRoute(func(c *gin.Context) { + c.Header("content-type", "text/html;charset=utf-8") + c.String(200, string(web.Html)) + }) + } + +} diff --git a/internal/schema/answer_schema.go b/internal/schema/answer_schema.go new file mode 100644 index 00000000..f34391ac --- /dev/null +++ b/internal/schema/answer_schema.go @@ -0,0 +1,162 @@ +package schema + +import ( + "time" +) + +// RemoveAnswerReq delete answer request +type RemoveAnswerReq struct { + // answer id + ID string `validate:"required" comment:"answer id" json:"id"` + // user id + UserID string `json:"-"` +} + +// GetAnswerListReq get answer list all request +type GetAnswerListReq struct { + // question id + QuestionID int64 `validate:"omitempty" comment:"question id" form:"question_id"` + // answer user id + UserID int64 `validate:"omitempty" comment:"answer user id" form:"user_id"` + // content markdown + Content string `validate:"omitempty" comment:"content markdown" form:"content"` + // content html + Html string `validate:"omitempty" comment:"content html" form:"html"` + // answer status(available: 1; deleted: 10) + Status int `validate:"omitempty" comment:" answer status(available: 1; deleted: 10)" form:"status"` + // adopted (1 failed 2 adopted) + Adopted int `validate:"omitempty" comment:"adopted (1 failed 2 adopted)" form:"adopted"` + // comment count + CommentCount int `validate:"omitempty" comment:"comment count" form:"comment_count"` + // vote count + VoteCount int `validate:"omitempty" comment:"vote count" form:"vote_count"` + // + CreateTime time.Time `validate:"omitempty" comment:"" form:"create_time"` + // + UpdateTime time.Time `validate:"omitempty" comment:"" form:"update_time"` +} + +// GetAnswerWithPageReq get answer list page request +type GetAnswerWithPageReq struct { + // page + Page int `validate:"omitempty,min=1" form:"page"` + // page size + PageSize int `validate:"omitempty,min=1" form:"page_size"` + // question id + QuestionID int64 `validate:"omitempty" comment:"question id" form:"question_id"` + // answer user id + UserID int64 `validate:"omitempty" comment:"answer user id" form:"user_id"` + // content markdown + Content string `validate:"omitempty" comment:"content markdown" form:"content"` + // content html + Html string `validate:"omitempty" comment:"content html" form:"html"` + // answer status(available: 1; deleted: 10) + Status int `validate:"omitempty" comment:" answer status(available: 1; deleted: 10)" form:"status"` + // adopted (1 failed 2 adopted) + Adopted int `validate:"omitempty" comment:"adopted (1 failed 2 adopted)" form:"adopted"` + // comment count + CommentCount int `validate:"omitempty" comment:"comment count" form:"comment_count"` + // vote count + VoteCount int `validate:"omitempty" comment:"vote count" form:"vote_count"` + // + CreateTime time.Time `validate:"omitempty" comment:"" form:"create_time"` + // + UpdateTime time.Time `validate:"omitempty" comment:"" form:"update_time"` +} + +// GetAnswerResp get answer response +type GetAnswerResp struct { + // answer id + ID int64 `json:"id"` + // question id + QuestionID int64 `json:"question_id"` + // answer user id + UserID int64 `json:"user_id"` + // content markdown + Content string `json:"content"` + // content html + Html string `json:"html"` + // answer status(available: 1; deleted: 10) + Status int `json:"status"` + // adopted (1 failed 2 adopted) + Adopted int `json:"adopted"` + // comment count + CommentCount int `json:"comment_count"` + // vote count + VoteCount int `json:"vote_count"` + // + CreateTime time.Time `json:"create_time"` + // + UpdateTime time.Time `json:"update_time"` +} + +const ( + Answer_Adopted_Failed = 1 + Answer_Adopted_Enable = 2 +) + +type AnswerAddReq struct { + QuestionId string `json:"question_id" ` // question_id + Content string `json:"content" ` // content + Html string `json:"html" ` // 解析后的html + UserID string `json:"-" ` // user_id +} + +type AnswerUpdateReq struct { + ID string `json:"id"` // id + QuestionId string `json:"question_id" ` // question_id + UserID string `json:"-" ` // user_id + Title string `json:"title" ` // title + Content string `json:"content"` // content + Html string `json:"html" ` // 解析后的html + EditSummary string `validate:"omitempty" json:"edit_summary"` //edit_summary +} + +type AnswerList struct { + QuestionId string `json:"question_id" ` // question_id + Order string `json:"order" ` // 1 Default 2 time + Page int `json:"page" form:"page"` //Query number of pages + PageSize int `json:"page_size" form:"page_size"` //Search page size + LoginUserID string `json:"-" ` +} + +type AnswerInfo struct { + ID string `json:"id" xorm:"id"` // id + QuestionId string `json:"question_id" xorm:"question_id"` // question_id + Content string `json:"content" xorm:"content"` // content + Html string `json:"html" xorm:"html"` // html + CreateTime int64 `json:"create_time" xorm:"created"` // create_time + UpdateTime int64 `json:"update_time" xorm:"updated"` // update_time + Adopted int `json:"adopted"` // 1 Failed 2 Adopted + UserId string `json:"-" ` + UserInfo *UserBasicInfo `json:"user_info,omitempty"` + UpdateUserInfo *UserBasicInfo `json:"update_user_info,omitempty"` + Collected bool `json:"collected"` + VoteStatus string `json:"vote_status"` + VoteCount int `json:"vote_count"` + QuestionInfo *QuestionInfo `json:"question_info,omitempty"` + + // MemberActions + MemberActions []*PermissionMemberAction `json:"member_actions"` +} + +type AdminAnswerInfo struct { + ID string `json:"id"` + QuestionId string `json:"question_id"` + Description string `json:"description"` + CreateTime int64 `json:"create_time"` + UpdateTime int64 `json:"update_time"` + Adopted int `json:"adopted"` + UserId string `json:"-" ` + UserInfo *UserBasicInfo `json:"user_info"` + VoteCount int `json:"vote_count"` + QuestionInfo struct { + Title string `json:"title"` + } `json:"question_info"` +} + +type AnswerAdoptedReq struct { + QuestionID string `json:"question_id" ` // question_id + AnswerID string `json:"answer_id" ` + UserID string `json:"-" ` +} diff --git a/internal/schema/backyard_user_schema.go b/internal/schema/backyard_user_schema.go new file mode 100644 index 00000000..690bbfc9 --- /dev/null +++ b/internal/schema/backyard_user_schema.go @@ -0,0 +1,72 @@ +package schema + +// UpdateUserStatusReq update user request +type UpdateUserStatusReq struct { + // user id + UserID string `validate:"required" json:"user_id"` + // user status + Status string `validate:"required,oneof=normal suspended deleted inactive" json:"status" enums:"normal,suspended,deleted,inactive"` +} + +const ( + Normal = "normal" + Suspended = "suspended" + Deleted = "deleted" + Inactive = "inactive" +) + +func (r *UpdateUserStatusReq) IsNormal() bool { return r.Status == Normal } +func (r *UpdateUserStatusReq) IsSuspended() bool { return r.Status == Suspended } +func (r *UpdateUserStatusReq) IsDeleted() bool { return r.Status == Deleted } +func (r *UpdateUserStatusReq) IsInactive() bool { return r.Status == Inactive } + +// GetUserPageReq get user list page request +type GetUserPageReq struct { + // page + Page int `validate:"omitempty,min=1" form:"page"` + // page size + PageSize int `validate:"omitempty,min=1" form:"page_size"` + // username + Username string `validate:"omitempty,gt=0,lte=50" form:"username"` + // email + EMail string `validate:"omitempty,gt=0,lte=100" form:"e_mail"` + // user status + Status string `validate:"omitempty,oneof=suspended deleted inactive" form:"status"` +} + +func (r *GetUserPageReq) IsSuspended() bool { return r.Status == Suspended } +func (r *GetUserPageReq) IsDeleted() bool { return r.Status == Deleted } +func (r *GetUserPageReq) IsInactive() bool { return r.Status == Inactive } + +// GetUserPageResp get user response +type GetUserPageResp struct { + // user id + UserID string `json:"user_id"` + // create time + CreatedAt int64 `json:"created_at"` + // delete time + DeletedAt int64 `json:"deleted_at"` + // suspended time + SuspendedAt int64 `json:"suspended_at"` + // username + Username string `json:"username"` + // email + EMail string `json:"e_mail"` + // rank + Rank int `json:"rank"` + // user status(normal,suspended,deleted,inactive) + Status string `json:"status"` + // display name + DisplayName string `json:"display_name"` + // avatar + Avatar string `json:"avatar"` +} + +// GetUserInfoReq get user request +type GetUserInfoReq struct { + UserID string `validate:"required" json:"user_id"` +} + +// GetUserInfoResp get user response +type GetUserInfoResp struct { +} diff --git a/internal/schema/collection_group_schema.go b/internal/schema/collection_group_schema.go new file mode 100644 index 00000000..a8c5a6bd --- /dev/null +++ b/internal/schema/collection_group_schema.go @@ -0,0 +1,114 @@ +package schema + +import "time" + +const ( + CG_DEFAULT = 1 + CG_DIY = 2 +) + +// CollectionSwitchReq switch collection request +type CollectionSwitchReq struct { + // object TagID + ObjectID string `validate:"required" json:"object_id"` + // user collection group TagID + GroupID string `validate:"required" json:"group_id"` +} + +// CollectionSwitchDTO collection data transfer object +type CollectionSwitchDTO struct { + ObjectID string + GroupID string + UserID string +} + +// CollectionSwitchResp switch collection response +type CollectionSwitchResp struct { + ObjectID string `json:"object_id"` + Switch bool `json:"switch"` + ObjectCollectionCount string `json:"object_collection_count"` +} + +// AddCollectionGroupReq add collection group request +type AddCollectionGroupReq struct { + // + UserID int64 `validate:"required" comment:"" json:"user_id"` + // the collection group name + Name string `validate:"required,gt=0,lte=50" comment:"the collection group name" json:"name"` + // mark this group is default, default 1 + DefaultGroup int `validate:"required" comment:"mark this group is default, default 1" json:"default_group"` + // + CreateTime time.Time `validate:"required" comment:"" json:"create_time"` + // + UpdateTime time.Time `validate:"required" comment:"" json:"update_time"` +} + +// RemoveCollectionGroupReq delete collection group request +type RemoveCollectionGroupReq struct { + // + ID int64 `validate:"required" comment:"" json:"id"` +} + +// UpdateCollectionGroupReq update collection group request +type UpdateCollectionGroupReq struct { + // + ID int64 `validate:"required" comment:"" json:"id"` + // + UserID int64 `validate:"omitempty" comment:"" json:"user_id"` + // the collection group name + Name string `validate:"omitempty,gt=0,lte=50" comment:"the collection group name" json:"name"` + // mark this group is default, default 1 + DefaultGroup int `validate:"omitempty" comment:"mark this group is default, default 1" json:"default_group"` + // + CreateTime time.Time `validate:"omitempty" comment:"" json:"create_time"` + // + UpdateTime time.Time `validate:"omitempty" comment:"" json:"update_time"` +} + +// GetCollectionGroupListReq get collection group list all request +type GetCollectionGroupListReq struct { + // + UserID int64 `validate:"omitempty" comment:"" form:"user_id"` + // the collection group name + Name string `validate:"omitempty,gt=0,lte=50" comment:"the collection group name" form:"name"` + // mark this group is default, default 1 + DefaultGroup int `validate:"omitempty" comment:"mark this group is default, default 1" form:"default_group"` + // + CreateTime time.Time `validate:"omitempty" comment:"" form:"create_time"` + // + UpdateTime time.Time `validate:"omitempty" comment:"" form:"update_time"` +} + +// GetCollectionGroupWithPageReq get collection group list page request +type GetCollectionGroupWithPageReq struct { + // page + Page int `validate:"omitempty,min=1" form:"page"` + // page size + PageSize int `validate:"omitempty,min=1" form:"page_size"` + // + UserID int64 `validate:"omitempty" comment:"" form:"user_id"` + // the collection group name + Name string `validate:"omitempty,gt=0,lte=50" comment:"the collection group name" form:"name"` + // mark this group is default, default 1 + DefaultGroup int `validate:"omitempty" comment:"mark this group is default, default 1" form:"default_group"` + // + CreateTime time.Time `validate:"omitempty" comment:"" form:"create_time"` + // + UpdateTime time.Time `validate:"omitempty" comment:"" form:"update_time"` +} + +// GetCollectionGroupResp get collection group response +type GetCollectionGroupResp struct { + // + ID int64 `json:"id"` + // + UserID int64 `json:"user_id"` + // the collection group name + Name string `json:"name"` + // mark this group is default, default 1 + DefaultGroup int `json:"default_group"` + // + CreateTime time.Time `json:"create_time"` + // + UpdateTime time.Time `json:"update_time"` +} diff --git a/internal/schema/collection_schema.go b/internal/schema/collection_schema.go new file mode 100644 index 00000000..af549e6f --- /dev/null +++ b/internal/schema/collection_schema.go @@ -0,0 +1,87 @@ +package schema + +import "time" + +// AddCollectionReq add collection request +type AddCollectionReq struct { + // user id + UserID int64 `validate:"required" comment:"user id" json:"user_id"` + // object id + ObjectID int64 `validate:"required" comment:"object id" json:"object_id"` + // user collection group id + UserCollectionGroupID int64 `validate:"required" comment:"user collection group id" json:"user_collection_group_id"` + // + CreateTime time.Time `validate:"required" comment:"" json:"create_time"` + // + UpdateTime time.Time `validate:"required" comment:"" json:"update_time"` +} + +// RemoveCollectionReq delete collection request +type RemoveCollectionReq struct { + // collection id + ID int64 `validate:"required" comment:"collection id" json:"id"` +} + +// UpdateCollectionReq update collection request +type UpdateCollectionReq struct { + // collection id + ID int64 `validate:"required" comment:"collection id" json:"id"` + // user id + UserID int64 `validate:"omitempty" comment:"user id" json:"user_id"` + // object id + ObjectID int64 `validate:"omitempty" comment:"object id" json:"object_id"` + // user collection group id + UserCollectionGroupID int64 `validate:"omitempty" comment:"user collection group id" json:"user_collection_group_id"` + // + CreateTime time.Time `validate:"omitempty" comment:"" json:"create_time"` + // + UpdateTime time.Time `validate:"omitempty" comment:"" json:"update_time"` +} + +// GetCollectionListReq get collection list all request +type GetCollectionListReq struct { + // user id + UserID int64 `validate:"omitempty" comment:"user id" form:"user_id"` + // object id + ObjectID int64 `validate:"omitempty" comment:"object id" form:"object_id"` + // user collection group id + UserCollectionGroupID int64 `validate:"omitempty" comment:"user collection group id" form:"user_collection_group_id"` + // + CreateTime time.Time `validate:"omitempty" comment:"" form:"create_time"` + // + UpdateTime time.Time `validate:"omitempty" comment:"" form:"update_time"` +} + +// GetCollectionWithPageReq get collection list page request +type GetCollectionWithPageReq struct { + // page + Page int `validate:"omitempty,min=1" form:"page"` + // page size + PageSize int `validate:"omitempty,min=1" form:"page_size"` + // user id + UserID int64 `validate:"omitempty" comment:"user id" form:"user_id"` + // object id + ObjectID int64 `validate:"omitempty" comment:"object id" form:"object_id"` + // user collection group id + UserCollectionGroupID int64 `validate:"omitempty" comment:"user collection group id" form:"user_collection_group_id"` + // + CreateTime time.Time `validate:"omitempty" comment:"" form:"create_time"` + // + UpdateTime time.Time `validate:"omitempty" comment:"" form:"update_time"` +} + +// GetCollectionResp get collection response +type GetCollectionResp struct { + // collection id + ID int64 `json:"id"` + // user id + UserID int64 `json:"user_id"` + // object id + ObjectID int64 `json:"object_id"` + // user collection group id + UserCollectionGroupID int64 `json:"user_collection_group_id"` + // + CreateTime time.Time `json:"create_time"` + // + UpdateTime time.Time `json:"update_time"` +} diff --git a/internal/schema/comment_schema.go b/internal/schema/comment_schema.go new file mode 100644 index 00000000..dfba4607 --- /dev/null +++ b/internal/schema/comment_schema.go @@ -0,0 +1,164 @@ +package schema + +import ( + "github.com/jinzhu/copier" + "github.com/segmentfault/answer/internal/entity" +) + +// AddCommentReq add comment request +type AddCommentReq struct { + // object id + ObjectID string `validate:"required" json:"object_id"` + // reply comment id + ReplyCommentID string `validate:"omitempty" json:"reply_comment_id"` + // original comment content + OriginalText string `validate:"required" json:"original_text"` + // parsed comment content + ParsedText string `validate:"required" json:"parsed_text"` + // @ user id list + MentionUsernameList []string `validate:"omitempty" json:"mention_username_list"` + // user id + UserID string `json:"-"` +} + +// RemoveCommentReq remove comment +type RemoveCommentReq struct { + // comment id + CommentID string `validate:"required" json:"comment_id"` + // user id + UserID string `json:"-"` +} + +// UpdateCommentReq update comment request +type UpdateCommentReq struct { + // comment id + CommentID string `validate:"required" json:"comment_id"` + // original comment content + OriginalText string `validate:"omitempty" json:"original_text"` + // parsed comment content + ParsedText string `validate:"omitempty" json:"parsed_text"` + // user id + UserID string `json:"-"` +} + +// GetCommentListReq get comment list all request +type GetCommentListReq struct { + // user id + UserID int64 `validate:"omitempty" comment:"user id" form:"user_id"` + // reply user id + ReplyUserID int64 `validate:"omitempty" comment:"reply user id" form:"reply_user_id"` + // reply comment id + ReplyCommentID int64 `validate:"omitempty" comment:"reply comment id" form:"reply_comment_id"` + // object id + ObjectID int64 `validate:"omitempty" comment:"object id" form:"object_id"` + // user vote amount + VoteCount int `validate:"omitempty" comment:"user vote amount" form:"vote_count"` + // comment status(available: 0; deleted: 10) + Status int `validate:"omitempty" comment:"comment status(available: 0; deleted: 10)" form:"status"` + // original comment content + OriginalText string `validate:"omitempty" comment:"original comment content" form:"original_text"` + // parsed comment content + ParsedText string `validate:"omitempty" comment:"parsed comment content" form:"parsed_text"` +} + +// GetCommentWithPageReq get comment list page request +type GetCommentWithPageReq struct { + // page + Page int `validate:"omitempty,min=1" form:"page"` + // page size + PageSize int `validate:"omitempty,min=1" form:"page_size"` + // object id + ObjectID string `validate:"required" form:"object_id"` + // query condition + QueryCond string `validate:"omitempty,oneof=vote" form:"query_cond"` + // user id + UserID string `json:"-"` +} + +// GetCommentReq get comment list page request +type GetCommentReq struct { + // object id + ID string `validate:"required" form:"id"` + // user id + UserID string `json:"-"` +} + +// GetCommentResp comment response +type GetCommentResp struct { + // comment id + CommentID string `json:"comment_id"` + // create time + CreatedAt int64 `json:"created_at"` + + // object id + ObjectID string `json:"object_id"` + // user vote amount + VoteCount int `json:"vote_count"` + // current user if already vote this comment + IsVote bool `json:"is_vote"` + // original comment content + OriginalText string `json:"original_text"` + // parsed comment content + ParsedText string `json:"parsed_text"` + + // user id + UserID string `json:"user_id"` + // username + Username string `json:"username"` + // user display name + UserDisplayName string `json:"user_display_name"` + // user avatar + UserAvatar string `json:"user_avatar"` + + // reply user id + ReplyUserID string `json:"reply_user_id"` + // reply user username + ReplyUsername string `json:"reply_username"` + // reply user display name + ReplyUserDisplayName string `json:"reply_user_display_name"` + // reply comment id + ReplyCommentID string `json:"reply_comment_id"` + + // MemberActions + MemberActions []*PermissionMemberAction `json:"member_actions"` +} + +func (r *GetCommentResp) SetFromComment(comment *entity.Comment) { + _ = copier.Copy(r, comment) + r.CommentID = comment.ID + r.CreatedAt = comment.CreatedAt.Unix() + r.ReplyUserID = comment.GetReplyUserID() + r.ReplyCommentID = comment.GetReplyCommentID() +} + +// GetCommentPersonalWithPageReq get comment list page request +type GetCommentPersonalWithPageReq struct { + // page + Page int `validate:"omitempty,min=1" form:"page"` + // page size + PageSize int `validate:"omitempty,min=1" form:"page_size"` + // username + Username string `validate:"omitempty,gt=0,lte=100" form:"username"` + // user id + UserID string `json:"-"` +} + +// GetCommentPersonalWithPageResp comment response +type GetCommentPersonalWithPageResp struct { + // comment id + CommentID string `json:"comment_id"` + // create time + CreatedAt int64 `json:"created_at"` + // object id + ObjectID string `json:"object_id"` + // question id + QuestionID string `json:"question_id"` + // answer id + AnswerID string `json:"answer_id"` + // object type + ObjectType string `json:"object_type" enums:"question,answer,tag,comment"` + // title + Title string `json:"title"` + // content + Content string `json:"content"` +} diff --git a/internal/schema/config_schema.go b/internal/schema/config_schema.go new file mode 100644 index 00000000..3a097a4f --- /dev/null +++ b/internal/schema/config_schema.go @@ -0,0 +1,55 @@ +package schema + +// AddConfigReq add config request +type AddConfigReq struct { + // the config key + Key string `validate:"omitempty,gt=0,lte=32" comment:"the config key" json:"key"` + // the config value, custom data structures and types + Value string `validate:"omitempty,gt=0,lte=128" comment:"the config value, custom data structures and types" json:"value"` +} + +// RemoveConfigReq delete config request +type RemoveConfigReq struct { + // config id + ID int `validate:"required" comment:"config id" json:"id"` +} + +// UpdateConfigReq update config request +type UpdateConfigReq struct { + // config id + ID int `validate:"required" comment:"config id" json:"id"` + // the config key + Key string `validate:"omitempty,gt=0,lte=32" comment:"the config key" json:"key"` + // the config value, custom data structures and types + Value string `validate:"omitempty,gt=0,lte=128" comment:"the config value, custom data structures and types" json:"value"` +} + +// GetConfigListReq get config list all request +type GetConfigListReq struct { + // the config key + Key string `validate:"omitempty,gt=0,lte=32" comment:"the config key" form:"key"` + // the config value, custom data structures and types + Value string `validate:"omitempty,gt=0,lte=128" comment:"the config value, custom data structures and types" form:"value"` +} + +// GetConfigWithPageReq get config list page request +type GetConfigWithPageReq struct { + // page + Page int `validate:"omitempty,min=1" form:"page"` + // page size + PageSize int `validate:"omitempty,min=1" form:"page_size"` + // the config key + Key string `validate:"omitempty,gt=0,lte=32" comment:"the config key" form:"key"` + // the config value, custom data structures and types + Value string `validate:"omitempty,gt=0,lte=128" comment:"the config value, custom data structures and types" form:"value"` +} + +// GetConfigResp get config response +type GetConfigResp struct { + // config id + ID int `json:"id"` + // the config key + Key string `json:"key"` + // the config value, custom data structures and types + Value string `json:"value"` +} diff --git a/internal/schema/err_schema.go b/internal/schema/err_schema.go new file mode 100644 index 00000000..3270b365 --- /dev/null +++ b/internal/schema/err_schema.go @@ -0,0 +1,9 @@ +package schema + +type ErrTypeData struct { + ErrType string `json:"err_type"` +} + +var ErrTypeModal = ErrTypeData{ErrType: "modal"} + +var ErrTypeToast = ErrTypeData{ErrType: "toast"} diff --git a/internal/schema/follow_schema.go b/internal/schema/follow_schema.go new file mode 100644 index 00000000..b978acb5 --- /dev/null +++ b/internal/schema/follow_schema.go @@ -0,0 +1,34 @@ +package schema + +// FollowReq follow object request +type FollowReq struct { + // object id + ObjectID string `validate:"required"form:"object_id" json:"object_id"` + // is cancel + IsCancel bool `validate:"omitempty"form:"is_cancel" json:"is_cancel"` +} + +// FollowResp response object's follows and current user follow status +type FollowResp struct { + // the followers of object + Follows int `json:"follows"` + // if user is followed object will be true,otherwise false + IsFollowed bool `json:"is_followed"` +} + +type FollowDTO struct { + // object TagID + ObjectID string + // is cancel + IsCancel bool + // user TagID + UserID string +} + +// UpdateFollowTagsReq update user follow tags +type UpdateFollowTagsReq struct { + // tag slug name list + SlugNameList []string `json:"slug_name_list"` + // user id + UserID string `json:"-"` +} diff --git a/internal/schema/forbidden_schema.go b/internal/schema/forbidden_schema.go new file mode 100644 index 00000000..7ac7c565 --- /dev/null +++ b/internal/schema/forbidden_schema.go @@ -0,0 +1,13 @@ +package schema + +const ( + ForbiddenReasonTypeInactive = "inactive" + ForbiddenReasonTypeUrlExpired = "url_expired" + ForbiddenReasonTypeUserSuspended = "suspended" +) + +// ForbiddenResp forbidden response +type ForbiddenResp struct { + // forbidden reason type + Type string `json:"type" enums:"inactive,url_expired"` +} diff --git a/internal/schema/lang_schema.go b/internal/schema/lang_schema.go new file mode 100644 index 00000000..98abe874 --- /dev/null +++ b/internal/schema/lang_schema.go @@ -0,0 +1,18 @@ +package schema + +// GetLangOption get label option +type GetLangOption struct { + Label string `json:"label"` + Value string `json:"value"` +} + +var GetLangOptions = []*GetLangOption{ + { + Label: "English(US)", + Value: "en_US", + }, + { + Label: "中文(CN)", + Value: "zh_CN", + }, +} diff --git a/internal/schema/notification_read_schema.go b/internal/schema/notification_read_schema.go new file mode 100644 index 00000000..0816866c --- /dev/null +++ b/internal/schema/notification_read_schema.go @@ -0,0 +1,71 @@ +package schema + +import "time" + +// AddNotificationReadReq add notification read record request +type AddNotificationReadReq struct { + // user id + UserID int64 `validate:"required" comment:"user id" json:"user_id"` + // message id + MessageID int64 `validate:"required" comment:"message id" json:"message_id"` + // read status(unread: 1; read 2) + IsRead int `validate:"required" comment:"read status(unread: 1; read 2)" json:"is_read"` +} + +// RemoveNotificationReadReq delete notification read record request +type RemoveNotificationReadReq struct { + // id + ID int `validate:"required" comment:"id" json:"id"` +} + +// UpdateNotificationReadReq update notification read record request +type UpdateNotificationReadReq struct { + // id + ID int `validate:"required" comment:"id" json:"id"` + // user id + UserID int64 `validate:"omitempty" comment:"user id" json:"user_id"` + // message id + MessageID int64 `validate:"omitempty" comment:"message id" json:"message_id"` + // read status(unread: 1; read 2) + IsRead int `validate:"omitempty" comment:"read status(unread: 1; read 2)" json:"is_read"` +} + +// GetNotificationReadListReq get notification read record list all request +type GetNotificationReadListReq struct { + // user id + UserID int64 `validate:"omitempty" comment:"user id" form:"user_id"` + // message id + MessageID int64 `validate:"omitempty" comment:"message id" form:"message_id"` + // read status(unread: 1; read 2) + IsRead int `validate:"omitempty" comment:"read status(unread: 1; read 2)" form:"is_read"` +} + +// GetNotificationReadWithPageReq get notification read record list page request +type GetNotificationReadWithPageReq struct { + // page + Page int `validate:"omitempty,min=1" form:"page"` + // page size + PageSize int `validate:"omitempty,min=1" form:"page_size"` + // user id + UserID int64 `validate:"omitempty" comment:"user id" form:"user_id"` + // message id + MessageID int64 `validate:"omitempty" comment:"message id" form:"message_id"` + // read status(unread: 1; read 2) + IsRead int `validate:"omitempty" comment:"read status(unread: 1; read 2)" form:"is_read"` +} + +// GetNotificationReadResp get notification read record response +type GetNotificationReadResp struct { + // id + ID int `json:"id"` + // create time + CreatedAt time.Time `json:"created_at"` + // update time + UpdatedAt time.Time `json:"updated_at"` + // user id + UserID int64 `json:"user_id"` + // message id + MessageID int64 `json:"message_id"` + // read status(unread: 1; read 2) + IsRead int `json:"is_read"` +} diff --git a/internal/schema/notification_schema.go b/internal/schema/notification_schema.go new file mode 100644 index 00000000..6a8f470f --- /dev/null +++ b/internal/schema/notification_schema.go @@ -0,0 +1,78 @@ +package schema + +const ( + NotificationTypeInbox = 1 + NotificationTypeAchievement = 2 + NotificationNotRead = 1 + NotificationRead = 2 + NotificationStatusNormal = 1 + NotificationStatusDelete = 10 +) + +var NotificationType = map[string]int{ + "inbox": NotificationTypeInbox, + "achievement": NotificationTypeAchievement, +} + +type NotificationContent struct { + ID string `json:"id"` + TriggerUserID string `json:"-"` //show userid + ReceiverUserID string `json:"-"` // receiver userid + UserInfo *UserBasicInfo `json:"user_info,omitempty"` + ObjectInfo ObjectInfo `json:"object_info"` + Rank int `json:"rank"` + NotificationAction string `json:"notification_action,omitempty"` + Type int `json:"-"` // 1 inbox 2 achievement + IsRead bool `json:"is_read"` + UpdateTime int64 `json:"update_time"` +} + +// NotificationMsg notification message +type NotificationMsg struct { + // trigger notification user id + TriggerUserID string + // receive notification user id + ReceiverUserID string + // type 1 inbox 2 achievement + Type int + // notification title + Title string + // notification object + ObjectID string + // notification object type + ObjectType string + // notification action + NotificationAction string + // if true no need to send notification to all followers + NoNeedPushAllFollow bool +} + +type ObjectInfo struct { + Title string `json:"title"` + ObjectID string `json:"object_id"` + ObjectMap map[string]string `json:"object_map"` + ObjectType string `json:"object_type"` +} + +type RedDot struct { + Inbox int64 `json:"inbox"` + Achievement int64 `json:"achievement"` +} + +type NotificationSearch struct { + Page int `json:"page" form:"page"` //Query number of pages + PageSize int `json:"page_size" form:"page_size"` //Search page size + Type int `json:"-" form:"-"` + TypeStr string `json:"type" form:"type"` // inbox achievement + UserID string `json:"-"` +} + +type NotificationClearRequest struct { + UserID string `json:"-"` + TypeStr string `json:"type" form:"type"` // inbox achievement +} + +type NotificationClearIDRequest struct { + UserID string `json:"-"` + ID string `json:"id" form:"id"` +} diff --git a/internal/schema/permission.go b/internal/schema/permission.go new file mode 100644 index 00000000..2652acfa --- /dev/null +++ b/internal/schema/permission.go @@ -0,0 +1,11 @@ +package schema + +const PermissionMemberActionTypeEdit = "edit" +const PermissionMemberActionTypeReason = "reason" + +// PermissionMemberAction permission member action +type PermissionMemberAction struct { + Action string `json:"action"` + Name string `json:"name"` + Type string `json:"type"` +} diff --git a/internal/schema/question_schema.go b/internal/schema/question_schema.go new file mode 100644 index 00000000..df21eda7 --- /dev/null +++ b/internal/schema/question_schema.go @@ -0,0 +1,175 @@ +package schema + +// RemoveQuestionReq delete question request +type RemoveQuestionReq struct { + // question id + ID string `validate:"required" comment:"question id" json:"id"` + UserID string `json:"-" ` // user_id + +} + +type CloseQuestionReq struct { + ID string `validate:"required" comment:"question id" json:"id"` + UserId string `json:"-" ` // user_id + CloseType int `json:"close_type" ` // close_type + CloseMsg string `json:"close_msg" ` // close_type +} + +type CloseQuestionMeta struct { + CloseType int `json:"close_type"` + CloseMsg string `json:"close_msg"` +} + +type QuestionAdd struct { + // question title + Title string `validate:"required,gte=6,lte=64" json:"title"` + // content + Content string `validate:"required,gte=6,lte=65535" json:"content"` + // html + Html string `validate:"required,gte=6,lte=65535" json:"html"` + // tags + Tags []*TagItem `validate:"required,dive" json:"tags"` + // user id + UserID string `json:"-"` +} + +type QuestionUpdate struct { + // question id + ID string `validate:"required" json:"id"` + // question title + Title string `validate:"required,gte=6,lte=64" json:"title"` + // content + Content string `validate:"required,gte=6,lte=65535" json:"content"` + // html + Html string `validate:"required,gte=6,lte=65535" json:"html"` + // tags + Tags []*TagItem `validate:"required,dive" json:"tags"` + // edit summary + EditSummary string `validate:"omitempty" json:"edit_summary"` + // user id + UserID string `json:"-"` +} + +type QuestionBaseInfo struct { + ID string `json:"id" ` + Title string `json:"title" xorm:"title"` // 标题 + ViewCount int `json:"view_count" xorm:"view_count"` // view_count + AnswerCount int `json:"answer_count" xorm:"answer_count"` // 回复总数 + CollectionCount int `json:"collection_count" xorm:"collection_count"` // 收藏总数 + FollowCount int `json:"follow_count" xorm:"follow_count"` // 关注数 + AcceptedAnswer bool `json:"accepted_answer"` +} + +type QuestionInfo struct { + ID string `json:"id" ` + Title string `json:"title" xorm:"title"` // 标题 + Content string `json:"content" xorm:"content"` // 内容 + Html string `json:"html" xorm:"html"` // 解析后的html + Tags []*TagResp `json:"tags" ` // tags + ViewCount int `json:"view_count" xorm:"view_count"` // view_count + UniqueViewCount int `json:"unique_view_count" xorm:"unique_view_count"` // unique_view_count + VoteCount int `json:"vote_count" xorm:"vote_count"` // vote_count + AnswerCount int `json:"answer_count" xorm:"answer_count"` // 回复总数 + CollectionCount int `json:"collection_count" xorm:"collection_count"` // 收藏总数 + FollowCount int `json:"follow_count" xorm:"follow_count"` // 关注数 + AcceptedAnswerId string `json:"accepted_answer_id" ` // accepted_answer_id + LastAnswerId string `json:"last_answer_id" ` // last_answer_id + CreateTime int64 `json:"create_time" ` // create_time + UpdateTime int64 `json:"-"` // update_time + PostUpdateTime int64 `json:"update_time"` + QuestionUpdateTime int64 `json:"edit_time"` + Status int `json:"status"` + Operation *Operation `json:"operation,omitempty"` + UserId string `json:"-" ` + UserInfo *UserBasicInfo `json:"user_info"` + UpdateUserInfo *UserBasicInfo `json:"update_user_info,omitempty"` + LastAnsweredUserInfo *UserBasicInfo `json:"last_answered_user_info,omitempty"` + Answered bool `json:"answered"` + Collected bool `json:"collected"` + VoteStatus string `json:"vote_status"` + IsFollowed bool `json:"is_followed"` + + // MemberActions + MemberActions []*PermissionMemberAction `json:"member_actions"` +} + +type AdminQuestionInfo struct { + ID string `json:"id"` + Title string `json:"title"` + VoteCount int `json:"vote_count"` + AnswerCount int `json:"answer_count"` + AcceptedAnswerID string `json:"accepted_answer_id"` + CreateTime int64 `json:"create_time"` + UpdateTime int64 `json:"update_time"` + EditTime int64 `json:"edit_time"` + UserID string `json:"-" ` + UserInfo *UserBasicInfo `json:"user_info"` +} + +type Operation struct { + Operation_Type string `json:"operation_type"` + Operation_Description string `json:"operation_description"` + Operation_Msg string `json:"operation_msg"` +} + +type GetCloseTypeResp struct { + // report name + Name string `json:"name"` + // report description + Description string `json:"description"` + // report source + Source string `json:"source"` + // report type + Type int `json:"type"` + // is have content + HaveContent bool `json:"have_content"` + // content type + ContentType string `json:"content_type"` +} + +type UserAnswerInfo struct { + AnswerID string `json:"answer_id"` + QuestionID string `json:"question_id"` + Adopted int `json:"adopted"` + VoteCount int `json:"vote_count"` + CreateTime int `json:"create_time"` + UpdateTime int `json:"update_time"` + QuestionInfo struct { + Title string `json:"title"` + Tags []interface{} `json:"tags"` + } `json:"question_info"` +} + +type UserQuestionInfo struct { + ID string `json:"question_id"` + Title string `json:"title"` + VoteCount int `json:"vote_count"` + Tags []interface{} `json:"tags"` + ViewCount int `json:"view_count"` + AnswerCount int `json:"answer_count"` + CollectionCount int `json:"collection_count"` + CreateTime int `json:"create_time"` + AcceptedAnswerId string `json:"accepted_answer_id"` +} + +type QuestionSearch struct { + Page int `json:"page" form:"page"` //Query number of pages + PageSize int `json:"page_size" form:"page_size"` //Search page size + Order string `json:"order" form:"order"` //Search order by + Tags []string `json:"tags" form:"tags"` //Search tag + TagIDs []string `json:"-" form:"-"` //Search tag + UserName string `json:"username" form:"username"` //Search username + UserID string `json:"-" form:"-"` +} + +type CmsQuestionSearch struct { + Page int `json:"page" form:"page"` //Query number of pages + PageSize int `json:"page_size" form:"page_size"` //Search page size + Status int `json:"-" form:"-"` + StatusStr string `json:"status" form:"status"` //Status 1 Available 2 closed 10 Deleted +} + +type AdminSetQuestionStatusRequest struct { + StatusStr string `json:"status" form:"status"` + QuestionID string `json:"question_id" form:"question_id"` +} diff --git a/internal/schema/rank_schema.go b/internal/schema/rank_schema.go new file mode 100644 index 00000000..81505c82 --- /dev/null +++ b/internal/schema/rank_schema.go @@ -0,0 +1,35 @@ +package schema + +// GetRankPersonalWithPageReq get rank list page request +type GetRankPersonalWithPageReq struct { + // page + Page int `validate:"omitempty,min=1" form:"page"` + // page size + PageSize int `validate:"omitempty,min=1" form:"page_size"` + // username + Username string `validate:"omitempty,gt=0,lte=100" form:"username"` + // user id + UserID string `json:"-"` +} + +// GetRankPersonalWithPageResp rank response +type GetRankPersonalWithPageResp struct { + // create time + CreatedAt int64 `json:"created_at"` + // object id + ObjectID string `json:"object_id"` + // question id + QuestionID string `json:"question_id"` + // answer id + AnswerID string `json:"answer_id"` + // object type + ObjectType string `json:"object_type" enums:"question,answer,tag,comment"` + // title + Title string `json:"title"` + // content + Content string `json:"content"` + // reputation + Reputation int `json:"reputation"` + // rank type + RankType string `json:"rank_type"` +} diff --git a/internal/schema/reason_schema.go b/internal/schema/reason_schema.go new file mode 100644 index 00000000..5aa3db76 --- /dev/null +++ b/internal/schema/reason_schema.go @@ -0,0 +1,15 @@ +package schema + +type ReasonItem struct { + ReasonType int `json:"reason_type"` + Name string `json:"name"` + Description string `json:"description"` + ContentType string `json:"content_type"` +} + +type ReasonReq struct { + // ObjectType + ObjectType string `validate:"required" form:"object_type" json:"object_type"` + // Action + Action string `validate:"required" form:"action" json:"action"` +} diff --git a/internal/schema/report_schema.go b/internal/schema/report_schema.go new file mode 100644 index 00000000..d3766558 --- /dev/null +++ b/internal/schema/report_schema.go @@ -0,0 +1,99 @@ +package schema + +import ( + "github.com/segmentfault/answer/internal/base/constant" + "time" +) + +// AddReportReq add report request +type AddReportReq struct { + // object id + ObjectID string `validate:"required,gt=0,lte=20" json:"object_id"` + // report type + ReportType int `validate:"required" json:"report_type"` + // report content + Content string `validate:"omitempty,gt=0,lte=500" json:"content"` + // user id + UserID string `json:"-"` +} + +// GetReportListReq get report list all request +type GetReportListReq struct { + // report source + Source string `validate:"required,oneof=question answer comment" form:"source"` +} + +// GetReportTypeResp get report response +type GetReportTypeResp struct { + // report name + Name string `json:"name"` + // report description + Description string `json:"description"` + // report source + Source string `json:"source"` + // report type + Type int `json:"type"` + // is have content + HaveContent bool `json:"have_content"` + // content type + ContentType string `json:"content_type"` +} + +// ReportHandleReq request handle request +type ReportHandleReq struct { + ID string `validate:"required" comment:"report id" form:"id" json:"id"` + FlagedType int `validate:"required" comment:"flaged type" form:"flaged_type" json:"flaged_type"` + FlagedContent string `validate:"omitempty" comment:"flaged content" form:"flaged_content" json:"flaged_content"` +} + +// GetReportListPageDTO report list data transfer object +type GetReportListPageDTO struct { + ObjectType string + Status string + Page int + PageSize int +} + +// GetReportListPageResp get report list +type GetReportListPageResp struct { + ID string `json:"id"` + ReportedUser *UserBasicInfo `json:"reported_user"` + ReportUser *UserBasicInfo `json:"report_user"` + + Content string `json:"content"` + FlagedContent string `json:"flaged_content"` + OType string `json:"object_type"` + + ObjectID string `json:"-"` + QuestionID string `json:"question_id"` + AnswerID string `json:"answer_id"` + CommentID string `json:"comment_id"` + + Title string `json:"title"` + Excerpt string `json:"excerpt"` + + // create time + CreatedAt time.Time `json:"-"` + CreatedAtParsed int64 `json:"created_at"` + + UpdatedAt time.Time `json:"_"` + UpdatedAtParsed int64 `json:"updated_at"` + + Reason *ReasonItem `json:"reason"` + FlagedReason *ReasonItem `json:"flaged_reason"` + + UserID string `json:"-"` + ReportedUserID string `json:"-"` + Status int `json:"-"` + ObjectType int `json:"-"` + ReportType int `json:"-"` + FlagedType int `json:"-"` +} + +// Format format result +func (r *GetReportListPageResp) Format() { + r.OType = constant.ObjectTypeNumberMapping[r.ObjectType] + + r.CreatedAtParsed = r.CreatedAt.Unix() + r.UpdatedAtParsed = r.UpdatedAt.Unix() +} diff --git a/internal/schema/revision_schema.go b/internal/schema/revision_schema.go new file mode 100644 index 00000000..826b3030 --- /dev/null +++ b/internal/schema/revision_schema.go @@ -0,0 +1,50 @@ +package schema + +import ( + "time" +) + +// AddRevisionDTO add revision request +type AddRevisionDTO struct { + // user id + UserID string + // object id + ObjectID string + // title + Title string + // content + Content string + // log + Log string +} + +// GetRevisionListReq get revision list all request +type GetRevisionListReq struct { + // object id + ObjectID string `validate:"required" comment:"object_id" form:"object_id"` +} + +// GetRevisionResp get revision response +type GetRevisionResp struct { + // id + ID string `json:"id"` + // user id + UserID string `json:"use_id"` + // object id + ObjectID string `json:"object_id"` + // object type + ObjectType int `json:"-"` + // title + Title string `json:"title"` + // content + Content string `json:"-"` + // content parsed + ContentParsed interface{} `json:"content"` + // revision status(normal: 1; delete 2) + Status int `json:"status"` + // create time + CreatedAt time.Time `json:"-"` + CreatedAtParsed int64 `json:"create_at"` + UserInfo UserBasicInfo `json:"user_info"` + Log string `json:"reason"` +} diff --git a/internal/schema/search_schema.go b/internal/schema/search_schema.go new file mode 100644 index 00000000..48e3f916 --- /dev/null +++ b/internal/schema/search_schema.go @@ -0,0 +1,46 @@ +package schema + +type SearchDTO struct { + // Query the query string + Query string + // UserID current login user ID + UserID string + Page int + Size int +} + +type SearchObject struct { + ID string `json:"id"` + Title string `json:"title"` + Excerpt string `json:"excerpt"` + CreatedAtParsed int64 `json:"created_at"` + VoteCount int `json:"vote_count"` + Accepted bool `json:"accepted"` + AnswerCount int `json:"answer_count"` + // user info + UserInfo *UserBasicInfo `json:"user_info"` + // tags + Tags []TagResp `json:"tags"` +} + +type TagResp struct { + SlugName string `json:"display_name"` + DisplayName string `json:"slug_name"` + // if main tag slug name is not empty, this tag is synonymous with the main tag + MainTagSlugName string `json:"main_tag_slug_name"` +} + +type SearchResp struct { + // object_type + ObjectType string `json:"object_type"` + // this object + Object SearchObject `json:"object"` +} + +type SearchListResp struct { + Total int64 `json:"count"` + // search response + SearchResp []SearchResp `json:"list"` + // extra fields + Extra interface{} `json:"extra"` +} diff --git a/internal/schema/simple_obj_info_schema.go b/internal/schema/simple_obj_info_schema.go new file mode 100644 index 00000000..312772db --- /dev/null +++ b/internal/schema/simple_obj_info_schema.go @@ -0,0 +1,14 @@ +package schema + +// SimpleObjectInfo simple object info +type SimpleObjectInfo struct { + ObjectID string `json:"object_id"` + ObjectCreator string `json:"object_creator"` + QuestionID string `json:"question_id"` + AnswerID string `json:"answer_id"` + CommentID string `json:"comment_id"` + TagID string `json:"tag_id"` + ObjectType string `json:"object_type"` + Title string `json:"title"` + Content string `json:"content"` +} diff --git a/internal/schema/siteinfo_schema.go b/internal/schema/siteinfo_schema.go new file mode 100644 index 00000000..1d29a34f --- /dev/null +++ b/internal/schema/siteinfo_schema.go @@ -0,0 +1,26 @@ +package schema + +// SiteGeneralReq site general request +type SiteGeneralReq struct { + Name string `validate:"required,gt=1,lte=128" comment:"site name" form:"name" json:"name"` + ShortDescription string `validate:"required,gt=3,lte=255" comment:"short site description" form:"short_description" json:"short_description"` + Description string `validate:"required,gt=3,lte=2000" comment:"site description" form:"description" json:"description"` +} + +// SiteInterfaceReq site interface request +type SiteInterfaceReq struct { + Logo string `validate:"omitempty,gt=0,lte=256" comment:"logo" form:"logo" json:"logo"` + Theme string `validate:"required,gt=1,lte=128" comment:"theme" form:"theme" json:"theme"` + Language string `validate:"required,gt=1,lte=128" comment:"interface language" form:"language" json:"language"` +} + +// SiteGeneralResp site general response +type SiteGeneralResp SiteGeneralReq + +// SiteInterfaceResp site interface response +type SiteInterfaceResp SiteInterfaceReq + +type SiteInfoResp struct { + General *SiteGeneralResp `json:"general"` + Face *SiteInterfaceResp `json:"interface"` +} diff --git a/internal/schema/tag_list_schema.go b/internal/schema/tag_list_schema.go new file mode 100644 index 00000000..e23d9114 --- /dev/null +++ b/internal/schema/tag_list_schema.go @@ -0,0 +1,65 @@ +package schema + +// AddTagListReq add tag list request +type AddTagListReq struct { + // tag_id + TagID int64 `validate:"required" comment:"tag_id" json:"tag_id"` + // object_id + ObjectID int64 `validate:"required" comment:"object_id" json:"object_id"` + // tag_list_status(available: 1; deleted: 10) + Status int `validate:"required" comment:"tag_list_status(available: 1; deleted: 10)" json:"status"` +} + +// RemoveTagListReq delete tag list request +type RemoveTagListReq struct { + // tag_list_id + ID int64 `validate:"required" comment:"tag_list_id" json:"id"` +} + +// UpdateTagListReq update tag list request +type UpdateTagListReq struct { + // tag_list_id + ID int64 `validate:"required" comment:"tag_list_id" json:"id"` + // tag_id + TagID int64 `validate:"omitempty" comment:"tag_id" json:"tag_id"` + // object_id + ObjectID int64 `validate:"omitempty" comment:"object_id" json:"object_id"` + // tag_list_status(available: 1; deleted: 10) + Status int `validate:"omitempty" comment:"tag_list_status(available: 1; deleted: 10)" json:"status"` +} + +// GetTagListListReq get tag list list all request +type GetTagListListReq struct { + // tag_id + TagID int64 `validate:"omitempty" comment:"tag_id" form:"tag_id"` + // object_id + ObjectID int64 `validate:"omitempty" comment:"object_id" form:"object_id"` + // tag_list_status(available: 1; deleted: 10) + Status int `validate:"omitempty" comment:"tag_list_status(available: 1; deleted: 10)" form:"status"` +} + +// GetTagListWithPageReq get tag list list page request +type GetTagListWithPageReq struct { + // page + Page int `validate:"omitempty,min=1" form:"page"` + // page size + PageSize int `validate:"omitempty,min=1" form:"page_size"` + // tag_id + TagID int64 `validate:"omitempty" comment:"tag_id" form:"tag_id"` + // object_id + ObjectID int64 `validate:"omitempty" comment:"object_id" form:"object_id"` + // tag_list_status(available: 1; deleted: 10) + Status int `validate:"omitempty" comment:"tag_list_status(available: 1; deleted: 10)" form:"status"` +} + +// GetTagListResp get tag list response +type GetTagListResp struct { + // tag_list_id + ID int64 `json:"id"` + // tag_id + TagID int64 `json:"tag_id"` + // object_id + ObjectID int64 `json:"object_id"` + // tag_list_status(available: 1; deleted: 10) + Status int `json:"status"` +} diff --git a/internal/schema/tag_schema.go b/internal/schema/tag_schema.go new file mode 100644 index 00000000..91d30ed2 --- /dev/null +++ b/internal/schema/tag_schema.go @@ -0,0 +1,220 @@ +package schema + +import ( + "strings" + + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/base/validator" + "github.com/segmentfault/pacman/errors" +) + +// SearchTagLikeReq get tag list all request +type SearchTagLikeReq struct { + // tag + Tag string `validate:"required,gt=0,lte=50" form:"tag"` +} + +// GetTagInfoReq get tag info request +type GetTagInfoReq struct { + // tag id + ID string `validate:"omitempty" form:"id"` + // tag slug name + Name string `validate:"omitempty,gt=0,lte=50" form:"name"` + // user id + UserID string `json:"-"` +} + +func (r *GetTagInfoReq) Check() (errField *validator.ErrorField, err error) { + if len(r.ID) == 0 && len(r.Name) == 0 { + return nil, errors.BadRequest(reason.RequestFormatError) + } + r.Name = strings.ToLower(r.Name) + return nil, nil +} + +// GetTagResp get tag response +type GetTagResp struct { + // tag id + TagID string `json:"tag_id"` + // created time + CreatedAt int64 `json:"created_at"` + // updated time + UpdatedAt int64 `json:"updated_at"` + // slug name + SlugName string `json:"slug_name"` + // display name + DisplayName string `json:"display_name"` + // excerpt + Excerpt string `json:"excerpt"` + // original text + OriginalText string `json:"original_text"` + // parsed text + ParsedText string `json:"parsed_text"` + // follower amount + FollowCount int `json:"follow_count"` + // question amount + QuestionCount int `json:"question_count"` + // is follower + IsFollower bool `json:"is_follower"` + // MemberActions + MemberActions []*PermissionMemberAction `json:"member_actions"` + // if main tag slug name is not empty, this tag is synonymous with the main tag + MainTagSlugName string `json:"main_tag_slug_name"` +} + +func (tr *GetTagResp) GetExcerpt() { + excerpt := strings.TrimSpace(tr.OriginalText) + idx := strings.Index(excerpt, "\n") + if idx >= 0 { + excerpt = excerpt[0:idx] + } + tr.Excerpt = excerpt +} + +// GetTagPageResp get tag response +type GetTagPageResp struct { + // tag_id + TagID string `json:"tag_id"` + // slug_name + SlugName string `json:"slug_name"` + // display_name + DisplayName string `json:"display_name"` + // excerpt + Excerpt string `json:"excerpt"` + // original text + OriginalText string `json:"original_text"` + // parsed_text + ParsedText string `json:"parsed_text"` + // follower amount + FollowCount int `json:"follow_count"` + // question amount + QuestionCount int `json:"question_count"` + // is follower + IsFollower bool `json:"is_follower"` + // created time + CreatedAt int64 `json:"created_at"` + // updated time + UpdatedAt int64 `json:"updated_at"` +} + +func (tr *GetTagPageResp) GetExcerpt() { + excerpt := strings.TrimSpace(tr.OriginalText) + idx := strings.Index(excerpt, "\n") + if idx >= 0 { + excerpt = excerpt[0:idx] + } + tr.Excerpt = excerpt +} + +type TagChange struct { + ObjectId string `json:"object_id"` // object_id + Tags []*TagItem `json:"tags"` // tags name + // user id + UserID string `json:"-"` +} + +type TagItem struct { + // slug_name + SlugName string `validate:"omitempty,gt=0,lte=50" json:"slug_name"` + // display_name + DisplayName string `validate:"omitempty,gt=0,lte=50" json:"display_name"` + // original text + OriginalText string `validate:"omitempty" json:"original_text"` + // parsed text + ParsedText string `validate:"omitempty" json:"parsed_text"` +} + +// RemoveTagReq delete tag request +type RemoveTagReq struct { + // tag_id + TagID string `validate:"required" json:"tag_id"` + // user id + UserID string `json:"-"` +} + +// UpdateTagReq update tag request +type UpdateTagReq struct { + // tag_id + TagID string `validate:"required" json:"tag_id"` + // slug_name + SlugName string `validate:"omitempty,gt=0,lte=50" json:"slug_name"` + // display_name + DisplayName string `validate:"omitempty,gt=0,lte=50" json:"display_name"` + // original text + OriginalText string `validate:"omitempty" json:"original_text"` + // parsed text + ParsedText string `validate:"omitempty" json:"parsed_text"` + // edit summary + EditSummary string `validate:"omitempty" json:"edit_summary"` + // user id + UserID string `json:"-"` +} + +func (r *UpdateTagReq) Check() (errField *validator.ErrorField, err error) { + if len(r.EditSummary) == 0 { + r.EditSummary = "tag.edit.summary" // to i18n + } + return nil, nil +} + +// GetTagWithPageReq get tag list page request +type GetTagWithPageReq struct { + // page + Page int `validate:"omitempty,min=1" form:"page"` + // page size + PageSize int `validate:"omitempty,min=1" form:"page_size"` + // slug_name + SlugName string `validate:"omitempty,gt=0,lte=50" form:"slug_name"` + // display_name + DisplayName string `validate:"omitempty,gt=0,lte=50" form:"display_name"` + // query condition + QueryCond string `validate:"omitempty,oneof=popular name newest" form:"query_cond"` + // user id + UserID string `json:"-"` +} + +// GetTagSynonymsReq get tag synonyms request +type GetTagSynonymsReq struct { + // tag_id + TagID string `validate:"required" form:"tag_id"` +} + +// GetTagSynonymsResp get tag synonyms response +type GetTagSynonymsResp struct { + // tag id + TagID string `json:"tag_id"` + // slug name + SlugName string `json:"slug_name"` + // display name + DisplayName string `json:"display_name"` + // if main tag slug name is not empty, this tag is synonymous with the main tag + MainTagSlugName string `json:"main_tag_slug_name"` +} + +// UpdateTagSynonymReq update tag request +type UpdateTagSynonymReq struct { + // tag_id + TagID string `validate:"required" json:"tag_id"` + // synonym tag list + SynonymTagList []*TagItem `validate:"required,dive" json:"synonym_tag_list"` + // user id + UserID string `json:"-"` +} + +func (req *UpdateTagSynonymReq) Format() { + for _, item := range req.SynonymTagList { + item.SlugName = strings.ToLower(item.SlugName) + } +} + +// GetFollowingTagsResp get following tags response +type GetFollowingTagsResp struct { + // tag id + TagID string `json:"tag_id"` + // slug name + SlugName string `json:"slug_name"` + // display name + DisplayName string `json:"display_name"` + // if main tag slug name is not empty, this tag is synonymous with the main tag + MainTagSlugName string `json:"main_tag_slug_name"` +} diff --git a/internal/schema/theme_schema.go b/internal/schema/theme_schema.go new file mode 100644 index 00000000..d5eb49b6 --- /dev/null +++ b/internal/schema/theme_schema.go @@ -0,0 +1,22 @@ +package schema + +// GetThemeOption get label option +type GetThemeOption struct { + Label string `json:"label"` + Value string `json:"value"` +} + +var GetThemeOptions = []*GetThemeOption{ + { + Label: "Default", + Value: "default", + }, + { + Label: "Black", + Value: "black", + }, + { + Label: "White", + Value: "white", + }, +} diff --git a/internal/schema/user_group_schema.go b/internal/schema/user_group_schema.go new file mode 100644 index 00000000..5ebac4a1 --- /dev/null +++ b/internal/schema/user_group_schema.go @@ -0,0 +1,35 @@ +package schema + +// AddUserGroupReq add user group request +type AddUserGroupReq struct { +} + +// RemoveUserGroupReq delete user group request +type RemoveUserGroupReq struct { + // user group id + ID int64 `validate:"required" comment:"user group id" json:"id"` +} + +// UpdateUserGroupReq update user group request +type UpdateUserGroupReq struct { + // user group id + ID int64 `validate:"required" comment:"user group id" json:"id"` +} + +// GetUserGroupListReq get user group list all request +type GetUserGroupListReq struct { +} + +// GetUserGroupWithPageReq get user group list page request +type GetUserGroupWithPageReq struct { + // page + Page int `validate:"omitempty,min=1" form:"page"` + // page size + PageSize int `validate:"omitempty,min=1" form:"page_size"` +} + +// GetUserGroupResp get user group response +type GetUserGroupResp struct { + // user group id + ID int64 `json:"id"` +} diff --git a/internal/schema/user_schema.go b/internal/schema/user_schema.go new file mode 100644 index 00000000..73607216 --- /dev/null +++ b/internal/schema/user_schema.go @@ -0,0 +1,327 @@ +package schema + +import ( + "encoding/json" + + "github.com/davecgh/go-spew/spew" + "github.com/jinzhu/copier" + "github.com/segmentfault/answer/internal/base/validator" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/pkg/checker" +) + +// UserVerifyEmailReq user verify email request +type UserVerifyEmailReq struct { + // code + Code string `validate:"required,gt=0,lte=500" form:"code"` + // content + Content string `json:"-"` +} + +// GetUserResp get user response +type GetUserResp struct { + // user id + ID string `json:"id"` + // create time + CreatedAt int64 `json:"created_at"` + // last login date + LastLoginDate int64 `json:"last_login_date"` + // username + Username string `json:"username"` + // email + EMail string `json:"e_mail"` + // mail status(1 pass 2 to be verified) + MailStatus int `json:"mail_status"` + // notice status(1 on 2off) + NoticeStatus int `json:"notice_status"` + // follow count + FollowCount int `json:"follow_count"` + // answer count + AnswerCount int `json:"answer_count"` + // question count + QuestionCount int `json:"question_count"` + // rank + Rank int `json:"rank"` + // authority group + AuthorityGroup int `json:"authority_group"` + // display name + DisplayName string `json:"display_name"` + // avatar + Avatar string `json:"avatar"` + // mobile + Mobile string `json:"mobile"` + // bio markdown + Bio string `json:"bio"` + // bio html + BioHtml string `json:"bio_html"` + // website + Website string `json:"website"` + // location + Location string `json:"location"` + // ip info + IPInfo string `json:"ip_info"` + // access token + AccessToken string `json:"access_token"` + // is admin + IsAdmin bool `json:"is_admin"` + // user status + Status string `json:"status"` +} + +func (r *GetUserResp) GetFromUserEntity(userInfo *entity.User) { + _ = copier.Copy(r, userInfo) + r.CreatedAt = userInfo.CreatedAt.Unix() + r.LastLoginDate = userInfo.LastLoginDate.Unix() + statusShow, ok := UserStatusShow[userInfo.Status] + if ok { + r.Status = statusShow + } + +} + +// GetOtherUserInfoByUsernameResp get user response +type GetOtherUserInfoByUsernameResp struct { + // user id + ID string `json:"id"` + // create time + CreatedAt int64 `json:"created_at"` + // last login date + LastLoginDate int64 `json:"last_login_date"` + // username + Username string `json:"username"` + // email + // follow count + FollowCount int `json:"follow_count"` + // answer count + AnswerCount int `json:"answer_count"` + // question count + QuestionCount int `json:"question_count"` + // rank + Rank int `json:"rank"` + // display name + DisplayName string `json:"display_name"` + // avatar + Avatar string `json:"avatar"` + // mobile + Mobile string `json:"mobile"` + // bio markdown + Bio string `json:"bio"` + // bio html + BioHtml string `json:"bio_html"` + // website + Website string `json:"website"` + // location + Location string `json:"location"` + // ip info + IPInfo string `json:"ip_info"` + // is admin + IsAdmin bool `json:"is_admin"` + Status string `json:"status"` + StatusMsg string `json:"status_msg,omitempty"` +} + +func (r *GetOtherUserInfoByUsernameResp) GetFromUserEntity(userInfo *entity.User) { + _ = copier.Copy(r, userInfo) + r.CreatedAt = userInfo.CreatedAt.Unix() + r.LastLoginDate = userInfo.LastLoginDate.Unix() + statusShow, ok := UserStatusShow[userInfo.Status] + if ok { + r.Status = statusShow + } + spew.Dump(userInfo) + if userInfo.MailStatus == entity.EmailStatusToBeVerified { + statusMsgShow, ok := UserStatusShowMsg[11] + if ok { + r.StatusMsg = statusMsgShow + } + } else { + statusMsgShow, ok := UserStatusShowMsg[userInfo.Status] + if ok { + r.StatusMsg = statusMsgShow + } + } + + spew.Dump(r) + +} + +const ( + Mail_State_Pass = 1 + Mail_State_Verifi = 2 + + Notice_Status_On = 1 + Notice_Status_Off = 2 + + //ActionRecord ReportType + ActionRecord_Type_Login = "login" + ActionRecord_Type_Email = "e_mail" + ActionRecord_Type_Find_Pass = "find_pass" +) + +var UserStatusShow = map[int]string{ + 1: "normal", + 9: "forbidden", + 10: "delete", +} +var UserStatusShowMsg = map[int]string{ + 1: "", + 9: "This user was suspended forever. This user doesn’t meet a community guideline.", + 10: "This user was deleted.", + 11: "This user is inactive.", +} + +// EmailLogin +type UserEmailLogin struct { + Email string `json:"e_mail" ` // e_mail + Pass string `json:"pass" ` // password + CaptchaID string `json:"captcha_id" ` // captcha_id + CaptchaCode string `json:"captcha_code" ` // captcha_code +} + +// Register +type UserRegister struct { + // name + Name string `validate:"required,gt=5,lte=50" json:"name"` + // email + Email string `validate:"required,email,gt=0,lte=500" json:"e_mail" ` + // password + Pass string `validate:"required,gte=8,lte=32" json:"pass"` + IP string `json:"-" ` +} + +func (u *UserRegister) Check() (errField *validator.ErrorField, err error) { + // TODO i18n + err = checker.PassWordCheck(8, 32, 0, u.Pass) + if err != nil { + return &validator.ErrorField{ + Key: "pass", + Value: err.Error(), + }, err + } + return nil, nil +} + +// UserModifyPassWordRequest +type UserModifyPassWordRequest struct { + UserId string `json:"-" ` // user_id + OldPass string `json:"old_pass" ` // old password + Pass string `json:"pass" ` // password +} + +func (u *UserModifyPassWordRequest) Check() (errField *validator.ErrorField, err error) { + // TODO i18n + err = checker.PassWordCheck(8, 32, 0, u.Pass) + if err != nil { + return &validator.ErrorField{ + Key: "pass", + Value: err.Error(), + }, err + } + return nil, nil +} + +type UpdateInfoRequest struct { + UserId string `json:"-" ` // user_id + UserName string `json:"username"` // name + DisplayName string `json:"display_name" ` // display_name + Avatar string `json:"avatar" ` // avatar + Bio string `json:"bio"` + BioHtml string `json:"bio_html"` + Website string `json:"website" ` // website + Location string `json:"location"` // location +} + +type UserRetrievePassWordRequest struct { + Email string `validate:"required,email,gt=0,lte=500" json:"e_mail" ` // e_mail + CaptchaID string `json:"captcha_id" ` // captcha_id + CaptchaCode string `json:"captcha_code" ` // captcha_code +} + +type UserRePassWordRequest struct { + Code string `validate:"required,gt=0,lte=100" json:"code" ` // code + Pass string `validate:"required,gt=0,lte=32" json:"pass" ` // Password + Content string `json:"-"` +} + +func (u *UserRePassWordRequest) Check() (errField *validator.ErrorField, err error) { + // TODO i18n + err = checker.PassWordCheck(8, 32, 0, u.Pass) + if err != nil { + return &validator.ErrorField{ + Key: "pass", + Value: err.Error(), + }, err + } + return nil, nil +} + +type UserNoticeSetRequest struct { + UserId string `json:"-" ` // user_id + NoticeSwitch bool `json:"notice_switch" ` +} + +type UserNoticeSetResp struct { + NoticeSwitch bool `json:"notice_switch"` +} + +type ActionRecordReq struct { + // action + Action string `validate:"required,oneof=login e_mail find_pass" form:"action"` + Ip string `json:"-"` +} + +type ActionRecordResp struct { + CaptchaID string `json:"captcha_id"` + CaptchaImg string `json:"captcha_img"` + Verify bool `json:"verify"` +} + +type UserBasicInfo struct { + UserId string `json:"-" ` // user_id + UserName string `json:"username" ` // name + Rank int `json:"rank" ` // rank + DisplayName string `json:"display_name"` // display_name + Avatar string `json:"avatar" ` // avatar + Website string `json:"website" ` // website + Location string `json:"location" ` // location + IpInfo string `json:"ip_info"` // ip info + Status int `json:"status"` // status +} + +type GetOtherUserInfoByUsernameReq struct { + Username string `validate:"required,gt=0,lte=500" form:"username"` +} + +type GetOtherUserInfoResp struct { + Info *GetOtherUserInfoByUsernameResp `json:"info"` + Has bool `json:"has"` +} + +type UserChangeEmailSendCodeReq struct { + Email string `validate:"required,email,gt=0,lte=500" json:"e_mail"` + UserID string `json:"-"` +} + +type EmailCodeContent struct { + Email string `json:"e_mail"` + UserID string `json:"user_id"` +} + +func (r *EmailCodeContent) ToJSONString() string { + codeBytes, _ := json.Marshal(r) + return string(codeBytes) +} + +func (r *EmailCodeContent) FromJSONString(data string) error { + return json.Unmarshal([]byte(data), &r) +} + +type UserChangeEmailVerifyReq struct { + Code string `validate:"required,gt=0,lte=500" json:"code"` + Content string `json:"-"` +} + +type UserVerifyEmailSendReq struct { + CaptchaID string `validate:"omitempty,gt=0,lte=500" json:"captcha_id"` + CaptchaCode string `validate:"omitempty,gt=0,lte=500" json:"captcha_code"` +} diff --git a/internal/schema/vote_schema.go b/internal/schema/vote_schema.go new file mode 100644 index 00000000..f1602902 --- /dev/null +++ b/internal/schema/vote_schema.go @@ -0,0 +1,66 @@ +package schema + +type VoteReq struct { + ObjectID string `validate:"required"form:"object_id" json:"object_id"` // id + IsCancel bool `validate:"omitempty"form:"is_cancel" json:"is_cancel"` // is cancel +} + +type VoteDTO struct { + // object TagID + ObjectID string + // is cancel + IsCancel bool + // user TagID + UserID string +} + +type VoteResp struct { + UpVotes int `json:"up_votes"` + DownVotes int `json:"down_votes"` + Votes int `json:"votes"` + VoteStatus string `json:"vote_status"` +} + +type GetVoteWithPageReq struct { + // page + Page int `validate:"omitempty,min=1" form:"page"` + // page size + PageSize int `validate:"omitempty,min=1" form:"page_size"` + // user id + UserID string `validate:"required" form:"user_id"` +} + +type VoteQuestion struct { + // object ID + ID string `json:"id"` + // title + Title string `json:"title"` +} + +type VoteAnswer struct { + // object ID + ID string `json:"id"` + // question ID + QuestionID string `json:"question_id"` + // title + Title string `json:"title"` +} + +type GetVoteWithPageResp struct { + // create time + CreatedAt int64 `json:"created_at"` + // object id + ObjectID string `json:"object_id"` + // question id + QuestionID string `json:"question_id"` + // answer id + AnswerID string `json:"answer_id"` + // object type + ObjectType string `json:"object_type" enums:"question,answer,tag,comment"` + // title + Title string `json:"title"` + // content + Content string `json:"content"` + // vote type + VoteType string `json:"vote_type"` +} diff --git a/internal/service/action/captcha_service.go b/internal/service/action/captcha_service.go new file mode 100644 index 00000000..2cadad8d --- /dev/null +++ b/internal/service/action/captcha_service.go @@ -0,0 +1,125 @@ +package action + +import ( + "context" + "image/color" + "strings" + + "github.com/mojocn/base64Captcha" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/pacman/errors" + "github.com/segmentfault/pacman/log" +) + +// CaptchaRepo captcha repository +type CaptchaRepo interface { + SetCaptcha(ctx context.Context, key, captcha string) (err error) + GetCaptcha(ctx context.Context, key string) (captcha string, err error) + SetActionType(ctx context.Context, ip, actionType string, amount int) (err error) + GetActionType(ctx context.Context, ip, actionType string) (amount int, err error) + DelActionType(ctx context.Context, ip, actionType string) (err error) +} + +// CaptchaService kit service +type CaptchaService struct { + captchaRepo CaptchaRepo +} + +// NewCaptchaService captcha service +func NewCaptchaService(captchaRepo CaptchaRepo) *CaptchaService { + return &CaptchaService{ + captchaRepo: captchaRepo, + } +} + +// ActionRecord action record +func (cs *CaptchaService) ActionRecord(ctx context.Context, req *schema.ActionRecordReq) (resp *schema.ActionRecordResp, err error) { + resp = &schema.ActionRecordResp{} + num, err := cs.captchaRepo.GetActionType(ctx, req.Ip, req.Action) + if err != nil { + num = 0 + } + // TODO config num to config file + if num >= 3 { + resp.CaptchaID, resp.CaptchaImg, err = cs.GenerateCaptcha(ctx) + resp.Verify = true + } + return +} + +// ActionRecordVerifyCaptcha +// Verify that you need to enter a CAPTCHA, and that the CAPTCHA is correct +func (cs *CaptchaService) ActionRecordVerifyCaptcha( + ctx context.Context, actionType string, ip string, id string, VerifyValue string) bool { + num, cahceErr := cs.captchaRepo.GetActionType(ctx, ip, actionType) + if cahceErr != nil { + return true + } + if num >= 3 { + pass, err := cs.VerifyCaptcha(ctx, id, VerifyValue) + if err != nil { + return false + } + return pass + } + return true +} + +func (cs *CaptchaService) ActionRecordAdd(ctx context.Context, actionType string, ip string) (int, error) { + var err error + num, cahceErr := cs.captchaRepo.GetActionType(ctx, ip, actionType) + if cahceErr != nil { + log.Error(err) + } + num++ + err = cs.captchaRepo.SetActionType(ctx, ip, actionType, num) + if err != nil { + return 0, err + } + return num, nil +} + +func (cs *CaptchaService) ActionRecordDel(ctx context.Context, actionType string, ip string) { + err := cs.captchaRepo.DelActionType(ctx, ip, actionType) + if err != nil { + log.Error(err) + } +} + +// GenerateCaptcha generate captcha +func (cs *CaptchaService) GenerateCaptcha(ctx context.Context) (key, captchaBase64 string, err error) { + driverString := base64Captcha.DriverString{ + Height: 40, + Width: 100, + NoiseCount: 0, + ShowLineOptions: 2 | 4, + Length: 4, + Source: "1234567890qwertyuioplkjhgfdsazxcvbnm", + BgColor: &color.RGBA{R: 3, G: 102, B: 214, A: 125}, + Fonts: []string{"wqy-microhei.ttc"}, + } + driver := driverString.ConvertFonts() + + id, content, answer := driver.GenerateIdQuestionAnswer() + item, err := driver.DrawCaptcha(content) + if err != nil { + return "", "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack() + } + err = cs.captchaRepo.SetCaptcha(ctx, id, answer) + if err != nil { + return "", "", err + } + + captchaBase64 = item.EncodeB64string() + return id, captchaBase64, nil +} + +// VerifyCaptcha generate captcha +func (cs *CaptchaService) VerifyCaptcha(ctx context.Context, key, captcha string) (isCorrect bool, err error) { + realCaptcha, err := cs.captchaRepo.GetCaptcha(ctx, key) + if err != nil { + return false, nil + } + return strings.TrimSpace(captcha) == realCaptcha, nil +} diff --git a/internal/service/activity/answer_activity.go b/internal/service/activity/answer_activity.go new file mode 100644 index 00000000..30933f08 --- /dev/null +++ b/internal/service/activity/answer_activity.go @@ -0,0 +1,77 @@ +package activity + +import ( + "context" + "time" + + "github.com/segmentfault/pacman/log" +) + +// AnswerActivityRepo answer activity +type AnswerActivityRepo interface { + AcceptAnswer(ctx context.Context, + answerObjID, questionUserID, answerUserID string, isSelf bool) (err error) + CancelAcceptAnswer(ctx context.Context, + answerObjID, questionUserID, answerUserID string) (err error) + DeleteAnswer(ctx context.Context, answerID string) (err error) +} + +// QuestionActivityRepo answer activity +type QuestionActivityRepo interface { + DeleteQuestion(ctx context.Context, questionID string) (err error) +} + +// AnswerActivityService user service +type AnswerActivityService struct { + answerActivityRepo AnswerActivityRepo + questionActivityRepo QuestionActivityRepo +} + +// NewAnswerActivityService new comment service +func NewAnswerActivityService( + answerActivityRepo AnswerActivityRepo, questionActivityRepo QuestionActivityRepo) *AnswerActivityService { + return &AnswerActivityService{ + answerActivityRepo: answerActivityRepo, + questionActivityRepo: questionActivityRepo, + } +} + +// AcceptAnswer accept answer change activity +func (as *AnswerActivityService) AcceptAnswer(ctx context.Context, + answerObjID, questionUserID, answerUserID string, isSelf bool) (err error) { + return as.answerActivityRepo.AcceptAnswer(ctx, answerObjID, questionUserID, answerUserID, isSelf) +} + +// CancelAcceptAnswer cancel accept answer change activity +func (as *AnswerActivityService) CancelAcceptAnswer(ctx context.Context, + answerObjID, questionUserID, answerUserID string) (err error) { + return as.answerActivityRepo.CancelAcceptAnswer(ctx, answerObjID, questionUserID, answerUserID) +} + +// DeleteAnswer delete answer change activity +func (as *AnswerActivityService) DeleteAnswer(ctx context.Context, answerID string, createdAt time.Time, + voteCount int) (err error) { + if voteCount >= 3 { + log.Infof("There is no need to roll back the reputation by answering likes above the target value. %s %d", answerID, voteCount) + return nil + } + if createdAt.Before(time.Now().AddDate(0, 0, -60)) { + log.Infof("There is no need to roll back the reputation by answer's existence time meets the target. %s %s", answerID, createdAt.String()) + return nil + } + return as.answerActivityRepo.DeleteAnswer(ctx, answerID) +} + +// DeleteQuestion delete question change activity +func (as *AnswerActivityService) DeleteQuestion(ctx context.Context, questionID string, createdAt time.Time, + voteCount int) (err error) { + if voteCount >= 3 { + log.Infof("There is no need to roll back the reputation by answering likes above the target value. %s %d", questionID, voteCount) + return nil + } + if createdAt.Before(time.Now().AddDate(0, 0, -60)) { + log.Infof("There is no need to roll back the reputation by answer's existence time meets the target. %s %s", questionID, createdAt.String()) + return nil + } + return as.questionActivityRepo.DeleteQuestion(ctx, questionID) +} diff --git a/internal/service/activity/user_active.go b/internal/service/activity/user_active.go new file mode 100644 index 00000000..356d91ee --- /dev/null +++ b/internal/service/activity/user_active.go @@ -0,0 +1,8 @@ +package activity + +import "context" + +// UserActiveActivityRepo interface +type UserActiveActivityRepo interface { + UserActive(ctx context.Context, userID string) (err error) +} diff --git a/internal/service/activity_common/activity.go b/internal/service/activity_common/activity.go new file mode 100644 index 00000000..20e2e575 --- /dev/null +++ b/internal/service/activity_common/activity.go @@ -0,0 +1,16 @@ +package activity_common + +import ( + "context" + + "github.com/segmentfault/answer/internal/entity" + "xorm.io/xorm" +) + +type ActivityRepo interface { + GetActivityTypeByObjID(ctx context.Context, objectId string, action string) (activityType, rank int, hasRank int, err error) + GetActivityTypeByObjKey(ctx context.Context, objectKey, action string) (activityType int, err error) + GetActivity(ctx context.Context, session *xorm.Session, objectID, userID string, activityType int) ( + existsActivity *entity.Activity, exist bool, err error) + GetUserIDObjectIDActivitySum(ctx context.Context, userID, object_id string) (int, error) +} diff --git a/internal/service/activity_common/follow.go b/internal/service/activity_common/follow.go new file mode 100644 index 00000000..baeff38c --- /dev/null +++ b/internal/service/activity_common/follow.go @@ -0,0 +1,10 @@ +package activity_common + +import "context" + +type FollowRepo interface { + GetFollowIDs(ctx context.Context, userID, objectType string) (followIDs []string, err error) + GetFollowAmount(ctx context.Context, objectID string) (followAmount int, err error) + GetFollowUserIDs(ctx context.Context, objectID string) (userIDs []string, err error) + IsFollowed(userId, objectId string) (bool, error) +} diff --git a/internal/service/activity_common/vote.go b/internal/service/activity_common/vote.go new file mode 100644 index 00000000..fb97885b --- /dev/null +++ b/internal/service/activity_common/vote.go @@ -0,0 +1,10 @@ +package activity_common + +import ( + "context" +) + +// VoteRepo activity repository +type VoteRepo interface { + GetVoteStatus(ctx context.Context, objectId, userId string) (status string) +} diff --git a/internal/service/activity_type/activity_type.go b/internal/service/activity_type/activity_type.go new file mode 100644 index 00000000..a8a1a564 --- /dev/null +++ b/internal/service/activity_type/activity_type.go @@ -0,0 +1,32 @@ +package activity_type + +import "github.com/segmentfault/answer/internal/repo/config" + +const ( + QuestionVoteUp = "question.vote_up" + QuestionVoteDown = "question.vote_down" + AnswerVoteUp = "answer.vote_up" + AnswerVoteDown = "answer.vote_down" + CommentVoteUp = "comment.vote_up" + CommentVoteDown = "comment.vote_down" +) + +var ( + activityTypeFlagMapping = map[string]string{ + QuestionVoteUp: "upvote", + QuestionVoteDown: "downvote", + AnswerVoteUp: "upvote", + AnswerVoteDown: "downvote", + CommentVoteUp: "upvote", + CommentVoteDown: "downvote", + } +) + +func Format(activityTypeID int) string { + activityTypeStr := config.ID2KeyMapping[activityTypeID] + activityTypeFlag := activityTypeFlagMapping[activityTypeStr] + if len(activityTypeFlag) == 0 { + return "edit" // to edit + } + return activityTypeFlag // todo i18n support +} diff --git a/internal/service/answer_common/answer.go b/internal/service/answer_common/answer.go new file mode 100644 index 00000000..286e8499 --- /dev/null +++ b/internal/service/answer_common/answer.go @@ -0,0 +1,81 @@ +package answercommon + +import ( + "context" + + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" +) + +type AnswerRepo interface { + AddAnswer(ctx context.Context, answer *entity.Answer) (err error) + RemoveAnswer(ctx context.Context, id string) (err error) + UpdateAnswer(ctx context.Context, answer *entity.Answer, Colar []string) (err error) + 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) + UpdateAdopted(ctx context.Context, id string, questionId string) error + GetByID(ctx context.Context, id string) (*entity.Answer, bool, error) + GetByUserIdQuestionId(ctx context.Context, userId string, questionId string) (*entity.Answer, bool, error) + SearchList(ctx context.Context, search *entity.AnswerSearch) ([]*entity.Answer, int64, error) + CmsSearchList(ctx context.Context, search *entity.CmsAnswerSearch) ([]*entity.Answer, int64, error) + UpdateAnswerStatus(ctx context.Context, answer *entity.Answer) (err error) +} + +// AnswerCommon user service +type AnswerCommon struct { + answerRepo AnswerRepo +} + +func NewAnswerCommon(answerRepo AnswerRepo) *AnswerCommon { + return &AnswerCommon{ + answerRepo: answerRepo, + } +} + +func (as *AnswerCommon) SearchAnswered(ctx context.Context, userId, questionId string) (bool, error) { + _, has, err := as.answerRepo.GetByUserIdQuestionId(ctx, userId, questionId) + if err != nil { + return has, err + } + return has, nil +} + +func (as *AnswerCommon) CmsSearchList(ctx context.Context, search *entity.CmsAnswerSearch) ([]*entity.Answer, int64, error) { + return as.answerRepo.CmsSearchList(ctx, search) +} + +func (as *AnswerCommon) Search(ctx context.Context, search *entity.AnswerSearch) ([]*entity.Answer, int64, error) { + list, count, err := as.answerRepo.SearchList(ctx, search) + if err != nil { + return list, count, err + } + return list, count, err +} + +func (as *AnswerCommon) ShowFormat(ctx context.Context, data *entity.Answer) *schema.AnswerInfo { + info := schema.AnswerInfo{} + info.ID = data.ID + info.QuestionId = data.QuestionID + info.Content = data.OriginalText + info.Html = data.ParsedText + info.Adopted = data.Adopted + info.VoteCount = data.VoteCount + info.CreateTime = data.CreatedAt.Unix() + info.UpdateTime = data.UpdatedAt.Unix() + info.UserId = data.UserID + return &info +} + +func (as *AnswerCommon) AdminShowFormat(ctx context.Context, data *entity.Answer) *schema.AdminAnswerInfo { + info := schema.AdminAnswerInfo{} + info.ID = data.ID + info.QuestionId = data.QuestionID + info.Description = data.ParsedText + info.Adopted = data.Adopted + info.VoteCount = data.VoteCount + info.CreateTime = data.CreatedAt.Unix() + info.UpdateTime = data.UpdatedAt.Unix() + info.UserId = data.UserID + return &info +} diff --git a/internal/service/answer_service.go b/internal/service/answer_service.go new file mode 100644 index 00000000..45e1eb13 --- /dev/null +++ b/internal/service/answer_service.go @@ -0,0 +1,431 @@ +package service + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/segmentfault/answer/internal/base/constant" + "github.com/segmentfault/answer/internal/service/activity" + "github.com/segmentfault/answer/internal/service/activity_common" + "github.com/segmentfault/answer/internal/service/notice_queue" + "github.com/segmentfault/answer/internal/service/revision_common" + + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + answercommon "github.com/segmentfault/answer/internal/service/answer_common" + collectioncommon "github.com/segmentfault/answer/internal/service/collection_common" + "github.com/segmentfault/answer/internal/service/permission" + questioncommon "github.com/segmentfault/answer/internal/service/question_common" + usercommon "github.com/segmentfault/answer/internal/service/user_common" + "github.com/segmentfault/pacman/errors" + "github.com/segmentfault/pacman/log" +) + +// AnswerService user service +type AnswerService struct { + answerRepo answercommon.AnswerRepo + questionRepo questioncommon.QuestionRepo + questionCommon *questioncommon.QuestionCommon + answerActivityService *activity.AnswerActivityService + userCommon *usercommon.UserCommon + collectionCommon *collectioncommon.CollectionCommon + userRepo usercommon.UserRepo + revisionService *revision_common.RevisionService + AnswerCommon *answercommon.AnswerCommon + voteRepo activity_common.VoteRepo +} + +func NewAnswerService( + answerRepo answercommon.AnswerRepo, + questionRepo questioncommon.QuestionRepo, + questionCommon *questioncommon.QuestionCommon, + userCommon *usercommon.UserCommon, + collectionCommon *collectioncommon.CollectionCommon, + userRepo usercommon.UserRepo, + revisionService *revision_common.RevisionService, + answerAcceptActivityRepo *activity.AnswerActivityService, + answerCommon *answercommon.AnswerCommon, + voteRepo activity_common.VoteRepo, +) *AnswerService { + return &AnswerService{ + answerRepo: answerRepo, + questionRepo: questionRepo, + userCommon: userCommon, + collectionCommon: collectionCommon, + questionCommon: questionCommon, + userRepo: userRepo, + revisionService: revisionService, + answerActivityService: answerAcceptActivityRepo, + AnswerCommon: answerCommon, + voteRepo: voteRepo, + } +} + +// RemoveAnswer delete answer +func (as *AnswerService) RemoveAnswer(ctx context.Context, id string) (err error) { + answerInfo, exist, err := as.answerRepo.GetByID(ctx, id) + if err != nil { + return err + } + if !exist { + return nil + } + + //user add question count + err = as.questionCommon.UpdateAnswerCount(ctx, answerInfo.QuestionID, -1) + if err != nil { + log.Error("IncreaseAnswerCount error", err.Error()) + } + + err = as.userCommon.UpdateAnswerCount(ctx, answerInfo.UserID, -1) + if err != nil { + log.Error("user IncreaseAnswerCount error", err.Error()) + } + + err = as.answerRepo.RemoveAnswer(ctx, id) + if err != nil { + return err + } + err = as.answerActivityService.DeleteAnswer(ctx, answerInfo.ID, answerInfo.CreatedAt, answerInfo.VoteCount) + if err != nil { + log.Errorf("delete answer activity change failed: %s", err.Error()) + } + return +} + +func (as *AnswerService) Insert(ctx context.Context, req *schema.AnswerAddReq) (string, error) { + questionInfo, exist, err := as.questionRepo.GetQuestion(ctx, req.QuestionId) + if err != nil { + return "", err + } + if !exist { + return "", errors.BadRequest(reason.QuestionNotFound) + } + now := time.Now() + insertData := new(entity.Answer) + insertData.UserID = req.UserID + insertData.OriginalText = req.Content + insertData.ParsedText = req.Html + insertData.Adopted = schema.Answer_Adopted_Failed + insertData.QuestionID = req.QuestionId + insertData.RevisionID = "0" + insertData.Status = entity.AnswerStatusAvailable + insertData.UpdatedAt = now + if err := as.answerRepo.AddAnswer(ctx, insertData); err != nil { + return "", err + } + err = as.questionCommon.UpdateAnswerCount(ctx, req.QuestionId, 1) + if err != nil { + log.Error("IncreaseAnswerCount error", err.Error()) + } + err = as.questionCommon.UpdateLastAnswer(ctx, req.QuestionId, insertData.ID) + if err != nil { + log.Error("UpdateLastAnswer error", err.Error()) + } + err = as.questionCommon.UpdataPostTime(ctx, req.QuestionId) + if err != nil { + return insertData.ID, err + } + + err = as.userCommon.UpdateAnswerCount(ctx, req.UserID, 1) + if err != nil { + log.Error("user IncreaseAnswerCount error", err.Error()) + } + + revisionDTO := &schema.AddRevisionDTO{ + UserID: insertData.UserID, + ObjectID: insertData.ID, + Title: "", + } + InfoJson, _ := json.Marshal(insertData) + revisionDTO.Content = string(InfoJson) + err = as.revisionService.AddRevision(ctx, revisionDTO, true) + if err != nil { + return insertData.ID, err + } + as.notificationAnswerTheQuestion(ctx, questionInfo.UserID, insertData.ID, req.UserID) + return insertData.ID, nil +} + +func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq) (string, error) { + questionInfo, exist, err := as.questionRepo.GetQuestion(ctx, req.QuestionId) + if err != nil { + return "", err + } + if !exist { + return "", errors.BadRequest(reason.QuestionNotFound) + } + now := time.Now() + insertData := new(entity.Answer) + insertData.ID = req.ID + insertData.QuestionID = req.QuestionId + insertData.UserID = req.UserID + insertData.OriginalText = req.Content + insertData.ParsedText = req.Html + insertData.UpdatedAt = now + if err := as.answerRepo.UpdateAnswer(ctx, insertData, []string{"original_text", "parsed_text", "update_time"}); err != nil { + return "", err + } + err = as.questionCommon.UpdataPostTime(ctx, req.QuestionId) + if err != nil { + return insertData.ID, err + } + revisionDTO := &schema.AddRevisionDTO{ + UserID: req.UserID, + ObjectID: req.ID, + Title: "", + Log: req.EditSummary, + } + InfoJson, _ := json.Marshal(insertData) + revisionDTO.Content = string(InfoJson) + err = as.revisionService.AddRevision(ctx, revisionDTO, true) + if err != nil { + return insertData.ID, err + } + as.notificationUpdateAnswer(ctx, questionInfo.UserID, insertData.ID, req.UserID) + return insertData.ID, nil +} + +// UpdateAdopted +func (as *AnswerService) UpdateAdopted(ctx context.Context, req *schema.AnswerAdoptedReq) error { + if req.UserID == "" { + return nil + } + + newAnswerInfo, exist, err := as.answerRepo.GetByID(ctx, req.AnswerID) + if err != nil { + return err + } + if !exist { + return errors.BadRequest(reason.AnswerNotFound) + } + + questionInfo, exist, err := as.questionRepo.GetQuestion(ctx, req.QuestionID) + if err != nil { + return err + } + if !exist { + return errors.BadRequest(reason.QuestionNotFound) + } + if questionInfo.UserID != req.UserID { + return fmt.Errorf("no permission to set answer") + } + if questionInfo.AcceptedAnswerID == req.AnswerID { + return nil + } + + var oldAnswerInfo *entity.Answer + if len(questionInfo.AcceptedAnswerID) > 0 && questionInfo.AcceptedAnswerID != "0" { + oldAnswerInfo, exist, err = as.answerRepo.GetByID(ctx, questionInfo.AcceptedAnswerID) + if err != nil { + return err + } + } + + err = as.answerRepo.UpdateAdopted(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) + return nil +} + +func (as *AnswerService) updateAnswerRank(ctx context.Context, userID string, + questionInfo *entity.Question, newAnswerInfo *entity.Answer, oldAnswerInfo *entity.Answer) { + + // if this question is already been answered, should cancel old answer rank + if oldAnswerInfo != nil { + err := as.answerActivityService.CancelAcceptAnswer( + ctx, questionInfo.AcceptedAnswerID, questionInfo.UserID, oldAnswerInfo.UserID) + if err != nil { + log.Error(err) + } + } + + err := as.answerActivityService.AcceptAnswer( + ctx, newAnswerInfo.ID, questionInfo.UserID, newAnswerInfo.UserID, newAnswerInfo.UserID == userID) + if err != nil { + log.Error(err) + } +} + +func (as *AnswerService) Get(ctx context.Context, answerID, loginUserId string) (*schema.AnswerInfo, *schema.QuestionInfo, bool, error) { + answerInfo, has, err := as.answerRepo.GetByID(ctx, answerID) + if err != nil { + return nil, nil, has, err + } + info := as.ShowFormat(ctx, answerInfo) + //todo questionFunc + questionInfo, err := as.questionCommon.Info(ctx, answerInfo.QuestionID, loginUserId) + if err != nil { + return nil, nil, has, err + } + //todo UserFunc + userinfo, has, err := as.userCommon.GetUserBasicInfoByID(ctx, answerInfo.UserID) + if err != nil { + return nil, nil, has, err + } + if has { + info.UserInfo = userinfo + info.UpdateUserInfo = userinfo + } + + if loginUserId == "" { + return info, questionInfo, has, nil + } + + info.VoteStatus = as.voteRepo.GetVoteStatus(ctx, answerID, loginUserId) + + CollectedMap, err := as.collectionCommon.SearchObjectCollected(ctx, loginUserId, []string{answerInfo.ID}) + if err != nil { + log.Error("CollectionFunc.SearchObjectCollected error", err) + } + _, ok := CollectedMap[answerInfo.ID] + if ok { + info.Collected = true + } + + return info, questionInfo, has, nil +} + +func (as *AnswerService) AdminSetAnswerStatus(ctx context.Context, answerID string, setStatusStr string) error { + setStatus, ok := entity.CmsAnswerSearchStatus[setStatusStr] + if !ok { + return fmt.Errorf("question status does not exist") + } + answerInfo, exist, err := as.answerRepo.GetAnswer(ctx, answerID) + if err != nil { + return err + } + if !exist { + return fmt.Errorf("answer does not exist") + } + answerInfo.Status = setStatus + err = as.answerRepo.UpdateAnswerStatus(ctx, answerInfo) + if err != nil { + return err + } + + if setStatus == entity.AnswerStatusDeleted { + err = as.answerActivityService.DeleteQuestion(ctx, answerInfo.ID, answerInfo.CreatedAt, answerInfo.VoteCount) + if err != nil { + log.Errorf("admin delete question then rank rollback error %s", err.Error()) + } + } + + msg := &schema.NotificationMsg{} + msg.ObjectID = answerInfo.ID + msg.Type = schema.NotificationTypeInbox + msg.ReceiverUserID = answerInfo.UserID + msg.TriggerUserID = answerInfo.UserID + msg.ObjectType = constant.AnswerObjectType + msg.NotificationAction = constant.YourAnswerWasDeleted + notice_queue.AddNotification(msg) + + return nil +} + +func (as *AnswerService) SearchList(ctx context.Context, search *schema.AnswerList) ([]*schema.AnswerInfo, int64, error) { + list := make([]*schema.AnswerInfo, 0) + dbSearch := entity.AnswerSearch{} + dbSearch.QuestionID = search.QuestionId + dbSearch.Page = search.Page + dbSearch.PageSize = search.PageSize + dbSearch.Order = search.Order + dblist, count, err := as.answerRepo.SearchList(ctx, &dbSearch) + if err != nil { + return list, count, err + } + AnswerList, err := as.SearchFormatInfo(ctx, dblist, search.LoginUserID) + if err != nil { + return AnswerList, count, err + } + return AnswerList, count, nil +} + +func (as *AnswerService) SearchFormatInfo(ctx context.Context, dblist []*entity.Answer, loginUserId string) ([]*schema.AnswerInfo, error) { + list := make([]*schema.AnswerInfo, 0) + //todo 依赖其他接口 + objectIds := make([]string, 0) + userIds := make([]string, 0) + for _, dbitem := range dblist { + item := as.ShowFormat(ctx, dbitem) + list = append(list, item) + objectIds = append(objectIds, dbitem.ID) + userIds = append(userIds, dbitem.UserID) + if loginUserId != "" { + //item.VoteStatus = as.activityFunc.GetVoteStatus(ctx, item.TagID, loginUserId) + item.VoteStatus = as.voteRepo.GetVoteStatus(ctx, item.ID, loginUserId) + } + } + 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] + item.UpdateUserInfo = userInfoMap[item.UserId] + } + } + + if loginUserId == "" { + return list, nil + } + + CollectedMap, err := as.collectionCommon.SearchObjectCollected(ctx, loginUserId, objectIds) + if err != nil { + log.Error("CollectionFunc.SearchObjectCollected error", err) + } + + for _, item := range list { + _, ok := CollectedMap[item.ID] + if ok { + item.Collected = true + } + } + + for _, item := range list { + item.MemberActions = permission.GetAnswerPermission(loginUserId, item.UserId) + } + + return list, nil +} + +func (as *AnswerService) ShowFormat(ctx context.Context, data *entity.Answer) *schema.AnswerInfo { + return as.AnswerCommon.ShowFormat(ctx, data) +} + +func (as *AnswerService) notificationUpdateAnswer(ctx context.Context, questionUserID, answerID, answerUserID string) { + msg := &schema.NotificationMsg{ + TriggerUserID: answerUserID, + ReceiverUserID: questionUserID, + Type: schema.NotificationTypeInbox, + ObjectID: answerID, + } + msg.ObjectType = constant.QuestionObjectType + msg.NotificationAction = constant.UpdateAnswer + notice_queue.AddNotification(msg) +} + +func (as *AnswerService) notificationAnswerTheQuestion(ctx context.Context, questionUserID, answerID, answerUserID string) { + msg := &schema.NotificationMsg{ + TriggerUserID: answerUserID, + ReceiverUserID: questionUserID, + Type: schema.NotificationTypeInbox, + ObjectID: answerID, + } + msg.ObjectType = constant.QuestionObjectType + msg.NotificationAction = constant.AnswerTheQuestion + notice_queue.AddNotification(msg) +} diff --git a/internal/service/auth/auth.go b/internal/service/auth/auth.go new file mode 100644 index 00000000..6f4ff43a --- /dev/null +++ b/internal/service/auth/auth.go @@ -0,0 +1,76 @@ +package auth + +import ( + "context" + + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/pkg/token" + "github.com/segmentfault/pacman/log" +) + +// AuthRepo auth repository +type AuthRepo interface { + GetUserCacheInfo(ctx context.Context, accessToken string) (userInfo *entity.UserCacheInfo, err error) + SetUserCacheInfo(ctx context.Context, accessToken string, userInfo *entity.UserCacheInfo) error + RemoveUserCacheInfo(ctx context.Context, accessToken string) (err error) + GetUserStatus(ctx context.Context, userID string) (userInfo *entity.UserCacheInfo, err error) + GetCmsUserCacheInfo(ctx context.Context, accessToken string) (userInfo *entity.UserCacheInfo, err error) + SetCmsUserCacheInfo(ctx context.Context, accessToken string, userInfo *entity.UserCacheInfo) error + RemoveCmsUserCacheInfo(ctx context.Context, accessToken string) (err error) +} + +// AuthService kit service +type AuthService struct { + authRepo AuthRepo +} + +// NewAuthService email service +func NewAuthService(authRepo AuthRepo) *AuthService { + return &AuthService{ + authRepo: authRepo, + } +} + +func (as *AuthService) GetUserCacheInfo(ctx context.Context, accessToken string) (userInfo *entity.UserCacheInfo, err error) { + userCacheInfo, err := as.authRepo.GetUserCacheInfo(ctx, accessToken) + if err != nil { + return nil, err + } + cacheInfo, _ := as.authRepo.GetUserStatus(ctx, userCacheInfo.UserID) + if cacheInfo != nil { + log.Infof("user status updated: %+v", cacheInfo) + userCacheInfo.UserStatus = cacheInfo.UserStatus + userCacheInfo.EmailStatus = cacheInfo.EmailStatus + // update current user cache info + err := as.authRepo.SetUserCacheInfo(ctx, accessToken, userCacheInfo) + if err != nil { + return nil, err + } + } + return userCacheInfo, nil +} + +func (as *AuthService) SetUserCacheInfo(ctx context.Context, userInfo *entity.UserCacheInfo) (accessToken string, err error) { + accessToken = token.GenerateToken() + err = as.authRepo.SetUserCacheInfo(ctx, accessToken, userInfo) + return accessToken, err +} + +func (as *AuthService) RemoveUserCacheInfo(ctx context.Context, accessToken string) (err error) { + return as.authRepo.RemoveUserCacheInfo(ctx, accessToken) +} + +//cms + +func (as *AuthService) GetCmsUserCacheInfo(ctx context.Context, accessToken string) (userInfo *entity.UserCacheInfo, err error) { + return as.authRepo.GetCmsUserCacheInfo(ctx, accessToken) +} + +func (as *AuthService) SetCmsUserCacheInfo(ctx context.Context, accessToken string, userInfo *entity.UserCacheInfo) (err error) { + err = as.authRepo.SetCmsUserCacheInfo(ctx, accessToken, userInfo) + return err +} + +func (as *AuthService) RemoveCmsUserCacheInfo(ctx context.Context, accessToken string) (err error) { + return as.authRepo.RemoveCmsUserCacheInfo(ctx, accessToken) +} diff --git a/internal/service/collection_common/collection.go b/internal/service/collection_common/collection.go new file mode 100644 index 00000000..719f1b4b --- /dev/null +++ b/internal/service/collection_common/collection.go @@ -0,0 +1,42 @@ +package collectioncommon + +import ( + "context" + + "github.com/segmentfault/answer/internal/entity" +) + +// CollectionRepo collection repository +type CollectionRepo interface { + AddCollection(ctx context.Context, collection *entity.Collection) (err error) + RemoveCollection(ctx context.Context, id string) (err error) + UpdateCollection(ctx context.Context, collection *entity.Collection, cols []string) (err error) + GetCollection(ctx context.Context, id int) (collection *entity.Collection, exist bool, err error) + GetCollectionList(ctx context.Context, collection *entity.Collection) (collectionList []*entity.Collection, err error) + GetOneByObjectIDAndUser(ctx context.Context, userId string, objectId string) (collection *entity.Collection, exist bool, err error) + SearchByObjectIDsAndUser(ctx context.Context, userId string, objectIds []string) (collectionList []*entity.Collection, err error) + CountByObjectID(ctx context.Context, objectId string) (total int64, err error) + GetCollectionPage(ctx context.Context, page, pageSize int, collection *entity.Collection) (collectionList []*entity.Collection, total int64, err error) + SearchObjectCollected(ctx context.Context, userId string, objectIds []string) (collectedMap map[string]bool, err error) + SearchList(ctx context.Context, search *entity.CollectionSearch) ([]*entity.Collection, int64, error) +} + +// CollectionService user service +type CollectionCommon struct { + collectionRepo CollectionRepo +} + +func NewCollectionCommon(collectionRepo CollectionRepo) *CollectionCommon { + return &CollectionCommon{ + collectionRepo: collectionRepo, + } +} + +// SearchObjectCollected search object is collected +func (ccs *CollectionCommon) SearchObjectCollected(ctx context.Context, userId string, objectIds []string) (collectedMap map[string]bool, err error) { + return ccs.collectionRepo.SearchObjectCollected(ctx, userId, objectIds) +} + +func (ccs *CollectionCommon) SearchList(ctx context.Context, search *entity.CollectionSearch) ([]*entity.Collection, int64, error) { + return ccs.collectionRepo.SearchList(ctx, search) +} diff --git a/internal/service/collection_group_service.go b/internal/service/collection_group_service.go new file mode 100644 index 00000000..1e2894db --- /dev/null +++ b/internal/service/collection_group_service.go @@ -0,0 +1,61 @@ +package service + +import ( + "context" + + "github.com/jinzhu/copier" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/pacman/errors" +) + +// CollectionGroupRepo collectionGroup repository +type CollectionGroupRepo interface { + AddCollectionGroup(ctx context.Context, collectionGroup *entity.CollectionGroup) (err error) + AddCollectionDefaultGroup(ctx context.Context, userID string) (collectionGroup *entity.CollectionGroup, err error) + UpdateCollectionGroup(ctx context.Context, collectionGroup *entity.CollectionGroup, cols []string) (err error) + GetCollectionGroup(ctx context.Context, id string) (collectionGroup *entity.CollectionGroup, exist bool, err error) + GetCollectionGroupPage(ctx context.Context, page, pageSize int, collectionGroup *entity.CollectionGroup) (collectionGroupList []*entity.CollectionGroup, total int64, err error) + GetDefaultID(ctx context.Context, userId string) (collectionGroup *entity.CollectionGroup, has bool, err error) +} + +// CollectionGroupService user service +type CollectionGroupService struct { + collectionGroupRepo CollectionGroupRepo +} + +func NewCollectionGroupService(collectionGroupRepo CollectionGroupRepo) *CollectionGroupService { + return &CollectionGroupService{ + collectionGroupRepo: collectionGroupRepo, + } +} + +// AddCollectionGroup add collection group +func (cs *CollectionGroupService) AddCollectionGroup(ctx context.Context, req *schema.AddCollectionGroupReq) (err error) { + collectionGroup := &entity.CollectionGroup{} + _ = copier.Copy(collectionGroup, req) + return cs.collectionGroupRepo.AddCollectionGroup(ctx, collectionGroup) +} + +// UpdateCollectionGroup update collection group +func (cs *CollectionGroupService) UpdateCollectionGroup(ctx context.Context, req *schema.UpdateCollectionGroupReq, cols []string) (err error) { + collectionGroup := &entity.CollectionGroup{} + _ = copier.Copy(collectionGroup, req) + return cs.collectionGroupRepo.UpdateCollectionGroup(ctx, collectionGroup, cols) +} + +// GetCollectionGroup get collection group one +func (cs *CollectionGroupService) GetCollectionGroup(ctx context.Context, id string) (resp *schema.GetCollectionGroupResp, err error) { + collectionGroup, exist, err := cs.collectionGroupRepo.GetCollectionGroup(ctx, id) + if err != nil { + return + } + if !exist { + return nil, errors.BadRequest(reason.UnknownError) + } + + resp = &schema.GetCollectionGroupResp{} + _ = copier.Copy(resp, collectionGroup) + return resp, nil +} diff --git a/internal/service/collection_service.go b/internal/service/collection_service.go new file mode 100644 index 00000000..2dcec893 --- /dev/null +++ b/internal/service/collection_service.go @@ -0,0 +1,149 @@ +package service + +import ( + "context" + "fmt" + + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + collectioncommon "github.com/segmentfault/answer/internal/service/collection_common" + questioncommon "github.com/segmentfault/answer/internal/service/question_common" + "github.com/segmentfault/pacman/errors" + "github.com/segmentfault/pacman/log" +) + +// CollectionService user service +type CollectionService struct { + collectionRepo collectioncommon.CollectionRepo + collectionGroupRepo CollectionGroupRepo + questionCommon *questioncommon.QuestionCommon +} + +func NewCollectionService( + collectionRepo collectioncommon.CollectionRepo, + collectionGroupRepo CollectionGroupRepo, + questionCommon *questioncommon.QuestionCommon, + +) *CollectionService { + return &CollectionService{ + collectionRepo: collectionRepo, + collectionGroupRepo: collectionGroupRepo, + questionCommon: questionCommon, + } +} +func (cs *CollectionService) CollectionSwitch(ctx context.Context, dto *schema.CollectionSwitchDTO) (resp *schema.CollectionSwitchResp, err error) { + resp = &schema.CollectionSwitchResp{} + dbData, has, err := cs.collectionRepo.GetOneByObjectIDAndUser(ctx, dto.UserID, dto.ObjectID) + if err != nil { + return + } + if has { + err = cs.collectionRepo.RemoveCollection(ctx, dbData.ID) + if err != nil { + return nil, err + } + err = cs.questionCommon.UpdateCollectionCount(ctx, dto.ObjectID, -1) + if err != nil { + log.Error("UpdateCollectionCount", err.Error()) + } + count, err := cs.objectCollectionCount(ctx, dto.ObjectID) + if err != nil { + return resp, err + } + resp.ObjectCollectionCount = fmt.Sprintf("%v", count) + resp.Switch = false + return resp, err + } + + if dto.GroupID == "" || dto.GroupID == "0" { + defaultGroup, has, err := cs.collectionGroupRepo.GetDefaultID(ctx, dto.UserID) + if err != nil { + return nil, err + } + if !has { + + defaultGroup, err := cs.collectionGroupRepo.AddCollectionDefaultGroup(ctx, dto.UserID) + if err != nil { + return nil, err + } + dto.GroupID = defaultGroup.ID + + } else { + dto.GroupID = defaultGroup.ID + } + } + collection := &entity.Collection{ + UserCollectionGroupID: dto.GroupID, + UserID: dto.UserID, + ObjectID: dto.ObjectID, + } + + err = cs.collectionRepo.AddCollection(ctx, collection) + if err != nil { + return + } + err = cs.questionCommon.UpdateCollectionCount(ctx, dto.ObjectID, 1) + if err != nil { + log.Error("UpdateCollectionCount", err.Error()) + } + count, err := cs.objectCollectionCount(ctx, dto.ObjectID) + if err != nil { + return + } + resp.ObjectCollectionCount = fmt.Sprintf("%d", count) + resp.Switch = true + return +} + +func (cs *CollectionService) objectCollectionCount(ctx context.Context, objectId string) (int64, error) { + count, err := cs.collectionRepo.CountByObjectID(ctx, objectId) + return count, err +} + +func (cs *CollectionService) add(ctx context.Context, collection *entity.Collection) error { + _, has, err := cs.collectionRepo.GetOneByObjectIDAndUser(ctx, collection.UserID, collection.ObjectID) + if err != nil { + return err + } + if has { + return errors.BadRequest("already collected") + } + + if collection.UserCollectionGroupID == "" || collection.UserCollectionGroupID == "0" { + defaultGroup, has, err := cs.collectionGroupRepo.GetDefaultID(ctx, collection.UserID) + if err != nil { + return err + } + if !has { + defaultGroup, err := cs.collectionGroupRepo.AddCollectionDefaultGroup(ctx, collection.UserID) + if err != nil { + return err + } + collection.UserCollectionGroupID = defaultGroup.ID + + } else { + collection.UserCollectionGroupID = defaultGroup.ID + } + } + err = cs.collectionRepo.AddCollection(ctx, collection) + if err != nil { + return err + } + return nil +} + +// Cancel +func (cs *CollectionService) cancel(ctx context.Context, collection *entity.Collection) error { + dbData, has, err := cs.collectionRepo.GetOneByObjectIDAndUser(ctx, collection.UserID, collection.ObjectID) + if err != nil { + return err + } + if !has { + return errors.BadRequest("collected record does not exist") + } + err = cs.collectionRepo.RemoveCollection(ctx, dbData.ID) + if err != nil { + return err + } + return nil +} diff --git a/internal/service/comment/comment_service.go b/internal/service/comment/comment_service.go new file mode 100644 index 00000000..7212ac49 --- /dev/null +++ b/internal/service/comment/comment_service.go @@ -0,0 +1,409 @@ +package comment + +import ( + "context" + + "github.com/jinzhu/copier" + "github.com/segmentfault/answer/internal/base/constant" + "github.com/segmentfault/answer/internal/base/pager" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/activity_common" + "github.com/segmentfault/answer/internal/service/comment_common" + "github.com/segmentfault/answer/internal/service/notice_queue" + object_info "github.com/segmentfault/answer/internal/service/object_info" + "github.com/segmentfault/answer/internal/service/permission" + usercommon "github.com/segmentfault/answer/internal/service/user_common" + "github.com/segmentfault/pacman/errors" + "github.com/segmentfault/pacman/log" +) + +// CommentRepo comment repository +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) + GetCommentPage(ctx context.Context, commentQuery *CommentQuery) ( + comments []*entity.Comment, total int64, err error) +} + +// CommentService user service +type CommentService struct { + commentRepo CommentRepo + commentCommonRepo comment_common.CommentCommonRepo + userRepo usercommon.UserRepo + voteCommon activity_common.VoteRepo + objectInfoService *object_info.ObjService +} + +type CommentQuery struct { + pager.PageCond + // object id + ObjectID string + // query condition + QueryCond string + // user id + UserID string +} + +func (c *CommentQuery) GetOrderBy() string { + if c.QueryCond == "vote" { + return "vote_count DESC,created_at ASC" + } + if c.QueryCond == "created_at" { + return "created_at DESC" + } + return "created_at ASC" +} + +// NewCommentService new comment service +func NewCommentService( + commentRepo CommentRepo, + commentCommonRepo comment_common.CommentCommonRepo, + userRepo usercommon.UserRepo, + objectInfoService *object_info.ObjService, + voteCommon activity_common.VoteRepo) *CommentService { + return &CommentService{ + commentRepo: commentRepo, + commentCommonRepo: commentCommonRepo, + userRepo: userRepo, + voteCommon: voteCommon, + objectInfoService: objectInfoService, + } +} + +// AddComment add comment +func (cs *CommentService) AddComment(ctx context.Context, req *schema.AddCommentReq) ( + resp *schema.GetCommentResp, err error) { + comment := &entity.Comment{} + _ = copier.Copy(comment, req) + comment.Status = entity.CommentStatusAvailable + + // add question id + objInfo, err := cs.objectInfoService.GetInfo(ctx, req.ObjectID) + if err != nil { + return nil, err + } + if objInfo.ObjectType == constant.QuestionObjectType || objInfo.ObjectType == constant.AnswerObjectType { + comment.QuestionID = objInfo.QuestionID + } + + if len(req.ReplyCommentID) > 0 { + replyComment, exist, err := cs.commentCommonRepo.GetComment(ctx, req.ReplyCommentID) + if err != nil { + return nil, err + } + if !exist { + return nil, errors.BadRequest(reason.CommentNotFound) + } + comment.SetReplyUserID(replyComment.UserID) + comment.SetReplyCommentID(replyComment.ID) + } else { + comment.SetReplyUserID("") + comment.SetReplyCommentID("") + } + + err = cs.commentRepo.AddComment(ctx, comment) + if err != nil { + return nil, err + } + + if objInfo.ObjectType == constant.QuestionObjectType { + cs.notificationQuestionComment(ctx, objInfo.ObjectCreator, comment.ID, req.UserID) + } else if objInfo.ObjectType == constant.AnswerObjectType { + cs.notificationAnswerComment(ctx, objInfo.ObjectCreator, comment.ID, req.UserID) + } + if len(req.MentionUsernameList) > 0 { + cs.notificationMention(ctx, req.MentionUsernameList, comment.ID, req.UserID) + } + + resp = &schema.GetCommentResp{} + resp.SetFromComment(comment) + resp.MemberActions = permission.GetCommentPermission(req.UserID, resp.UserID) + + // get reply user info + if len(resp.ReplyUserID) > 0 { + replyUser, exist, err := cs.userRepo.GetByUserID(ctx, resp.ReplyUserID) + if err != nil { + return nil, err + } + if exist { + resp.ReplyUsername = replyUser.Username + resp.ReplyUserDisplayName = replyUser.DisplayName + } + cs.notificationCommentReply(ctx, replyUser.ID, objInfo.QuestionID, req.UserID) + } + + // get user info + userInfo, exist, err := cs.userRepo.GetByUserID(ctx, resp.UserID) + if err != nil { + return nil, err + } + if exist { + resp.Username = userInfo.Username + resp.UserDisplayName = userInfo.DisplayName + resp.UserAvatar = userInfo.Avatar + } + return resp, nil +} + +// RemoveComment delete comment +func (cs *CommentService) RemoveComment(ctx context.Context, req *schema.RemoveCommentReq) (err error) { + if err := cs.checkCommentWhetherOwner(ctx, req.UserID, req.CommentID); err != nil { + return err + } + return cs.commentRepo.RemoveComment(ctx, req.CommentID) +} + +// UpdateComment update comment +func (cs *CommentService) UpdateComment(ctx context.Context, req *schema.UpdateCommentReq) (err error) { + if err := cs.checkCommentWhetherOwner(ctx, req.UserID, req.CommentID); err != nil { + return err + } + comment := &entity.Comment{} + _ = copier.Copy(comment, req) + comment.ID = req.CommentID + return cs.commentRepo.UpdateComment(ctx, comment) +} + +// GetComment get comment one +func (cs *CommentService) GetComment(ctx context.Context, req *schema.GetCommentReq) (resp *schema.GetCommentResp, err error) { + comment, exist, err := cs.commentCommonRepo.GetComment(ctx, req.ID) + if err != nil { + return + } + if !exist { + return nil, errors.BadRequest(reason.UnknownError) + } + + resp = &schema.GetCommentResp{ + CommentID: comment.ID, + CreatedAt: comment.CreatedAt.Unix(), + UserID: comment.UserID, + ReplyUserID: comment.GetReplyUserID(), + ReplyCommentID: comment.GetReplyCommentID(), + ObjectID: comment.ObjectID, + VoteCount: comment.VoteCount, + OriginalText: comment.OriginalText, + ParsedText: comment.ParsedText, + } + + // get comment user info + if len(resp.UserID) > 0 { + commentUser, exist, err := cs.userRepo.GetByUserID(ctx, resp.UserID) + if err != nil { + return nil, err + } + if exist { + resp.Username = commentUser.Username + resp.UserDisplayName = commentUser.DisplayName + resp.UserAvatar = commentUser.Avatar + } + } + + // get reply user info + if len(resp.ReplyUserID) > 0 { + replyUser, exist, err := cs.userRepo.GetByUserID(ctx, resp.ReplyUserID) + if err != nil { + return nil, err + } + if exist { + resp.ReplyUsername = replyUser.Username + resp.ReplyUserDisplayName = replyUser.DisplayName + } + } + + // check if current user vote this comment + resp.IsVote = cs.checkIsVote(ctx, req.UserID, resp.CommentID) + + resp.MemberActions = permission.GetCommentPermission(req.UserID, resp.UserID) + return resp, nil +} + +// GetCommentWithPage get comment list page +func (cs *CommentService) GetCommentWithPage(ctx context.Context, req *schema.GetCommentWithPageReq) ( + pageModel *pager.PageModel, err error) { + dto := &CommentQuery{ + PageCond: pager.PageCond{Page: req.Page, PageSize: req.PageSize}, + ObjectID: req.ObjectID, + QueryCond: req.QueryCond, + } + commentList, total, err := cs.commentRepo.GetCommentPage(ctx, dto) + if err != nil { + return nil, err + } + resp := make([]*schema.GetCommentResp, 0) + for _, comment := range commentList { + commentResp := &schema.GetCommentResp{ + CommentID: comment.ID, + CreatedAt: comment.CreatedAt.Unix(), + UserID: comment.UserID, + ReplyUserID: comment.GetReplyUserID(), + ReplyCommentID: comment.GetReplyCommentID(), + ObjectID: comment.ObjectID, + VoteCount: comment.VoteCount, + OriginalText: comment.OriginalText, + ParsedText: comment.ParsedText, + } + + // get comment user info + if len(commentResp.UserID) > 0 { + commentUser, exist, err := cs.userRepo.GetByUserID(ctx, commentResp.UserID) + if err != nil { + return nil, err + } + if exist { + commentResp.Username = commentUser.Username + commentResp.UserDisplayName = commentUser.DisplayName + commentResp.UserAvatar = commentUser.Avatar + } + } + + // get reply user info + if len(commentResp.ReplyUserID) > 0 { + replyUser, exist, err := cs.userRepo.GetByUserID(ctx, commentResp.ReplyUserID) + if err != nil { + return nil, err + } + if exist { + commentResp.ReplyUsername = replyUser.Username + commentResp.ReplyUserDisplayName = replyUser.DisplayName + } + } + + // check if current user vote this comment + commentResp.IsVote = cs.checkIsVote(ctx, req.UserID, commentResp.CommentID) + + commentResp.MemberActions = permission.GetCommentPermission(req.UserID, commentResp.UserID) + resp = append(resp, commentResp) + } + return pager.NewPageModel(req.Page, req.PageSize, total, resp), nil +} + +func (cs *CommentService) checkCommentWhetherOwner(ctx context.Context, userID, commentID string) error { + // check comment if user self + comment, exist, err := cs.commentCommonRepo.GetComment(ctx, commentID) + if err != nil { + return err + } + if !exist { + return errors.BadRequest(reason.CommentNotFound) + } + if comment.UserID != userID { + return errors.BadRequest(reason.CommentEditWithoutPermission) + } + return nil +} + +func (cs *CommentService) checkIsVote(ctx context.Context, userID, commentID string) (isVote bool) { + status := cs.voteCommon.GetVoteStatus(ctx, commentID, userID) + return len(status) > 0 +} + +// GetCommentPersonalWithPage get personal comment list page +func (cs *CommentService) GetCommentPersonalWithPage(ctx context.Context, req *schema.GetCommentPersonalWithPageReq) ( + pageModel *pager.PageModel, err error) { + if len(req.Username) > 0 { + userInfo, exist, err := cs.userRepo.GetByUsername(ctx, req.Username) + if err != nil { + return nil, err + } + if !exist { + return nil, errors.BadRequest(reason.UserNotFound) + } + req.UserID = userInfo.ID + } + if len(req.UserID) == 0 { + return nil, errors.BadRequest(reason.UserNotFound) + } + + dto := &CommentQuery{ + PageCond: pager.PageCond{Page: req.Page, PageSize: req.PageSize}, + UserID: req.UserID, + QueryCond: "created_at", + } + commentList, total, err := cs.commentRepo.GetCommentPage(ctx, dto) + if err != nil { + return nil, err + } + resp := make([]*schema.GetCommentPersonalWithPageResp, 0) + for _, comment := range commentList { + commentResp := &schema.GetCommentPersonalWithPageResp{ + CommentID: comment.ID, + CreatedAt: comment.CreatedAt.Unix(), + ObjectID: comment.ObjectID, + Content: comment.ParsedText, // todo trim + } + if len(comment.ObjectID) > 0 { + objInfo, err := cs.objectInfoService.GetInfo(ctx, comment.ObjectID) + if err != nil { + log.Error(err) + } else { + commentResp.ObjectType = objInfo.ObjectType + commentResp.Title = objInfo.Title + commentResp.QuestionID = objInfo.QuestionID + commentResp.AnswerID = objInfo.AnswerID + } + } + resp = append(resp, commentResp) + } + return pager.NewPageModel(req.Page, req.PageSize, total, resp), nil +} + +func (cs *CommentService) notificationQuestionComment(ctx context.Context, questionUserID, commentID, commentUserID string) { + msg := &schema.NotificationMsg{ + ReceiverUserID: questionUserID, + TriggerUserID: commentUserID, + Type: schema.NotificationTypeInbox, + ObjectID: commentID, + } + msg.ObjectType = constant.CommentObjectType + msg.NotificationAction = constant.CommentQuestion + notice_queue.AddNotification(msg) +} + +func (cs *CommentService) notificationAnswerComment(ctx context.Context, answerUserID, commentID, commentUserID string) { + msg := &schema.NotificationMsg{ + ReceiverUserID: answerUserID, + TriggerUserID: commentUserID, + Type: schema.NotificationTypeInbox, + ObjectID: commentID, + } + msg.ObjectType = constant.CommentObjectType + msg.NotificationAction = constant.CommentAnswer + notice_queue.AddNotification(msg) +} + +func (cs *CommentService) notificationCommentReply(ctx context.Context, replyUserID, commentID, commentUserID string) { + msg := &schema.NotificationMsg{ + ReceiverUserID: replyUserID, + TriggerUserID: commentUserID, + Type: schema.NotificationTypeInbox, + ObjectID: commentID, + } + msg.ObjectType = constant.CommentObjectType + msg.NotificationAction = constant.ReplyToYou + notice_queue.AddNotification(msg) +} + +func (cs *CommentService) notificationMention(ctx context.Context, mentionUsernameList []string, commentID, commentUserID string) { + for _, username := range mentionUsernameList { + userInfo, exist, err := cs.userRepo.GetByUsername(ctx, username) + if err != nil { + log.Error(err) + continue + } + if exist { + msg := &schema.NotificationMsg{ + ReceiverUserID: userInfo.ID, + TriggerUserID: commentUserID, + Type: schema.NotificationTypeInbox, + ObjectID: commentID, + } + msg.ObjectType = constant.CommentObjectType + msg.NotificationAction = constant.MentionYou + notice_queue.AddNotification(msg) + } + } +} diff --git a/internal/service/comment_common/comment_service.go b/internal/service/comment_common/comment_service.go new file mode 100644 index 00000000..ea657605 --- /dev/null +++ b/internal/service/comment_common/comment_service.go @@ -0,0 +1,43 @@ +package comment_common + +import ( + "context" + + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/pacman/errors" +) + +// CommentCommonRepo comment repository +type CommentCommonRepo interface { + GetComment(ctx context.Context, commentID string) (comment *entity.Comment, exist bool, err error) +} + +// CommentCommonService user service +type CommentCommonService struct { + commentRepo CommentCommonRepo +} + +// NewCommentCommonService new comment service +func NewCommentCommonService( + commentRepo CommentCommonRepo) *CommentCommonService { + return &CommentCommonService{ + commentRepo: commentRepo, + } +} + +// GetComment get comment one +func (cs *CommentCommonService) GetComment(ctx context.Context, commentID string) (resp *schema.GetCommentResp, err error) { + comment, exist, err := cs.commentRepo.GetComment(ctx, commentID) + if err != nil { + return + } + if !exist { + return nil, errors.BadRequest(reason.UnknownError) + } + + resp = &schema.GetCommentResp{} + resp.SetFromComment(comment) + return resp, nil +} diff --git a/internal/service/config/config_service.go b/internal/service/config/config_service.go new file mode 100644 index 00000000..dd1429fb --- /dev/null +++ b/internal/service/config/config_service.go @@ -0,0 +1,22 @@ +package config + +// ConfigRepo config repository +type ConfigRepo interface { + Get(key string) (interface{}, error) + GetString(key string) (string, error) + GetInt(key string) (int, error) + GetArrayString(key string) ([]string, error) + GetConfigType(key string) (int, error) + GetConfigById(id int, value any) (err error) +} + +// ConfigService user service +type ConfigService struct { + configRepo ConfigRepo +} + +func NewConfigService(configRepo ConfigRepo) *ConfigService { + return &ConfigService{ + configRepo: configRepo, + } +} diff --git a/internal/service/export/email_service.go b/internal/service/export/email_service.go new file mode 100644 index 00000000..c50cf885 --- /dev/null +++ b/internal/service/export/email_service.go @@ -0,0 +1,195 @@ +package export + +import ( + "bytes" + "encoding/json" + "fmt" + "html/template" + "net/smtp" + + "github.com/jordan-wright/email" + "github.com/segmentfault/answer/internal/service/config" + "github.com/segmentfault/pacman/log" + "golang.org/x/net/context" +) + +// EmailService kit service +type EmailService struct { + configRepo config.ConfigRepo + emailRepo EmailRepo +} + +// EmailRepo email repository +type EmailRepo interface { + SetCode(ctx context.Context, code, content string) error + VerifyCode(ctx context.Context, code string) (content string, err error) +} + +// NewEmailService email service +func NewEmailService(configRepo config.ConfigRepo, emailRepo EmailRepo) *EmailService { + return &EmailService{ + configRepo: configRepo, + emailRepo: emailRepo, + } +} + +// EmailConfig email config +type EmailConfig struct { + EmailWebName string `json:"email_web_name"` + EmailFrom string `json:"email_from"` + EmailFromPass string `json:"email_from_pass"` + EmailFromHostname string `json:"email_from_hostname"` + EmailFromSMTP string `json:"email_from_smtp"` + EmailFromName string `json:"email_from_name"` + EmailRegisterTitle string `json:"email_register_title"` + EmailRegisterBody string `json:"email_register_body"` + EmailPassResetTitle string `json:"email_pass_reset_title"` + EmailPassResetBody string `json:"email_pass_reset_body"` + EmailChangeTitle string `json:"email_change_title"` + EmailChangeBody string `json:"email_change_body"` +} + +type RegisterTemplateData struct { + SiteName string + RegisterUrl string +} + +type PassResetTemplateData struct { + SiteName string + PassResetUrl string +} + +type ChangeEmailTemplateData struct { + SiteName string + ChangeEmailUrl string +} + +// Send email send +func (es *EmailService) Send(ctx context.Context, emailAddr, title, body, code, content string) { + emailClient := email.NewEmail() + + ec, err := es.getEmailConfig() + if err != nil { + log.Error(err) + return + } + + emailClient.From = fmt.Sprintf("%s <%s>", ec.EmailFromName, ec.EmailFrom) + emailClient.To = []string{emailAddr} + emailClient.Subject = title + emailClient.HTML = []byte(body) + err = emailClient.Send(ec.EmailFromSMTP, smtp.PlainAuth("", ec.EmailFrom, ec.EmailFromPass, ec.EmailFromHostname)) + if err != nil { + log.Error(err) + } + + err = es.emailRepo.SetCode(ctx, code, content) + if err != nil { + log.Error(err) + } + return +} + +// VerifyUrlExpired email send +func (es *EmailService) VerifyUrlExpired(ctx context.Context, code string) (content string) { + content, err := es.emailRepo.VerifyCode(ctx, code) + if err != nil { + log.Error(err) + } + return content +} + +func (es *EmailService) RegisterTemplate(registerUrl string) (title, body string, err error) { + ec, err := es.getEmailConfig() + if err != nil { + return + } + + templateData := RegisterTemplateData{ec.EmailWebName, registerUrl} + tmpl, err := template.New("register_title").Parse(ec.EmailRegisterTitle) + if err != nil { + return "", "", err + } + titleBuf := &bytes.Buffer{} + bodyBuf := &bytes.Buffer{} + err = tmpl.Execute(titleBuf, templateData) + if err != nil { + return "", "", err + } + + tmpl, err = template.New("register_body").Parse(ec.EmailRegisterBody) + err = tmpl.Execute(bodyBuf, templateData) + if err != nil { + return "", "", err + } + + return titleBuf.String(), bodyBuf.String(), nil +} + +func (es *EmailService) PassResetTemplate(passResetUrl string) (title, body string, err error) { + ec, err := es.getEmailConfig() + if err != nil { + return + } + + templateData := PassResetTemplateData{ec.EmailWebName, passResetUrl} + tmpl, err := template.New("pass_reset_title").Parse(ec.EmailPassResetTitle) + if err != nil { + return "", "", err + } + titleBuf := &bytes.Buffer{} + bodyBuf := &bytes.Buffer{} + err = tmpl.Execute(titleBuf, templateData) + if err != nil { + return "", "", err + } + + tmpl, err = template.New("pass_reset_body").Parse(ec.EmailPassResetBody) + err = tmpl.Execute(bodyBuf, templateData) + if err != nil { + return "", "", err + } + return titleBuf.String(), bodyBuf.String(), nil +} + +func (es *EmailService) ChangeEmailTemplate(changeEmailUrl string) (title, body string, err error) { + ec, err := es.getEmailConfig() + if err != nil { + return + } + + templateData := ChangeEmailTemplateData{ + SiteName: ec.EmailWebName, + ChangeEmailUrl: changeEmailUrl, + } + tmpl, err := template.New("email_change_title").Parse(ec.EmailChangeTitle) + if err != nil { + return "", "", err + } + titleBuf := &bytes.Buffer{} + bodyBuf := &bytes.Buffer{} + err = tmpl.Execute(titleBuf, templateData) + if err != nil { + return "", "", err + } + + tmpl, err = template.New("email_change_body").Parse(ec.EmailChangeBody) + err = tmpl.Execute(bodyBuf, templateData) + if err != nil { + return "", "", err + } + return titleBuf.String(), bodyBuf.String(), nil +} + +func (es *EmailService) getEmailConfig() (ec *EmailConfig, err error) { + emailConf, err := es.configRepo.GetString("email.config") + if err != nil { + return nil, err + } + ec = &EmailConfig{} + err = json.Unmarshal([]byte(emailConf), ec) + if err != nil { + return nil, err + } + return ec, nil +} diff --git a/internal/service/follow/follow_service.go b/internal/service/follow/follow_service.go new file mode 100644 index 00000000..0ac58c3f --- /dev/null +++ b/internal/service/follow/follow_service.go @@ -0,0 +1,103 @@ +package follow + +import ( + "context" + + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/activity_common" + tagcommon "github.com/segmentfault/answer/internal/service/tag_common" +) + +type FollowRepo interface { + Follow(ctx context.Context, objectId, userId string) error + FollowCancel(ctx context.Context, objectId, userId string) error +} + +type FollowService struct { + tagRepo tagcommon.TagRepo + followRepo FollowRepo + followCommonRepo activity_common.FollowRepo +} + +func NewFollowService( + followRepo FollowRepo, + followCommonRepo activity_common.FollowRepo, + tagRepo tagcommon.TagRepo, +) *FollowService { + return &FollowService{ + followRepo: followRepo, + followCommonRepo: followCommonRepo, + tagRepo: tagRepo, + } +} + +// Follow or cancel follow object +func (fs *FollowService) Follow(ctx context.Context, dto *schema.FollowDTO) (resp schema.FollowResp, err error) { + if dto.IsCancel { + err = fs.followRepo.FollowCancel(ctx, dto.ObjectID, dto.UserID) + } else { + err = fs.followRepo.Follow(ctx, dto.ObjectID, dto.UserID) + } + if err != nil { + return resp, err + } + follows, err := fs.followCommonRepo.GetFollowAmount(ctx, dto.ObjectID) + if err != nil { + return resp, err + } + + resp.Follows = follows + resp.IsFollowed = !dto.IsCancel + return resp, nil +} + +// UpdateFollowTags update user follow tags +func (fs *FollowService) UpdateFollowTags(ctx context.Context, req *schema.UpdateFollowTagsReq) (err error) { + objIDs, err := fs.followCommonRepo.GetFollowIDs(ctx, req.UserID, entity.Tag{}.TableName()) + if err != nil { + return + } + oldFollowTagList, err := fs.tagRepo.GetTagListByIDs(ctx, objIDs) + if err != nil { + return err + } + oldTagMapping := make(map[string]bool) + for _, tag := range oldFollowTagList { + oldTagMapping[tag.SlugName] = true + } + + newTagMapping := make(map[string]bool) + for _, tag := range req.SlugNameList { + newTagMapping[tag] = true + } + + // cancel follow + for _, tag := range oldFollowTagList { + if !newTagMapping[tag.SlugName] { + err := fs.followRepo.FollowCancel(ctx, tag.ID, req.UserID) + if err != nil { + return err + } + } + } + + // new follow + for _, tagSlugName := range req.SlugNameList { + if !oldTagMapping[tagSlugName] { + tagInfo, exist, err := fs.tagRepo.GetTagBySlugName(ctx, tagSlugName) + if err != nil { + return err + } + if !exist { + continue + } + err = fs.followRepo.Follow(ctx, tagInfo.ID, req.UserID) + if err != nil { + return err + } + } + } + + return nil +} diff --git a/internal/service/meta/meta_service.go b/internal/service/meta/meta_service.go new file mode 100644 index 00000000..9596762a --- /dev/null +++ b/internal/service/meta/meta_service.go @@ -0,0 +1,75 @@ +package meta + +import ( + "context" + + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/pacman/errors" +) + +// MetaRepo meta repository +type MetaRepo interface { + AddMeta(ctx context.Context, meta *entity.Meta) (err error) + RemoveMeta(ctx context.Context, id int) (err error) + UpdateMeta(ctx context.Context, meta *entity.Meta) (err error) + GetMetaByObjectIdAndKey(ctx context.Context, objectId, key string) (meta *entity.Meta, exist bool, err error) + GetMetaList(ctx context.Context, meta *entity.Meta) (metas []*entity.Meta, err error) +} + +// MetaService user service +type MetaService struct { + metaRepo MetaRepo +} + +func NewMetaService(metaRepo MetaRepo) *MetaService { + return &MetaService{ + metaRepo: metaRepo, + } +} + +// AddMeta add meta +func (ms *MetaService) AddMeta(ctx context.Context, objID, key, value string) (err error) { + meta := &entity.Meta{ + ObjectID: objID, + Key: key, + Value: value, + } + return ms.metaRepo.AddMeta(ctx, meta) +} + +// RemoveMeta delete meta +func (ms *MetaService) RemoveMeta(ctx context.Context, id int) (err error) { + return ms.metaRepo.RemoveMeta(ctx, id) +} + +// UpdateMeta update meta +func (ms *MetaService) UpdateMeta(ctx context.Context, metaID int, key, value string) (err error) { + meta := &entity.Meta{ + ID: metaID, + Key: key, + Value: value, + } + return ms.metaRepo.UpdateMeta(ctx, meta) +} + +// GetMetaByObjectIdAndKey get meta one +func (ms *MetaService) GetMetaByObjectIdAndKey(ctx context.Context, objectID, key string) (meta *entity.Meta, err error) { + meta, exist, err := ms.metaRepo.GetMetaByObjectIdAndKey(ctx, objectID, key) + if err != nil { + return + } + if !exist { + return nil, errors.BadRequest(reason.UnknownError) + } + return meta, nil +} + +// GetMetaList get meta list all +func (ms *MetaService) GetMetaList(ctx context.Context, objID string) (metas []*entity.Meta, err error) { + metas, err = ms.metaRepo.GetMetaList(ctx, &entity.Meta{ObjectID: objID}) + if err != nil { + return nil, err + } + return metas, err +} diff --git a/internal/service/notice_queue/notice_queue.go b/internal/service/notice_queue/notice_queue.go new file mode 100644 index 00000000..8811ec47 --- /dev/null +++ b/internal/service/notice_queue/notice_queue.go @@ -0,0 +1,13 @@ +package notice_queue + +import ( + "github.com/segmentfault/answer/internal/schema" +) + +var ( + NotificationQueue = make(chan *schema.NotificationMsg, 128) +) + +func AddNotification(msg *schema.NotificationMsg) { + NotificationQueue <- msg +} diff --git a/internal/service/notification/notification_read_service.go b/internal/service/notification/notification_read_service.go new file mode 100644 index 00000000..791c8d93 --- /dev/null +++ b/internal/service/notification/notification_read_service.go @@ -0,0 +1,101 @@ +package notification + +import ( + "context" + + "github.com/jinzhu/copier" + "github.com/segmentfault/answer/internal/base/pager" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/pacman/errors" +) + +// NotificationReadRepo notificationRead repository +type NotificationReadRepo interface { + AddNotificationRead(ctx context.Context, notificationRead *entity.NotificationRead) (err error) + RemoveNotificationRead(ctx context.Context, id int) (err error) + UpdateNotificationRead(ctx context.Context, notificationRead *entity.NotificationRead) (err error) + GetNotificationRead(ctx context.Context, id int) (notificationRead *entity.NotificationRead, exist bool, err error) + GetNotificationReadList(ctx context.Context, notificationRead *entity.NotificationRead) (notificationReads []*entity.NotificationRead, err error) + GetNotificationReadPage(ctx context.Context, page, pageSize int, notificationRead *entity.NotificationRead) (notificationReads []*entity.NotificationRead, total int64, err error) +} + +// NotificationReadService user service +type NotificationReadService struct { + notificationReadRepo NotificationReadRepo +} + +func NewNotificationReadService(notificationReadRepo NotificationReadRepo) *NotificationReadService { + return &NotificationReadService{ + notificationReadRepo: notificationReadRepo, + } +} + +// AddNotificationRead add notification read record +func (ns *NotificationReadService) AddNotificationRead(ctx context.Context, req *schema.AddNotificationReadReq) (err error) { + notificationRead := &entity.NotificationRead{} + _ = copier.Copy(notificationRead, req) + return ns.notificationReadRepo.AddNotificationRead(ctx, notificationRead) +} + +// RemoveNotificationRead delete notification read record +func (ns *NotificationReadService) RemoveNotificationRead(ctx context.Context, id int) (err error) { + return ns.notificationReadRepo.RemoveNotificationRead(ctx, id) +} + +// UpdateNotificationRead update notification read record +func (ns *NotificationReadService) UpdateNotificationRead(ctx context.Context, req *schema.UpdateNotificationReadReq) (err error) { + notificationRead := &entity.NotificationRead{} + _ = copier.Copy(notificationRead, req) + return ns.notificationReadRepo.UpdateNotificationRead(ctx, notificationRead) +} + +// GetNotificationRead get notification read record one +func (ns *NotificationReadService) GetNotificationRead(ctx context.Context, id int) (resp *schema.GetNotificationReadResp, err error) { + notificationRead, exist, err := ns.notificationReadRepo.GetNotificationRead(ctx, id) + if err != nil { + return + } + if !exist { + return nil, errors.BadRequest(reason.UnknownError) + } + + resp = &schema.GetNotificationReadResp{} + _ = copier.Copy(resp, notificationRead) + return resp, nil +} + +// GetNotificationReadList get notification read record list all +func (ns *NotificationReadService) GetNotificationReadList(ctx context.Context, req *schema.GetNotificationReadListReq) (resp *[]schema.GetNotificationReadResp, err error) { + notificationRead := &entity.NotificationRead{} + _ = copier.Copy(notificationRead, req) + + notificationReads, err := ns.notificationReadRepo.GetNotificationReadList(ctx, notificationRead) + if err != nil { + return + } + + resp = &[]schema.GetNotificationReadResp{} + _ = copier.Copy(resp, notificationReads) + return +} + +// GetNotificationReadWithPage get notification read record list page +func (ns *NotificationReadService) GetNotificationReadWithPage(ctx context.Context, req *schema.GetNotificationReadWithPageReq) (pageModel *pager.PageModel, err error) { + notificationRead := &entity.NotificationRead{} + _ = copier.Copy(notificationRead, req) + + page := req.Page + pageSize := req.PageSize + + notificationReads, total, err := ns.notificationReadRepo.GetNotificationReadPage(ctx, page, pageSize, notificationRead) + if err != nil { + return + } + + resp := &[]schema.GetNotificationReadResp{} + _ = copier.Copy(resp, notificationReads) + + return pager.NewPageModel(page, pageSize, total, resp), nil +} diff --git a/internal/service/notification/notification_service.go b/internal/service/notification/notification_service.go new file mode 100644 index 00000000..4b6e896b --- /dev/null +++ b/internal/service/notification/notification_service.go @@ -0,0 +1,120 @@ +package notification + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/schema" + notficationcommon "github.com/segmentfault/answer/internal/service/notification_common" + "github.com/segmentfault/pacman/log" +) + +// NotificationService user service +type NotificationService struct { + data *data.Data + notificationRepo notficationcommon.NotificationRepo + notificationCommon *notficationcommon.NotificationCommon +} + +func NewNotificationService( + data *data.Data, + notificationRepo notficationcommon.NotificationRepo, + notificationCommon *notficationcommon.NotificationCommon, +) *NotificationService { + return &NotificationService{ + data: data, + notificationRepo: notificationRepo, + notificationCommon: notificationCommon, + } +} + +func (ns *NotificationService) GetRedDot(ctx context.Context, userID string) (*schema.RedDot, error) { + redBot := &schema.RedDot{} + inboxKey := fmt.Sprintf("answer_RedDot_%d_%s", schema.NotificationTypeInbox, userID) + achievementKey := fmt.Sprintf("answer_RedDot_%d_%s", schema.NotificationTypeAchievement, userID) + inboxValue, err := ns.data.Cache.GetInt64(ctx, inboxKey) + if err != nil { + redBot.Inbox = 0 + } else { + redBot.Inbox = inboxValue + } + achievementValue, err := ns.data.Cache.GetInt64(ctx, achievementKey) + if err != nil { + redBot.Achievement = 0 + } else { + redBot.Achievement = achievementValue + } + return redBot, nil +} + +func (ns *NotificationService) ClearRedDot(ctx context.Context, userID string, botTypeStr string) (*schema.RedDot, error) { + botType, ok := schema.NotificationType[botTypeStr] + if ok { + key := fmt.Sprintf("answer_RedDot_%d_%s", botType, userID) + err := ns.data.Cache.Del(ctx, key) + if err != nil { + log.Error("ClearRedDot del cache error", err.Error()) + } + } + return ns.GetRedDot(ctx, userID) +} + +func (ns *NotificationService) ClearUnRead(ctx context.Context, userID string, botTypeStr string) error { + botType, ok := schema.NotificationType[botTypeStr] + if ok { + err := ns.notificationRepo.ClearUnRead(ctx, userID, botType) + if err != nil { + return err + } + } + return nil +} + +func (ns *NotificationService) ClearIDUnRead(ctx context.Context, userID string, id string) error { + notificationInfo, exist, err := ns.notificationRepo.GetById(ctx, id) + if err != nil { + log.Error("notificationRepo.GetById error", err.Error()) + return nil + } + if !exist { + return nil + } + if notificationInfo.UserID == userID && notificationInfo.IsRead == schema.NotificationNotRead { + err := ns.notificationRepo.ClearIDUnRead(ctx, userID, id) + if err != nil { + return err + } + } + + return nil +} + +func (ns *NotificationService) GetList(ctx context.Context, search *schema.NotificationSearch) ([]*schema.NotificationContent, int64, error) { + list := make([]*schema.NotificationContent, 0) + searchType, ok := schema.NotificationType[search.TypeStr] + if !ok { + return list, 0, nil + } + search.Type = searchType + dblist, count, err := ns.notificationRepo.SearchList(ctx, search) + if err != nil { + return list, count, err + } + for _, dbitem := range dblist { + item := &schema.NotificationContent{} + err := json.Unmarshal([]byte(dbitem.Content), item) + if err != nil { + log.Error("NotificationContent Unmarshal Error", err.Error()) + continue + } + item.ID = dbitem.ID + item.UpdateTime = dbitem.UpdatedAt.Unix() + if dbitem.IsRead == schema.NotificationRead { + item.IsRead = true + } + list = append(list, item) + } + return list, count, nil +} diff --git a/internal/service/notification_common/notification.go b/internal/service/notification_common/notification.go new file mode 100644 index 00000000..7f386f78 --- /dev/null +++ b/internal/service/notification_common/notification.go @@ -0,0 +1,215 @@ +package notificationcommon + +import ( + "context" + "fmt" + "time" + + "github.com/goccy/go-json" + "github.com/jinzhu/copier" + "github.com/segmentfault/answer/internal/base/constant" + "github.com/segmentfault/answer/internal/base/data" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/activity_common" + "github.com/segmentfault/answer/internal/service/notice_queue" + "github.com/segmentfault/answer/internal/service/object_info" + usercommon "github.com/segmentfault/answer/internal/service/user_common" + "github.com/segmentfault/pacman/errors" + "github.com/segmentfault/pacman/log" +) + +type NotificationRepo interface { + AddNotification(ctx context.Context, notification *entity.Notification) (err error) + SearchList(ctx context.Context, search *schema.NotificationSearch) ([]*entity.Notification, int64, error) + ClearUnRead(ctx context.Context, userID string, notificationType int) (err error) + ClearIDUnRead(ctx context.Context, userID string, id string) (err error) + GetByUserIdObjectIdTypeId(ctx context.Context, userID, objectID string, notificationType int) (*entity.Notification, bool, error) + UpdateNotificationContent(ctx context.Context, notification *entity.Notification) (err error) + GetById(ctx context.Context, id string) (*entity.Notification, bool, error) +} + +type NotificationCommon struct { + data *data.Data + notificationRepo NotificationRepo + activityRepo activity_common.ActivityRepo + followRepo activity_common.FollowRepo + userCommon *usercommon.UserCommon + objectInfoService *object_info.ObjService +} + +func NewNotificationCommon( + data *data.Data, + notificationRepo NotificationRepo, + userCommon *usercommon.UserCommon, + activityRepo activity_common.ActivityRepo, + followRepo activity_common.FollowRepo, + objectInfoService *object_info.ObjService, +) *NotificationCommon { + notification := &NotificationCommon{ + data: data, + notificationRepo: notificationRepo, + activityRepo: activityRepo, + followRepo: followRepo, + userCommon: userCommon, + objectInfoService: objectInfoService, + } + notification.HandleNotification() + return notification +} + +func (ns *NotificationCommon) HandleNotification() { + go func() { + for msg := range notice_queue.NotificationQueue { + log.Debugf("received notification %+v", msg) + err := ns.AddNotification(context.TODO(), msg) + if err != nil { + log.Error(err) + } + } + }() +} + +// AddNotification +// need set +// UserID +// Type 1 inbox 2 achievement +// [inbox] Activity +// [achievement] Rank +// ObjectInfo.Title +// ObjectInfo.ObjectID +// ObjectInfo.ObjectType +func (ns *NotificationCommon) AddNotification(ctx context.Context, msg *schema.NotificationMsg) error { + req := &schema.NotificationContent{ + TriggerUserID: msg.TriggerUserID, + ReceiverUserID: msg.ReceiverUserID, + ObjectInfo: schema.ObjectInfo{ + Title: msg.Title, + ObjectID: msg.ObjectID, + ObjectType: msg.ObjectType, + }, + NotificationAction: msg.NotificationAction, + Type: msg.Type, + } + var questionID string // just for notify all followers + objInfo, err := ns.objectInfoService.GetInfo(ctx, req.ObjectInfo.ObjectID) + if err != nil { + log.Error(err) + } else { + req.ObjectInfo.Title = objInfo.Title + questionID = objInfo.QuestionID + objectMap := make(map[string]string) + objectMap["question"] = objInfo.QuestionID + objectMap["answer"] = objInfo.AnswerID + objectMap["comment"] = objInfo.CommentID + req.ObjectInfo.ObjectMap = objectMap + } + + if msg.Type == schema.NotificationTypeAchievement { + notificationInfo, exist, err := ns.notificationRepo.GetByUserIdObjectIdTypeId(ctx, req.ReceiverUserID, req.ObjectInfo.ObjectID, req.Type) + if err != nil { + return errors.InternalServer(reason.UnknownError).WithError(err).WithStack() + } + rank, err := ns.activityRepo.GetUserIDObjectIDActivitySum(ctx, req.ReceiverUserID, req.ObjectInfo.ObjectID) + if err != nil { + return errors.InternalServer(reason.UnknownError).WithError(err).WithStack() + } + req.Rank = rank + if exist { + //modify notification + updateContent := &schema.NotificationContent{} + err := json.Unmarshal([]byte(notificationInfo.Content), updateContent) + if err != nil { + return errors.InternalServer(reason.UnknownError).WithError(err).WithStack() + } + updateContent.Rank = rank + content, err := json.Marshal(updateContent) + if err != nil { + return errors.InternalServer(reason.UnknownError).WithError(err).WithStack() + } + notificationInfo.Content = string(content) + err = ns.notificationRepo.UpdateNotificationContent(ctx, notificationInfo) + if err != nil { + return errors.InternalServer(reason.UnknownError).WithError(err).WithStack() + } + return nil + } + } + + info := &entity.Notification{} + now := time.Now() + info.UserID = req.ReceiverUserID + info.Type = req.Type + info.IsRead = schema.NotificationNotRead + info.Status = schema.NotificationStatusNormal + info.CreatedAt = now + info.UpdatedAt = now + info.ObjectID = req.ObjectInfo.ObjectID + + userBasicInfo, exist, err := ns.userCommon.GetUserBasicInfoByID(ctx, req.TriggerUserID) + if err != nil { + return errors.InternalServer(reason.UnknownError).WithError(err).WithStack() + } + if !exist { + return errors.InternalServer(reason.UserNotFound).WithError(err).WithStack() + } + req.UserInfo = userBasicInfo + content, err := json.Marshal(req) + if err != nil { + return errors.InternalServer(reason.UnknownError).WithError(err).WithStack() + } + info.Content = string(content) + err = ns.notificationRepo.AddNotification(ctx, info) + if err != nil { + return errors.InternalServer(reason.UnknownError).WithError(err).WithStack() + } + err = ns.addRedDot(ctx, info.UserID, info.Type) + if err != nil { + log.Error("addRedDot Error", err.Error()) + } + + go ns.SendNotificationToAllFollower(context.Background(), msg, questionID) + return nil +} + +func (ns *NotificationCommon) addRedDot(ctx context.Context, userID string, botType int) error { + key := fmt.Sprintf("answer_RedDot_%d_%s", botType, userID) + err := ns.data.Cache.SetInt64(ctx, key, 1, 30*24*time.Hour) //Expiration time is one month. + if err != nil { + return errors.InternalServer(reason.UnknownError).WithError(err).WithStack() + } + return nil +} + +// SendNotificationToAllFollower send notification to all followers +func (ns *NotificationCommon) SendNotificationToAllFollower(ctx context.Context, msg *schema.NotificationMsg, + questionID string) { + if msg.NoNeedPushAllFollow { + return + } + if msg.NotificationAction != constant.UpdateQuestion && + msg.NotificationAction != constant.AnswerTheQuestion && + msg.NotificationAction != constant.UpdateAnswer && + msg.NotificationAction != constant.AdoptAnswer { + return + } + condObjectID := msg.ObjectID + if len(questionID) > 0 { + condObjectID = questionID + } + userIDs, err := ns.followRepo.GetFollowUserIDs(ctx, condObjectID) + if err != nil { + log.Error(err) + return + } + log.Infof("send notification to all followers: %s %d", condObjectID, len(userIDs)) + for _, userID := range userIDs { + t := &schema.NotificationMsg{} + _ = copier.Copy(t, msg) + t.ReceiverUserID = userID + t.TriggerUserID = msg.TriggerUserID + t.NoNeedPushAllFollow = true + notice_queue.AddNotification(t) + } +} diff --git a/internal/service/object_info/object_info.go b/internal/service/object_info/object_info.go new file mode 100644 index 00000000..1daa7a14 --- /dev/null +++ b/internal/service/object_info/object_info.go @@ -0,0 +1,135 @@ +package object_info + +import ( + "context" + + "github.com/segmentfault/answer/internal/base/constant" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/schema" + answercommon "github.com/segmentfault/answer/internal/service/answer_common" + "github.com/segmentfault/answer/internal/service/comment_common" + questioncommon "github.com/segmentfault/answer/internal/service/question_common" + tagcommon "github.com/segmentfault/answer/internal/service/tag_common" + "github.com/segmentfault/answer/pkg/obj" + "github.com/segmentfault/pacman/errors" +) + +// ObjService user service +type ObjService struct { + answerRepo answercommon.AnswerRepo + questionRepo questioncommon.QuestionRepo + commentRepo comment_common.CommentCommonRepo + tagRepo tagcommon.TagRepo +} + +// NewObjService new object service +func NewObjService( + answerRepo answercommon.AnswerRepo, + questionRepo questioncommon.QuestionRepo, + commentRepo comment_common.CommentCommonRepo, + tagRepo tagcommon.TagRepo) *ObjService { + return &ObjService{ + answerRepo: answerRepo, + questionRepo: questionRepo, + commentRepo: commentRepo, + tagRepo: tagRepo, + } +} + +// GetInfo get object simple information +func (os *ObjService) GetInfo(ctx context.Context, objectID string) (objInfo *schema.SimpleObjectInfo, err error) { + objectType, err := obj.GetObjectTypeStrByObjectID(objectID) + if err != nil { + return nil, err + } + switch objectType { + case constant.QuestionObjectType: + questionInfo, exist, err := os.questionRepo.GetQuestion(ctx, objectID) + if err != nil { + return nil, err + } + if !exist { + break + } + objInfo = &schema.SimpleObjectInfo{ + ObjectID: questionInfo.ID, + ObjectCreator: questionInfo.UserID, + QuestionID: questionInfo.ID, + ObjectType: objectType, + Title: questionInfo.Title, + Content: questionInfo.ParsedText, // todo trim + } + case constant.AnswerObjectType: + answerInfo, exist, err := os.answerRepo.GetAnswer(ctx, objectID) + if err != nil { + return nil, err + } + if !exist { + break + } + questionInfo, exist, err := os.questionRepo.GetQuestion(ctx, answerInfo.QuestionID) + if err != nil { + return nil, err + } + objInfo = &schema.SimpleObjectInfo{ + ObjectID: answerInfo.ID, + ObjectCreator: answerInfo.UserID, + QuestionID: answerInfo.QuestionID, + AnswerID: answerInfo.ID, + ObjectType: objectType, + Title: questionInfo.Title, // this should be question title + Content: answerInfo.ParsedText, // todo trim + } + case constant.CommentObjectType: + commentInfo, exist, err := os.commentRepo.GetComment(ctx, objectID) + if err != nil { + return nil, err + } + if !exist { + break + } + objInfo = &schema.SimpleObjectInfo{ + ObjectID: commentInfo.ID, + ObjectCreator: commentInfo.UserID, + ObjectType: objectType, + Content: commentInfo.ParsedText, // todo trim + CommentID: commentInfo.ID, + } + if len(commentInfo.QuestionID) > 0 { + questionInfo, exist, err := os.questionRepo.GetQuestion(ctx, commentInfo.QuestionID) + if err != nil { + return nil, err + } + if exist { + objInfo.QuestionID = questionInfo.ID + objInfo.Title = questionInfo.Title + } + answerInfo, exist, err := os.answerRepo.GetAnswer(ctx, commentInfo.ObjectID) + if err != nil { + return nil, err + } + if exist { + objInfo.AnswerID = answerInfo.ID + } + } + case constant.TagObjectType: + tagInfo, exist, err := os.tagRepo.GetTagByID(ctx, objectID) + if err != nil { + return nil, err + } + if !exist { + break + } + objInfo = &schema.SimpleObjectInfo{ + ObjectID: tagInfo.ID, + TagID: tagInfo.ID, + ObjectType: objectType, + Title: tagInfo.ParsedText, + Content: tagInfo.ParsedText, // todo trim + } + } + if objInfo == nil { + err = errors.BadRequest(reason.ObjectNotFound) + } + return objInfo, err +} diff --git a/internal/service/permission/comment_permission.go b/internal/service/permission/comment_permission.go new file mode 100644 index 00000000..784703dd --- /dev/null +++ b/internal/service/permission/comment_permission.go @@ -0,0 +1,112 @@ +package permission + +import "github.com/segmentfault/answer/internal/schema" + +// TODO: There is currently no permission management +func GetCommentPermission(userID string, commentCreatorUserID string) ( + actions []*schema.PermissionMemberAction) { + actions = make([]*schema.PermissionMemberAction, 0) + if len(userID) > 0 { + actions = append(actions, &schema.PermissionMemberAction{ + Action: "report", + Name: "Flag", + Type: "reason", + }) + } + if userID != commentCreatorUserID { + return actions + } + actions = append(actions, []*schema.PermissionMemberAction{ + { + Action: "edit", + Name: "Edit", + Type: "edit", + }, + { + Action: "delete", + Name: "Delete", + Type: "reason", + }, + }...) + return actions +} + +func GetTagPermission(userID string, tagCreatorUserID string) ( + actions []*schema.PermissionMemberAction) { + if userID != tagCreatorUserID { + return []*schema.PermissionMemberAction{} + } + return []*schema.PermissionMemberAction{ + { + Action: "edit", + Name: "Edit", + Type: "edit", + }, + { + Action: "delete", + Name: "Delete", + Type: "reason", + }, + } +} + +func GetAnswerPermission(userID string, answerAuthID string) ( + actions []*schema.PermissionMemberAction) { + actions = make([]*schema.PermissionMemberAction, 0) + if len(userID) > 0 { + actions = append(actions, &schema.PermissionMemberAction{ + Action: "report", + Name: "Flag", + Type: "reason", + }) + } + if userID != answerAuthID { + return actions + } + actions = append(actions, []*schema.PermissionMemberAction{ + { + Action: "edit", + Name: "Edit", + Type: "edit", + }, + { + Action: "delete", + Name: "Delete", + Type: "confirm", + }, + }...) + return actions +} + +func GetQuestionPermission(userID string, questionAuthID string) ( + actions []*schema.PermissionMemberAction) { + actions = make([]*schema.PermissionMemberAction, 0) + if len(userID) > 0 { + actions = append(actions, &schema.PermissionMemberAction{ + Action: "report", + Name: "Flag", + Type: "reason", + }) + } + if userID != questionAuthID { + return actions + } + actions = append(actions, []*schema.PermissionMemberAction{ + { + Action: "edit", + Name: "Edit", + Type: "edit", + }, + { + Action: "close", + Name: "Close", + Type: "confirm", + }, + { + Action: "delete", + Name: "Delete", + Type: "confirm", + }, + }...) + return actions +} diff --git a/internal/service/provider.go b/internal/service/provider.go new file mode 100644 index 00000000..6280ffce --- /dev/null +++ b/internal/service/provider.go @@ -0,0 +1,68 @@ +package service + +import ( + "github.com/google/wire" + "github.com/segmentfault/answer/internal/service/action" + "github.com/segmentfault/answer/internal/service/activity" + answercommon "github.com/segmentfault/answer/internal/service/answer_common" + "github.com/segmentfault/answer/internal/service/auth" + collectioncommon "github.com/segmentfault/answer/internal/service/collection_common" + "github.com/segmentfault/answer/internal/service/comment" + "github.com/segmentfault/answer/internal/service/comment_common" + "github.com/segmentfault/answer/internal/service/export" + "github.com/segmentfault/answer/internal/service/follow" + "github.com/segmentfault/answer/internal/service/meta" + "github.com/segmentfault/answer/internal/service/notification" + notficationcommon "github.com/segmentfault/answer/internal/service/notification_common" + "github.com/segmentfault/answer/internal/service/object_info" + questioncommon "github.com/segmentfault/answer/internal/service/question_common" + "github.com/segmentfault/answer/internal/service/rank" + "github.com/segmentfault/answer/internal/service/reason" + "github.com/segmentfault/answer/internal/service/report" + "github.com/segmentfault/answer/internal/service/report_backyard" + "github.com/segmentfault/answer/internal/service/report_handle_backyard" + "github.com/segmentfault/answer/internal/service/revision_common" + "github.com/segmentfault/answer/internal/service/tag" + tagcommon "github.com/segmentfault/answer/internal/service/tag_common" + "github.com/segmentfault/answer/internal/service/uploader" + "github.com/segmentfault/answer/internal/service/user_backyard" + usercommon "github.com/segmentfault/answer/internal/service/user_common" +) + +// ProviderSetService is providers. +var ProviderSetService = wire.NewSet( + comment.NewCommentService, + comment_common.NewCommentCommonService, + report.NewReportService, + NewVoteService, + tag.NewTagService, + follow.NewFollowService, + NewCollectionGroupService, + NewCollectionService, + action.NewCaptchaService, + auth.NewAuthService, + NewUserService, + NewQuestionService, + NewAnswerService, + export.NewEmailService, + tagcommon.NewTagCommonService, + usercommon.NewUserCommon, + questioncommon.NewQuestionCommon, + answercommon.NewAnswerCommon, + uploader.NewUploaderService, + collectioncommon.NewCollectionCommon, + revision_common.NewRevisionService, + NewRevisionService, + rank.NewRankService, + NewSearchService, + meta.NewMetaService, + object_info.NewObjService, + report_handle_backyard.NewReportHandle, + report_backyard.NewReportBackyardService, + user_backyard.NewUserBackyardService, + reason.NewReasonService, + NewSiteInfoService, + notficationcommon.NewNotificationCommon, + notification.NewNotificationService, + activity.NewAnswerActivityService, +) diff --git a/internal/service/question_common/question.go b/internal/service/question_common/question.go new file mode 100644 index 00000000..07ce1998 --- /dev/null +++ b/internal/service/question_common/question.go @@ -0,0 +1,374 @@ +package questioncommon + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/segmentfault/answer/internal/service/activity_common" + "github.com/segmentfault/answer/internal/service/config" + "github.com/segmentfault/answer/internal/service/meta" + + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + answercommon "github.com/segmentfault/answer/internal/service/answer_common" + collectioncommon "github.com/segmentfault/answer/internal/service/collection_common" + tagcommon "github.com/segmentfault/answer/internal/service/tag_common" + usercommon "github.com/segmentfault/answer/internal/service/user_common" + "github.com/segmentfault/pacman/log" +) + +// QuestionRepo question repository +type QuestionRepo interface { + AddQuestion(ctx context.Context, question *entity.Question) (err error) + RemoveQuestion(ctx context.Context, id string) (err error) + UpdateQuestion(ctx context.Context, question *entity.Question, Cols []string) (err error) + GetQuestion(ctx context.Context, id string) (question *entity.Question, exist bool, err error) + GetQuestionList(ctx context.Context, question *entity.Question) (questions []*entity.Question, err error) + GetQuestionPage(ctx context.Context, page, pageSize int, question *entity.Question) (questions []*entity.Question, total int64, err error) + SearchList(ctx context.Context, search *schema.QuestionSearch) ([]*entity.QuestionTag, int64, error) + UpdateQuestionStatus(ctx context.Context, question *entity.Question) (err error) + SearchByTitleLike(ctx context.Context, title string) (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) + UpdateAccepted(ctx context.Context, question *entity.Question) (err error) + UpdateLastAnswer(ctx context.Context, question *entity.Question) (err error) + FindByID(ctx context.Context, id []string) (questionList []*entity.Question, err error) + CmsSearchList(ctx context.Context, search *schema.CmsQuestionSearch) ([]*entity.Question, int64, error) +} + +// QuestionCommon user service +type QuestionCommon struct { + questionRepo QuestionRepo + answerRepo answercommon.AnswerRepo + voteRepo activity_common.VoteRepo + followCommon activity_common.FollowRepo + tagCommon *tagcommon.TagCommonService + userCommon *usercommon.UserCommon + collectionCommon *collectioncommon.CollectionCommon + AnswerCommon *answercommon.AnswerCommon + metaService *meta.MetaService + configRepo config.ConfigRepo +} + +func NewQuestionCommon(questionRepo QuestionRepo, + answerRepo answercommon.AnswerRepo, + voteRepo activity_common.VoteRepo, + followCommon activity_common.FollowRepo, + tagCommon *tagcommon.TagCommonService, + userCommon *usercommon.UserCommon, + collectionCommon *collectioncommon.CollectionCommon, + answerCommon *answercommon.AnswerCommon, + metaService *meta.MetaService, + configRepo config.ConfigRepo, + +) *QuestionCommon { + return &QuestionCommon{ + questionRepo: questionRepo, + answerRepo: answerRepo, + voteRepo: voteRepo, + followCommon: followCommon, + tagCommon: tagCommon, + userCommon: userCommon, + collectionCommon: collectionCommon, + AnswerCommon: answerCommon, + metaService: metaService, + configRepo: configRepo, + } +} + +func (qs *QuestionCommon) UpdataPv(ctx context.Context, questionId string) error { + return qs.questionRepo.UpdatePvCount(ctx, questionId) +} +func (qs *QuestionCommon) UpdateAnswerCount(ctx context.Context, questionId string, num int) error { + return qs.questionRepo.UpdateAnswerCount(ctx, questionId, num) +} +func (qs *QuestionCommon) UpdateCollectionCount(ctx context.Context, questionId string, num int) error { + return qs.questionRepo.UpdateCollectionCount(ctx, questionId, num) +} + +func (qs *QuestionCommon) UpdateAccepted(ctx context.Context, questionId, AnswerId string) error { + question := &entity.Question{} + question.ID = questionId + question.AcceptedAnswerID = AnswerId + return qs.questionRepo.UpdateAccepted(ctx, question) +} + +func (qs *QuestionCommon) UpdateLastAnswer(ctx context.Context, questionId, AnswerId string) error { + question := &entity.Question{} + question.ID = questionId + question.LastAnswerID = AnswerId + return qs.questionRepo.UpdateLastAnswer(ctx, question) +} + +func (qs *QuestionCommon) UpdataPostTime(ctx context.Context, questionId string) error { + questioninfo := &entity.Question{} + now := time.Now() + questioninfo.ID = questionId + questioninfo.PostUpdateTime = now + return qs.questionRepo.UpdateQuestion(ctx, questioninfo, []string{"post_update_time"}) +} + +func (qs *QuestionCommon) FindInfoByID(ctx context.Context, questionIds []string, loginUserID string) (map[string]*schema.QuestionInfo, error) { + list := make(map[string]*schema.QuestionInfo) + listAddTag := make([]*entity.QuestionTag, 0) + questionList, err := qs.questionRepo.FindByID(ctx, questionIds) + if err != nil { + return list, err + } + for _, item := range questionList { + itemAddTag := &entity.QuestionTag{} + itemAddTag.Question = *item + listAddTag = append(listAddTag, itemAddTag) + } + QuestionInfo, err := qs.ListFormat(ctx, listAddTag, loginUserID) + if err != nil { + return list, err + } + for _, item := range QuestionInfo { + list[item.ID] = item + } + return list, nil +} + +func (qs *QuestionCommon) Info(ctx context.Context, questionId string, loginUserID string) (showinfo *schema.QuestionInfo, err error) { + dbinfo, has, err := qs.questionRepo.GetQuestion(ctx, questionId) + if err != nil { + return showinfo, err + } + if !has { + return showinfo, fmt.Errorf("the question could not be found") + } + showinfo = qs.ShowFormat(ctx, dbinfo) + + if showinfo.Status == 2 { + metainfo, err := qs.metaService.GetMetaByObjectIdAndKey(ctx, dbinfo.ID, entity.QuestionCloseReasonKey) + if err != nil { + log.Error(err) + } else { + //metainfo.Value + closemsg := &schema.CloseQuestionMeta{} + err := json.Unmarshal([]byte(metainfo.Value), closemsg) + if err != nil { + log.Error("json.Unmarshal CloseQuestionMeta error", err.Error()) + } else { + closeinfo := &schema.GetReportTypeResp{} + err = qs.configRepo.GetConfigById(closemsg.CloseType, closeinfo) + if err != nil { + log.Error("json.Unmarshal QuestionCloseJson error", err.Error()) + } else { + operation := &schema.Operation{} + operation.Operation_Type = closeinfo.Name + operation.Operation_Description = closeinfo.Description + operation.Operation_Msg = closemsg.CloseMsg + showinfo.Operation = operation + } + + } + + } + } + + tagmap, err := qs.tagCommon.GetObjectTag(ctx, questionId) + if err != nil { + return showinfo, err + } + showinfo.Tags = tagmap + + userinfo, has, err := qs.userCommon.GetUserBasicInfoByID(ctx, dbinfo.UserID) + if err != nil { + return showinfo, err + } + if has { + showinfo.UserInfo = userinfo + showinfo.UpdateUserInfo = userinfo + showinfo.LastAnsweredUserInfo = userinfo + } + + if loginUserID == "" { + return showinfo, nil + } + + showinfo.VoteStatus = qs.voteRepo.GetVoteStatus(ctx, questionId, loginUserID) + + // // check is followed + isFollowed, _ := qs.followCommon.IsFollowed(loginUserID, questionId) + showinfo.IsFollowed = isFollowed + + has, err = qs.AnswerCommon.SearchAnswered(ctx, loginUserID, dbinfo.ID) + if err != nil { + log.Error("AnswerFunc.SearchAnswered", err) + } + showinfo.Answered = has + + //login user Collected information + + CollectedMap, err := qs.collectionCommon.SearchObjectCollected(ctx, loginUserID, []string{dbinfo.ID}) + if err != nil { + log.Error("CollectionFunc.SearchObjectCollected", err) + } + _, ok := CollectedMap[dbinfo.ID] + if ok { + showinfo.Collected = true + } + + return showinfo, nil +} + +func (qs *QuestionCommon) ListFormat(ctx context.Context, questionList []*entity.QuestionTag, loginUserID string) ([]*schema.QuestionInfo, error) { + list := make([]*schema.QuestionInfo, 0) + objectIds := make([]string, 0) + userIds := make([]string, 0) + + for _, questionInfo := range questionList { + item := qs.ShowListFormat(ctx, questionInfo) + list = append(list, item) + objectIds = append(objectIds, item.ID) + userIds = append(userIds, questionInfo.UserID) + } + tagsMap, err := qs.tagCommon.BatchGetObjectTag(ctx, objectIds) + if err != nil { + return list, err + } + + userInfoMap, err := qs.userCommon.BatchUserBasicInfoByID(ctx, userIds) + if err != nil { + return list, err + } + + 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] + item.UpdateUserInfo = userInfoMap[item.UserId] + item.LastAnsweredUserInfo = userInfoMap[item.UserId] + } + } + + 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) + } + + for _, item := range list { + _, ok := CollectedMap[item.ID] + if ok { + item.Collected = true + } + } + return list, nil +} + +// RemoveQuestion delete question +func (qs *QuestionCommon) RemoveQuestion(ctx context.Context, req *schema.RemoveQuestionReq) (err error) { + questionInfo, has, err := qs.questionRepo.GetQuestion(ctx, req.ID) + if err != nil { + return err + } + if !has { + return nil + } + questionInfo.Status = entity.QuestionStatusDeleted + err = qs.questionRepo.UpdateQuestionStatus(ctx, questionInfo) + if err != nil { + return err + } + + //user add question count + err = qs.userCommon.UpdateQuestionCount(ctx, questionInfo.UserID, -1) + if err != nil { + log.Error("user UpdateQuestionCount error", err.Error()) + } + + // todo rank remove + + return nil +} + +func (qs *QuestionCommon) CloseQuestion(ctx context.Context, req *schema.CloseQuestionReq) error { + questionInfo, has, err := qs.questionRepo.GetQuestion(ctx, req.ID) + if err != nil { + return err + } + if !has { + return nil + } + questionInfo.Status = entity.QuestionStatusclosed + err = qs.questionRepo.UpdateQuestionStatus(ctx, questionInfo) + if err != nil { + return err + } + + closeMeta, _ := json.Marshal(schema.CloseQuestionMeta{ + CloseType: req.CloseType, + CloseMsg: req.CloseMsg, + }) + err = qs.metaService.AddMeta(ctx, req.ID, entity.QuestionCloseReasonKey, string(closeMeta)) + if err != nil { + return err + } + return nil +} + +// RemoveAnswer delete answer +func (as *QuestionCommon) RemoveAnswer(ctx context.Context, id string) (err error) { + answerinfo, has, err := as.answerRepo.GetByID(ctx, id) + if err != nil { + return err + } + if !has { + return nil + } + + //user add question count + + err = as.UpdateAnswerCount(ctx, answerinfo.QuestionID, -1) + if err != nil { + log.Error("UpdateAnswerCount error", err.Error()) + } + + err = as.userCommon.UpdateAnswerCount(ctx, answerinfo.UserID, -1) + if err != nil { + log.Error("user UpdateAnswerCount error", err.Error()) + } + + return as.answerRepo.RemoveAnswer(ctx, id) +} + +func (qs *QuestionCommon) ShowListFormat(ctx context.Context, data *entity.QuestionTag) *schema.QuestionInfo { + return qs.ShowFormat(ctx, &data.Question) +} + +func (qs *QuestionCommon) ShowFormat(ctx context.Context, data *entity.Question) *schema.QuestionInfo { + info := schema.QuestionInfo{} + info.ID = data.ID + info.Title = data.Title + info.Content = data.OriginalText + info.Html = data.ParsedText + info.ViewCount = data.ViewCount + info.UniqueViewCount = data.UniqueViewCount + info.VoteCount = data.VoteCount + info.AnswerCount = data.AnswerCount + info.CollectionCount = data.CollectionCount + info.FollowCount = data.FollowCount + info.AcceptedAnswerId = data.AcceptedAnswerID + info.LastAnswerId = data.LastAnswerID + info.CreateTime = data.CreatedAt.Unix() + info.UpdateTime = data.UpdatedAt.Unix() + info.PostUpdateTime = data.PostUpdateTime.Unix() + info.QuestionUpdateTime = data.UpdatedAt.Unix() + info.Status = data.Status + info.UserId = data.UserID + info.Tags = make([]*schema.TagResp, 0) + return &info +} diff --git a/internal/service/question_service.go b/internal/service/question_service.go new file mode 100644 index 00000000..70cdd27a --- /dev/null +++ b/internal/service/question_service.go @@ -0,0 +1,628 @@ +package service + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/jinzhu/copier" + "github.com/segmentfault/answer/internal/base/constant" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/base/translator" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/activity" + collectioncommon "github.com/segmentfault/answer/internal/service/collection_common" + "github.com/segmentfault/answer/internal/service/meta" + "github.com/segmentfault/answer/internal/service/notice_queue" + "github.com/segmentfault/answer/internal/service/permission" + questioncommon "github.com/segmentfault/answer/internal/service/question_common" + "github.com/segmentfault/answer/internal/service/revision_common" + tagcommon "github.com/segmentfault/answer/internal/service/tag_common" + usercommon "github.com/segmentfault/answer/internal/service/user_common" + "github.com/segmentfault/pacman/errors" + "github.com/segmentfault/pacman/i18n" + "github.com/segmentfault/pacman/log" + "golang.org/x/net/context" +) + +// QuestionRepo question repository + +// QuestionService user service +type QuestionService struct { + questionRepo questioncommon.QuestionRepo + tagCommon *tagcommon.TagCommonService + questioncommon *questioncommon.QuestionCommon + userCommon *usercommon.UserCommon + revisionService *revision_common.RevisionService + metaService *meta.MetaService + collectionCommon *collectioncommon.CollectionCommon + answerActivityService *activity.AnswerActivityService +} + +func NewQuestionService( + questionRepo questioncommon.QuestionRepo, + tagCommon *tagcommon.TagCommonService, + questioncommon *questioncommon.QuestionCommon, + userCommon *usercommon.UserCommon, + revisionService *revision_common.RevisionService, + metaService *meta.MetaService, + collectionCommon *collectioncommon.CollectionCommon, + answerActivityService *activity.AnswerActivityService, +) *QuestionService { + return &QuestionService{ + questionRepo: questionRepo, + tagCommon: tagCommon, + questioncommon: questioncommon, + userCommon: userCommon, + revisionService: revisionService, + metaService: metaService, + collectionCommon: collectionCommon, + answerActivityService: answerActivityService, + } +} + +func (qs *QuestionService) CloseQuestion(ctx context.Context, req *schema.CloseQuestionReq) error { + questionInfo, has, err := qs.questionRepo.GetQuestion(ctx, req.ID) + if err != nil { + return err + } + if !has { + return nil + } + questionInfo.Status = entity.QuestionStatusclosed + err = qs.questionRepo.UpdateQuestionStatus(ctx, questionInfo) + if err != nil { + return err + } + + closeMeta, _ := json.Marshal(schema.CloseQuestionMeta{ + CloseType: req.CloseType, + CloseMsg: req.CloseMsg, + }) + err = qs.metaService.AddMeta(ctx, req.ID, entity.QuestionCloseReasonKey, string(closeMeta)) + if err != nil { + return err + } + return nil +} + +// CloseMsgList list close question condition +func (qs *QuestionService) CloseMsgList(ctx context.Context, lang i18n.Language) ( + resp []*schema.GetCloseTypeResp, err error) { + resp = make([]*schema.GetCloseTypeResp, 0) + err = json.Unmarshal([]byte(constant.QuestionCloseJson), &resp) + if err != nil { + return nil, errors.InternalServer(reason.UnknownError).WithError(err).WithStack() + } + for _, t := range resp { + t.Name = translator.GlobalTrans.Tr(lang, t.Name) + t.Description = translator.GlobalTrans.Tr(lang, t.Description) + } + return resp, err +} + +// AddQuestion add question +func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.QuestionAdd) (questionInfo *schema.QuestionInfo, err error) { + questionInfo = &schema.QuestionInfo{} + question := &entity.Question{} + now := time.Now() + question.UserID = req.UserID + question.Title = req.Title + question.OriginalText = req.Content + question.ParsedText = req.Html + question.AcceptedAnswerID = "0" + question.LastAnswerID = "0" + question.PostUpdateTime = now + question.Status = entity.QuestionStatusAvailable + question.RevisionID = "0" + question.CreatedAt = now + question.UpdatedAt = now + err = qs.questionRepo.AddQuestion(ctx, question) + if err != nil { + return + } + objectTagData := schema.TagChange{} + objectTagData.ObjectId = question.ID + objectTagData.Tags = req.Tags + objectTagData.UserID = req.UserID + err = qs.ChangeTag(ctx, &objectTagData) + if err != nil { + return + } + + revisionDTO := &schema.AddRevisionDTO{ + UserID: question.UserID, + ObjectID: question.ID, + Title: "", + } + InfoJson, _ := json.Marshal(question) + revisionDTO.Content = string(InfoJson) + err = qs.revisionService.AddRevision(ctx, revisionDTO, true) + if err != nil { + return + } + + //user add question count + err = qs.userCommon.UpdateQuestionCount(ctx, question.UserID, 1) + if err != nil { + log.Error("user IncreaseQuestionCount error", err.Error()) + } + + questionInfo, err = qs.GetQuestion(ctx, question.ID, question.UserID) + return +} + +// RemoveQuestion delete question +func (qs *QuestionService) RemoveQuestion(ctx context.Context, req *schema.RemoveQuestionReq) (err error) { + questionInfo, has, err := qs.questionRepo.GetQuestion(ctx, req.ID) + if err != nil { + return err + } + if !has { + return nil + } + questionInfo.Status = entity.QuestionStatusDeleted + err = qs.questionRepo.UpdateQuestionStatus(ctx, questionInfo) + if err != nil { + return err + } + + //user add question count + err = qs.userCommon.UpdateQuestionCount(ctx, questionInfo.UserID, -1) + if err != nil { + log.Error("user IncreaseQuestionCount error", err.Error()) + } + + err = qs.answerActivityService.DeleteQuestion(ctx, questionInfo.ID, questionInfo.CreatedAt, questionInfo.VoteCount) + if err != nil { + log.Errorf("user DeleteQuestion rank rollback error %s", err.Error()) + } + + return nil +} + +// UpdateQuestion update question +func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.QuestionUpdate) (questionInfo *schema.QuestionInfo, err error) { + questionInfo = &schema.QuestionInfo{} + now := time.Now() + question := &entity.Question{} + question.UserID = req.UserID + question.Title = req.Title + question.OriginalText = req.Content + question.ParsedText = req.Html + question.ID = req.ID + question.UpdatedAt = now + dbinfo, has, err := qs.questionRepo.GetQuestion(ctx, question.ID) + if err != nil { + return + } + if !has { + return + } + if dbinfo.UserID != req.UserID { + return + } + err = qs.questionRepo.UpdateQuestion(ctx, question, []string{"title", "original_text", "parsed_text", "updated_at"}) + if err != nil { + return + } + objectTagData := schema.TagChange{} + objectTagData.ObjectId = question.ID + objectTagData.Tags = req.Tags + objectTagData.UserID = req.UserID + err = qs.ChangeTag(ctx, &objectTagData) + if err != nil { + return + } + + revisionDTO := &schema.AddRevisionDTO{ + UserID: question.UserID, + ObjectID: question.ID, + Title: "", + Log: req.EditSummary, + } + InfoJson, _ := json.Marshal(question) + revisionDTO.Content = string(InfoJson) + err = qs.revisionService.AddRevision(ctx, revisionDTO, true) + if err != nil { + return + } + + questionInfo, err = qs.GetQuestion(ctx, question.ID, question.UserID) + return +} + +// GetQuestion get question one +func (qs *QuestionService) GetQuestion(ctx context.Context, id, loginUserID string) (resp *schema.QuestionInfo, err error) { + question, err := qs.questioncommon.Info(ctx, id, loginUserID) + if err != nil { + return + } + err = qs.questioncommon.UpdataPv(ctx, id) + if err != nil { + log.Error("UpdataPv", err) + } + question.MemberActions = permission.GetQuestionPermission(loginUserID, question.UserId) + return question, nil +} + +func (qs *QuestionService) ChangeTag(ctx context.Context, objectTagData *schema.TagChange) error { + return qs.tagCommon.ObjectChangeTag(ctx, objectTagData) +} + +func (qs *QuestionService) SearchUserList(ctx context.Context, userName, order string, page, pageSize int, loginUserID string) ([]*schema.UserQuestionInfo, int64, error) { + userlist := make([]*schema.UserQuestionInfo, 0) + + userinfo, Exist, err := qs.userCommon.GetUserBasicInfoByUserName(ctx, userName) + if err != nil { + return userlist, 0, err + } + if !Exist { + return userlist, 0, nil + } + search := &schema.QuestionSearch{} + search.Order = order + search.Page = page + search.PageSize = pageSize + search.UserID = userinfo.UserId + questionlist, count, err := qs.SearchList(ctx, search, loginUserID) + if err != nil { + return userlist, 0, err + } + for _, item := range questionlist { + info := &schema.UserQuestionInfo{} + _ = copier.Copy(info, item) + userlist = append(userlist, info) + } + return userlist, count, nil +} + +func (qs *QuestionService) SearchUserAnswerList(ctx context.Context, userName, order string, page, pageSize int, loginUserID string) ([]*schema.UserAnswerInfo, int64, error) { + answerlist := make([]*schema.AnswerInfo, 0) + userAnswerlist := make([]*schema.UserAnswerInfo, 0) + userinfo, Exist, err := qs.userCommon.GetUserBasicInfoByUserName(ctx, userName) + if err != nil { + return userAnswerlist, 0, err + } + if !Exist { + return userAnswerlist, 0, nil + } + answersearch := &entity.AnswerSearch{} + answersearch.UserID = userinfo.UserId + answersearch.PageSize = pageSize + answersearch.Page = page + if order == "newest" { + answersearch.Order = entity.Answer_Search_OrderBy_Time + } else { + answersearch.Order = entity.Answer_Search_OrderBy_Default + } + questionIDs := make([]string, 0) + answerList, count, err := qs.questioncommon.AnswerCommon.Search(ctx, answersearch) + if err != nil { + return userAnswerlist, count, err + } + for _, item := range answerList { + answerinfo := qs.questioncommon.AnswerCommon.ShowFormat(ctx, item) + answerlist = append(answerlist, answerinfo) + questionIDs = append(questionIDs, item.QuestionID) + } + questionMaps, err := qs.questioncommon.FindInfoByID(ctx, questionIDs, loginUserID) + if err != nil { + return userAnswerlist, count, err + } + for _, item := range answerlist { + _, ok := questionMaps[item.QuestionId] + if ok { + item.QuestionInfo = questionMaps[item.QuestionId] + } + } + for _, item := range answerlist { + info := &schema.UserAnswerInfo{} + _ = copier.Copy(info, item) + info.AnswerID = item.ID + info.QuestionID = item.QuestionId + userAnswerlist = append(userAnswerlist, info) + } + return userAnswerlist, count, nil +} + +func (qs *QuestionService) SearchUserCollectionList(ctx context.Context, page, pageSize int, loginUserID string) ([]*schema.QuestionInfo, int64, error) { + list := make([]*schema.QuestionInfo, 0) + userinfo, Exist, err := qs.userCommon.GetUserBasicInfoByID(ctx, loginUserID) + if err != nil { + return list, 0, err + } + if !Exist { + return list, 0, nil + } + collectionSearch := &entity.CollectionSearch{} + collectionSearch.UserID = userinfo.UserId + collectionSearch.Page = page + collectionSearch.PageSize = pageSize + collectionlist, count, err := qs.collectionCommon.SearchList(ctx, collectionSearch) + if err != nil { + return list, 0, err + } + questionIDs := make([]string, 0) + for _, item := range collectionlist { + questionIDs = append(questionIDs, item.ObjectID) + } + + questionMaps, err := qs.questioncommon.FindInfoByID(ctx, questionIDs, loginUserID) + if err != nil { + return list, count, err + } + for _, id := range questionIDs { + _, ok := questionMaps[id] + if ok { + questionMaps[id].LastAnsweredUserInfo = nil + questionMaps[id].UpdateUserInfo = nil + questionMaps[id].Content = "" + questionMaps[id].Html = "" + list = append(list, questionMaps[id]) + } + } + + return list, count, nil +} + +func (qs *QuestionService) SearchUserTopList(ctx context.Context, userName string, loginUserID string) ([]*schema.UserQuestionInfo, []*schema.UserAnswerInfo, error) { + answerlist := make([]*schema.AnswerInfo, 0) + + userAnswerlist := make([]*schema.UserAnswerInfo, 0) + userQuestionlist := make([]*schema.UserQuestionInfo, 0) + + userinfo, Exist, err := qs.userCommon.GetUserBasicInfoByUserName(ctx, userName) + if err != nil { + return userQuestionlist, userAnswerlist, err + } + if !Exist { + return userQuestionlist, userAnswerlist, nil + } + search := &schema.QuestionSearch{} + search.Order = "score" + search.Page = 0 + search.PageSize = 5 + search.UserID = userinfo.UserId + questionlist, _, err := qs.SearchList(ctx, search, loginUserID) + if err != nil { + return userQuestionlist, userAnswerlist, err + } + answersearch := &entity.AnswerSearch{} + answersearch.UserID = userinfo.UserId + answersearch.PageSize = 5 + answersearch.Order = entity.Answer_Search_OrderBy_Vote + questionIDs := make([]string, 0) + answerList, _, err := qs.questioncommon.AnswerCommon.Search(ctx, answersearch) + if err != nil { + return userQuestionlist, userAnswerlist, err + } + for _, item := range answerList { + answerinfo := qs.questioncommon.AnswerCommon.ShowFormat(ctx, item) + answerlist = append(answerlist, answerinfo) + questionIDs = append(questionIDs, item.QuestionID) + } + questionMaps, err := qs.questioncommon.FindInfoByID(ctx, questionIDs, loginUserID) + if err != nil { + return userQuestionlist, userAnswerlist, err + } + for _, item := range answerlist { + _, ok := questionMaps[item.QuestionId] + if ok { + item.QuestionInfo = questionMaps[item.QuestionId] + } + } + + for _, item := range questionlist { + info := &schema.UserQuestionInfo{} + _ = copier.Copy(info, item) + userQuestionlist = append(userQuestionlist, info) + } + + for _, item := range answerlist { + info := &schema.UserAnswerInfo{} + _ = copier.Copy(info, item) + info.AnswerID = item.ID + info.QuestionID = item.QuestionId + userAnswerlist = append(userAnswerlist, info) + } + + 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 + } + for _, question := range dblist { + item := &schema.QuestionBaseInfo{} + item.ID = question.ID + item.Title = question.Title + item.ViewCount = question.ViewCount + item.AnswerCount = question.AnswerCount + item.CollectionCount = question.CollectionCount + item.FollowCount = question.FollowCount + if question.AcceptedAnswerID != "0" { + item.AcceptedAnswer = true + } + list = append(list, item) + } + + return list, nil +} + +// SimilarQuestion +func (qs *QuestionService) SimilarQuestion(ctx context.Context, questionID string, loginUserID string) ([]*schema.QuestionInfo, int64, error) { + list := make([]*schema.QuestionInfo, 0) + questionInfo, err := qs.GetQuestion(ctx, questionID, loginUserID) + if err != nil { + return list, 0, err + } + tagNames := make([]string, 0, len(questionInfo.Tags)) + for _, tag := range questionInfo.Tags { + tagNames = append(tagNames, tag.SlugName) + } + search := &schema.QuestionSearch{} + search.Order = "frequent" + search.Page = 0 + search.PageSize = 6 + search.Tags = tagNames + return qs.SearchList(ctx, search, loginUserID) +} + +// SearchList +func (qs *QuestionService) SearchList(ctx context.Context, req *schema.QuestionSearch, loginUserID string) ([]*schema.QuestionInfo, int64, error) { + if len(req.Tags) > 0 { + taginfo, err := qs.tagCommon.GetTagListByNames(ctx, req.Tags) + if err != nil { + log.Error("tagCommon.GetTagListByNames error", err) + } + for _, tag := range taginfo { + req.TagIDs = append(req.TagIDs, tag.ID) + } + } + list := make([]*schema.QuestionInfo, 0) + if req.UserName != "" { + userinfo, exist, err := qs.userCommon.GetUserBasicInfoByUserName(ctx, req.UserName) + if err != nil { + return list, 0, err + } + if !exist { + return list, 0, err + } + req.UserID = userinfo.UserId + } + questionList, count, err := qs.questionRepo.SearchList(ctx, req) + if err != nil { + return list, count, err + } + list, err = qs.questioncommon.ListFormat(ctx, questionList, loginUserID) + if err != nil { + return list, count, err + } + return list, count, nil +} + +func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, questionID string, setStatusStr string) error { + setStatus, ok := entity.CmsQuestionSearchStatus[setStatusStr] + if !ok { + return fmt.Errorf("question status does not exist") + } + questionInfo, exist, err := qs.questionRepo.GetQuestion(ctx, questionID) + if err != nil { + return err + } + if !exist { + return errors.BadRequest(reason.QuestionNotFound) + } + questionInfo.Status = setStatus + err = qs.questionRepo.UpdateQuestionStatus(ctx, questionInfo) + if err != nil { + return err + } + + if setStatus == entity.QuestionStatusDeleted { + err = qs.answerActivityService.DeleteQuestion(ctx, questionInfo.ID, questionInfo.CreatedAt, questionInfo.VoteCount) + if err != nil { + log.Errorf("admin delete question then rank rollback error %s", err.Error()) + } + } + msg := &schema.NotificationMsg{} + msg.ObjectID = questionInfo.ID + msg.Type = schema.NotificationTypeInbox + msg.ReceiverUserID = questionInfo.UserID + msg.TriggerUserID = questionInfo.UserID + msg.ObjectType = constant.QuestionObjectType + msg.NotificationAction = constant.YourQuestionWasDeleted + notice_queue.AddNotification(msg) + return nil +} + +func (qs *QuestionService) CmsSearchList(ctx context.Context, search *schema.CmsQuestionSearch, loginUserID string) ([]*schema.AdminQuestionInfo, int64, error) { + list := make([]*schema.AdminQuestionInfo, 0) + + status, ok := entity.CmsQuestionSearchStatus[search.StatusStr] + if ok { + search.Status = status + } + + if search.Status == 0 { + search.Status = 1 + } + dblist, count, err := qs.questionRepo.CmsSearchList(ctx, search) + if err != nil { + return list, count, err + } + userIds := make([]string, 0) + for _, dbitem := range dblist { + item := &schema.AdminQuestionInfo{} + _ = copier.Copy(item, dbitem) + item.CreateTime = dbitem.CreatedAt.Unix() + item.UpdateTime = dbitem.PostUpdateTime.Unix() + item.EditTime = dbitem.UpdatedAt.Unix() + list = append(list, item) + userIds = append(userIds, dbitem.UserID) + } + userInfoMap, err := qs.userCommon.BatchUserBasicInfoByID(ctx, userIds) + if err != nil { + return list, count, err + } + for _, item := range list { + _, ok = userInfoMap[item.UserID] + if ok { + item.UserInfo = userInfoMap[item.UserID] + } + } + + return list, count, nil +} + +// CmsSearchList +func (qs *QuestionService) CmsSearchAnswerList(ctx context.Context, search *entity.CmsAnswerSearch, loginUserID string) ([]*schema.AdminAnswerInfo, int64, error) { + answerlist := make([]*schema.AdminAnswerInfo, 0) + + status, ok := entity.CmsAnswerSearchStatus[search.StatusStr] + if ok { + search.Status = status + } + + if search.Status == 0 { + search.Status = 1 + } + dblist, count, err := qs.questioncommon.AnswerCommon.CmsSearchList(ctx, search) + if err != nil { + return answerlist, count, err + } + questionIDs := make([]string, 0) + userIds := make([]string, 0) + for _, item := range dblist { + answerinfo := qs.questioncommon.AnswerCommon.AdminShowFormat(ctx, item) + answerlist = append(answerlist, answerinfo) + questionIDs = append(questionIDs, item.QuestionID) + userIds = append(userIds, item.UserID) + } + userInfoMap, err := qs.userCommon.BatchUserBasicInfoByID(ctx, userIds) + if err != nil { + return answerlist, count, err + } + + questionMaps, err := qs.questioncommon.FindInfoByID(ctx, questionIDs, loginUserID) + if err != nil { + return answerlist, count, err + } + for _, item := range answerlist { + _, ok := questionMaps[item.QuestionId] + if ok { + item.QuestionInfo.Title = questionMaps[item.QuestionId].Title + } + _, ok = userInfoMap[item.UserId] + if ok { + item.UserInfo = userInfoMap[item.UserId] + } + } + return answerlist, count, nil +} diff --git a/internal/service/rank/rank_service.go b/internal/service/rank/rank_service.go new file mode 100644 index 00000000..8eca2a9b --- /dev/null +++ b/internal/service/rank/rank_service.go @@ -0,0 +1,144 @@ +package rank + +import ( + "context" + + "github.com/segmentfault/answer/internal/base/pager" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/activity_type" + "github.com/segmentfault/answer/internal/service/config" + "github.com/segmentfault/answer/internal/service/object_info" + usercommon "github.com/segmentfault/answer/internal/service/user_common" + "github.com/segmentfault/pacman/errors" + "github.com/segmentfault/pacman/log" + "xorm.io/xorm" +) + +const ( + QuestionAddRank = "rank.question.add" + QuestionEditRank = "rank.question.edit" + QuestionDeleteRank = "rank.question.delete" + QuestionVoteUpRank = "rank.question.vote_up" + QuestionVoteDownRank = "rank.question.vote_down" + AnswerAddRank = "rank.answer.add" + AnswerEditRank = "rank.answer.edit" + AnswerDeleteRank = "rank.answer.delete" + AnswerAcceptRank = "rank.answer.accept" + AnswerVoteUpRank = "rank.answer.vote_up" + AnswerVoteDownRank = "rank.answer.vote_down" + CommentAddRank = "rank.comment.add" + CommentEditRank = "rank.comment.edit" + CommentDeleteRank = "rank.comment.delete" + ReportAddRank = "rank.report.add" + TagAddRank = "rank.tag.add" + TagEditRank = "rank.tag.edit" + TagDeleteRank = "rank.tag.delete" + TagSynonymRank = "rank.tag.synonym" + LinkUrlLimitRank = "rank.link.url_limit" + VoteDetailRank = "rank.vote.detail" +) + +type UserRankRepo interface { + TriggerUserRank(ctx context.Context, session *xorm.Session, userId string, rank int, activityType int) (isReachStandard bool, err error) + UserRankPage(ctx context.Context, userId string, page, pageSize int) (rankPage []*entity.Activity, total int64, err error) +} + +// RankService rank service +type RankService struct { + userRepo usercommon.UserRepo + configRepo config.ConfigRepo + userRankRepo UserRankRepo + objectInfoService *object_info.ObjService +} + +// NewRankService new rank service +func NewRankService( + userRepo usercommon.UserRepo, + userRankRepo UserRankRepo, + objectInfoService *object_info.ObjService, + configRepo config.ConfigRepo) *RankService { + return &RankService{ + userRepo: userRepo, + configRepo: configRepo, + userRankRepo: userRankRepo, + objectInfoService: objectInfoService, + } +} + +// CheckRankPermission check whether the user reputation meets the permission +func (rs *RankService) CheckRankPermission(ctx context.Context, userID string, action string) (can bool, err error) { + if len(userID) == 0 { + return false, nil + } + + // get the rank of the current user + userInfo, exist, err := rs.userRepo.GetByUserID(ctx, userID) + if err != nil { + return false, err + } + if !exist { + return false, nil + } + currentUserRank := userInfo.Rank + + // get the amount of rank required for the current operation + requireRank, err := rs.configRepo.GetInt(action) + if err != nil { + return false, err + } + + if currentUserRank < requireRank { + log.Debugf("user %s want to do action %s, but rank %d < %d", + userInfo.DisplayName, action, currentUserRank, requireRank) + return false, nil + } + return true, nil +} + +// GetRankPersonalWithPage get personal comment list page +func (rs *RankService) GetRankPersonalWithPage(ctx context.Context, req *schema.GetRankPersonalWithPageReq) ( + pageModel *pager.PageModel, err error) { + if len(req.Username) > 0 { + userInfo, exist, err := rs.userRepo.GetByUsername(ctx, req.Username) + if err != nil { + return nil, err + } + if !exist { + return nil, errors.BadRequest(reason.UserNotFound) + } + req.UserID = userInfo.ID + } + if len(req.UserID) == 0 { + return nil, errors.BadRequest(reason.UserNotFound) + } + + userRankPage, total, err := rs.userRankRepo.UserRankPage(ctx, req.UserID, req.Page, req.PageSize) + if err != nil { + return nil, err + } + resp := make([]*schema.GetRankPersonalWithPageResp, 0) + for _, userRankInfo := range userRankPage { + commentResp := &schema.GetRankPersonalWithPageResp{ + CreatedAt: userRankInfo.CreatedAt.Unix(), + ObjectID: userRankInfo.ObjectID, + Reputation: userRankInfo.Rank, + } + if len(userRankInfo.ObjectID) > 0 { + objInfo, err := rs.objectInfoService.GetInfo(ctx, userRankInfo.ObjectID) + if err != nil { + log.Error(err) + } else { + commentResp.RankType = activity_type.Format(userRankInfo.ActivityType) + commentResp.ObjectType = objInfo.ObjectType + commentResp.Title = objInfo.Title + commentResp.Content = objInfo.Content + commentResp.QuestionID = objInfo.QuestionID + commentResp.AnswerID = objInfo.AnswerID + } + } + resp = append(resp, commentResp) + } + return pager.NewPageModel(req.Page, req.PageSize, total, resp), nil +} diff --git a/internal/service/reason/reason_service.go b/internal/service/reason/reason_service.go new file mode 100644 index 00000000..6956e023 --- /dev/null +++ b/internal/service/reason/reason_service.go @@ -0,0 +1,21 @@ +package reason + +import ( + "context" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/reason_common" +) + +type ReasonService struct { + reasonRepo reason_common.ReasonRepo +} + +func NewReasonService(reasonRepo reason_common.ReasonRepo) *ReasonService { + return &ReasonService{ + reasonRepo: reasonRepo, + } +} + +func (rs ReasonService) GetReasons(ctx context.Context, req schema.ReasonReq) (resp []schema.ReasonItem, err error) { + return rs.reasonRepo.ListReasons(ctx, req) +} diff --git a/internal/service/reason_common/reason.go b/internal/service/reason_common/reason.go new file mode 100644 index 00000000..8b5dd41b --- /dev/null +++ b/internal/service/reason_common/reason.go @@ -0,0 +1,10 @@ +package reason_common + +import ( + "context" + "github.com/segmentfault/answer/internal/schema" +) + +type ReasonRepo interface { + ListReasons(ctx context.Context, req schema.ReasonReq) (resp []schema.ReasonItem, err error) +} diff --git a/internal/service/report/report_service.go b/internal/service/report/report_service.go new file mode 100644 index 00000000..5e90c08d --- /dev/null +++ b/internal/service/report/report_service.go @@ -0,0 +1,79 @@ +package report + +import ( + "encoding/json" + + "github.com/segmentfault/answer/internal/base/constant" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/base/translator" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/object_info" + "github.com/segmentfault/answer/internal/service/report_common" + "github.com/segmentfault/answer/pkg/obj" + "github.com/segmentfault/pacman/errors" + "github.com/segmentfault/pacman/i18n" + "golang.org/x/net/context" +) + +// ReportService user service +type ReportService struct { + reportRepo report_common.ReportRepo + objectInfoService *object_info.ObjService +} + +// NewReportService new report service +func NewReportService(reportRepo report_common.ReportRepo, + objectInfoService *object_info.ObjService) *ReportService { + return &ReportService{ + reportRepo: reportRepo, + objectInfoService: objectInfoService, + } +} + +// AddReport add report +func (rs *ReportService) AddReport(ctx context.Context, req *schema.AddReportReq) (err error) { + objectTypeNumber, err := obj.GetObjectTypeNumberByObjectID(req.ObjectID) + if err != nil { + return err + } + + // TODO this reported user id should be get by revision + objInfo, err := rs.objectInfoService.GetInfo(ctx, req.ObjectID) + if err != nil { + return err + } + + report := &entity.Report{ + UserID: req.UserID, + ReportedUserID: objInfo.ObjectCreator, + ObjectID: req.ObjectID, + ObjectType: objectTypeNumber, + ReportType: req.ReportType, + Content: req.Content, + Status: entity.ReportStatusPending, + } + return rs.reportRepo.AddReport(ctx, report) +} + +// GetReportTypeList get report list all +func (rs *ReportService) GetReportTypeList(ctx context.Context, lang i18n.Language, req *schema.GetReportListReq) ( + resp []*schema.GetReportTypeResp, err error) { + resp = make([]*schema.GetReportTypeResp, 0) + switch req.Source { + case constant.QuestionObjectType: + err = json.Unmarshal([]byte(constant.QuestionReportJson), &resp) + case constant.AnswerObjectType: + err = json.Unmarshal([]byte(constant.AnswerReportJson), &resp) + case constant.CommentObjectType: + err = json.Unmarshal([]byte(constant.CommentReportJson), &resp) + } + if err != nil { + err = errors.BadRequest(reason.UnknownError) + } + for _, t := range resp { + t.Name = translator.GlobalTrans.Tr(lang, t.Name) + t.Description = translator.GlobalTrans.Tr(lang, t.Description) + } + return resp, err +} diff --git a/internal/service/report_backyard/report_backyard.go b/internal/service/report_backyard/report_backyard.go new file mode 100644 index 00000000..aa8dac44 --- /dev/null +++ b/internal/service/report_backyard/report_backyard.go @@ -0,0 +1,224 @@ +package report_backyard + +import ( + "context" + "github.com/segmentfault/answer/internal/service/config" + "strings" + + "github.com/jinzhu/copier" + "github.com/segmentfault/answer/internal/base/pager" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/repo/common" + "github.com/segmentfault/answer/internal/schema" + answercommon "github.com/segmentfault/answer/internal/service/answer_common" + "github.com/segmentfault/answer/internal/service/comment_common" + questioncommon "github.com/segmentfault/answer/internal/service/question_common" + "github.com/segmentfault/answer/internal/service/report_common" + "github.com/segmentfault/answer/internal/service/report_handle_backyard" + usercommon "github.com/segmentfault/answer/internal/service/user_common" + "github.com/segmentfault/pacman/errors" +) + +// ReportBackyardService user service +type ReportBackyardService struct { + reportRepo report_common.ReportRepo + commonUser *usercommon.UserCommon + commonRepo *common.CommonRepo + answerRepo answercommon.AnswerRepo + questionRepo questioncommon.QuestionRepo + commentCommonRepo comment_common.CommentCommonRepo + reportHandle *report_handle_backyard.ReportHandle + configRepo config.ConfigRepo +} + +// NewReportBackyardService new report service +func NewReportBackyardService( + reportRepo report_common.ReportRepo, + commonUser *usercommon.UserCommon, + commonRepo *common.CommonRepo, + answerRepo answercommon.AnswerRepo, + questionRepo questioncommon.QuestionRepo, + commentCommonRepo comment_common.CommentCommonRepo, + reportHandle *report_handle_backyard.ReportHandle, + configRepo config.ConfigRepo) *ReportBackyardService { + return &ReportBackyardService{ + reportRepo: reportRepo, + commonUser: commonUser, + commonRepo: commonRepo, + answerRepo: answerRepo, + questionRepo: questionRepo, + commentCommonRepo: commentCommonRepo, + reportHandle: reportHandle, + configRepo: configRepo, + } +} + +// ListReportPage list report pages +func (rs *ReportBackyardService) ListReportPage(ctx context.Context, dto schema.GetReportListPageDTO) (pageModel *pager.PageModel, err error) { + var ( + resp []*schema.GetReportListPageResp + flags []entity.Report + total int64 + + flagedUserIds, + userIds []string + + flagedUsers, + users map[string]*schema.UserBasicInfo + ) + + pageModel = &pager.PageModel{} + + flags, total, err = rs.reportRepo.GetReportListPage(ctx, dto) + if err != nil { + return + } + + _ = copier.Copy(&resp, flags) + for _, r := range resp { + flagedUserIds = append(flagedUserIds, r.ReportedUserID) + userIds = append(userIds, r.UserID) + r.Format() + } + + // flaged users + flagedUsers, err = rs.commonUser.BatchUserBasicInfoByID(ctx, flagedUserIds) + + // flag users + users, err = rs.commonUser.BatchUserBasicInfoByID(ctx, userIds) + for _, r := range resp { + r.ReportedUser = flagedUsers[r.ReportedUserID] + r.ReportUser = users[r.UserID] + } + + rs.parseObject(ctx, &resp) + return pager.NewPageModel(dto.Page, dto.PageSize, total, resp), nil +} + +// HandleReported handle the reported object +func (rs *ReportBackyardService) HandleReported(ctx context.Context, req schema.ReportHandleReq) (err error) { + var ( + reported = entity.Report{} + handleData = entity.Report{ + FlagedContent: req.FlagedContent, + FlagedType: req.FlagedType, + Status: entity.ReportStatusCompleted, + } + exist = false + ) + + reported, exist, err = rs.reportRepo.GetByID(ctx, req.ID) + if err != nil { + err = errors.BadRequest(reason.ReportHandleFailed).WithError(err).WithStack() + return + } + if !exist { + err = errors.NotFound(reason.ReportNotFound) + return + } + + // check if handle or not + if reported.Status != entity.ReportStatusPending { + return + } + + if err = rs.reportHandle.HandleObject(ctx, reported, req); err != nil { + return + } + + err = rs.reportRepo.UpdateByID(ctx, reported.ID, handleData) + return +} + +func (rs *ReportBackyardService) parseObject(ctx context.Context, resp *[]*schema.GetReportListPageResp) { + var ( + res = *resp + ) + + for i, r := range res { + var ( + objIds map[string]string + exists, + ok bool + err error + questionId, + answerId, + commentId string + question *entity.Question + answer *entity.Answer + cmt *entity.Comment + ) + + objIds, err = rs.commonRepo.GetObjectIDMap(r.ObjectID) + if err != nil { + continue + } + + questionId, ok = objIds["question"] + if !ok { + continue + } + + question, exists, err = rs.questionRepo.GetQuestion(ctx, questionId) + if err != nil || !exists { + continue + } + + answerId, ok = objIds["answer"] + if ok { + answer, _, err = rs.answerRepo.GetAnswer(ctx, answerId) + } + + commentId, ok = objIds["comment"] + if ok { + cmt, _, err = rs.commentCommonRepo.GetComment(ctx, commentId) + } + + switch r.OType { + case "question": + r.QuestionID = questionId + r.Title = question.Title + r.Excerpt = rs.cutOutTagParsedText(question.OriginalText) + + case "answer": + r.QuestionID = questionId + r.AnswerID = answerId + r.Title = question.Title + r.Excerpt = rs.cutOutTagParsedText(answer.OriginalText) + + case "comment": + r.QuestionID = questionId + r.AnswerID = answerId + r.CommentID = commentId + r.Title = question.Title + r.Excerpt = rs.cutOutTagParsedText(cmt.OriginalText) + } + + // parse reason + if r.ReportType > 0 { + r.Reason = &schema.ReasonItem{ + ReasonType: r.ReportType, + } + err = rs.configRepo.GetConfigById(r.ReportType, r.Reason) + } + if r.FlagedType > 0 { + r.FlagedReason = &schema.ReasonItem{ + ReasonType: r.FlagedType, + } + _ = rs.configRepo.GetConfigById(r.FlagedType, r.FlagedReason) + } + + res[i] = r + } + resp = &res +} + +func (rs *ReportBackyardService) cutOutTagParsedText(parsedText string) string { + parsedText = strings.TrimSpace(parsedText) + idx := strings.Index(parsedText, "\n") + if idx >= 0 { + parsedText = parsedText[0:idx] + } + return parsedText +} diff --git a/internal/service/report_common/report_common.go b/internal/service/report_common/report_common.go new file mode 100644 index 00000000..b26bfd64 --- /dev/null +++ b/internal/service/report_common/report_common.go @@ -0,0 +1,15 @@ +package report_common + +import ( + "context" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" +) + +// ReportRepo report repository +type ReportRepo interface { + AddReport(ctx context.Context, report *entity.Report) (err error) + GetReportListPage(ctx context.Context, query schema.GetReportListPageDTO) (reports []entity.Report, total int64, err error) + GetByID(ctx context.Context, id string) (report entity.Report, exist bool, err error) + UpdateByID(ctx context.Context, id string, handleData entity.Report) (err error) +} diff --git a/internal/service/report_handle_backyard/report_handle.go b/internal/service/report_handle_backyard/report_handle.go new file mode 100644 index 00000000..e774aefe --- /dev/null +++ b/internal/service/report_handle_backyard/report_handle.go @@ -0,0 +1,85 @@ +package report_handle_backyard + +import ( + "context" + "github.com/segmentfault/answer/internal/service/config" + + "github.com/segmentfault/answer/internal/base/constant" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/comment" + "github.com/segmentfault/answer/internal/service/notice_queue" + questioncommon "github.com/segmentfault/answer/internal/service/question_common" + "github.com/segmentfault/answer/pkg/obj" +) + +type ReportHandle struct { + questionCommon *questioncommon.QuestionCommon + commentRepo comment.CommentRepo + configRepo config.ConfigRepo +} + +func NewReportHandle( + questionCommon *questioncommon.QuestionCommon, + commentRepo comment.CommentRepo, + configRepo config.ConfigRepo) *ReportHandle { + return &ReportHandle{ + questionCommon: questionCommon, + commentRepo: commentRepo, + configRepo: configRepo, + } +} + +// HandleObject this handle object status +func (rh *ReportHandle) HandleObject(ctx context.Context, reported entity.Report, req schema.ReportHandleReq) (err error) { + var ( + objectID = reported.ObjectID + reportedUserID = reported.ReportedUserID + objectKey string + reasonDelete, _ = rh.configRepo.GetConfigType("reason.needs_delete") + reasonClose, _ = rh.configRepo.GetConfigType("reason.needs_close") + ) + + objectKey, err = obj.GetObjectTypeStrByObjectID(objectID) + if err != nil { + return err + } + switch objectKey { + case "question": + switch req.FlagedType { + case reasonDelete: + err = rh.questionCommon.RemoveQuestion(ctx, &schema.RemoveQuestionReq{ID: objectID}) + case reasonClose: + err = rh.questionCommon.CloseQuestion(ctx, &schema.CloseQuestionReq{ + ID: objectID, + CloseType: req.FlagedType, + CloseMsg: req.FlagedContent, + }) + } + case "answer": + switch req.FlagedType { + case reasonDelete: + err = rh.questionCommon.RemoveAnswer(ctx, objectID) + } + case "comment": + switch req.FlagedType { + case reasonDelete: + err = rh.commentRepo.RemoveComment(ctx, objectID) + rh.sendNotification(ctx, reportedUserID, objectID, constant.YourCommentWasDeleted) + } + } + return +} + +// sendNotification send rank triggered notification +func (rh *ReportHandle) sendNotification(ctx context.Context, reportedUserID, objectID, notificationAction string) { + msg := &schema.NotificationMsg{ + TriggerUserID: reportedUserID, + ReceiverUserID: reportedUserID, + Type: schema.NotificationTypeInbox, + ObjectID: objectID, + ObjectType: constant.ReportObjectType, + NotificationAction: notificationAction, + } + notice_queue.AddNotification(msg) +} diff --git a/internal/service/revision/revision.go b/internal/service/revision/revision.go new file mode 100644 index 00000000..57891ef9 --- /dev/null +++ b/internal/service/revision/revision.go @@ -0,0 +1,16 @@ +package revision + +import ( + "context" + "github.com/segmentfault/answer/internal/entity" + "xorm.io/xorm" +) + +// RevisionRepo revision repository +type RevisionRepo interface { + AddRevision(ctx context.Context, revision *entity.Revision, autoUpdateRevisionID bool) (err error) + GetRevision(ctx context.Context, id string) (revision *entity.Revision, exist bool, err error) + GetLastRevisionByObjectID(ctx context.Context, objectID string) (revision *entity.Revision, exist bool, err error) + GetRevisionList(ctx context.Context, revision *entity.Revision) (revisionList []entity.Revision, err error) + UpdateObjectRevisionId(ctx context.Context, revision *entity.Revision, session *xorm.Session) (err error) +} diff --git a/internal/service/revision_common/revision_service.go b/internal/service/revision_common/revision_service.go new file mode 100644 index 00000000..3fa366ae --- /dev/null +++ b/internal/service/revision_common/revision_service.go @@ -0,0 +1,40 @@ +package revision_common + +import ( + "context" + + "github.com/segmentfault/answer/internal/service/revision" + usercommon "github.com/segmentfault/answer/internal/service/user_common" + + "github.com/jinzhu/copier" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" +) + +// RevisionService user service +type RevisionService struct { + revisionRepo revision.RevisionRepo + userRepo usercommon.UserRepo +} + +func NewRevisionService(revisionRepo revision.RevisionRepo, userRepo usercommon.UserRepo) *RevisionService { + return &RevisionService{ + revisionRepo: revisionRepo, + userRepo: userRepo, + } +} + +// AddRevision add revision +// +// autoUpdateRevisionID bool : if autoUpdateRevisionID is true , the object.revision_id will be updated, +// if not need auto update object.revision_id, it must be false. +// example: user can edit the object, but need audit, the revision_id will be updated when admin approved +func (rs *RevisionService) AddRevision(ctx context.Context, req *schema.AddRevisionDTO, autoUpdateRevisionID bool) (err error) { + rev := &entity.Revision{} + _ = copier.Copy(rev, req) + err = rs.revisionRepo.AddRevision(ctx, rev, autoUpdateRevisionID) + if err != nil { + return err + } + return nil +} diff --git a/internal/service/revision_service.go b/internal/service/revision_service.go new file mode 100644 index 00000000..50c743e9 --- /dev/null +++ b/internal/service/revision_service.go @@ -0,0 +1,161 @@ +package service + +import ( + "context" + "encoding/json" + + "github.com/jinzhu/copier" + "github.com/segmentfault/answer/internal/base/constant" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + questioncommon "github.com/segmentfault/answer/internal/service/question_common" + "github.com/segmentfault/answer/internal/service/revision" + usercommon "github.com/segmentfault/answer/internal/service/user_common" + "github.com/segmentfault/pacman/errors" + "github.com/segmentfault/pacman/log" +) + +// RevisionService user service +type RevisionService struct { + revisionRepo revision.RevisionRepo + userRepo usercommon.UserRepo + questionCommon *questioncommon.QuestionCommon + answerService *AnswerService +} + +func NewRevisionService( + revisionRepo revision.RevisionRepo, + userRepo usercommon.UserRepo, + questionCommon *questioncommon.QuestionCommon, + answerService *AnswerService) *RevisionService { + return &RevisionService{ + revisionRepo: revisionRepo, + userRepo: userRepo, + questionCommon: questionCommon, + answerService: answerService, + } +} + +// GetRevision get revision one +func (rs *RevisionService) GetRevision(ctx context.Context, id string) (resp schema.GetRevisionResp, err error) { + var ( + rev *entity.Revision + exists bool + ) + + resp = schema.GetRevisionResp{} + + rev, exists, err = rs.revisionRepo.GetRevision(ctx, id) + if err != nil { + return + } + + if !exists { + err = errors.BadRequest(reason.ObjectNotFound) + return + } + + _ = copier.Copy(&resp, rev) + rs.parseItem(ctx, &resp) + + return +} + +// GetRevisionList get revision list all +func (rs *RevisionService) GetRevisionList(ctx context.Context, req *schema.GetRevisionListReq) (resp []schema.GetRevisionResp, err error) { + var ( + rev entity.Revision + revs []entity.Revision + ) + + resp = []schema.GetRevisionResp{} + + _ = copier.Copy(&rev, req) + + revs, err = rs.revisionRepo.GetRevisionList(ctx, &rev) + if err != nil { + return + } + + for _, r := range revs { + var ( + userInfo *entity.User + uinfo schema.UserBasicInfo + item schema.GetRevisionResp + exists bool + ) + + _ = copier.Copy(&item, r) + rs.parseItem(ctx, &item) + + // get user info + userInfo, exists, err = rs.userRepo.GetByUserID(ctx, item.UserID) + if err != nil { + return + } + + if exists { + err = copier.Copy(&uinfo, userInfo) + item.UserInfo = uinfo + } + + resp = append(resp, item) + } + + return +} + +func (rs *RevisionService) parseItem(ctx context.Context, item *schema.GetRevisionResp) { + var ( + err error + question entity.Question + questionInfo *schema.QuestionInfo + answer entity.Answer + answerInfo *schema.AnswerInfo + tag entity.Tag + tagInfo *schema.GetTagResp + ) + + switch item.ObjectType { + case constant.ObjectTypeStrMapping["question"]: + err = json.Unmarshal([]byte(item.Content), &question) + if err != nil { + break + } + questionInfo = rs.questionCommon.ShowFormat(ctx, &question) + item.ContentParsed = questionInfo + log.Error(item.ContentParsed) + case constant.ObjectTypeStrMapping["answer"]: + err = json.Unmarshal([]byte(item.Content), &answer) + if err != nil { + break + } + answerInfo = rs.answerService.ShowFormat(ctx, &answer) + item.ContentParsed = answerInfo + case constant.ObjectTypeStrMapping["tag"]: + err = json.Unmarshal([]byte(item.Content), &tag) + if err != nil { + break + } + tagInfo = &schema.GetTagResp{ + TagID: tag.ID, + CreatedAt: tag.CreatedAt.Unix(), + UpdatedAt: tag.UpdatedAt.Unix(), + SlugName: tag.SlugName, + DisplayName: tag.DisplayName, + OriginalText: tag.OriginalText, + ParsedText: tag.ParsedText, + FollowCount: tag.FollowCount, + QuestionCount: tag.QuestionCount, + } + tagInfo.GetExcerpt() + item.ContentParsed = tagInfo + } + + if err != nil { + item.ContentParsed = item.Content + } + + item.CreatedAtParsed = item.CreatedAt.Unix() +} diff --git a/internal/service/search/accepted_answer.go b/internal/service/search/accepted_answer.go new file mode 100644 index 00000000..3928e81e --- /dev/null +++ b/internal/service/search/accepted_answer.go @@ -0,0 +1,53 @@ +package search + +import ( + "context" + "strings" + + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/search_common" +) + +type AcceptedAnswerSearch struct { + repo search_common.SearchRepo + w string + page int + size int +} + +func NewAcceptedAnswerSearch(repo search_common.SearchRepo) *AcceptedAnswerSearch { + return &AcceptedAnswerSearch{ + repo: repo, + } +} + +func (s *AcceptedAnswerSearch) Parse(dto *schema.SearchDTO) (ok bool) { + var ( + q, + w, + p string + ) + + q = dto.Query + w = dto.Query + p = `isaccepted:yes` + + if strings.Index(q, p) == 0 { + ok = true + w = strings.TrimPrefix(q, p) + } + + s.w = strings.TrimSpace(w) + s.page = dto.Page + s.size = dto.Size + return +} +func (s *AcceptedAnswerSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) { + + words := strings.Split(s.w, " ") + if len(words) > 3 { + words = words[:4] + } + + return s.repo.SearchAnswers(ctx, words, true, "", s.page, s.size) +} diff --git a/internal/service/search/answer.go b/internal/service/search/answer.go new file mode 100644 index 00000000..2a33a686 --- /dev/null +++ b/internal/service/search/answer.go @@ -0,0 +1,52 @@ +package search + +import ( + "context" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/search_common" + "strings" +) + +type AnswerSearch struct { + repo search_common.SearchRepo + w string + page int + size int +} + +func NewAnswerSearch(repo search_common.SearchRepo) *AnswerSearch { + return &AnswerSearch{ + repo: repo, + } +} + +func (s *AnswerSearch) Parse(dto *schema.SearchDTO) (ok bool) { + var ( + q, + w, + p string + ) + + q = dto.Query + w = dto.Query + p = `is:answer` + + if strings.Index(q, p) == 0 { + ok = true + w = strings.TrimPrefix(q, p) + } + + s.w = strings.TrimSpace(w) + s.page = dto.Page + s.size = dto.Size + return +} +func (s *AnswerSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) { + + words := strings.Split(s.w, " ") + if len(words) > 3 { + words = words[:4] + } + + return s.repo.SearchAnswers(ctx, words, false, "", s.page, s.size) +} diff --git a/internal/service/search/answers.go b/internal/service/search/answers.go new file mode 100644 index 00000000..0fe5e053 --- /dev/null +++ b/internal/service/search/answers.go @@ -0,0 +1,63 @@ +package search + +import ( + "context" + "regexp" + "strings" + + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/search_common" + "github.com/segmentfault/answer/pkg/converter" +) + +type AnswersSearch struct { + repo search_common.SearchRepo + exp int + w string + page int + size int +} + +func NewAnswersSearch(repo search_common.SearchRepo) *AnswersSearch { + return &AnswersSearch{ + repo: repo, + } +} + +func (s *AnswersSearch) Parse(dto *schema.SearchDTO) (ok bool) { + var ( + q, + w, + p, + exp string + ) + + q = dto.Query + w = dto.Query + p = `(?m)^answers:([0-9]+)` + + re := regexp.MustCompile(p) + res := re.FindStringSubmatch(q) + if len(res) == 2 { + exp = res[1] + trimLen := len(res[0]) + w = q[trimLen:] + ok = true + } + + s.exp = converter.StringToInt(exp) + s.w = strings.TrimSpace(w) + s.page = dto.Page + s.size = dto.Size + return +} + +func (s *AnswersSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) { + + words := strings.Split(s.w, " ") + if len(words) > 3 { + words = words[:4] + } + + return s.repo.SearchQuestions(ctx, words, false, s.exp, s.page, s.size) +} diff --git a/internal/service/search/author.go b/internal/service/search/author.go new file mode 100644 index 00000000..29bba1a0 --- /dev/null +++ b/internal/service/search/author.go @@ -0,0 +1,93 @@ +package search + +import ( + "context" + "regexp" + "strings" + + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/search_common" + usercommon "github.com/segmentfault/answer/internal/service/user_common" +) + +type AuthorSearch struct { + repo search_common.SearchRepo + userRepo usercommon.UserRepo + exp string + w string + page int + size int +} + +func NewAuthorSearch(repo search_common.SearchRepo, userRepo usercommon.UserRepo) *AuthorSearch { + return &AuthorSearch{ + repo: repo, + userRepo: userRepo, + } +} + +// Parse +// example: "user:12345" -> {exp="" w="12345"} +func (s *AuthorSearch) Parse(dto *schema.SearchDTO) (ok bool) { + var ( + exp, + q, + w, + p, + me, + name string + user *entity.User + has bool + err error + ) + exp = "" + q = dto.Query + w = q + p = `(?m)^user:([a-z0-9._-]+)` + me = "user:me" + + re := regexp.MustCompile(p) + res := re.FindStringSubmatch(q) + if len(res) == 2 { + name = res[1] + user, has, err = s.userRepo.GetByUsername(nil, name) + + if err == nil && has { + exp = user.ID + trimLen := len(res[0]) + w = q[trimLen:] + ok = true + } + } else if strings.Index(q, me) == 0 { + exp = dto.UserID + w = strings.TrimPrefix(q, me) + ok = true + } + + w = strings.TrimSpace(w) + s.exp = exp + s.w = w + s.page = dto.Page + s.size = dto.Size + return +} + +func (s *AuthorSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) { + var ( + words []string + ) + + if len(s.exp) == 0 { + return + } + + words = strings.Split(s.w, " ") + if len(words) > 3 { + words = words[:4] + } + + resp, total, err = s.repo.SearchContents(ctx, words, "", s.exp, -1, s.page, s.size) + + return +} diff --git a/internal/service/search/in_question.go b/internal/service/search/in_question.go new file mode 100644 index 00000000..2483daf2 --- /dev/null +++ b/internal/service/search/in_question.go @@ -0,0 +1,63 @@ +package search + +import ( + "context" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/search_common" + "regexp" + "strings" +) + +type InQuestionSearch struct { + repo search_common.SearchRepo + w string + exp string + page int + size int +} + +func NewInQuestionSearch(repo search_common.SearchRepo) *InQuestionSearch { + return &InQuestionSearch{ + repo: repo, + } +} + +func (s *InQuestionSearch) Parse(dto *schema.SearchDTO) (ok bool) { + var ( + w, + q, + p, + exp string + ) + + q = dto.Query + w = dto.Query + p = `(?m)^inquestion:([0-9]+)` + + re := regexp.MustCompile(p) + res := re.FindStringSubmatch(q) + if len(res) == 2 { + exp = res[1] + trimLen := len(res[0]) + w = q[trimLen:] + ok = true + } + + s.exp = exp + s.w = strings.TrimSpace(w) + s.page = dto.Page + s.size = dto.Size + return +} +func (s *InQuestionSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) { + var ( + words []string + ) + + words = strings.Split(s.w, " ") + if len(words) > 3 { + words = words[:4] + } + + return s.repo.SearchAnswers(ctx, words, false, s.exp, s.page, s.size) +} diff --git a/internal/service/search/not_accepted_question.go b/internal/service/search/not_accepted_question.go new file mode 100644 index 00000000..320a8f58 --- /dev/null +++ b/internal/service/search/not_accepted_question.go @@ -0,0 +1,56 @@ +package search + +import ( + "context" + "strings" + + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/search_common" +) + +type NotAcceptedQuestion struct { + repo search_common.SearchRepo + w string + page int + size int +} + +func NewNotAcceptedQuestion(repo search_common.SearchRepo) *NotAcceptedQuestion { + return &NotAcceptedQuestion{ + repo: repo, + } +} + +func (s *NotAcceptedQuestion) Parse(dto *schema.SearchDTO) (ok bool) { + var ( + q, + w, + p string + ) + + q = dto.Query + w = dto.Query + p = `hasaccepted:no` + + if strings.Index(q, p) == 0 { + ok = true + w = strings.TrimPrefix(q, p) + } + + s.w = strings.TrimSpace(w) + s.page = dto.Page + s.size = dto.Size + return +} +func (s *NotAcceptedQuestion) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) { + var ( + words []string + ) + + words = strings.Split(s.w, " ") + if len(words) > 3 { + words = words[:4] + } + + return s.repo.SearchQuestions(ctx, words, true, -1, s.page, s.size) +} diff --git a/internal/service/search/object.go b/internal/service/search/object.go new file mode 100644 index 00000000..2b0c63be --- /dev/null +++ b/internal/service/search/object.go @@ -0,0 +1,45 @@ +package search + +import ( + "context" + "strings" + + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/search_common" +) + +type ObjectSearch struct { + repo search_common.SearchRepo + w string + page int + size int +} + +func NewObjectSearch(repo search_common.SearchRepo) *ObjectSearch { + return &ObjectSearch{ + repo: repo, + } +} + +func (s *ObjectSearch) Parse(dto *schema.SearchDTO) (ok bool) { + var ( + w string + ) + w = strings.TrimSpace(dto.Query) + if len(w) > 0 { + ok = true + } + + s.w = w + s.page = dto.Page + s.size = dto.Size + return +} +func (s *ObjectSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) { + + words := strings.Split(s.w, " ") + if len(words) > 3 { + words = words[:4] + } + return s.repo.SearchContents(ctx, words, "", "", -1, s.page, s.size) +} diff --git a/internal/service/search/question.go b/internal/service/search/question.go new file mode 100644 index 00000000..ba107493 --- /dev/null +++ b/internal/service/search/question.go @@ -0,0 +1,53 @@ +package search + +import ( + "context" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/search_common" + "strings" +) + +type QuestionSearch struct { + repo search_common.SearchRepo + w string + page int + size int +} + +func NewQuestionSearch(repo search_common.SearchRepo) *QuestionSearch { + return &QuestionSearch{ + repo: repo, + } +} + +func (s *QuestionSearch) Parse(dto *schema.SearchDTO) (ok bool) { + var ( + q, + w, + p string + ) + + q = dto.Query + w = dto.Query + p = `is:question` + + if strings.Index(q, p) == 0 { + ok = true + w = strings.TrimPrefix(q, p) + } + + s.w = strings.TrimSpace(w) + s.page = dto.Page + s.size = dto.Size + return +} + +func (s *QuestionSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) { + + words := strings.Split(s.w, " ") + if len(words) > 3 { + words = words[:4] + } + + return s.repo.SearchQuestions(ctx, words, false, -1, s.page, s.size) +} diff --git a/internal/service/search/score.go b/internal/service/search/score.go new file mode 100644 index 00000000..7a5f31fb --- /dev/null +++ b/internal/service/search/score.go @@ -0,0 +1,61 @@ +package search + +import ( + "context" + "regexp" + "strings" + + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/search_common" + "github.com/segmentfault/answer/pkg/converter" +) + +type ScoreSearch struct { + repo search_common.SearchRepo + exp int + w string + page int + size int +} + +func NewScoreSearch(repo search_common.SearchRepo) *ScoreSearch { + return &ScoreSearch{ + repo: repo, + } +} + +func (s *ScoreSearch) Parse(dto *schema.SearchDTO) (ok bool) { + exp := "" + q := dto.Query + w := q + p := `(?m)^score:([0-9]+)` + + re := regexp.MustCompile(p) + res := re.FindStringSubmatch(w) + if len(res) == 2 { + exp = res[1] + trimLen := len(res[0]) + w = q[trimLen:] + ok = true + } + + w = strings.TrimSpace(w) + s.exp = converter.StringToInt(exp) + s.w = w + s.page = dto.Page + s.size = dto.Size + return +} +func (s *ScoreSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) { + var ( + words []string + ) + + words = strings.Split(s.w, " ") + if len(words) > 3 { + words = words[:4] + } + + resp, total, err = s.repo.SearchContents(ctx, words, "", "", s.exp, s.page, s.size) + return +} diff --git a/internal/service/search/tag.go b/internal/service/search/tag.go new file mode 100644 index 00000000..90f62304 --- /dev/null +++ b/internal/service/search/tag.go @@ -0,0 +1,96 @@ +package search + +import ( + "context" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/activity_common" + "github.com/segmentfault/answer/internal/service/search_common" + tagcommon "github.com/segmentfault/answer/internal/service/tag_common" + "regexp" + "strings" +) + +type TagSearch struct { + repo search_common.SearchRepo + tagRepo tagcommon.TagRepo + followCommon activity_common.FollowRepo + page int + size int + exp string + w string + userID string + Extra schema.GetTagPageResp +} + +func NewTagSearch(repo search_common.SearchRepo, tagRepo tagcommon.TagRepo, followCommon activity_common.FollowRepo) *TagSearch { + return &TagSearch{ + repo: repo, + tagRepo: tagRepo, + followCommon: followCommon, + } +} + +// Parse +// example: "[tag]hello" -> {exp="tag" w="hello"} +func (ts *TagSearch) Parse(dto *schema.SearchDTO) (ok bool) { + exp := "" + w := dto.Query + q := w + p := `(?m)^\[([a-zA-Z0-9-\+\.#]+)\]` + + re := regexp.MustCompile(p) + res := re.FindStringSubmatch(q) + if len(res) == 2 { + exp = res[1] + trimLen := len(res[0]) + w = q[trimLen:] + ok = true + } + w = strings.TrimSpace(w) + ts.exp = exp + ts.w = w + ts.page = dto.Page + ts.size = dto.Size + ts.userID = dto.UserID + return ok +} + +func (ts *TagSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) { + var ( + words []string + tag *entity.Tag + exists, followed bool + ) + tag, exists, err = ts.tagRepo.GetTagBySlugName(nil, ts.exp) + if err != nil { + return + } + + if ts.userID != "" { + followed, err = ts.followCommon.IsFollowed(ts.userID, tag.ID) + } + + ts.Extra = schema.GetTagPageResp{ + TagID: tag.ID, + SlugName: tag.SlugName, + DisplayName: tag.DisplayName, + OriginalText: tag.OriginalText, + ParsedText: tag.ParsedText, + QuestionCount: tag.QuestionCount, + IsFollower: followed, + } + ts.Extra.GetExcerpt() + + if !exists { + return + } + words = strings.Split(ts.w, " ") + if len(words) > 3 { + words = words[:4] + } + + resp, total, err = ts.repo.SearchContents(ctx, words, tag.ID, "", -1, ts.page, ts.size) + + return +} diff --git a/internal/service/search/views.go b/internal/service/search/views.go new file mode 100644 index 00000000..c2366cbb --- /dev/null +++ b/internal/service/search/views.go @@ -0,0 +1,45 @@ +package search + +import ( + "context" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/search_common" + "regexp" + "strings" +) + +type ViewsSearch struct { + repo search_common.SearchRepo + exp string + q string +} + +func NewViewsSearch(repo search_common.SearchRepo) *ViewsSearch { + return &ViewsSearch{ + repo: repo, + } +} + +func (s *ViewsSearch) Parse(dto *schema.SearchDTO) (ok bool) { + exp := "" + w := dto.Query + q := w + p := `(?m)^views:([0-9]+)` + + re := regexp.MustCompile(p) + res := re.FindStringSubmatch(q) + if len(res) == 2 { + exp = res[1] + trimLen := len(res[0]) + q = w[trimLen:] + ok = true + } + + q = strings.TrimSpace(q) + s.exp = exp + s.q = q + return +} +func (s *ViewsSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) { + return +} diff --git a/internal/service/search/within.go b/internal/service/search/within.go new file mode 100644 index 00000000..80a96eab --- /dev/null +++ b/internal/service/search/within.go @@ -0,0 +1,57 @@ +package search + +import ( + "context" + + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/search_common" +) + +type WithinSearch struct { + repo search_common.SearchRepo + w string + page int + size int +} + +func NewWithinSearch(repo search_common.SearchRepo) *WithinSearch { + return &WithinSearch{ + repo: repo, + } +} + +func (s *WithinSearch) Parse(dto *schema.SearchDTO) (ok bool) { + var ( + q string + w []rune + hasEnd bool + ) + + q = dto.Query + + if q[0:1] == `"` { + for _, v := range []rune(q) { + if len(w) == 0 && string(v) == `"` { + continue + } else if string(v) == `"` { + hasEnd = true + break + } else { + w = append(w, v) + } + } + } + + if hasEnd { + ok = true + } + + s.w = string(w) + s.page = dto.Page + s.size = dto.Size + return +} + +func (s *WithinSearch) Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) { + return s.repo.SearchContents(ctx, []string{s.w}, "", "", -1, s.page, s.size) +} diff --git a/internal/service/search_common/search.go b/internal/service/search_common/search.go new file mode 100644 index 00000000..7b9634ba --- /dev/null +++ b/internal/service/search_common/search.go @@ -0,0 +1,12 @@ +package search_common + +import ( + "context" + "github.com/segmentfault/answer/internal/schema" +) + +type SearchRepo interface { + SearchContents(ctx context.Context, words []string, tagID, userID string, votes int, page, size int) (resp []schema.SearchResp, total int64, err error) + SearchQuestions(ctx context.Context, words []string, limitNoAccepted bool, answers, page, size int) (resp []schema.SearchResp, total int64, err error) + SearchAnswers(ctx context.Context, words []string, limitAccepted bool, questionID string, page, size int) (resp []schema.SearchResp, total int64, err error) +} diff --git a/internal/service/search_service.go b/internal/service/search_service.go new file mode 100644 index 00000000..04a4ba59 --- /dev/null +++ b/internal/service/search_service.go @@ -0,0 +1,89 @@ +package service + +import ( + "context" + + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/activity_common" + "github.com/segmentfault/answer/internal/service/search" + "github.com/segmentfault/answer/internal/service/search_common" + tagcommon "github.com/segmentfault/answer/internal/service/tag_common" + usercommon "github.com/segmentfault/answer/internal/service/user_common" +) + +type Search interface { + Parse(dto *schema.SearchDTO) (ok bool) + Search(ctx context.Context) (resp []schema.SearchResp, total int64, err error) +} + +type SearchService struct { + searchRepo search_common.SearchRepo + tagSearch *search.TagSearch + withinSearch *search.WithinSearch + authorSearch *search.AuthorSearch + scoreSearch *search.ScoreSearch + answersSearch *search.AnswersSearch + notAcceptedQuestion *search.NotAcceptedQuestion + acceptedAnswerSearch *search.AcceptedAnswerSearch + inQuestionSearch *search.InQuestionSearch + questionSearch *search.QuestionSearch + answerSearch *search.AnswerSearch + viewsSearch *search.ViewsSearch + objectSearch *search.ObjectSearch +} + +func NewSearchService( + searchRepo search_common.SearchRepo, + tagRepo tagcommon.TagRepo, + userRepo usercommon.UserRepo, + followCommon activity_common.FollowRepo, +) *SearchService { + return &SearchService{ + searchRepo: searchRepo, + tagSearch: search.NewTagSearch(searchRepo, tagRepo, followCommon), + withinSearch: search.NewWithinSearch(searchRepo), + authorSearch: search.NewAuthorSearch(searchRepo, userRepo), + scoreSearch: search.NewScoreSearch(searchRepo), + answersSearch: search.NewAnswersSearch(searchRepo), + acceptedAnswerSearch: search.NewAcceptedAnswerSearch(searchRepo), + notAcceptedQuestion: search.NewNotAcceptedQuestion(searchRepo), + inQuestionSearch: search.NewInQuestionSearch(searchRepo), + questionSearch: search.NewQuestionSearch(searchRepo), + answerSearch: search.NewAnswerSearch(searchRepo), + viewsSearch: search.NewViewsSearch(searchRepo), + objectSearch: search.NewObjectSearch(searchRepo), + } +} + +func (ss *SearchService) Search(ctx context.Context, dto *schema.SearchDTO) (resp []schema.SearchResp, total int64, extra interface{}, err error) { + extra = nil + switch { + case ss.tagSearch.Parse(dto): + resp, total, err = ss.tagSearch.Search(ctx) + extra = ss.tagSearch.Extra + case ss.withinSearch.Parse(dto): + resp, total, err = ss.withinSearch.Search(ctx) + case ss.authorSearch.Parse(dto): + resp, total, err = ss.authorSearch.Search(ctx) + case ss.scoreSearch.Parse(dto): + resp, total, err = ss.scoreSearch.Search(ctx) + case ss.answersSearch.Parse(dto): + resp, total, err = ss.answersSearch.Search(ctx) + case ss.acceptedAnswerSearch.Parse(dto): + resp, total, err = ss.acceptedAnswerSearch.Search(ctx) + case ss.notAcceptedQuestion.Parse(dto): + resp, total, err = ss.notAcceptedQuestion.Search(ctx) + case ss.inQuestionSearch.Parse(dto): + resp, total, err = ss.inQuestionSearch.Search(ctx) + case ss.questionSearch.Parse(dto): + resp, total, err = ss.questionSearch.Search(ctx) + case ss.answerSearch.Parse(dto): + resp, total, err = ss.answerSearch.Search(ctx) + case ss.viewsSearch.Parse(dto): + resp, total, err = ss.viewsSearch.Search(ctx) + default: + ss.objectSearch.Parse(dto) + resp, total, err = ss.objectSearch.Search(ctx) + } + return resp, total, extra, nil +} diff --git a/internal/service/service_config/service_config.go b/internal/service/service_config/service_config.go new file mode 100644 index 00000000..33e1f626 --- /dev/null +++ b/internal/service/service_config/service_config.go @@ -0,0 +1,7 @@ +package service_config + +type ServiceConfig struct { + SecretKey string `json:"secret_key" mapstructure:"secret_key"` + WebHost string `json:"web_host" mapstructure:"web_host"` + UploadPath string `json:"upload_path" mapstructure:"upload_path"` +} diff --git a/internal/service/siteinfo_common/siteinfo.go b/internal/service/siteinfo_common/siteinfo.go new file mode 100644 index 00000000..0d1c9a91 --- /dev/null +++ b/internal/service/siteinfo_common/siteinfo.go @@ -0,0 +1,11 @@ +package siteinfo_common + +import ( + "context" + "github.com/segmentfault/answer/internal/entity" +) + +type SiteInfoRepo interface { + SaveByType(ctx context.Context, siteType string, data *entity.SiteInfo) (err error) + GetByType(ctx context.Context, siteType string) (siteInfo *entity.SiteInfo, exist bool, err error) +} diff --git a/internal/service/siteinfo_service.go b/internal/service/siteinfo_service.go new file mode 100644 index 00000000..1e59473d --- /dev/null +++ b/internal/service/siteinfo_service.go @@ -0,0 +1,115 @@ +package service + +import ( + "context" + "encoding/json" + + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/siteinfo_common" + "github.com/segmentfault/pacman/errors" +) + +type SiteInfoService struct { + siteInfoRepo siteinfo_common.SiteInfoRepo +} + +func NewSiteInfoService(siteInfoRepo siteinfo_common.SiteInfoRepo) *SiteInfoService { + return &SiteInfoService{ + siteInfoRepo: siteInfoRepo, + } +} + +func (s *SiteInfoService) GetSiteGeneral(ctx context.Context) (resp schema.SiteGeneralResp, err error) { + var ( + siteType = "general" + siteInfo *entity.SiteInfo + exist bool + ) + resp = schema.SiteGeneralResp{} + + siteInfo, exist, err = s.siteInfoRepo.GetByType(ctx, siteType) + if !exist { + return + } + + _ = json.Unmarshal([]byte(siteInfo.Content), &resp) + return +} + +func (s *SiteInfoService) GetSiteInterface(ctx context.Context) (resp schema.SiteInterfaceResp, err error) { + var ( + siteType = "interface" + siteInfo *entity.SiteInfo + exist bool + ) + resp = schema.SiteInterfaceResp{} + + siteInfo, exist, err = s.siteInfoRepo.GetByType(ctx, siteType) + if !exist { + return + } + + _ = json.Unmarshal([]byte(siteInfo.Content), &resp) + return +} + +func (s *SiteInfoService) SaveSiteGeneral(ctx context.Context, req schema.SiteGeneralReq) (err error) { + var ( + siteType = "general" + content []byte + ) + content, err = json.Marshal(req) + + data := entity.SiteInfo{ + Type: siteType, + Content: string(content), + } + + err = s.siteInfoRepo.SaveByType(ctx, siteType, &data) + return +} + +func (s *SiteInfoService) SaveSiteInterface(ctx context.Context, req schema.SiteInterfaceReq) (err error) { + var ( + siteType = "interface" + themeExist, + langExist bool + content []byte + ) + + // check theme + for _, theme := range schema.GetThemeOptions { + if theme.Value == req.Theme { + themeExist = true + break + } + } + if !themeExist { + err = errors.BadRequest(reason.ThemeNotFound) + return + } + + // check language + for _, lang := range schema.GetLangOptions { + if lang.Value == req.Language { + langExist = true + break + } + } + if !langExist { + err = errors.BadRequest(reason.LangNotFound) + return + } + + content, err = json.Marshal(req) + + data := entity.SiteInfo{ + Type: siteType, + Content: string(content), + } + + err = s.siteInfoRepo.SaveByType(ctx, siteType, &data) + return +} diff --git a/internal/service/tag/tag_service.go b/internal/service/tag/tag_service.go new file mode 100644 index 00000000..dcbb9304 --- /dev/null +++ b/internal/service/tag/tag_service.go @@ -0,0 +1,383 @@ +package tag + +import ( + "context" + "encoding/json" + "strings" + + "github.com/segmentfault/answer/internal/service/revision_common" + + "github.com/jinzhu/copier" + "github.com/segmentfault/answer/internal/base/pager" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/activity_common" + "github.com/segmentfault/answer/internal/service/permission" + tagcommon "github.com/segmentfault/answer/internal/service/tag_common" + "github.com/segmentfault/answer/pkg/converter" + "github.com/segmentfault/pacman/errors" + "github.com/segmentfault/pacman/log" +) + +// TagService user service +type TagService struct { + tagRepo tagcommon.TagRepo + revisionService *revision_common.RevisionService + followCommon activity_common.FollowRepo +} + +// NewTagService new tag service +func NewTagService( + tagRepo tagcommon.TagRepo, + revisionService *revision_common.RevisionService, + followCommon activity_common.FollowRepo) *TagService { + return &TagService{ + tagRepo: tagRepo, + revisionService: revisionService, + followCommon: followCommon, + } +} + +// SearchTagLike get tag list all +func (ts *TagService) SearchTagLike(ctx context.Context, req *schema.SearchTagLikeReq) (resp []string, err error) { + tags, err := ts.tagRepo.GetTagListByName(ctx, req.Tag) + if err != nil { + return + } + for _, tag := range tags { + //resp = append(resp, tag.DisplayName) + resp = append(resp, tag.SlugName) + } + return resp, nil +} + +// RemoveTag delete tag +func (ts *TagService) RemoveTag(ctx context.Context, tagID string) (err error) { + // TODO permission + + err = ts.tagRepo.RemoveTag(ctx, tagID) + if err != nil { + return err + } + return nil +} + +// UpdateTag update tag +func (ts *TagService) UpdateTag(ctx context.Context, req *schema.UpdateTagReq) (err error) { + tag := &entity.Tag{} + _ = copier.Copy(tag, req) + tag.ID = req.TagID + err = ts.tagRepo.UpdateTag(ctx, tag) + if err != nil { + return err + } + + tagInfo, exist, err := ts.tagRepo.GetTagByID(ctx, req.TagID) + if err != nil { + return err + } + if !exist { + return errors.BadRequest(reason.TagNotFound) + } + if tagInfo.MainTagID == 0 && len(req.SlugName) > 0 { + log.Debugf("tag %s update slug_name", tagInfo.SlugName) + tagList, err := ts.tagRepo.GetTagList(ctx, &entity.Tag{MainTagID: converter.StringToInt64(tagInfo.ID)}) + if err != nil { + return err + } + updateTagSlugNames := make([]string, 0) + for _, tag := range tagList { + updateTagSlugNames = append(updateTagSlugNames, tag.SlugName) + } + err = ts.tagRepo.UpdateTagSynonym(ctx, updateTagSlugNames, converter.StringToInt64(tagInfo.ID), tagInfo.MainTagSlugName) + if err != nil { + return err + } + } + + revisionDTO := &schema.AddRevisionDTO{ + UserID: req.UserID, + ObjectID: tag.ID, + Title: tag.SlugName, + Log: req.EditSummary, + } + tagInfoJson, _ := json.Marshal(tagInfo) + revisionDTO.Content = string(tagInfoJson) + err = ts.revisionService.AddRevision(ctx, revisionDTO, true) + if err != nil { + return err + } + return +} + +// GetTagInfo get tag one +func (ts *TagService) GetTagInfo(ctx context.Context, req *schema.GetTagInfoReq) (resp *schema.GetTagResp, err error) { + var ( + tagInfo *entity.Tag + exist bool + ) + if len(req.ID) > 0 { + tagInfo, exist, err = ts.tagRepo.GetTagByID(ctx, req.ID) + } else { + tagInfo, exist, err = ts.tagRepo.GetTagBySlugName(ctx, req.Name) + } + if err != nil { + return nil, err + } + if !exist { + return nil, errors.BadRequest(reason.TagNotFound) + } + + resp = &schema.GetTagResp{} + // if tag is synonyms get original tag info + if tagInfo.MainTagID > 0 { + tagInfo, exist, err = ts.tagRepo.GetTagByID(ctx, converter.IntToString(tagInfo.MainTagID)) + if err != nil { + return nil, err + } + if !exist { + return nil, errors.BadRequest(reason.TagNotFound) + } + resp.MainTagSlugName = tagInfo.SlugName + } + resp.TagID = tagInfo.ID + resp.CreatedAt = tagInfo.CreatedAt.Unix() + resp.UpdatedAt = tagInfo.UpdatedAt.Unix() + resp.SlugName = tagInfo.SlugName + resp.DisplayName = tagInfo.DisplayName + resp.OriginalText = tagInfo.OriginalText + resp.ParsedText = tagInfo.ParsedText + resp.FollowCount = tagInfo.FollowCount + resp.QuestionCount = tagInfo.QuestionCount + resp.IsFollower = ts.checkTagIsFollow(ctx, req.UserID, tagInfo.ID) + resp.MemberActions = permission.GetTagPermission(req.UserID, req.UserID) + resp.GetExcerpt() + return resp, nil +} + +// GetFollowingTags get following tags +func (ts *TagService) GetFollowingTags(ctx context.Context, userID string) ( + resp []*schema.GetFollowingTagsResp, err error) { + resp = make([]*schema.GetFollowingTagsResp, 0) + if len(userID) == 0 { + return resp, nil + } + objIDs, err := ts.followCommon.GetFollowIDs(ctx, userID, entity.Tag{}.TableName()) + if err != nil { + return nil, err + } + tagList, err := ts.tagRepo.GetTagListByIDs(ctx, objIDs) + if err != nil { + return nil, err + } + for _, t := range tagList { + tagInfo := &schema.GetFollowingTagsResp{ + TagID: t.ID, + SlugName: t.SlugName, + DisplayName: t.DisplayName, + } + if t.MainTagID > 0 { + mainTag, exist, err := ts.tagRepo.GetTagByID(ctx, converter.IntToString(t.MainTagID)) + if err != nil { + return nil, err + } + if exist { + tagInfo.MainTagSlugName = mainTag.SlugName + } + } + resp = append(resp, tagInfo) + } + return resp, nil +} + +// GetTagSynonyms get tag synonyms +func (ts *TagService) GetTagSynonyms(ctx context.Context, req *schema.GetTagSynonymsReq) ( + resp []*schema.GetTagSynonymsResp, err error) { + tag, exist, err := ts.tagRepo.GetTagByID(ctx, req.TagID) + if err != nil { + return + } + if !exist { + return nil, errors.BadRequest(reason.TagNotFound) + } + + var tagList []*entity.Tag + var mainTagSlugName string + if tag.MainTagID > 0 { + tagList, err = ts.tagRepo.GetTagList(ctx, &entity.Tag{MainTagID: tag.MainTagID}) + } else { + tagList, err = ts.tagRepo.GetTagList(ctx, &entity.Tag{MainTagID: converter.StringToInt64(tag.ID)}) + } + if err != nil { + return + } + + // get main tag slug name + if tag.MainTagID > 0 { + for _, tagInfo := range tagList { + if tag.MainTagID == 0 { + mainTagSlugName = tagInfo.SlugName + break + } + } + } else { + mainTagSlugName = tag.SlugName + } + + resp = make([]*schema.GetTagSynonymsResp, 0) + for _, t := range tagList { + resp = append(resp, &schema.GetTagSynonymsResp{ + TagID: t.ID, + SlugName: t.SlugName, + DisplayName: t.DisplayName, + MainTagSlugName: mainTagSlugName, + }) + } + return +} + +// UpdateTagSynonym add tag synonym +func (ts *TagService) UpdateTagSynonym(ctx context.Context, req *schema.UpdateTagSynonymReq) (err error) { + // format tag slug name + req.Format() + addSynonymTagList := make([]string, 0) + removeSynonymTagList := make([]string, 0) + mainTagInfo, exist, err := ts.tagRepo.GetTagByID(ctx, req.TagID) + if err != nil { + return err + } + if !exist { + return errors.BadRequest(reason.TagNotFound) + } + + // find all exist tag + for _, item := range req.SynonymTagList { + addSynonymTagList = append(addSynonymTagList, item.SlugName) + } + tagListInDB, err := ts.tagRepo.GetTagListByNames(ctx, addSynonymTagList) + if err != nil { + return err + } + existTagMapping := make(map[string]*entity.Tag, 0) + for _, tag := range tagListInDB { + existTagMapping[tag.SlugName] = tag + } + + // add tag list + needAddTagList := make([]*entity.Tag, 0) + for _, tag := range req.SynonymTagList { + if existTagMapping[tag.SlugName] != nil { + continue + } + item := &entity.Tag{} + item.SlugName = tag.SlugName + item.DisplayName = tag.DisplayName + item.OriginalText = tag.OriginalText + item.ParsedText = tag.ParsedText + item.Status = entity.TagStatusAvailable + needAddTagList = append(needAddTagList, item) + } + + if len(needAddTagList) > 0 { + err = ts.tagRepo.AddTagList(ctx, needAddTagList) + if err != nil { + return err + } + // update tag revision + for _, tag := range needAddTagList { + existTagMapping[tag.SlugName] = tag + revisionDTO := &schema.AddRevisionDTO{ + UserID: req.UserID, + ObjectID: tag.ID, + Title: tag.SlugName, + } + tagInfoJson, _ := json.Marshal(tag) + revisionDTO.Content = string(tagInfoJson) + err = ts.revisionService.AddRevision(ctx, revisionDTO, true) + if err != nil { + return err + } + } + } + + // get all old synonyms list + oldSynonymList, err := ts.tagRepo.GetTagList(ctx, &entity.Tag{MainTagID: converter.StringToInt64(mainTagInfo.ID)}) + if err != nil { + return err + } + for _, oldSynonym := range oldSynonymList { + if existTagMapping[oldSynonym.SlugName] == nil { + removeSynonymTagList = append(removeSynonymTagList, oldSynonym.SlugName) + } + } + + // remove old synonyms + if len(removeSynonymTagList) > 0 { + err = ts.tagRepo.UpdateTagSynonym(ctx, removeSynonymTagList, 0, "") + if err != nil { + return err + } + } + + // update new synonyms + if len(addSynonymTagList) > 0 { + err = ts.tagRepo.UpdateTagSynonym(ctx, addSynonymTagList, converter.StringToInt64(req.TagID), mainTagInfo.SlugName) + if err != nil { + return err + } + } + return nil +} + +// GetTagWithPage get tag list page +func (ts *TagService) GetTagWithPage(ctx context.Context, req *schema.GetTagWithPageReq) (pageModel *pager.PageModel, err error) { + tag := &entity.Tag{} + _ = copier.Copy(tag, req) + + page := req.Page + pageSize := req.PageSize + + tags, total, err := ts.tagRepo.GetTagPage(ctx, page, pageSize, tag, req.QueryCond) + if err != nil { + return + } + + resp := make([]*schema.GetTagPageResp, 0) + for _, tag := range tags { + resp = append(resp, &schema.GetTagPageResp{ + TagID: tag.ID, + SlugName: tag.SlugName, + DisplayName: tag.DisplayName, + OriginalText: cutOutTagParsedText(tag.OriginalText), + ParsedText: cutOutTagParsedText(tag.ParsedText), + FollowCount: tag.FollowCount, + QuestionCount: tag.QuestionCount, + IsFollower: ts.checkTagIsFollow(ctx, req.UserID, tag.ID), + CreatedAt: tag.CreatedAt.Unix(), + UpdatedAt: tag.UpdatedAt.Unix(), + }) + } + return pager.NewPageModel(page, pageSize, total, resp), nil +} + +// checkTagIsFollow get tag list page +func (ts *TagService) checkTagIsFollow(ctx context.Context, userID, tagID string) bool { + if len(userID) == 0 { + return false + } + followed, err := ts.followCommon.IsFollowed(userID, tagID) + if err != nil { + log.Error(err) + } + return followed +} + +func cutOutTagParsedText(parsedText string) string { + parsedText = strings.TrimSpace(parsedText) + idx := strings.Index(parsedText, "\n") + if idx >= 0 { + parsedText = parsedText[0:idx] + } + return parsedText +} diff --git a/internal/service/tag_common/tag_common.go b/internal/service/tag_common/tag_common.go new file mode 100644 index 00000000..ba0eee07 --- /dev/null +++ b/internal/service/tag_common/tag_common.go @@ -0,0 +1,279 @@ +package tagcommon + +import ( + "context" + "encoding/json" + "strings" + + "github.com/segmentfault/answer/internal/service/revision_common" + + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/pacman/log" +) + +type TagRepo 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) (tagList []*entity.Tag, err error) + GetTagListByNames(ctx context.Context, names []string) (tagList []*entity.Tag, err error) + RemoveTag(ctx context.Context, tagID string) (err error) + UpdateTag(ctx context.Context, tag *entity.Tag) (err error) + UpdateTagQuestionCount(ctx context.Context, tagID string, questionCount int) (err error) + UpdateTagSynonym(ctx context.Context, tagSlugNameList []string, mainTagID int64, mainTagSlugName string) (err error) + GetTagByID(ctx context.Context, tagID string) (tag *entity.Tag, exist bool, err error) + GetTagList(ctx context.Context, tag *entity.Tag) (tagList []*entity.Tag, err error) + GetTagPage(ctx context.Context, page, pageSize int, tag *entity.Tag, queryCond string) (tagList []*entity.Tag, total int64, err error) +} + +type TagRelRepo interface { + AddTagRelList(ctx context.Context, tagList []*entity.TagRel) (err error) + RemoveTagRelListByIDs(ctx context.Context, ids []int64) (err error) + RemoveTagRelListByObjectID(ctx context.Context, objectId string) (err error) + EnableTagRelByIDs(ctx context.Context, ids []int64) (err error) + GetObjectTagRelWithoutStatus(ctx context.Context, objectId, tagID string) (tagRel *entity.TagRel, exist bool, err error) + GetObjectTagRelList(ctx context.Context, objectId string) (tagListList []*entity.TagRel, err error) + BatchGetObjectTagRelList(ctx context.Context, objectIds []string) (tagListList []*entity.TagRel, err error) + CountTagRelByTagID(ctx context.Context, tagID string) (count int64, err error) +} + +// TagCommonService user service +type TagCommonService struct { + revisionService *revision_common.RevisionService + tagRepo TagRepo + tagRelRepo TagRelRepo +} + +// NewTagCommonService new tag service +func NewTagCommonService(tagRepo TagRepo, tagRelRepo TagRelRepo, + revisionService *revision_common.RevisionService) *TagCommonService { + return &TagCommonService{ + tagRepo: tagRepo, + tagRelRepo: tagRelRepo, + revisionService: revisionService, + } +} + +// GetTagListByName +func (ts *TagCommonService) GetTagListByName(ctx context.Context, tagName string) (tagInfo *entity.Tag, exist bool, err error) { + tagName = strings.ToLower(tagName) + return ts.tagRepo.GetTagBySlugName(ctx, tagName) +} + +func (ts *TagCommonService) GetTagListByNames(ctx context.Context, tagNames []string) ([]*entity.Tag, error) { + for k, tagname := range tagNames { + tagNames[k] = strings.ToLower(tagname) + } + return ts.tagRepo.GetTagListByNames(ctx, tagNames) +} + +// + +// GetObjectTag get object tag +func (ts *TagCommonService) GetObjectTag(ctx context.Context, objectId string) (objTags []*schema.TagResp, err error) { + objTags = make([]*schema.TagResp, 0) + tagIDList := make([]string, 0) + tagList, err := ts.tagRelRepo.GetObjectTagRelList(ctx, objectId) + if err != nil { + return nil, err + } + for _, tag := range tagList { + tagIDList = append(tagIDList, tag.TagID) + } + tagsInfoList, err := ts.tagRepo.GetTagListByIDs(ctx, tagIDList) + if err != nil { + return nil, err + } + for _, tagInfo := range tagsInfoList { + objTags = append(objTags, &schema.TagResp{ + SlugName: tagInfo.SlugName, + DisplayName: tagInfo.DisplayName, + MainTagSlugName: tagInfo.MainTagSlugName, + }) + } + return objTags, nil +} + +// 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) + tagIDList := make([]string, 0) + tagsInfoMap := make(map[string]*entity.Tag) + + tagList, err := ts.tagRelRepo.BatchGetObjectTagRelList(ctx, objectIds) + if err != nil { + return objectIdTagMap, err + } + for _, tag := range tagList { + tagIDList = append(tagIDList, tag.TagID) + } + tagsInfoList, err := ts.tagRepo.GetTagListByIDs(ctx, tagIDList) + if err != nil { + return objectIdTagMap, err + } + for _, item := range tagsInfoList { + tagsInfoMap[item.ID] = item + } + for _, item := range tagList { + _, ok := tagsInfoMap[item.TagID] + if ok { + tagInfo := tagsInfoMap[item.TagID] + t := &schema.TagResp{ + SlugName: tagInfo.SlugName, + DisplayName: tagInfo.DisplayName, + MainTagSlugName: tagInfo.MainTagSlugName, + } + objectIdTagMap[item.ObjectID] = append(objectIdTagMap[item.ObjectID], t) + } + } + return objectIdTagMap, nil +} + +// ObjectChangeTag change object tag list +func (ts *TagCommonService) ObjectChangeTag(ctx context.Context, objectTagData *schema.TagChange) (err error) { + if len(objectTagData.Tags) == 0 { + return nil + } + + thisObjTagNameList := make([]string, 0) + thisObjTagIDList := make([]string, 0) + for _, t := range objectTagData.Tags { + t.SlugName = strings.ToLower(t.SlugName) + thisObjTagNameList = append(thisObjTagNameList, t.SlugName) + } + + // find tags name + tagListInDb, err := ts.tagRepo.GetTagListByNames(ctx, thisObjTagNameList) + if err != nil { + return err + } + + tagInDbMapping := make(map[string]*entity.Tag) + for _, tag := range tagListInDb { + tagInDbMapping[tag.SlugName] = tag + thisObjTagIDList = append(thisObjTagIDList, tag.ID) + } + + addTagList := make([]*entity.Tag, 0) + for _, tag := range objectTagData.Tags { + _, ok := tagInDbMapping[tag.SlugName] + if ok { + continue + } + item := &entity.Tag{} + item.SlugName = tag.SlugName + item.DisplayName = tag.DisplayName + item.OriginalText = tag.OriginalText + item.ParsedText = tag.ParsedText + item.Status = entity.TagStatusAvailable + addTagList = append(addTagList, item) + } + + if len(addTagList) > 0 { + err = ts.tagRepo.AddTagList(ctx, addTagList) + if err != nil { + return err + } + for _, tag := range addTagList { + thisObjTagIDList = append(thisObjTagIDList, tag.ID) + revisionDTO := &schema.AddRevisionDTO{ + UserID: objectTagData.UserID, + ObjectID: tag.ID, + Title: tag.SlugName, + } + tagInfoJson, _ := json.Marshal(tag) + revisionDTO.Content = string(tagInfoJson) + err = ts.revisionService.AddRevision(ctx, revisionDTO, true) + if err != nil { + return err + } + } + } + + err = ts.CreateOrUpdateTagRelList(ctx, objectTagData.ObjectId, thisObjTagIDList) + if err != nil { + return err + } + return nil +} + +// RefreshTagQuestionCount refresh tag question count +func (ts *TagCommonService) RefreshTagQuestionCount(ctx context.Context, tagIDs []string) (err error) { + for _, tagID := range tagIDs { + count, err := ts.tagRelRepo.CountTagRelByTagID(ctx, tagID) + if err != nil { + return err + } + err = ts.tagRepo.UpdateTagQuestionCount(ctx, tagID, int(count)) + if err != nil { + return err + } + log.Debugf("tag count updated %s %d", tagID, count) + } + return nil +} + +// CreateOrUpdateTagRelList if tag relation is exists update status, if not create it +func (ts *TagCommonService) CreateOrUpdateTagRelList(ctx context.Context, objectId string, tagIDs []string) (err error) { + addTagIDMapping := make(map[string]bool) + needRefreshTagIDs := make([]string, 0) + for _, t := range tagIDs { + addTagIDMapping[t] = true + } + + // get all old relation + oldTagRelList, err := ts.tagRelRepo.GetObjectTagRelList(ctx, objectId) + if err != nil { + return err + } + var deleteTagRel []int64 + for _, rel := range oldTagRelList { + if !addTagIDMapping[rel.TagID] { + deleteTagRel = append(deleteTagRel, rel.ID) + needRefreshTagIDs = append(needRefreshTagIDs, rel.TagID) + } + } + + addTagRelList := make([]*entity.TagRel, 0) + enableTagRelList := make([]int64, 0) + for _, tagID := range tagIDs { + needRefreshTagIDs = append(needRefreshTagIDs, tagID) + rel, exist, err := ts.tagRelRepo.GetObjectTagRelWithoutStatus(ctx, objectId, tagID) + if err != nil { + return err + } + // if not exist add tag relation + if !exist { + addTagRelList = append(addTagRelList, &entity.TagRel{ + TagID: tagID, ObjectID: objectId, Status: entity.TagStatusAvailable, + }) + } + // if exist and has been removed, that should be enabled + if exist && rel.Status != entity.TagStatusAvailable { + enableTagRelList = append(enableTagRelList, rel.ID) + } + } + + if len(deleteTagRel) > 0 { + if err = ts.tagRelRepo.RemoveTagRelListByIDs(ctx, deleteTagRel); err != nil { + return err + } + } + if len(addTagRelList) > 0 { + if err = ts.tagRelRepo.AddTagRelList(ctx, addTagRelList); err != nil { + return err + } + } + if len(enableTagRelList) > 0 { + if err = ts.tagRelRepo.EnableTagRelByIDs(ctx, enableTagRelList); err != nil { + return err + } + } + + err = ts.RefreshTagQuestionCount(ctx, needRefreshTagIDs) + if err != nil { + log.Error(err) + } + return nil +} diff --git a/internal/service/unique/uniqid_service.go b/internal/service/unique/uniqid_service.go new file mode 100644 index 00000000..a8bdc02c --- /dev/null +++ b/internal/service/unique/uniqid_service.go @@ -0,0 +1,11 @@ +package unique + +import ( + "context" +) + +// UniqueIDRepo unique id repository +type UniqueIDRepo interface { + GenUniqueID(ctx context.Context, key string) (uniqueID int64, err error) + GenUniqueIDStr(ctx context.Context, key string) (uniqueID string, err error) +} diff --git a/internal/service/uploader/upload.go b/internal/service/uploader/upload.go new file mode 100644 index 00000000..a202ba8c --- /dev/null +++ b/internal/service/uploader/upload.go @@ -0,0 +1,64 @@ +package uploader + +import ( + "fmt" + "mime/multipart" + "path" + "path/filepath" + + "github.com/gin-gonic/gin" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/service/service_config" + "github.com/segmentfault/answer/pkg/dir" + "github.com/segmentfault/answer/pkg/uid" + "github.com/segmentfault/pacman/errors" +) + +const ( + avatarSubPath = "avatar" + postSubPath = "post" +) + +// UploaderService user service +type UploaderService struct { + serviceConfig *service_config.ServiceConfig +} + +// NewUploaderService new upload service +func NewUploaderService(serviceConfig *service_config.ServiceConfig) *UploaderService { + _, err := dir.CreatePathIsNotExist(filepath.Join(serviceConfig.UploadPath, avatarSubPath)) + if err != nil { + panic(err) + } + _, err = dir.CreatePathIsNotExist(filepath.Join(serviceConfig.UploadPath, postSubPath)) + if err != nil { + panic(err) + } + return &UploaderService{ + serviceConfig: serviceConfig, + } +} + +func (us *UploaderService) UploadAvatarFile(ctx *gin.Context, file *multipart.FileHeader, fileExt string) ( + url string, err error) { + newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt) + avatarFilePath := path.Join(avatarSubPath, newFilename) + return us.uploadFile(ctx, file, avatarFilePath) +} + +func (us *UploaderService) UploadPostFile(ctx *gin.Context, file *multipart.FileHeader, fileExt string) ( + url string, err error) { + newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt) + avatarFilePath := path.Join(postSubPath, newFilename) + return us.uploadFile(ctx, file, avatarFilePath) +} + +func (us *UploaderService) uploadFile(ctx *gin.Context, file *multipart.FileHeader, fileSubPath string) ( + url string, err error) { + filePath := path.Join(us.serviceConfig.UploadPath, fileSubPath) + if err := ctx.SaveUploadedFile(file, filePath); err != nil { + return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack() + } + url = fmt.Sprintf("%s/uploads/%s", us.serviceConfig.WebHost, fileSubPath) + return url, nil +} diff --git a/internal/service/user_backyard/user_backyard.go b/internal/service/user_backyard/user_backyard.go new file mode 100644 index 00000000..570b1531 --- /dev/null +++ b/internal/service/user_backyard/user_backyard.go @@ -0,0 +1,124 @@ +package user_backyard + +import ( + "context" + "fmt" + "time" + + "github.com/jinzhu/copier" + "github.com/segmentfault/answer/internal/base/pager" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/pacman/errors" +) + +// UserBackyardRepo user repository +type UserBackyardRepo interface { + UpdateUserStatus(ctx context.Context, userID string, userStatus, mailStatus int, email string) (err error) + GetUserInfo(ctx context.Context, userID string) (user *entity.User, exist bool, err error) + GetUserPage(ctx context.Context, page, pageSize int, user *entity.User) (users []*entity.User, total int64, err error) +} + +// UserBackyardService user service +type UserBackyardService struct { + userRepo UserBackyardRepo +} + +func NewUserBackyardService(userRepo UserBackyardRepo) *UserBackyardService { + return &UserBackyardService{ + userRepo: userRepo, + } +} + +// UpdateUserStatus update user +func (us *UserBackyardService) UpdateUserStatus(ctx context.Context, req *schema.UpdateUserStatusReq) (err error) { + userInfo, exist, err := us.userRepo.GetUserInfo(ctx, req.UserID) + if err != nil { + return + } + if !exist { + return errors.BadRequest(reason.UserNotFound) + } + // if user status is deleted + if userInfo.Status == entity.UserStatusDeleted { + return nil + } + + if req.IsInactive() { + userInfo.MailStatus = entity.EmailStatusToBeVerified + } + if req.IsDeleted() { + userInfo.Status = entity.UserStatusDeleted + userInfo.EMail = fmt.Sprintf("%s.%d", userInfo.EMail, time.Now().UnixNano()) + } + if req.IsSuspended() { + userInfo.Status = entity.UserStatusSuspended + } + if req.IsNormal() { + userInfo.Status = entity.UserStatusAvailable + userInfo.MailStatus = entity.EmailStatusAvailable + } + return us.userRepo.UpdateUserStatus(ctx, userInfo.ID, userInfo.Status, userInfo.MailStatus, userInfo.EMail) +} + +// GetUserInfo get user one +func (us *UserBackyardService) GetUserInfo(ctx context.Context, userID string) (resp *schema.GetUserInfoResp, err error) { + user, exist, err := us.userRepo.GetUserInfo(ctx, userID) + if err != nil { + return + } + if !exist { + return nil, errors.BadRequest(reason.UserNotFound) + } + + resp = &schema.GetUserInfoResp{} + _ = copier.Copy(resp, user) + return resp, nil +} + +// GetUserPage get user list page +func (us *UserBackyardService) GetUserPage(ctx context.Context, req *schema.GetUserPageReq) (pageModel *pager.PageModel, err error) { + user := &entity.User{} + _ = copier.Copy(user, req) + + if req.IsInactive() { + user.MailStatus = entity.EmailStatusToBeVerified + user.Status = entity.UserStatusAvailable + } else if req.IsSuspended() { + user.Status = entity.UserStatusSuspended + } else if req.IsDeleted() { + user.Status = entity.UserStatusDeleted + } + + users, total, err := us.userRepo.GetUserPage(ctx, req.Page, req.PageSize, user) + if err != nil { + return + } + + resp := make([]*schema.GetUserPageResp, 0) + for _, u := range users { + t := &schema.GetUserPageResp{ + UserID: u.ID, + CreatedAt: u.CreatedAt.Unix(), + Username: u.Username, + EMail: u.EMail, + Rank: u.Rank, + DisplayName: u.DisplayName, + Avatar: u.Avatar, + } + if u.Status == entity.UserStatusDeleted { + t.Status = schema.Deleted + t.DeletedAt = u.DeletedAt.Unix() + } else if u.Status == entity.UserStatusSuspended { + t.Status = schema.Suspended + t.SuspendedAt = u.SuspendedAt.Unix() + } else if u.MailStatus == entity.EmailStatusToBeVerified { + t.Status = schema.Inactive + } else { + t.Status = schema.Normal + } + resp = append(resp, t) + } + return pager.NewPageModel(req.Page, req.PageSize, total, resp), nil +} diff --git a/internal/service/user_common/user.go b/internal/service/user_common/user.go new file mode 100644 index 00000000..9a7be622 --- /dev/null +++ b/internal/service/user_common/user.go @@ -0,0 +1,89 @@ +package usercommon + +import ( + "context" + + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" +) + +type UserRepo interface { + AddUser(ctx context.Context, user *entity.User) (err error) + IncreaseAnswerCount(ctx context.Context, userID string, amount int) (err error) + IncreaseQuestionCount(ctx context.Context, userID string, amount int) (err error) + UpdateLastLoginDate(ctx context.Context, userID string) (err error) + UpdateEmailStatus(ctx context.Context, userID string, emailStatus int) error + UpdateNoticeStatus(ctx context.Context, userID string, noticeStatus int) error + UpdateEmail(ctx context.Context, userID, email string) error + UpdatePass(ctx context.Context, Data *entity.User) error + UpdateInfo(ctx context.Context, userInfo *entity.User) (err error) + GetByUserID(ctx context.Context, userID string) (userInfo *entity.User, exist bool, err error) + BatchGetByID(ctx context.Context, ids []string) ([]*entity.User, error) + GetByUsername(ctx context.Context, username string) (userInfo *entity.User, exist bool, err error) + GetByEmail(ctx context.Context, email string) (userInfo *entity.User, exist bool, err error) +} + +// UserCommon user service +type UserCommon struct { + userRepo UserRepo +} + +func NewUserCommon(userRepo UserRepo) *UserCommon { + return &UserCommon{ + userRepo: userRepo, + } +} + +func (us *UserCommon) GetUserBasicInfoByID(ctx context.Context, ID string) (*schema.UserBasicInfo, bool, error) { + dbInfo, has, err := us.userRepo.GetByUserID(ctx, ID) + if err != nil { + return nil, has, err + } + info := us.UserBasicInfoFormat(ctx, dbInfo) + return info, has, nil +} + +func (us *UserCommon) GetUserBasicInfoByUserName(ctx context.Context, username string) (*schema.UserBasicInfo, bool, error) { + userInfo, exist, err := us.userRepo.GetByUsername(ctx, username) + if err != nil { + return nil, exist, err + } + info := us.UserBasicInfoFormat(ctx, userInfo) + return info, exist, nil +} + +func (us *UserCommon) UpdateAnswerCount(ctx context.Context, userID string, num int) error { + return us.userRepo.IncreaseAnswerCount(ctx, userID, num) +} + +func (us *UserCommon) UpdateQuestionCount(ctx context.Context, userID string, num int) error { + return us.userRepo.IncreaseQuestionCount(ctx, userID, num) +} + +func (us *UserCommon) BatchUserBasicInfoByID(ctx context.Context, IDs []string) (map[string]*schema.UserBasicInfo, error) { + userMap := make(map[string]*schema.UserBasicInfo) + dbInfo, err := us.userRepo.BatchGetByID(ctx, IDs) + if err != nil { + return userMap, err + } + for _, item := range dbInfo { + info := us.UserBasicInfoFormat(ctx, item) + userMap[item.ID] = info + } + return userMap, nil +} + +// UserBasicInfoFormat +func (us *UserCommon) UserBasicInfoFormat(ctx context.Context, dbinfo *entity.User) *schema.UserBasicInfo { + info := new(schema.UserBasicInfo) + info.UserId = dbinfo.ID + info.UserName = dbinfo.Username + info.Rank = dbinfo.Rank + info.DisplayName = dbinfo.DisplayName + info.Avatar = dbinfo.Avatar + info.Website = dbinfo.Website + info.Location = dbinfo.Location + info.IpInfo = dbinfo.IPInfo + info.Status = dbinfo.Status + return info +} diff --git a/internal/service/user_group_service.go b/internal/service/user_group_service.go new file mode 100644 index 00000000..9a7e833d --- /dev/null +++ b/internal/service/user_group_service.go @@ -0,0 +1,101 @@ +package service + +import ( + "context" + + "github.com/jinzhu/copier" + "github.com/segmentfault/answer/internal/base/pager" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/pacman/errors" +) + +// UserGroupRepo userGroup repository +type UserGroupRepo interface { + AddUserGroup(ctx context.Context, userGroup *entity.UserGroup) (err error) + RemoveUserGroup(ctx context.Context, id int) (err error) + UpdateUserGroup(ctx context.Context, userGroup *entity.UserGroup) (err error) + GetUserGroup(ctx context.Context, id int) (userGroup *entity.UserGroup, exist bool, err error) + GetUserGroupList(ctx context.Context, userGroup *entity.UserGroup) (userGroups []*entity.UserGroup, err error) + GetUserGroupPage(ctx context.Context, page, pageSize int, userGroup *entity.UserGroup) (userGroups []*entity.UserGroup, total int64, err error) +} + +// UserGroupService user service +type UserGroupService struct { + userGroupRepo UserGroupRepo +} + +func NewUserGroupService(userGroupRepo UserGroupRepo) *UserGroupService { + return &UserGroupService{ + userGroupRepo: userGroupRepo, + } +} + +// AddUserGroup add user group +func (us *UserGroupService) AddUserGroup(ctx context.Context, req *schema.AddUserGroupReq) (err error) { + userGroup := &entity.UserGroup{} + _ = copier.Copy(userGroup, req) + return us.userGroupRepo.AddUserGroup(ctx, userGroup) +} + +// RemoveUserGroup delete user group +func (us *UserGroupService) RemoveUserGroup(ctx context.Context, id int) (err error) { + return us.userGroupRepo.RemoveUserGroup(ctx, id) +} + +// UpdateUserGroup update user group +func (us *UserGroupService) UpdateUserGroup(ctx context.Context, req *schema.UpdateUserGroupReq) (err error) { + userGroup := &entity.UserGroup{} + _ = copier.Copy(userGroup, req) + return us.userGroupRepo.UpdateUserGroup(ctx, userGroup) +} + +// GetUserGroup get user group one +func (us *UserGroupService) GetUserGroup(ctx context.Context, id int) (resp *schema.GetUserGroupResp, err error) { + userGroup, exist, err := us.userGroupRepo.GetUserGroup(ctx, id) + if err != nil { + return + } + if !exist { + return nil, errors.BadRequest(reason.UnknownError) + } + + resp = &schema.GetUserGroupResp{} + _ = copier.Copy(resp, userGroup) + return resp, nil +} + +// GetUserGroupList get user group list all +func (us *UserGroupService) GetUserGroupList(ctx context.Context, req *schema.GetUserGroupListReq) (resp *[]schema.GetUserGroupResp, err error) { + userGroup := &entity.UserGroup{} + _ = copier.Copy(userGroup, req) + + userGroups, err := us.userGroupRepo.GetUserGroupList(ctx, userGroup) + if err != nil { + return + } + + resp = &[]schema.GetUserGroupResp{} + _ = copier.Copy(resp, userGroups) + return +} + +// GetUserGroupWithPage get user group list page +func (us *UserGroupService) GetUserGroupWithPage(ctx context.Context, req *schema.GetUserGroupWithPageReq) (pageModel *pager.PageModel, err error) { + userGroup := &entity.UserGroup{} + _ = copier.Copy(userGroup, req) + + page := req.Page + pageSize := req.PageSize + + userGroups, total, err := us.userGroupRepo.GetUserGroupPage(ctx, page, pageSize, userGroup) + if err != nil { + return + } + + resp := &[]schema.GetUserGroupResp{} + _ = copier.Copy(resp, userGroups) + + return pager.NewPageModel(page, pageSize, total, resp), nil +} diff --git a/internal/service/user_service.go b/internal/service/user_service.go new file mode 100644 index 00000000..dede0fd4 --- /dev/null +++ b/internal/service/user_service.go @@ -0,0 +1,548 @@ +package service + +import ( + "context" + "fmt" + "regexp" + "strings" + + "github.com/Chain-Zhang/pinyin" + "github.com/google/uuid" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/activity" + "github.com/segmentfault/answer/internal/service/auth" + "github.com/segmentfault/answer/internal/service/export" + "github.com/segmentfault/answer/internal/service/service_config" + usercommon "github.com/segmentfault/answer/internal/service/user_common" + "github.com/segmentfault/answer/pkg/checker" + "github.com/segmentfault/answer/pkg/uid" + "github.com/segmentfault/pacman/errors" + "github.com/segmentfault/pacman/log" + "golang.org/x/crypto/bcrypt" +) + +// UserRepo user repository + +// UserService user service +type UserService struct { + userRepo usercommon.UserRepo + userActivity activity.UserActiveActivityRepo + serviceConfig *service_config.ServiceConfig + emailService *export.EmailService + authService *auth.AuthService +} + +func NewUserService(userRepo usercommon.UserRepo, + userActivity activity.UserActiveActivityRepo, + emailService *export.EmailService, + authService *auth.AuthService, + serviceConfig *service_config.ServiceConfig) *UserService { + return &UserService{ + userRepo: userRepo, + userActivity: userActivity, + emailService: emailService, + serviceConfig: serviceConfig, + authService: authService, + } +} + +// GetUserInfoByUserID get user info by user id +func (us *UserService) GetUserInfoByUserID(ctx context.Context, token, userID string) (resp *schema.GetUserResp, err error) { + userInfo, exist, err := us.userRepo.GetByUserID(ctx, userID) + if err != nil { + return nil, err + } + if !exist { + return nil, errors.BadRequest(reason.UserNotFound) + } + resp = &schema.GetUserResp{} + resp.GetFromUserEntity(userInfo) + resp.AccessToken = token + return resp, nil +} + +func (us *UserService) GetOtherUserInfoByUsername(ctx context.Context, username string) ( + resp *schema.GetOtherUserInfoResp, err error) { + userInfo, exist, err := us.userRepo.GetByUsername(ctx, username) + if err != nil { + return nil, err + } + resp = &schema.GetOtherUserInfoResp{} + if !exist { + return resp, nil + } + resp.Has = true + resp.Info = &schema.GetOtherUserInfoByUsernameResp{} + resp.Info.GetFromUserEntity(userInfo) + return resp, nil +} + +// EmailLogin email login +func (us *UserService) EmailLogin(ctx context.Context, req *schema.UserEmailLogin) (resp *schema.GetUserResp, err error) { + userInfo, exist, err := us.userRepo.GetByEmail(ctx, req.Email) + if err != nil { + return nil, err + } + if !exist || userInfo.Status == entity.UserStatusDeleted { + return nil, errors.BadRequest(reason.EmailOrPasswordWrong) + } + if !us.verifyPassword(ctx, req.Pass, userInfo.Pass) { + return nil, errors.BadRequest(reason.EmailOrPasswordWrong) + } + + err = us.userRepo.UpdateLastLoginDate(ctx, userInfo.ID) + if err != nil { + log.Error("UpdateLastLoginDate", err.Error()) + } + + resp = &schema.GetUserResp{} + resp.GetFromUserEntity(userInfo) + userCacheInfo := &entity.UserCacheInfo{ + UserID: userInfo.ID, + EmailStatus: userInfo.MailStatus, + UserStatus: userInfo.Status, + } + resp.AccessToken, err = us.authService.SetUserCacheInfo(ctx, userCacheInfo) + if err != nil { + return nil, err + } + resp.IsAdmin = userInfo.IsAdmin + if resp.IsAdmin { + err = us.authService.SetCmsUserCacheInfo(ctx, resp.AccessToken, userCacheInfo) + if err != nil { + return nil, err + } + } + + return resp, nil +} + +// RetrievePassWord . +func (us *UserService) RetrievePassWord(ctx context.Context, req *schema.UserRetrievePassWordRequest) (string, error) { + userInfo, has, err := us.userRepo.GetByEmail(ctx, req.Email) + if err != nil { + return "", err + } + if !has { + return "", errors.BadRequest(reason.UserNotFound) + } + + // send email + data := &schema.EmailCodeContent{ + Email: req.Email, + UserID: userInfo.ID, + } + code := uuid.NewString() + verifyEmailUrl := fmt.Sprintf("%s/users/password-reset?code=%s", us.serviceConfig.WebHost, code) + title, body, err := us.emailService.PassResetTemplate(verifyEmailUrl) + if err != nil { + return "", err + } + go us.emailService.Send(ctx, req.Email, title, body, code, data.ToJSONString()) + return code, nil +} + +// UseRePassWord +func (us *UserService) UseRePassWord(ctx context.Context, req *schema.UserRePassWordRequest) (resp *schema.GetUserResp, err error) { + data := &schema.EmailCodeContent{} + err = data.FromJSONString(req.Content) + if err != nil { + return nil, errors.BadRequest(reason.EmailVerifyUrlExpired) + } + + userInfo, exist, err := us.userRepo.GetByEmail(ctx, data.Email) + if err != nil { + return nil, err + } + if !exist { + return nil, errors.BadRequest(reason.UserNotFound) + } + enpass, err := us.encryptPassword(ctx, req.Pass) + if err != nil { + return nil, err + } + userInfo.Pass = enpass + err = us.userRepo.UpdatePass(ctx, userInfo) + if err != nil { + return nil, err + } + resp = &schema.GetUserResp{} + return resp, nil +} + +func (us *UserService) UserModifyPassWordVerification(ctx context.Context, request *schema.UserModifyPassWordRequest) (bool, error) { + + userInfo, has, err := us.userRepo.GetByUserID(ctx, request.UserId) + if err != nil { + return false, err + } + if !has { + return false, fmt.Errorf("user does not exist") + } + isPass := us.verifyPassword(ctx, request.OldPass, userInfo.Pass) + if !isPass { + return false, nil + } + + return true, nil +} + +// UserModifyPassWord +func (us *UserService) UserModifyPassWord(ctx context.Context, request *schema.UserModifyPassWordRequest) error { + enpass, err := us.encryptPassword(ctx, request.Pass) + if err != nil { + return err + } + userInfo, has, err := us.userRepo.GetByUserID(ctx, request.UserId) + if err != nil { + return err + } + if !has { + return fmt.Errorf("user does not exist") + } + isPass := us.verifyPassword(ctx, request.OldPass, userInfo.Pass) + if !isPass { + return fmt.Errorf("the old password verification failed") + } + userInfo.Pass = enpass + err = us.userRepo.UpdatePass(ctx, userInfo) + if err != nil { + return err + } + return nil +} + +// UpdateInfo +func (us *UserService) UpdateInfo(ctx context.Context, request *schema.UpdateInfoRequest) error { + // formatName, pass := us.CheckUserName(ctx, request.Username) + // if !pass { + // return fmt.Errorf("username format error") + // } + // dbuserinfo, has, err := us.userRepo.GetUserInfoByUserID(ctx, request.UserID) + // if err != nil { + // return err + // } + // if !has { + // return fmt.Errorf("user does not exist") + // } + // dbNameUserInfo, has, err := us.userRepo.GetOtherUserInfoByUsername(ctx, formatName) + // if err != nil { + // return err + // } + // if has { + // if dbuserinfo.TagID != dbNameUserInfo.TagID { + // return fmt.Errorf("username already exists") + // } + // } + + userinfo := entity.User{} + userinfo.ID = request.UserId + //userinfo.Username = formatName + userinfo.Avatar = request.Avatar + userinfo.DisplayName = request.DisplayName + userinfo.Bio = request.Bio + userinfo.BioHtml = request.BioHtml + userinfo.Location = request.Location + userinfo.Website = request.Website + err := us.userRepo.UpdateInfo(ctx, &userinfo) + if err != nil { + return err + } + return nil +} + +func (us *UserService) UserEmailHas(ctx context.Context, email string) (bool, error) { + _, has, err := us.userRepo.GetByEmail(ctx, email) + if err != nil { + return false, err + } + return has, nil +} + +// UserRegisterByEmail user register +func (us *UserService) UserRegisterByEmail(ctx context.Context, registerUserInfo *schema.UserRegister) ( + resp *schema.GetUserResp, err error) { + _, has, err := us.userRepo.GetByEmail(ctx, registerUserInfo.Email) + if err != nil { + return nil, err + } + if has { + return nil, errors.BadRequest(reason.EmailDuplicate) + } + + userInfo := &entity.User{} + userInfo.EMail = registerUserInfo.Email + userInfo.DisplayName = registerUserInfo.Name + userInfo.Pass, err = us.encryptPassword(ctx, registerUserInfo.Pass) + if err != nil { + return nil, err + } + userInfo.Username, err = us.makeUserName(ctx, registerUserInfo.Name) + if err != nil { + return nil, err + } + userInfo.IPInfo = registerUserInfo.IP + userInfo.MailStatus = entity.EmailStatusToBeVerified + userInfo.Status = entity.UserStatusAvailable + err = us.userRepo.AddUser(ctx, userInfo) + if err != nil { + return nil, err + } + + // send email + data := &schema.EmailCodeContent{ + Email: registerUserInfo.Email, + UserID: userInfo.ID, + } + code := uuid.NewString() + verifyEmailUrl := fmt.Sprintf("%s/users/account-activation?code=%s", us.serviceConfig.WebHost, code) + title, body, err := us.emailService.RegisterTemplate(verifyEmailUrl) + if err != nil { + return nil, err + } + go us.emailService.Send(ctx, userInfo.EMail, title, body, code, data.ToJSONString()) + + // return user info and token + resp = &schema.GetUserResp{} + resp.GetFromUserEntity(userInfo) + userCacheInfo := &entity.UserCacheInfo{ + UserID: userInfo.ID, + EmailStatus: userInfo.MailStatus, + UserStatus: userInfo.Status, + } + resp.AccessToken, err = us.authService.SetUserCacheInfo(ctx, userCacheInfo) + if err != nil { + return nil, err + } + resp.IsAdmin = userInfo.IsAdmin + if resp.IsAdmin { + err = us.authService.SetCmsUserCacheInfo(ctx, resp.AccessToken, &entity.UserCacheInfo{UserID: userInfo.ID}) + if err != nil { + return nil, err + } + } + return resp, nil +} + +func (us *UserService) UserVerifyEmailSend(ctx context.Context, userID string) error { + userInfo, has, err := us.userRepo.GetByUserID(ctx, userID) + if err != nil { + return err + } + if !has { + return errors.BadRequest(reason.UserNotFound) + } + + data := &schema.EmailCodeContent{ + Email: userInfo.EMail, + UserID: userInfo.ID, + } + code := uuid.NewString() + verifyEmailUrl := fmt.Sprintf("%s/users/account-activation?code=%s", us.serviceConfig.WebHost, code) + title, body, err := us.emailService.RegisterTemplate(verifyEmailUrl) + if err != nil { + return err + } + go us.emailService.Send(ctx, userInfo.EMail, title, body, code, data.ToJSONString()) + return nil +} + +func (us *UserService) UserNoticeSet(ctx context.Context, userId string, noticeSwitch bool) ( + resp *schema.UserNoticeSetResp, err error) { + userInfo, has, err := us.userRepo.GetByUserID(ctx, userId) + if err != nil { + return nil, err + } + if !has { + return nil, errors.BadRequest(reason.UserNotFound) + } + if noticeSwitch { + userInfo.NoticeStatus = schema.Notice_Status_On + } else { + userInfo.NoticeStatus = schema.Notice_Status_Off + } + err = us.userRepo.UpdateNoticeStatus(ctx, userInfo.ID, userInfo.NoticeStatus) + return &schema.UserNoticeSetResp{NoticeSwitch: noticeSwitch}, err +} + +func (us *UserService) UserVerifyEmail(ctx context.Context, req *schema.UserVerifyEmailReq) (resp *schema.GetUserResp, err error) { + data := &schema.EmailCodeContent{} + err = data.FromJSONString(req.Content) + if err != nil { + return nil, errors.BadRequest(reason.EmailVerifyUrlExpired) + } + + userInfo, has, err := us.userRepo.GetByEmail(ctx, data.Email) + if err != nil { + return nil, err + } + if !has { + return nil, errors.BadRequest(reason.UserNotFound) + } + userInfo.MailStatus = entity.EmailStatusAvailable + err = us.userRepo.UpdateEmailStatus(ctx, userInfo.ID, userInfo.MailStatus) + if err != nil { + return nil, err + } + if err = us.userActivity.UserActive(ctx, userInfo.ID); err != nil { + log.Error(err) + } + + resp = &schema.GetUserResp{} + resp.GetFromUserEntity(userInfo) + userCacheInfo := &entity.UserCacheInfo{ + UserID: userInfo.ID, + EmailStatus: userInfo.MailStatus, + UserStatus: userInfo.Status, + } + resp.AccessToken, err = us.authService.SetUserCacheInfo(ctx, userCacheInfo) + if err != nil { + return nil, err + } + resp.IsAdmin = userInfo.IsAdmin + if resp.IsAdmin { + err = us.authService.SetCmsUserCacheInfo(ctx, resp.AccessToken, &entity.UserCacheInfo{UserID: userInfo.ID}) + if err != nil { + return nil, err + } + } + return resp, nil +} + +// makeUserName +// Generate a unique Username based on the NickName +// todo Waiting to be realized +func (us *UserService) makeUserName(ctx context.Context, userName string) (string, error) { + userName = us.formatUserName(ctx, userName) + _, has, err := us.userRepo.GetByUsername(ctx, userName) + if err != nil { + return "", err + } + //If the user name is duplicated, it is generated recursively from the new one. + if has { + userName = uid.IDStr() + return us.makeUserName(ctx, userName) + } + return userName, nil +} + +// formatUserName +// Generate a Username through a nickname +func (us *UserService) formatUserName(ctx context.Context, Name string) string { + formatName, pass := us.CheckUserName(ctx, Name) + if !pass { + //todo 重新给用户 生成随机 username + return uid.IDStr() + } + return formatName +} + +func (us *UserService) CheckUserName(ctx context.Context, name string) (string, bool) { + name = strings.Replace(name, " ", "_", -1) + name = strings.ToLower(name) + //Chinese processing + has := checker.IsChinese(name) + if has { + str, err := pinyin.New(name).Split("").Mode(pinyin.WithoutTone).Convert() + if err != nil { + log.Error("pinyin Error", err) + return "", false + } else { + name = str + } + } + //Format filtering + re, err := regexp.Compile(`^[a-z0-9._-]{4,20}$`) + if err != nil { + log.Error("regexp.Compile Error", err, "name", name) + } + match := re.MatchString(name) + if !match { + return "", false + } + return name, true +} + +// verifyPassword +// Compare whether the password is correct +func (us *UserService) verifyPassword(ctx context.Context, LoginPass, UserPass string) bool { + err := bcrypt.CompareHashAndPassword([]byte(UserPass), []byte(LoginPass)) + if err != nil { + return false + } + return true +} + +// encryptPassword +// The password does irreversible encryption. +func (us *UserService) encryptPassword(ctx context.Context, Pass string) (string, error) { + hashPwd, err := bcrypt.GenerateFromPassword([]byte(Pass), bcrypt.DefaultCost) + //This encrypted string can be saved to the database and can be used as password matching verification + return string(hashPwd), err +} + +// UserChangeEmailSendCode user change email verification +func (us *UserService) UserChangeEmailSendCode(ctx context.Context, req *schema.UserChangeEmailSendCodeReq) error { + _, exist, err := us.userRepo.GetByUserID(ctx, req.UserID) + if err != nil { + return err + } + if !exist { + return errors.BadRequest(reason.UserNotFound) + } + + _, exist, err = us.userRepo.GetByEmail(ctx, req.Email) + if err != nil { + return err + } + if exist { + return errors.BadRequest(reason.EmailDuplicate) + } + + data := &schema.EmailCodeContent{ + Email: req.Email, + UserID: req.UserID, + } + code := uuid.NewString() + verifyEmailUrl := fmt.Sprintf("%s/users/confirm-new-email?code=%s", us.serviceConfig.WebHost, code) + title, body, err := us.emailService.ChangeEmailTemplate(verifyEmailUrl) + if err != nil { + return err + } + log.Infof("send email confirmation %s", verifyEmailUrl) + + go us.emailService.Send(context.Background(), req.Email, title, body, code, data.ToJSONString()) + return nil +} + +// UserChangeEmailVerify user change email verify code +func (us *UserService) UserChangeEmailVerify(ctx context.Context, content string) (err error) { + data := &schema.EmailCodeContent{} + err = data.FromJSONString(content) + if err != nil { + return errors.BadRequest(reason.EmailVerifyUrlExpired) + } + + _, exist, err := us.userRepo.GetByEmail(ctx, data.Email) + if err != nil { + return err + } + if exist { + return errors.BadRequest(reason.EmailDuplicate) + } + + _, exist, err = us.userRepo.GetByUserID(ctx, data.UserID) + if err != nil { + return err + } + if !exist { + return errors.BadRequest(reason.UserNotFound) + } + err = us.userRepo.UpdateEmail(ctx, data.UserID, data.Email) + if err != nil { + return err + } + return nil +} diff --git a/internal/service/vote_service.go b/internal/service/vote_service.go new file mode 100644 index 00000000..ecf12712 --- /dev/null +++ b/internal/service/vote_service.go @@ -0,0 +1,197 @@ +package service + +import ( + "context" + + "github.com/segmentfault/answer/internal/base/pager" + "github.com/segmentfault/answer/internal/entity" + "github.com/segmentfault/answer/internal/service/activity_type" + "github.com/segmentfault/answer/internal/service/comment_common" + "github.com/segmentfault/answer/internal/service/config" + "github.com/segmentfault/answer/internal/service/object_info" + "github.com/segmentfault/answer/pkg/obj" + "github.com/segmentfault/pacman/log" + + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/internal/schema" + answercommon "github.com/segmentfault/answer/internal/service/answer_common" + questioncommon "github.com/segmentfault/answer/internal/service/question_common" + "github.com/segmentfault/answer/internal/service/unique" + "github.com/segmentfault/pacman/errors" +) + +// VoteRepo activity repository +type VoteRepo interface { + VoteUp(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) + VoteDown(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) + VoteUpCancel(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) + VoteDownCancel(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) + GetVoteResultByObjectId(ctx context.Context, objectID string) (resp *schema.VoteResp, err error) + ListUserVotes(ctx context.Context, userID string, req schema.GetVoteWithPageReq, activityTypes []int) (voteList []entity.Activity, total int64, err error) +} + +// VoteService user service +type VoteService struct { + voteRepo VoteRepo + UniqueIDRepo unique.UniqueIDRepo + configRepo config.ConfigRepo + questionRepo questioncommon.QuestionRepo + answerRepo answercommon.AnswerRepo + commentCommonRepo comment_common.CommentCommonRepo + objectService *object_info.ObjService +} + +func NewVoteService( + VoteRepo VoteRepo, + uniqueIDRepo unique.UniqueIDRepo, + configRepo config.ConfigRepo, + questionRepo questioncommon.QuestionRepo, + answerRepo answercommon.AnswerRepo, + commentCommonRepo comment_common.CommentCommonRepo, + objectService *object_info.ObjService, +) *VoteService { + return &VoteService{ + voteRepo: VoteRepo, + UniqueIDRepo: uniqueIDRepo, + configRepo: configRepo, + questionRepo: questionRepo, + answerRepo: answerRepo, + commentCommonRepo: commentCommonRepo, + objectService: objectService, + } +} + +// VoteUp vote up +func (as *VoteService) VoteUp(ctx context.Context, dto *schema.VoteDTO) (voteResp *schema.VoteResp, err error) { + voteResp = &schema.VoteResp{} + + var objectUserID string + + objectUserID, err = as.GetObjectUserId(ctx, dto.ObjectID) + if err != nil { + return + } + + // check user is voting self or not + if objectUserID == dto.UserID { + err = errors.BadRequest(reason.DisallowVoteYourSelf) + return + } + + if dto.IsCancel { + return as.voteRepo.VoteUpCancel(ctx, dto.ObjectID, dto.UserID, objectUserID) + } else { + return as.voteRepo.VoteUp(ctx, dto.ObjectID, dto.UserID, objectUserID) + } +} + +// VoteDown vote down +func (as *VoteService) VoteDown(ctx context.Context, dto *schema.VoteDTO) (voteResp *schema.VoteResp, err error) { + voteResp = &schema.VoteResp{} + + var objectUserID string + + objectUserID, err = as.GetObjectUserId(ctx, dto.ObjectID) + if err != nil { + return + } + + // check user is voting self or not + if objectUserID == dto.UserID { + err = errors.BadRequest(reason.DisallowVoteYourSelf) + return + } + + if dto.IsCancel { + return as.voteRepo.VoteDownCancel(ctx, dto.ObjectID, dto.UserID, objectUserID) + } else { + return as.voteRepo.VoteDown(ctx, dto.ObjectID, dto.UserID, objectUserID) + } +} + +func (vs *VoteService) GetObjectUserId(ctx context.Context, objectID string) (userID string, err error) { + var objectKey string + objectKey, err = obj.GetObjectTypeStrByObjectID(objectID) + + if err != nil { + err = nil + return + } + + switch objectKey { + case "question": + object, has, e := vs.questionRepo.GetQuestion(ctx, objectID) + if e != nil || !has { + err = errors.BadRequest(reason.QuestionNotFound).WithError(e).WithStack() + return + } + userID = object.UserID + case "answer": + object, has, e := vs.answerRepo.GetAnswer(ctx, objectID) + if e != nil || !has { + err = errors.BadRequest(reason.AnswerNotFound).WithError(e).WithStack() + return + } + userID = object.UserID + case "comment": + object, has, e := vs.commentCommonRepo.GetComment(ctx, objectID) + if e != nil || !has { + err = errors.BadRequest(reason.CommentNotFound).WithError(e).WithStack() + return + } + userID = object.UserID + default: + err = errors.BadRequest(reason.DisallowVote).WithError(err).WithStack() + return + } + + return +} + +// ListUserVotes list user's votes +func (vs *VoteService) ListUserVotes(ctx context.Context, req schema.GetVoteWithPageReq) (model *pager.PageModel, err error) { + var ( + resp []schema.GetVoteWithPageResp + typeKeys = []string{ + "question.vote_up", + "question.vote_down", + "answer.vote_up", + "answer.vote_down", + } + activityTypes []int + ) + + for _, typeKey := range typeKeys { + t, err := vs.configRepo.GetConfigType(typeKey) + if err != nil { + continue + } + activityTypes = append(activityTypes, t) + } + + voteList, total, err := vs.voteRepo.ListUserVotes(ctx, req.UserID, req, activityTypes) + if err != nil { + return + } + + for _, voteInfo := range voteList { + objInfo, err := vs.objectService.GetInfo(ctx, voteInfo.ObjectID) + if err != nil { + log.Error(err) + } + + item := schema.GetVoteWithPageResp{ + CreatedAt: voteInfo.CreatedAt.Unix(), + ObjectID: objInfo.ObjectID, + QuestionID: objInfo.QuestionID, + AnswerID: objInfo.AnswerID, + ObjectType: objInfo.ObjectType, + Title: objInfo.Title, + Content: objInfo.Content, + VoteType: activity_type.Format(voteInfo.ActivityType), + } + resp = append(resp, item) + } + + return pager.NewPageModel(req.Page, req.PageSize, total, resp), err +} diff --git a/pkg/checker/chinese.go b/pkg/checker/chinese.go new file mode 100644 index 00000000..3a6ebd22 --- /dev/null +++ b/pkg/checker/chinese.go @@ -0,0 +1,14 @@ +package checker + +import "unicode" + +func IsChinese(str string) bool { + var count int + for _, v := range str { + if unicode.Is(unicode.Han, v) { + count++ + break + } + } + return count > 0 +} diff --git a/pkg/checker/password.go b/pkg/checker/password.go new file mode 100644 index 00000000..4b64ff60 --- /dev/null +++ b/pkg/checker/password.go @@ -0,0 +1,46 @@ +package checker + +import ( + "fmt" + "regexp" +) + +const ( + levelD = iota + LevelC + LevelB + LevelA + LevelS +) + +/* + * minLength: 指定密码的最小长度 + * maxLength:指定密码的最大长度 + * minLevel:指定密码最低要求的强度等级 + * pwd:明文密码 + */ +func PassWordCheck(minLength, maxLength, minLevel int, pwd string) error { + // 首先校验密码长度是否在范围内 + if len(pwd) < minLength { + return fmt.Errorf("BAD PASSWORD: The password is shorter than %d characters", minLength) + } + if len(pwd) > maxLength { + return fmt.Errorf("BAD PASSWORD: The password is logner than %d characters", maxLength) + } + + // 初始化密码强度等级为D,利用正则校验密码强度,若匹配成功则强度自增1 + var level int = levelD + patternList := []string{`[0-9]+`, `[a-z]+`, `[A-Z]+`, `[~!@#$%^&*?_-]+`} + for _, pattern := range patternList { + match, _ := regexp.MatchString(pattern, pwd) + if match { + level++ + } + } + + // 如果最终密码强度低于要求的最低强度,返回并报错 + if level < minLevel { + return fmt.Errorf("The password does not satisfy the current policy requirements. ") + } + return nil +} diff --git a/pkg/converter/str.go b/pkg/converter/str.go new file mode 100644 index 00000000..74d9b2ec --- /dev/null +++ b/pkg/converter/str.go @@ -0,0 +1,26 @@ +package converter + +import ( + "fmt" + "strconv" +) + +func StringToInt64(str string) int64 { + num, err := strconv.ParseInt(str, 10, 64) + if err != nil { + return 0 + } + return num +} + +func StringToInt(str string) int { + num, err := strconv.Atoi(str) + if err != nil { + return 0 + } + return num +} + +func IntToString(data int64) string { + return fmt.Sprintf("%d", data) +} diff --git a/pkg/dir/dir.go b/pkg/dir/dir.go new file mode 100644 index 00000000..fd6ee306 --- /dev/null +++ b/pkg/dir/dir.go @@ -0,0 +1,19 @@ +package dir + +import "os" + +func CreatePathIsNotExist(path string) (bool, error) { + _, err := os.Stat(path) + if err == nil { + return true, nil + } + if os.IsNotExist(err) { + // create directory + if err := os.MkdirAll(path, os.ModePerm); err != nil { + return false, err + } else { + return true, nil + } + } + return false, err +} diff --git a/pkg/encrypt/encrypt.go b/pkg/encrypt/encrypt.go new file mode 100644 index 00000000..5d658e64 --- /dev/null +++ b/pkg/encrypt/encrypt.go @@ -0,0 +1,82 @@ +package encrypt + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "fmt" + "io" + "net/url" +) + +// StringEncrypt +func StringEncrypt(message, key string) (encoded string, err error) { + //Create byte array from the input string + plainText := []byte(message) + + //Create a new AES cipher using the key + block, err := aes.NewCipher([]byte(key)) + + //IF NewCipher failed, exit: + if err != nil { + return + } + + //Make the cipher text a byte array of size BlockSize + the length of the message + cipherText := make([]byte, aes.BlockSize+len(plainText)) + + //iv is the ciphertext up to the blocksize (16) + iv := cipherText[:aes.BlockSize] + if _, err = io.ReadFull(rand.Reader, iv); err != nil { + return + } + + //Encrypt the data: + stream := cipher.NewCFBEncrypter(block, iv) + stream.XORKeyStream(cipherText[aes.BlockSize:], plainText) + + //Return string encoded in base64 + return base64.RawStdEncoding.EncodeToString(cipherText), err +} + +// StringDecrypt +func StringDecrypt(secure, key string) (decoded string, err error) { + //Remove base64 encoding: + cipherText, err := base64.RawStdEncoding.DecodeString(secure) + + //IF DecodeString failed, exit: + if err != nil { + return + } + + //Create a new AES cipher with the key and encrypted message + block, err := aes.NewCipher([]byte(key)) + + //IF NewCipher failed, exit: + if err != nil { + return + } + + //IF the length of the cipherText is less than 16 Bytes: + if len(cipherText) < aes.BlockSize { + err = fmt.Errorf("ciphertext block size is too short") + return + } + + iv := cipherText[:aes.BlockSize] + cipherText = cipherText[aes.BlockSize:] + + //Decrypt the message + stream := cipher.NewCFBDecrypter(block, iv) + stream.XORKeyStream(cipherText, cipherText) + + return string(cipherText), err +} + +func StringUrlEnCode(str string) string { + return url.QueryEscape(str) +} +func StringUrlDeCode(str string) (string, error) { + return url.QueryUnescape(str) +} diff --git a/pkg/library/captcha/captcha.go b/pkg/library/captcha/captcha.go new file mode 100644 index 00000000..5c278aff --- /dev/null +++ b/pkg/library/captcha/captcha.go @@ -0,0 +1,75 @@ +package captcha + +import ( + "fmt" + "image/color" + "sync" + + "github.com/mojocn/base64Captcha" +) + +var store base64Captcha.Store +var once sync.Once + +func NewCaptcha() { + once.Do(func() { + //var err error + //RedisDb, err = arch.App.Cache.GetQuestion("cache") + //if err != nil { + // store = base64Captcha.DefaultMemStore + // return + //} + //var ctx = context.Background() + //_, err = RedisDb.Ping(ctx).Result() + // + //if err != nil { + // store = base64Captcha.DefaultMemStore + // return + //} + store = RedisStore{} + }) +} + +// CaptchaClient +type CaptchaClient struct { +} + +// NewCaptchaClient +func NewCaptchaClient() *CaptchaClient { + return &CaptchaClient{} +} + +func MakeCaptcha() (id, b64s string, err error) { + var driver base64Captcha.Driver + //Configure the parameters of the CAPTCHA + driverString := base64Captcha.DriverString{ + Height: 40, + Width: 100, + NoiseCount: 0, + ShowLineOptions: 2 | 4, + Length: 4, + Source: "1234567890qwertyuioplkjhgfdsazxcvbnm", + BgColor: &color.RGBA{R: 3, G: 102, B: 214, A: 125}, + Fonts: []string{"wqy-microhei.ttc"}, + } + //ConvertFonts Load fonts by name + driver = driverString.ConvertFonts() + //Create Captcha + captcha := base64Captcha.NewCaptcha(driver, store) + //Generate + id, b64s, err = captcha.Generate() + return id, b64s, err + +} + +// VerifyCaptcha Verification code +func VerifyCaptcha(id string, VerifyValue string) bool { + fmt.Println(id, VerifyValue) + if store.Verify(id, VerifyValue, true) { + //verify successfully + return true + } else { + //Verification failed + return false + } +} diff --git a/pkg/library/captcha/redis.go b/pkg/library/captcha/redis.go new file mode 100644 index 00000000..3536b018 --- /dev/null +++ b/pkg/library/captcha/redis.go @@ -0,0 +1,46 @@ +package captcha + +import ( + "context" + "fmt" + + "github.com/segmentfault/pacman/contrib/cache/memory" +) + +const CAPTCHA = "captcha:" + +var RedisDb = memory.NewCache() + +type RedisStore struct { +} + +func (r RedisStore) Set(id string, value string) error { + key := CAPTCHA + id + ctx := context.Background() + err := RedisDb.SetString(ctx, key, value, 2) + return err +} + +func (r RedisStore) Get(id string, clear bool) string { + key := CAPTCHA + id + ctx := context.Background() + val, err := RedisDb.GetString(ctx, key) + if err != nil { + fmt.Println(err) + return "" + } + if clear { + err := RedisDb.Del(ctx, key) + if err != nil { + fmt.Println(err) + return "" + } + } + return val +} + +func (r RedisStore) Verify(id, answer string, clear bool) bool { + v := RedisStore{}.Get(id, clear) + fmt.Println("key:" + id + ";value:" + v + ";answer:" + answer) + return v == answer +} diff --git a/pkg/library/email/email.go b/pkg/library/email/email.go new file mode 100644 index 00000000..4f37070e --- /dev/null +++ b/pkg/library/email/email.go @@ -0,0 +1,103 @@ +package email + +import ( + "bytes" + "fmt" + "net/smtp" + "text/template" + + "github.com/jordan-wright/email" + "github.com/segmentfault/pacman/log" +) + +// EmailClient +type EmailClient struct { + email *email.Email + config *Config +} + +// Config . +type Config struct { + WebName string `json:"web_name"` + WebHost string `json:"web_host"` + SecretKey string `json:"secret_key"` + UserSessionKey string `json:"user_session_key"` + EmailFrom string `json:"email_from"` + EmailFromPass string `json:"email_from_pass"` + EmailFromHostname string `json:"email_from_hostname"` + EmailFromSMTP string `json:"email_from_smtp"` + EmailFromName string `json:"email_from_name"` + RegisterTitle string `json:"register_title"` + RegisterBody string `json:"register_body"` + PassResetTitle string `json:"pass_reset_title"` + PassResetBody string `json:"pass_reset_body"` +} + +// NewEmailClient +func NewEmailClient() *EmailClient { + return &EmailClient{ + email: email.NewEmail(), + } +} + +func (s *EmailClient) Send(ToEmail, Title, Body string) { + from := s.config.EmailFrom + fromPass := s.config.EmailFromPass + fromName := s.config.EmailFromName + fromSmtp := s.config.EmailFromSMTP + fromHostName := s.config.EmailFromHostname + s.email.From = fmt.Sprintf("%s <%s>", fromName, from) + s.email.To = []string{ToEmail} + s.email.Subject = Title + s.email.HTML = []byte(Body) + err := s.email.Send(fromSmtp, smtp.PlainAuth("", from, fromPass, fromHostName)) + if err != nil { + log.Error(err) + } +} + +func (s *EmailClient) RegisterTemplate(RegisterUrl string) (Title, Body string, err error) { + webName := s.config.WebName + templateData := RegisterTemplateData{webName, RegisterUrl} + tmpl, err := template.New("register_title").Parse(s.config.RegisterTitle) + if err != nil { + return "", "", err + } + title := new(bytes.Buffer) + body := new(bytes.Buffer) + err = tmpl.Execute(title, templateData) + if err != nil { + return "", "", err + } + + tmpl, err = template.New("register_body").Parse(s.config.RegisterBody) + err = tmpl.Execute(body, templateData) + if err != nil { + return "", "", err + } + + return title.String(), body.String(), nil +} + +func (s *EmailClient) PassResetTemplate(PassResetUrl string) (Title, Body string, err error) { + webName := s.config.WebName + templateData := PassResetTemplateData{webName, PassResetUrl} + tmpl, err := template.New("pass_reset_title").Parse(s.config.PassResetTitle) + if err != nil { + return "", "", err + } + title := new(bytes.Buffer) + body := new(bytes.Buffer) + err = tmpl.Execute(title, templateData) + if err != nil { + return "", "", err + } + + tmpl, err = template.New("pass_reset_body").Parse(s.config.PassResetBody) + err = tmpl.Execute(body, templateData) + if err != nil { + return "", "", err + } + + return title.String(), body.String(), nil +} diff --git a/pkg/library/email/models.go b/pkg/library/email/models.go new file mode 100644 index 00000000..315c5386 --- /dev/null +++ b/pkg/library/email/models.go @@ -0,0 +1,11 @@ +package email + +type RegisterTemplateData struct { + SiteName string + RegisterUrl string +} + +type PassResetTemplateData struct { + SiteName string + PassResetUrl string +} diff --git a/pkg/obj/obj.go b/pkg/obj/obj.go new file mode 100644 index 00000000..3c62ebc9 --- /dev/null +++ b/pkg/obj/obj.go @@ -0,0 +1,36 @@ +package obj + +import ( + "github.com/segmentfault/answer/internal/base/constant" + "github.com/segmentfault/answer/internal/base/reason" + "github.com/segmentfault/answer/pkg/converter" + "github.com/segmentfault/pacman/errors" +) + +// GetObjectTypeStrByObjectID get object key by object id +func GetObjectTypeStrByObjectID(objectID string) (objectTypeStr string, err error) { + if err := checkObjectID(objectID); err != nil { + return "", err + } + objectTypeNumber := converter.StringToInt(objectID[1:4]) + objectTypeStr, ok := constant.ObjectTypeNumberMapping[objectTypeNumber] + if ok { + return objectTypeStr, nil + } + return "", errors.BadRequest(reason.ObjectNotFound) +} + +// GetObjectTypeNumberByObjectID get object type by object id +func GetObjectTypeNumberByObjectID(objectID string) (objectTypeNumber int, err error) { + if err := checkObjectID(objectID); err != nil { + return 0, err + } + return converter.StringToInt(objectID[1:4]), nil +} + +func checkObjectID(objectID string) (err error) { + if len(objectID) < 5 { + return errors.BadRequest(reason.ObjectNotFound) + } + return nil +} diff --git a/pkg/token/token.go b/pkg/token/token.go new file mode 100644 index 00000000..7e675304 --- /dev/null +++ b/pkg/token/token.go @@ -0,0 +1,9 @@ +package token + +import "github.com/google/uuid" + +// GenerateToken generate token +func GenerateToken() string { + uid, _ := uuid.NewUUID() + return uid.String() +} diff --git a/pkg/uid/id.go b/pkg/uid/id.go new file mode 100644 index 00000000..bd29f083 --- /dev/null +++ b/pkg/uid/id.go @@ -0,0 +1,40 @@ +package uid + +import ( + "math/rand" + "time" + + "github.com/bwmarrin/snowflake" +) + +// SnowFlakeID snowflake id +type SnowFlakeID struct { + *snowflake.Node +} + +var snowFlakeIDGenerator *SnowFlakeID + +func init() { + //todo + rand.Seed(time.Now().UnixNano()) + node, err := snowflake.NewNode(int64(rand.Intn(1000)) + 1) + if err != nil { + panic(err.Error()) + } + snowFlakeIDGenerator = &SnowFlakeID{node} +} + +func ID() snowflake.ID { + id := snowFlakeIDGenerator.Generate() + return id +} + +func IDStr12() string { + id := snowFlakeIDGenerator.Generate() + return id.Base58() +} + +func IDStr() string { + id := snowFlakeIDGenerator.Generate() + return id.Base32() +} diff --git a/script/gen-api.sh b/script/gen-api.sh new file mode 100755 index 00000000..f76b6f7a --- /dev/null +++ b/script/gen-api.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd ../ +swag init --generalInfo ./cmd/answer/main.go diff --git a/script/prebuild.sh b/script/prebuild.sh new file mode 100644 index 00000000..6c7da8a7 --- /dev/null +++ b/script/prebuild.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +git clone https://git.backyard.segmentfault.com/opensource/pacman.git /tmp/sf-pacman + +cat <<'EOF' > go.work +go 1.18 + +use ( + . + /tmp/sf-pacman + /tmp/sf-pacman/contrib/cache/redis + /tmp/sf-pacman/contrib/cache/memory + /tmp/sf-pacman/contrib/conf/viper + /tmp/sf-pacman/contrib/log/zap + /tmp/sf-pacman/contrib/i18n + /tmp/sf-pacman/contrib/server/http +) +EOF + +go work sync diff --git a/ui/.browserslistrc b/ui/.browserslistrc new file mode 100644 index 00000000..ff4a4ecc --- /dev/null +++ b/ui/.browserslistrc @@ -0,0 +1,12 @@ +[production] +> 20% +not dead +not op_mini all + +[development] +last 1 chrome version +last 1 firefox version +last 1 safari version + +[ssr] +node 16 diff --git a/ui/.editorconfig b/ui/.editorconfig new file mode 100644 index 00000000..ff673e09 --- /dev/null +++ b/ui/.editorconfig @@ -0,0 +1,18 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 80 + + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/ui/.env b/ui/.env new file mode 100644 index 00000000..a41cd62e --- /dev/null +++ b/ui/.env @@ -0,0 +1,2 @@ +REACT_APP_API_URL=/ +CDN_PATH=/ diff --git a/ui/.env.development b/ui/.env.development new file mode 100644 index 00000000..fb45a724 --- /dev/null +++ b/ui/.env.development @@ -0,0 +1,2 @@ +REACT_APP_API_URL=http://10.0.10.98:2060 +CDN_PATH=https://cdn.dev.segmentfault.com/// diff --git a/ui/.env.production b/ui/.env.production new file mode 100644 index 00000000..03a22953 --- /dev/null +++ b/ui/.env.production @@ -0,0 +1,2 @@ +REACT_APP_API_URL=http://10.0.10.98:2060 +CDN_PATH=https://static.segmentfault.com/// diff --git a/ui/.env.test b/ui/.env.test new file mode 100644 index 00000000..aed51498 --- /dev/null +++ b/ui/.env.test @@ -0,0 +1,2 @@ +REACT_APP_LANG = en +CDN_PATH=https://static-test.segmentfault.com/// diff --git a/ui/.eslintignore b/ui/.eslintignore new file mode 100644 index 00000000..a93b8236 --- /dev/null +++ b/ui/.eslintignore @@ -0,0 +1,7 @@ +public +config-overrides.js +commitlint.config.js +build +.eslintrc.js +node_modules/ +src/types/ diff --git a/ui/.eslintrc.js b/ui/.eslintrc.js new file mode 100644 index 00000000..877a0039 --- /dev/null +++ b/ui/.eslintrc.js @@ -0,0 +1,87 @@ +module.exports = { + env: { + browser: true, + es2021: true, + }, + extends: [ + 'react-app', + 'react-app/jest', + 'airbnb', + 'airbnb-typescript', + 'plugin:import/typescript', + 'plugin:prettier/recommended', + ], + overrides: [], + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 'latest', + sourceType: 'module', + project: './tsconfig.json', + }, + plugins: ['react', '@typescript-eslint'], + rules: { + 'no-unused-vars': 'off', + 'no-console': 'off', + 'import/prefer-default-export': 'off', + 'no-param-reassign': 'off', + 'react/react-in-jsx-scope': 'off', + 'react/function-component-definition': 'off', + 'react/button-has-type': 'off', + 'react/no-unescaped-entities': 'off', + 'react/require-default-props': 'off', + 'arrow-body-style': 'off', + 'react/prop-types': 0, + 'react/no-danger': 'off', + 'jsx-a11y/no-static-element-interactions': 'off', + 'jsx-a11y/label-has-associated-control': 'off', + 'jsx-a11y/tabindex-no-positive': 'off', + 'func-names': 'off', + 'no-alert': 'off', + 'prefer-promise-reject-errors': 'off', + '@typescript-eslint/naming-convention': 'off', + 'no-debugger': 'off', + 'max-len': 'off', + 'import/extensions': 'off', + 'react-hooks/exhaustive-deps': 'off', + 'react/jsx-props-no-spreading': 'off', + '@typescript-eslint/default-param-last': 'off', + 'import/order': [ + 'error', + { + groups: [ + 'builtin', + 'external', + ['internal', 'parent', 'sibling', 'index'], + 'unknown', + ], + pathGroups: [ + { + pattern: 'react*', + group: 'external', + position: 'before', + }, + { + pattern: '@answer/**', + group: 'internal', + }, + { + pattern: './**', + group: 'internal', + position: 'after', + }, + { + pattern: '*.scss', + patternOptions: { matchBase: true }, + group: 'unknown', + position: 'after', + }, + ], + pathGroupsExcludedImportTypes: ['react'], + 'newlines-between': 'always', + }, + ], + }, +}; diff --git a/ui/.gitignore b/ui/.gitignore new file mode 100644 index 00000000..c4796f76 --- /dev/null +++ b/ui/.gitignore @@ -0,0 +1,32 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* +Thumbs.db +.idea/ +*.sublime-project +*.sublime-workspace +*.log +yarn.lock +package-lock.json +.eslintcache +/.vscode/ diff --git a/ui/.gitlab-ci.yml b/ui/.gitlab-ci.yml new file mode 100644 index 00000000..df03dd15 --- /dev/null +++ b/ui/.gitlab-ci.yml @@ -0,0 +1,93 @@ +include: + - project: 'segmentfault/devops/templates' + file: + - .deploy-cdn.yml + - .deploy-helm.yml + +variables: + FF_USE_FASTZIP: 'true' + PROJECT_NAME: 'answer_static' + +stages: + - install + - publish + - deploy + +# 静态资源构建 +install: + image: dockerhub.qingcloud.com/sf_base/node-build:14 + stage: install + allow_failure: false + + cache: + - key: + files: + - pnpm-lock.yml + paths: + - node_modules/ + policy: pull-push + script: + - pnpm install + - if [ "$CI_COMMIT_BRANCH" = "dev" ]; then + sed -i "s//$PROJECT_NAME/g" .env.development; + sed -i "s//$CI_COMMIT_SHORT_SHA/g" .env.development; + pnpm run build:dev; + elif [ "$CI_COMMIT_BRANCH" = "main" ]; then + sed -i "s//$PROJECT_NAME/g" .env.test; + sed -i "s//$CI_COMMIT_SHORT_SHA/g" .env.test; + pnpm run build:test; + elif [ "$CI_COMMIT_BRANCH" = "release" ]; then + sed -i "s//$PROJECT_NAME/g" .env.production; + sed -i "s//$CI_COMMIT_SHORT_SHA/g" .env.production; + pnpm run build:prod; + fi + artifacts: + paths: + - build/ + +publish:cdn:dev: + extends: .deploy-cdn + stage: publish + only: + - dev + variables: + AssetsPath: ./build + Project: $PROJECT_NAME + Version: $CI_COMMIT_SHORT_SHA + Destination: dev + +publish:cdn:test: + extends: .deploy-cdn + stage: publish + only: + - main + variables: + AssetsPath: ./build + Project: $PROJECT_NAME + Version: $CI_COMMIT_SHORT_SHA + Destination: test + +publish:cdn:prod: + extends: .deploy-cdn + stage: publish + only: + - release + variables: + AssetsPath: ./build + Project: $PROJECT_NAME + Version: $CI_COMMIT_SHORT_SHA + Destination: prod + +deploy:dev: + extends: .deploy-helm + stage: deploy + only: + - dev + needs: + - publish:cdn:dev + variables: + KubernetesCluster: dev + KubernetesNamespace: 'sf-test' + DockerTag: $CI_COMMIT_SHORT_SHA + ChartName: answer-web + InstallPolicy: replace diff --git a/ui/.husky/commit-msg b/ui/.husky/commit-msg new file mode 100755 index 00000000..5426a932 --- /dev/null +++ b/ui/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx commitlint --edit $1 diff --git a/ui/.husky/pre-commit b/ui/.husky/pre-commit new file mode 100755 index 00000000..36af2198 --- /dev/null +++ b/ui/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx lint-staged diff --git a/ui/.lintstagedrc.json b/ui/.lintstagedrc.json new file mode 100644 index 00000000..8f3e37e6 --- /dev/null +++ b/ui/.lintstagedrc.json @@ -0,0 +1,5 @@ +{ + "*.{js,jsx,ts,tsx}": ["eslint --cache --fix"], + "*.{js,jsx,less,md,json}": ["prettier --write"], + "*.ts?(x)": ["prettier --parser=typescript --write"] +} diff --git a/ui/.prettierrc.json b/ui/.prettierrc.json new file mode 100644 index 00000000..83fdbcaa --- /dev/null +++ b/ui/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "trailingComma": "all", + "tabWidth": 2, + "singleQuote": true, + "jsxBracketSameLine": true, + "printWidth": 80 +} diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md new file mode 100644 index 00000000..e69de29b diff --git a/ui/LICENSE b/ui/LICENSE new file mode 100644 index 00000000..05e79fe1 --- /dev/null +++ b/ui/LICENSE @@ -0,0 +1,9 @@ +# The MIT License + +Copyright 2022 robin, contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/ui/README.md b/ui/README.md new file mode 100644 index 00000000..0d2a9d9d --- /dev/null +++ b/ui/README.md @@ -0,0 +1,64 @@ +# Answer + +`Answer` is a modern Q&A community application ✨ + +To learn more about the philosophy and goals of the project, visit [Answer](https://answer.dev.segmentfault.com). + +### 📦 Prerequisites + +- [Node.js](https://nodejs.org/) `>=14` +- [pnpm](https://pnpm.io/) `>=7` + +pnpm is required by building the Answer project. To installing the pnpm tools with below commands: + +```bash +corepack enable +corepack prepare pnpm@v7.12.2 --activate +``` + +With Node.js v16.17 or newer, you may install the latest version of pnpm by just specifying the tag: + +```bash +corepack prepare pnpm@latest --activate +``` + +## 🔨 Development + +clone the repo locally and run following command in your terminal: + +```shell +$ git clone `answer repo` answer +$ cd answer +$ pnpm install +$ pnpm run start +``` + +now, your browser should already open automatically, and autoload `http://localhost:3000`. +you can also manually visit it. + +## 👷 Workflow + +when cloning repo, and run `pnpm install` to init dependencies. you can use project commands below: + +- `pnpm run start` run Answer web locally. +- `pnpm run build:dev` build code for environment `dev` +- `pnpm run build:test` build code for environment `test` +- `pnpm run build:prod` build code for environment `prod` +- `pnpm run lint` lint and fix the code style +- `pnpm run cz` run `git commit` by `commitizen` + +## 🖥 Environment Support + +| [IE / Edge](http://godban.github.io/browsers-support-badges/)
Edge / IE | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | +| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Edge, IE11 | last 2 versions | last 2 versions | last 2 versions | + +## Build with + +- [React.js](https://reactjs.org/) - Our front end is a React.js app. +- [Bootstrap](https://getbootstrap.com/) - UI library. +- [React Bootstrap](https://react-bootstrap.github.io/) - UI Library(rebuilt for React.) +- [axios](https://github.com/axios/axios) - Request library +- [SWR](https://swr.bootcss.com/) - Request library +- [react-i18next](https://react.i18next.com/) - International library +- [zustand](https://github.com/pmndrs/zustand) - State-management library diff --git a/ui/commitlint.config.js b/ui/commitlint.config.js new file mode 100644 index 00000000..84dcb122 --- /dev/null +++ b/ui/commitlint.config.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], +}; diff --git a/ui/config-overrides.js b/ui/config-overrides.js new file mode 100644 index 00000000..65e8a206 --- /dev/null +++ b/ui/config-overrides.js @@ -0,0 +1,37 @@ +const path = require('path'); + +module.exports = { + webpack: function (config, env) { + if (process.env.NODE_ENV === 'production') { + config.output.publicPath = process.env.CDN_PATH; + } + + config.resolve.alias = { + ...config.resolve.alias, + '@': path.resolve(__dirname, 'src'), + '@answer/pages': path.resolve(__dirname, 'src/pages'), + '@answer/components': path.resolve(__dirname, 'src/components'), + '@answer/stores': path.resolve(__dirname, 'src/stores'), + '@answer/hooks': path.resolve(__dirname, 'src/hooks'), + '@answer/utils': path.resolve(__dirname, 'src/utils'), + '@answer/common': path.resolve(__dirname, 'src/common'), + '@answer/services': path.resolve(__dirname, 'src/services'), + }; + + return config; + }, + + devServer: function (configFunction) { + return function (proxy, allowedHost) { + const config = configFunction(proxy, allowedHost); + config.proxy = { + '/answer': { + target: 'http://10.0.10.98:2060', + changeOrigin: true, + secure: false, + }, + }; + return config; + }; + }, +}; diff --git a/ui/package.json b/ui/package.json new file mode 100644 index 00000000..338a9eb4 --- /dev/null +++ b/ui/package.json @@ -0,0 +1,109 @@ +{ + "name": "answer-static", + "version": "0.1.0", + "private": true, + "homepage": ".", + "scripts": { + "start": "react-app-rewired start", + "build:dev": "env-cmd -f .env.development react-app-rewired build", + "build:test": "env-cmd -f .env.test react-app-rewired build", + "build:prod": "env-cmd -f .env.production react-app-rewired build", + "build": "env-cmd -f .env react-app-rewired build", + "test": "react-app-rewired test", + "eject": "react-scripts eject", + "lint": "eslint . --cache --fix --ext .ts,.tsx", + "cz": "cz", + "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0", + "prettier": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,scss,md}\"" + }, + "config": { + "commitizen": { + "path": "./node_modules/cz-conventional-changelog" + } + }, + "dependencies": { + "@testing-library/jest-dom": "^4.2.4", + "ahooks": "^3.7.0", + "axios": "^0.27.2", + "bootstrap": "^5.2.0", + "bootstrap-icons": "^1.9.1", + "classnames": "^2.3.1", + "codemirror": "5.65.0", + "copy-to-clipboard": "^3.3.2", + "dayjs": "^1.11.5", + "highlight.js": "^11.6.0", + "i18next": "^21.9.0", + "i18next-chained-backend": "^3.0.2", + "i18next-http-backend": "^1.4.1", + "i18next-localstorage-backend": "^3.1.3", + "katex": "^0.16.2", + "lodash": "^4.17.21", + "marked": "^4.0.19", + "mermaid": "^9.1.7", + "next-share": "^0.18.1", + "qs": "^6.11.0", + "react": "^18.2.0", + "react-bootstrap": "^2.5.0", + "react-dom": "^18.2.0", + "react-helmet": "^6.1.0", + "react-i18next": "^11.18.3", + "react-router-dom": "^6.4.0", + "swr": "^1.3.0", + "zustand": "^4.1.1" + }, + "devDependencies": { + "@babel/core": "^7.18.10", + "@babel/plugin-syntax-flow": "^7.18.6", + "@babel/plugin-transform-react-jsx": "^7.14.9", + "@commitlint/cli": "^17.0.3", + "@commitlint/config-conventional": "^17.0.3", + "@fullhuman/postcss-purgecss": "^4.1.3", + "@popperjs/core": "^2.11.5", + "@testing-library/dom": "^8.17.1", + "@testing-library/react": "^13.3.0", + "@testing-library/user-event": "^13.5.0", + "@types/jest": "^27.5.2", + "@types/lodash": "^4.14.184", + "@types/marked": "^4.0.6", + "@types/node": "^16.11.47", + "@types/qs": "^6.9.7", + "@types/react": "^18.0.17", + "@types/react-dom": "^18.0.6", + "@types/react-helmet": "^6.1.5", + "@typescript-eslint/eslint-plugin": "^5.0.0", + "@typescript-eslint/parser": "^5.33.0", + "commitizen": "^4.2.5", + "conventional-changelog-cli": "^2.2.2", + "customize-cra": "^1.0.0", + "cz-conventional-changelog": "^3.3.0", + "env-cmd": "^10.1.0", + "eslint": "^8.0.1", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-airbnb-typescript": "^17.0.0", + "eslint-config-prettier": "^8.5.0", + "eslint-config-standard-with-typescript": "^22.0.0", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-jsx-a11y": "^6.6.1", + "eslint-plugin-n": "^15.0.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-promise": "^6.0.0", + "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react-hooks": "^4.6.0", + "husky": "^8.0.1", + "lint-staged": "^13.0.3", + "postcss": "^8.0.0", + "prettier": "^2.7.1", + "purgecss-webpack-plugin": "^4.1.3", + "react-app-rewired": "^2.2.1", + "react-scripts": "5.0.1", + "sass": "^1.54.4", + "tsconfig-paths-webpack-plugin": "^4.0.0", + "typescript": "*", + "web-vitals": "^2.1.4" + }, + "license": "MIT", + "engines": { + "node": ">=14", + "pnpm": ">=7" + } +} diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml new file mode 100644 index 00000000..c6bfcd1e --- /dev/null +++ b/ui/pnpm-lock.yaml @@ -0,0 +1,11723 @@ +lockfileVersion: 5.4 + +specifiers: + '@babel/core': ^7.18.10 + '@babel/plugin-syntax-flow': ^7.18.6 + '@babel/plugin-transform-react-jsx': ^7.14.9 + '@commitlint/cli': ^17.0.3 + '@commitlint/config-conventional': ^17.0.3 + '@fullhuman/postcss-purgecss': ^4.1.3 + '@popperjs/core': ^2.11.5 + '@testing-library/dom': ^8.17.1 + '@testing-library/jest-dom': ^4.2.4 + '@testing-library/react': ^13.3.0 + '@testing-library/user-event': ^13.5.0 + '@types/jest': ^27.5.2 + '@types/lodash': ^4.14.184 + '@types/marked': ^4.0.6 + '@types/node': ^16.11.47 + '@types/qs': ^6.9.7 + '@types/react': ^18.0.17 + '@types/react-dom': ^18.0.6 + '@types/react-helmet': ^6.1.5 + '@typescript-eslint/eslint-plugin': ^5.0.0 + '@typescript-eslint/parser': ^5.33.0 + ahooks: ^3.7.0 + axios: ^0.27.2 + bootstrap: ^5.2.0 + bootstrap-icons: ^1.9.1 + classnames: ^2.3.1 + codemirror: 5.65.0 + commitizen: ^4.2.5 + conventional-changelog-cli: ^2.2.2 + copy-to-clipboard: ^3.3.2 + customize-cra: ^1.0.0 + cz-conventional-changelog: ^3.3.0 + dayjs: ^1.11.5 + env-cmd: ^10.1.0 + eslint: ^8.0.1 + eslint-config-airbnb: ^19.0.4 + eslint-config-airbnb-typescript: ^17.0.0 + eslint-config-prettier: ^8.5.0 + eslint-config-standard-with-typescript: ^22.0.0 + eslint-plugin-import: ^2.25.2 + eslint-plugin-jsx-a11y: ^6.6.1 + eslint-plugin-n: ^15.0.0 + eslint-plugin-prettier: ^4.2.1 + eslint-plugin-promise: ^6.0.0 + eslint-plugin-react: ^7.30.1 + eslint-plugin-react-hooks: ^4.6.0 + highlight.js: ^11.6.0 + husky: ^8.0.1 + i18next: ^21.9.0 + i18next-chained-backend: ^3.0.2 + i18next-http-backend: ^1.4.1 + i18next-localstorage-backend: ^3.1.3 + katex: ^0.16.2 + lint-staged: ^13.0.3 + lodash: ^4.17.21 + marked: ^4.0.19 + mermaid: ^9.1.7 + next-share: ^0.18.1 + postcss: ^8.0.0 + prettier: ^2.7.1 + purgecss-webpack-plugin: ^4.1.3 + qs: ^6.11.0 + react: ^18.2.0 + react-app-rewired: ^2.2.1 + react-bootstrap: ^2.5.0 + react-dom: ^18.2.0 + react-helmet: ^6.1.0 + react-i18next: ^11.18.3 + react-router-dom: ^6.4.0 + react-scripts: 5.0.1 + sass: ^1.54.4 + swr: ^1.3.0 + tsconfig-paths-webpack-plugin: ^4.0.0 + typescript: '*' + web-vitals: ^2.1.4 + zustand: ^4.1.1 + +dependencies: + '@testing-library/jest-dom': 4.2.4 + ahooks: 3.7.1_react@18.2.0 + axios: 0.27.2 + bootstrap: 5.2.1_@popperjs+core@2.11.6 + bootstrap-icons: 1.9.1 + classnames: 2.3.2 + codemirror: 5.65.0 + copy-to-clipboard: 3.3.2 + dayjs: 1.11.5 + highlight.js: 11.6.0 + i18next: 21.9.2 + i18next-chained-backend: 3.1.0 + i18next-http-backend: 1.4.4 + i18next-localstorage-backend: 3.1.3 + katex: 0.16.2 + lodash: 4.17.21 + marked: 4.1.0 + mermaid: 9.1.7 + next-share: 0.18.1_lbqamd2wfmenkveygahn4wdfcq + qs: 6.11.0 + react: 18.2.0 + react-bootstrap: 2.5.0_7ey2zzynotv32rpkwno45fsx4e + react-dom: 18.2.0_react@18.2.0 + react-helmet: 6.1.0_react@18.2.0 + react-i18next: 11.18.6_ulhmqqxshznzmtuvahdi5nasbq + react-router-dom: 6.4.0_biqbaboplfbrettd7655fr4n2y + swr: 1.3.0_react@18.2.0 + zustand: 4.1.1_react@18.2.0 + +devDependencies: + '@babel/core': 7.19.1 + '@babel/plugin-syntax-flow': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-react-jsx': 7.19.0_@babel+core@7.19.1 + '@commitlint/cli': 17.1.2 + '@commitlint/config-conventional': 17.1.0 + '@fullhuman/postcss-purgecss': 4.1.3_postcss@8.4.16 + '@popperjs/core': 2.11.6 + '@testing-library/dom': 8.18.1 + '@testing-library/react': 13.4.0_biqbaboplfbrettd7655fr4n2y + '@testing-library/user-event': 13.5.0_znccgeejomvff3jrsk3ljovfpu + '@types/jest': 27.5.2 + '@types/lodash': 4.14.185 + '@types/marked': 4.0.7 + '@types/node': 16.11.59 + '@types/qs': 6.9.7 + '@types/react': 18.0.20 + '@types/react-dom': 18.0.6 + '@types/react-helmet': 6.1.5 + '@typescript-eslint/eslint-plugin': 5.38.0_wsb62dxj2oqwgas4kadjymcmry + '@typescript-eslint/parser': 5.38.0_irgkl5vooow2ydyo6aokmferha + commitizen: 4.2.5 + conventional-changelog-cli: 2.2.2 + customize-cra: 1.0.0 + cz-conventional-changelog: 3.3.0 + env-cmd: 10.1.0 + eslint: 8.23.1 + eslint-config-airbnb: 19.0.4_4zstfqq5uopk5xuvotejlnl36y + eslint-config-airbnb-typescript: 17.0.0_j57hrpt2hfp47otngkwtnuyxpa + eslint-config-prettier: 8.5.0_eslint@8.23.1 + eslint-config-standard-with-typescript: 22.0.0_fsqc7gnfr7ufpr4slszrtm5abq + eslint-plugin-import: 2.26.0_cxqatnnjiq7ozd2bkspxnuicdq + eslint-plugin-jsx-a11y: 6.6.1_eslint@8.23.1 + eslint-plugin-n: 15.2.5_eslint@8.23.1 + eslint-plugin-prettier: 4.2.1_cabrci5exjdaojcvd6xoxgeowu + eslint-plugin-promise: 6.0.1_eslint@8.23.1 + eslint-plugin-react: 7.31.8_eslint@8.23.1 + eslint-plugin-react-hooks: 4.6.0_eslint@8.23.1 + husky: 8.0.1 + lint-staged: 13.0.3 + postcss: 8.4.16 + prettier: 2.7.1 + purgecss-webpack-plugin: 4.1.3 + react-app-rewired: 2.2.1_react-scripts@5.0.1 + react-scripts: 5.0.1_r727nmttzgvwuocpb6eyxi2m5i + sass: 1.54.9 + tsconfig-paths-webpack-plugin: 4.0.0 + typescript: 4.8.3 + web-vitals: 2.1.4 + +packages: + + /@ampproject/remapping/2.2.0: + resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/gen-mapping': 0.1.1 + '@jridgewell/trace-mapping': 0.3.15 + + /@apideck/better-ajv-errors/0.3.6_ajv@8.11.0: + resolution: {integrity: sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==} + engines: {node: '>=10'} + peerDependencies: + ajv: '>=8' + dependencies: + ajv: 8.11.0 + json-schema: 0.4.0 + jsonpointer: 5.0.1 + leven: 3.1.0 + + /@babel/code-frame/7.18.6: + resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.18.6 + + /@babel/compat-data/7.19.1: + resolution: {integrity: sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg==} + engines: {node: '>=6.9.0'} + + /@babel/core/7.19.1: + resolution: {integrity: sha512-1H8VgqXme4UXCRv7/Wa1bq7RVymKOzC7znjyFM8KiEzwFqcKUKYNoQef4GhdklgNvoBXyW4gYhuBNCM5o1zImw==} + engines: {node: '>=6.9.0'} + dependencies: + '@ampproject/remapping': 2.2.0 + '@babel/code-frame': 7.18.6 + '@babel/generator': 7.19.0 + '@babel/helper-compilation-targets': 7.19.1_@babel+core@7.19.1 + '@babel/helper-module-transforms': 7.19.0 + '@babel/helpers': 7.19.0 + '@babel/parser': 7.19.1 + '@babel/template': 7.18.10 + '@babel/traverse': 7.19.1 + '@babel/types': 7.19.0 + convert-source-map: 1.8.0 + debug: 4.3.4 + gensync: 1.0.0-beta.2 + json5: 2.2.1 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + + /@babel/eslint-parser/7.19.1_zdglor7vg7osicr5spasq6cc5a: + resolution: {integrity: sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==} + engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} + peerDependencies: + '@babel/core': '>=7.11.0' + eslint: ^7.5.0 || ^8.0.0 + dependencies: + '@babel/core': 7.19.1 + '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 + eslint: 8.23.1 + eslint-visitor-keys: 2.1.0 + semver: 6.3.0 + + /@babel/generator/7.19.0: + resolution: {integrity: sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.19.0 + '@jridgewell/gen-mapping': 0.3.2 + jsesc: 2.5.2 + + /@babel/helper-annotate-as-pure/7.18.6: + resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.19.0 + + /@babel/helper-builder-binary-assignment-operator-visitor/7.18.9: + resolution: {integrity: sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-explode-assignable-expression': 7.18.6 + '@babel/types': 7.19.0 + + /@babel/helper-compilation-targets/7.19.1_@babel+core@7.19.1: + resolution: {integrity: sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/compat-data': 7.19.1 + '@babel/core': 7.19.1 + '@babel/helper-validator-option': 7.18.6 + browserslist: 4.21.4 + semver: 6.3.0 + + /@babel/helper-create-class-features-plugin/7.19.0_@babel+core@7.19.1: + resolution: {integrity: sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-function-name': 7.19.0 + '@babel/helper-member-expression-to-functions': 7.18.9 + '@babel/helper-optimise-call-expression': 7.18.6 + '@babel/helper-replace-supers': 7.19.1 + '@babel/helper-split-export-declaration': 7.18.6 + transitivePeerDependencies: + - supports-color + + /@babel/helper-create-regexp-features-plugin/7.19.0_@babel+core@7.19.1: + resolution: {integrity: sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-annotate-as-pure': 7.18.6 + regexpu-core: 5.2.1 + + /@babel/helper-define-polyfill-provider/0.3.3_@babel+core@7.19.1: + resolution: {integrity: sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==} + peerDependencies: + '@babel/core': ^7.4.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-compilation-targets': 7.19.1_@babel+core@7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + debug: 4.3.4 + lodash.debounce: 4.0.8 + resolve: 1.22.1 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + + /@babel/helper-environment-visitor/7.18.9: + resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} + engines: {node: '>=6.9.0'} + + /@babel/helper-explode-assignable-expression/7.18.6: + resolution: {integrity: sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.19.0 + + /@babel/helper-function-name/7.19.0: + resolution: {integrity: sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.18.10 + '@babel/types': 7.19.0 + + /@babel/helper-hoist-variables/7.18.6: + resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.19.0 + + /@babel/helper-member-expression-to-functions/7.18.9: + resolution: {integrity: sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.19.0 + + /@babel/helper-module-imports/7.18.6: + resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.19.0 + + /@babel/helper-module-transforms/7.19.0: + resolution: {integrity: sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-module-imports': 7.18.6 + '@babel/helper-simple-access': 7.18.6 + '@babel/helper-split-export-declaration': 7.18.6 + '@babel/helper-validator-identifier': 7.19.1 + '@babel/template': 7.18.10 + '@babel/traverse': 7.19.1 + '@babel/types': 7.19.0 + transitivePeerDependencies: + - supports-color + + /@babel/helper-optimise-call-expression/7.18.6: + resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.19.0 + + /@babel/helper-plugin-utils/7.19.0: + resolution: {integrity: sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==} + engines: {node: '>=6.9.0'} + + /@babel/helper-remap-async-to-generator/7.18.9_@babel+core@7.19.1: + resolution: {integrity: sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-wrap-function': 7.19.0 + '@babel/types': 7.19.0 + transitivePeerDependencies: + - supports-color + + /@babel/helper-replace-supers/7.19.1: + resolution: {integrity: sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-member-expression-to-functions': 7.18.9 + '@babel/helper-optimise-call-expression': 7.18.6 + '@babel/traverse': 7.19.1 + '@babel/types': 7.19.0 + transitivePeerDependencies: + - supports-color + + /@babel/helper-simple-access/7.18.6: + resolution: {integrity: sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.19.0 + + /@babel/helper-skip-transparent-expression-wrappers/7.18.9: + resolution: {integrity: sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.19.0 + + /@babel/helper-split-export-declaration/7.18.6: + resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.19.0 + + /@babel/helper-string-parser/7.18.10: + resolution: {integrity: sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==} + engines: {node: '>=6.9.0'} + + /@babel/helper-validator-identifier/7.19.1: + resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} + engines: {node: '>=6.9.0'} + + /@babel/helper-validator-option/7.18.6: + resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==} + engines: {node: '>=6.9.0'} + + /@babel/helper-wrap-function/7.19.0: + resolution: {integrity: sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-function-name': 7.19.0 + '@babel/template': 7.18.10 + '@babel/traverse': 7.19.1 + '@babel/types': 7.19.0 + transitivePeerDependencies: + - supports-color + + /@babel/helpers/7.19.0: + resolution: {integrity: sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.18.10 + '@babel/traverse': 7.19.1 + '@babel/types': 7.19.0 + transitivePeerDependencies: + - supports-color + + /@babel/highlight/7.18.6: + resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.19.1 + chalk: 2.4.2 + js-tokens: 4.0.0 + + /@babel/parser/7.19.1: + resolution: {integrity: sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.19.0 + + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/7.18.9_@babel+core@7.19.1: + resolution: {integrity: sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-skip-transparent-expression-wrappers': 7.18.9 + '@babel/plugin-proposal-optional-chaining': 7.18.9_@babel+core@7.19.1 + + /@babel/plugin-proposal-async-generator-functions/7.19.1_@babel+core@7.19.1: + resolution: {integrity: sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-remap-async-to-generator': 7.18.9_@babel+core@7.19.1 + '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.19.1 + transitivePeerDependencies: + - supports-color + + /@babel/plugin-proposal-class-properties/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-create-class-features-plugin': 7.19.0_@babel+core@7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + transitivePeerDependencies: + - supports-color + + /@babel/plugin-proposal-class-static-block/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-create-class-features-plugin': 7.19.0_@babel+core@7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/plugin-syntax-class-static-block': 7.14.5_@babel+core@7.19.1 + transitivePeerDependencies: + - supports-color + + /@babel/plugin-proposal-decorators/7.19.1_@babel+core@7.19.1: + resolution: {integrity: sha512-LfIKNBBY7Q1OX5C4xAgRQffOg2OnhAo9fnbcOHgOC9Yytm2Sw+4XqHufRYU86tHomzepxtvuVaNO+3EVKR4ivw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-create-class-features-plugin': 7.19.0_@babel+core@7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-replace-supers': 7.19.1 + '@babel/helper-split-export-declaration': 7.18.6 + '@babel/plugin-syntax-decorators': 7.19.0_@babel+core@7.19.1 + transitivePeerDependencies: + - supports-color + + /@babel/plugin-proposal-dynamic-import/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.19.1 + + /@babel/plugin-proposal-export-namespace-from/7.18.9_@babel+core@7.19.1: + resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.19.1 + + /@babel/plugin-proposal-json-strings/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.19.1 + + /@babel/plugin-proposal-logical-assignment-operators/7.18.9_@babel+core@7.19.1: + resolution: {integrity: sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.19.1 + + /@babel/plugin-proposal-nullish-coalescing-operator/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.19.1 + + /@babel/plugin-proposal-numeric-separator/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.19.1 + + /@babel/plugin-proposal-object-rest-spread/7.18.9_@babel+core@7.19.1: + resolution: {integrity: sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.19.1 + '@babel/core': 7.19.1 + '@babel/helper-compilation-targets': 7.19.1_@babel+core@7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.19.1 + '@babel/plugin-transform-parameters': 7.18.8_@babel+core@7.19.1 + + /@babel/plugin-proposal-optional-catch-binding/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.19.1 + + /@babel/plugin-proposal-optional-chaining/7.18.9_@babel+core@7.19.1: + resolution: {integrity: sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-skip-transparent-expression-wrappers': 7.18.9 + '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.19.1 + + /@babel/plugin-proposal-private-methods/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-create-class-features-plugin': 7.19.0_@babel+core@7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + transitivePeerDependencies: + - supports-color + + /@babel/plugin-proposal-private-property-in-object/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-create-class-features-plugin': 7.19.0_@babel+core@7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.19.1 + transitivePeerDependencies: + - supports-color + + /@babel/plugin-proposal-unicode-property-regex/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==} + engines: {node: '>=4'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-create-regexp-features-plugin': 7.19.0_@babel+core@7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.19.1: + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-bigint/7.8.3_@babel+core@7.19.1: + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-class-properties/7.12.13_@babel+core@7.19.1: + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-class-static-block/7.14.5_@babel+core@7.19.1: + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-decorators/7.19.0_@babel+core@7.19.1: + resolution: {integrity: sha512-xaBZUEDntt4faL1yN8oIFlhfXeQAWJW7CLKYsHTUqriCUbj8xOra8bfxxKGi/UwExPFBuPdH4XfHc9rGQhrVkQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-dynamic-import/7.8.3_@babel+core@7.19.1: + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-export-namespace-from/7.8.3_@babel+core@7.19.1: + resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-flow/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-import-assertions/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-import-meta/7.10.4_@babel+core@7.19.1: + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.19.1: + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-jsx/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-logical-assignment-operators/7.10.4_@babel+core@7.19.1: + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.19.1: + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.19.1: + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.19.1: + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.19.1: + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.19.1: + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-private-property-in-object/7.14.5_@babel+core@7.19.1: + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-top-level-await/7.14.5_@babel+core@7.19.1: + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-syntax-typescript/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-arrow-functions/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-async-to-generator/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-module-imports': 7.18.6 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-remap-async-to-generator': 7.18.9_@babel+core@7.19.1 + transitivePeerDependencies: + - supports-color + + /@babel/plugin-transform-block-scoped-functions/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-block-scoping/7.18.9_@babel+core@7.19.1: + resolution: {integrity: sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-classes/7.19.0_@babel+core@7.19.1: + resolution: {integrity: sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-compilation-targets': 7.19.1_@babel+core@7.19.1 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-function-name': 7.19.0 + '@babel/helper-optimise-call-expression': 7.18.6 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-replace-supers': 7.19.1 + '@babel/helper-split-export-declaration': 7.18.6 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + /@babel/plugin-transform-computed-properties/7.18.9_@babel+core@7.19.1: + resolution: {integrity: sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-destructuring/7.18.13_@babel+core@7.19.1: + resolution: {integrity: sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-dotall-regex/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-create-regexp-features-plugin': 7.19.0_@babel+core@7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-duplicate-keys/7.18.9_@babel+core@7.19.1: + resolution: {integrity: sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-exponentiation-operator/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.18.9 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-flow-strip-types/7.19.0_@babel+core@7.19.1: + resolution: {integrity: sha512-sgeMlNaQVbCSpgLSKP4ZZKfsJVnFnNQlUSk6gPYzR/q7tzCgQF2t8RBKAP6cKJeZdveei7Q7Jm527xepI8lNLg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/plugin-syntax-flow': 7.18.6_@babel+core@7.19.1 + + /@babel/plugin-transform-for-of/7.18.8_@babel+core@7.19.1: + resolution: {integrity: sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-function-name/7.18.9_@babel+core@7.19.1: + resolution: {integrity: sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-compilation-targets': 7.19.1_@babel+core@7.19.1 + '@babel/helper-function-name': 7.19.0 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-literals/7.18.9_@babel+core@7.19.1: + resolution: {integrity: sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-member-expression-literals/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-modules-amd/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-module-transforms': 7.19.0 + '@babel/helper-plugin-utils': 7.19.0 + babel-plugin-dynamic-import-node: 2.3.3 + transitivePeerDependencies: + - supports-color + + /@babel/plugin-transform-modules-commonjs/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-module-transforms': 7.19.0 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-simple-access': 7.18.6 + babel-plugin-dynamic-import-node: 2.3.3 + transitivePeerDependencies: + - supports-color + + /@babel/plugin-transform-modules-systemjs/7.19.0_@babel+core@7.19.1: + resolution: {integrity: sha512-x9aiR0WXAWmOWsqcsnrzGR+ieaTMVyGyffPVA7F8cXAGt/UxefYv6uSHZLkAFChN5M5Iy1+wjE+xJuPt22H39A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-hoist-variables': 7.18.6 + '@babel/helper-module-transforms': 7.19.0 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-validator-identifier': 7.19.1 + babel-plugin-dynamic-import-node: 2.3.3 + transitivePeerDependencies: + - supports-color + + /@babel/plugin-transform-modules-umd/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-module-transforms': 7.19.0 + '@babel/helper-plugin-utils': 7.19.0 + transitivePeerDependencies: + - supports-color + + /@babel/plugin-transform-named-capturing-groups-regex/7.19.1_@babel+core@7.19.1: + resolution: {integrity: sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-create-regexp-features-plugin': 7.19.0_@babel+core@7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-new-target/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-object-super/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-replace-supers': 7.19.1 + transitivePeerDependencies: + - supports-color + + /@babel/plugin-transform-parameters/7.18.8_@babel+core@7.19.1: + resolution: {integrity: sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-property-literals/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-react-constant-elements/7.18.12_@babel+core@7.19.1: + resolution: {integrity: sha512-Q99U9/ttiu+LMnRU8psd23HhvwXmKWDQIpocm0JKaICcZHnw+mdQbHm6xnSy7dOl8I5PELakYtNBubNQlBXbZw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-react-display-name/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-react-jsx-development/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/plugin-transform-react-jsx': 7.19.0_@babel+core@7.19.1 + + /@babel/plugin-transform-react-jsx/7.19.0_@babel+core@7.19.1: + resolution: {integrity: sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-module-imports': 7.18.6 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.19.1 + '@babel/types': 7.19.0 + + /@babel/plugin-transform-react-pure-annotations/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-regenerator/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + regenerator-transform: 0.15.0 + + /@babel/plugin-transform-reserved-words/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-runtime/7.19.1_@babel+core@7.19.1: + resolution: {integrity: sha512-2nJjTUFIzBMP/f/miLxEK9vxwW/KUXsdvN4sR//TmuDhe6yU2h57WmIOE12Gng3MDP/xpjUV/ToZRdcf8Yj4fA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-module-imports': 7.18.6 + '@babel/helper-plugin-utils': 7.19.0 + babel-plugin-polyfill-corejs2: 0.3.3_@babel+core@7.19.1 + babel-plugin-polyfill-corejs3: 0.6.0_@babel+core@7.19.1 + babel-plugin-polyfill-regenerator: 0.4.1_@babel+core@7.19.1 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + + /@babel/plugin-transform-shorthand-properties/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-spread/7.19.0_@babel+core@7.19.1: + resolution: {integrity: sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-skip-transparent-expression-wrappers': 7.18.9 + + /@babel/plugin-transform-sticky-regex/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-template-literals/7.18.9_@babel+core@7.19.1: + resolution: {integrity: sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-typeof-symbol/7.18.9_@babel+core@7.19.1: + resolution: {integrity: sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-typescript/7.19.1_@babel+core@7.19.1: + resolution: {integrity: sha512-+ILcOU+6mWLlvCwnL920m2Ow3wWx3Wo8n2t5aROQmV55GZt+hOiLvBaa3DNzRjSEHa1aauRs4/YLmkCfFkhhRQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-create-class-features-plugin': 7.19.0_@babel+core@7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/plugin-syntax-typescript': 7.18.6_@babel+core@7.19.1 + transitivePeerDependencies: + - supports-color + + /@babel/plugin-transform-unicode-escapes/7.18.10_@babel+core@7.19.1: + resolution: {integrity: sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-unicode-regex/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-create-regexp-features-plugin': 7.19.0_@babel+core@7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/preset-env/7.19.1_@babel+core@7.19.1: + resolution: {integrity: sha512-c8B2c6D16Lp+Nt6HcD+nHl0VbPKVnNPTpszahuxJJnurfMtKeZ80A+qUv48Y7wqvS+dTFuLuaM9oYxyNHbCLWA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.19.1 + '@babel/core': 7.19.1 + '@babel/helper-compilation-targets': 7.19.1_@babel+core@7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-validator-option': 7.18.6 + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.18.9_@babel+core@7.19.1 + '@babel/plugin-proposal-async-generator-functions': 7.19.1_@babel+core@7.19.1 + '@babel/plugin-proposal-class-properties': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-proposal-class-static-block': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-proposal-dynamic-import': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-proposal-export-namespace-from': 7.18.9_@babel+core@7.19.1 + '@babel/plugin-proposal-json-strings': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-proposal-logical-assignment-operators': 7.18.9_@babel+core@7.19.1 + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-proposal-numeric-separator': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-proposal-object-rest-spread': 7.18.9_@babel+core@7.19.1 + '@babel/plugin-proposal-optional-catch-binding': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-proposal-optional-chaining': 7.18.9_@babel+core@7.19.1 + '@babel/plugin-proposal-private-methods': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-proposal-private-property-in-object': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-proposal-unicode-property-regex': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.19.1 + '@babel/plugin-syntax-class-properties': 7.12.13_@babel+core@7.19.1 + '@babel/plugin-syntax-class-static-block': 7.14.5_@babel+core@7.19.1 + '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.19.1 + '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.19.1 + '@babel/plugin-syntax-import-assertions': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.19.1 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.19.1 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.19.1 + '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.19.1 + '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.19.1 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.19.1 + '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.19.1 + '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.19.1 + '@babel/plugin-syntax-top-level-await': 7.14.5_@babel+core@7.19.1 + '@babel/plugin-transform-arrow-functions': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-async-to-generator': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-block-scoped-functions': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-block-scoping': 7.18.9_@babel+core@7.19.1 + '@babel/plugin-transform-classes': 7.19.0_@babel+core@7.19.1 + '@babel/plugin-transform-computed-properties': 7.18.9_@babel+core@7.19.1 + '@babel/plugin-transform-destructuring': 7.18.13_@babel+core@7.19.1 + '@babel/plugin-transform-dotall-regex': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-duplicate-keys': 7.18.9_@babel+core@7.19.1 + '@babel/plugin-transform-exponentiation-operator': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-for-of': 7.18.8_@babel+core@7.19.1 + '@babel/plugin-transform-function-name': 7.18.9_@babel+core@7.19.1 + '@babel/plugin-transform-literals': 7.18.9_@babel+core@7.19.1 + '@babel/plugin-transform-member-expression-literals': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-modules-amd': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-modules-commonjs': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-modules-systemjs': 7.19.0_@babel+core@7.19.1 + '@babel/plugin-transform-modules-umd': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-named-capturing-groups-regex': 7.19.1_@babel+core@7.19.1 + '@babel/plugin-transform-new-target': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-object-super': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-parameters': 7.18.8_@babel+core@7.19.1 + '@babel/plugin-transform-property-literals': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-regenerator': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-reserved-words': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-shorthand-properties': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-spread': 7.19.0_@babel+core@7.19.1 + '@babel/plugin-transform-sticky-regex': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-template-literals': 7.18.9_@babel+core@7.19.1 + '@babel/plugin-transform-typeof-symbol': 7.18.9_@babel+core@7.19.1 + '@babel/plugin-transform-unicode-escapes': 7.18.10_@babel+core@7.19.1 + '@babel/plugin-transform-unicode-regex': 7.18.6_@babel+core@7.19.1 + '@babel/preset-modules': 0.1.5_@babel+core@7.19.1 + '@babel/types': 7.19.0 + babel-plugin-polyfill-corejs2: 0.3.3_@babel+core@7.19.1 + babel-plugin-polyfill-corejs3: 0.6.0_@babel+core@7.19.1 + babel-plugin-polyfill-regenerator: 0.4.1_@babel+core@7.19.1 + core-js-compat: 3.25.2 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + + /@babel/preset-modules/0.1.5_@babel+core@7.19.1: + resolution: {integrity: sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/plugin-proposal-unicode-property-regex': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-dotall-regex': 7.18.6_@babel+core@7.19.1 + '@babel/types': 7.19.0 + esutils: 2.0.3 + + /@babel/preset-react/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-validator-option': 7.18.6 + '@babel/plugin-transform-react-display-name': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-react-jsx': 7.19.0_@babel+core@7.19.1 + '@babel/plugin-transform-react-jsx-development': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-react-pure-annotations': 7.18.6_@babel+core@7.19.1 + + /@babel/preset-typescript/7.18.6_@babel+core@7.19.1: + resolution: {integrity: sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-validator-option': 7.18.6 + '@babel/plugin-transform-typescript': 7.19.1_@babel+core@7.19.1 + transitivePeerDependencies: + - supports-color + + /@babel/runtime-corejs3/7.19.1: + resolution: {integrity: sha512-j2vJGnkopRzH+ykJ8h68wrHnEUmtK//E723jjixiAl/PPf6FhqY/vYRcMVlNydRKQjQsTsYEjpx+DZMIvnGk/g==} + engines: {node: '>=6.9.0'} + dependencies: + core-js-pure: 3.25.2 + regenerator-runtime: 0.13.9 + + /@babel/runtime/7.19.0: + resolution: {integrity: sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.9 + + /@babel/template/7.18.10: + resolution: {integrity: sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.18.6 + '@babel/parser': 7.19.1 + '@babel/types': 7.19.0 + + /@babel/traverse/7.19.1: + resolution: {integrity: sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.18.6 + '@babel/generator': 7.19.0 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-function-name': 7.19.0 + '@babel/helper-hoist-variables': 7.18.6 + '@babel/helper-split-export-declaration': 7.18.6 + '@babel/parser': 7.19.1 + '@babel/types': 7.19.0 + debug: 4.3.4 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + /@babel/types/7.19.0: + resolution: {integrity: sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.18.10 + '@babel/helper-validator-identifier': 7.19.1 + to-fast-properties: 2.0.0 + + /@bcoe/v8-coverage/0.2.3: + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + /@braintree/sanitize-url/6.0.0: + resolution: {integrity: sha512-mgmE7XBYY/21erpzhexk4Cj1cyTQ9LzvnTxtzM17BJ7ERMNE6W72mQRo0I1Ud8eFJ+RVVIcBNhLFZ3GX4XFz5w==} + dev: false + + /@commitlint/cli/17.1.2: + resolution: {integrity: sha512-h/4Hlka3bvCLbnxf0Er2ri5A44VMlbMSkdTRp8Adv2tRiklSTRIoPGs7OEXDv3EoDs2AAzILiPookgM4Gi7LOw==} + engines: {node: '>=v14'} + hasBin: true + dependencies: + '@commitlint/format': 17.0.0 + '@commitlint/lint': 17.1.0 + '@commitlint/load': 17.1.2 + '@commitlint/read': 17.1.0 + '@commitlint/types': 17.0.0 + execa: 5.1.1 + lodash: 4.17.21 + resolve-from: 5.0.0 + resolve-global: 1.0.0 + yargs: 17.5.1 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + dev: true + + /@commitlint/config-conventional/17.1.0: + resolution: {integrity: sha512-WU2p0c9/jLi8k2q2YrDV96Y8XVswQOceIQ/wyJvQxawJSCasLdRB3kUIYdNjOCJsxkpoUlV/b90ZPxp1MYZDiA==} + engines: {node: '>=v14'} + dependencies: + conventional-changelog-conventionalcommits: 5.0.0 + dev: true + + /@commitlint/config-validator/17.1.0: + resolution: {integrity: sha512-Q1rRRSU09ngrTgeTXHq6ePJs2KrI+axPTgkNYDWSJIuS1Op4w3J30vUfSXjwn5YEJHklK3fSqWNHmBhmTR7Vdg==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/types': 17.0.0 + ajv: 8.11.0 + dev: true + + /@commitlint/ensure/17.0.0: + resolution: {integrity: sha512-M2hkJnNXvEni59S0QPOnqCKIK52G1XyXBGw51mvh7OXDudCmZ9tZiIPpU882p475Mhx48Ien1MbWjCP1zlyC0A==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/types': 17.0.0 + lodash: 4.17.21 + dev: true + + /@commitlint/execute-rule/17.0.0: + resolution: {integrity: sha512-nVjL/w/zuqjCqSJm8UfpNaw66V9WzuJtQvEnCrK4jDw6qKTmZB+1JQ8m6BQVZbNBcwfYdDNKnhIhqI0Rk7lgpQ==} + engines: {node: '>=v14'} + dev: true + + /@commitlint/format/17.0.0: + resolution: {integrity: sha512-MZzJv7rBp/r6ZQJDEodoZvdRM0vXu1PfQvMTNWFb8jFraxnISMTnPBWMMjr2G/puoMashwaNM//fl7j8gGV5lA==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/types': 17.0.0 + chalk: 4.1.2 + dev: true + + /@commitlint/is-ignored/17.1.0: + resolution: {integrity: sha512-JITWKDMHhIh8IpdIbcbuH9rEQJty1ZWelgjleTFrVRAcEwN/sPzk1aVUXRIZNXMJWbZj8vtXRJnFihrml8uECQ==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/types': 17.0.0 + semver: 7.3.7 + dev: true + + /@commitlint/lint/17.1.0: + resolution: {integrity: sha512-ltpqM2ogt/+SDhUaScFo0MdscncEF96lvQTPMM/VTTWlw7sTGLLWkOOppsee2MN/uLNNWjQ7kqkd4h6JqoM9AQ==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/is-ignored': 17.1.0 + '@commitlint/parse': 17.0.0 + '@commitlint/rules': 17.0.0 + '@commitlint/types': 17.0.0 + dev: true + + /@commitlint/load/17.1.2: + resolution: {integrity: sha512-sk2p/jFYAWLChIfOIp/MGSIn/WzZ0vkc3afw+l4X8hGEYkvDe4gQUUAVxjl/6xMRn0HgnSLMZ04xXh5pkTsmgg==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/config-validator': 17.1.0 + '@commitlint/execute-rule': 17.0.0 + '@commitlint/resolve-extends': 17.1.0 + '@commitlint/types': 17.0.0 + '@types/node': 14.18.29 + chalk: 4.1.2 + cosmiconfig: 7.0.1 + cosmiconfig-typescript-loader: 4.1.0_3owiowz3ujipd4k6pbqn3n7oui + lodash: 4.17.21 + resolve-from: 5.0.0 + ts-node: 10.9.1_ck2axrxkiif44rdbzjywaqjysa + typescript: 4.8.3 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + dev: true + + /@commitlint/message/17.0.0: + resolution: {integrity: sha512-LpcwYtN+lBlfZijHUdVr8aNFTVpHjuHI52BnfoV01TF7iSLnia0jttzpLkrLmI8HNQz6Vhr9UrxDWtKZiMGsBw==} + engines: {node: '>=v14'} + dev: true + + /@commitlint/parse/17.0.0: + resolution: {integrity: sha512-cKcpfTIQYDG1ywTIr5AG0RAiLBr1gudqEsmAGCTtj8ffDChbBRxm6xXs2nv7GvmJN7msOt7vOKleLvcMmRa1+A==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/types': 17.0.0 + conventional-changelog-angular: 5.0.13 + conventional-commits-parser: 3.2.4 + dev: true + + /@commitlint/read/17.1.0: + resolution: {integrity: sha512-73BoFNBA/3Ozo2JQvGsE0J8SdrJAWGfZQRSHqvKaqgmY042Su4gXQLqvAzgr55S9DI1l9TiU/5WDuh8IE86d/g==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/top-level': 17.0.0 + '@commitlint/types': 17.0.0 + fs-extra: 10.1.0 + git-raw-commits: 2.0.11 + minimist: 1.2.6 + dev: true + + /@commitlint/resolve-extends/17.1.0: + resolution: {integrity: sha512-jqKm00LJ59T0O8O4bH4oMa4XyJVEOK4GzH8Qye9XKji+Q1FxhZznxMV/bDLyYkzbTodBt9sL0WLql8wMtRTbqQ==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/config-validator': 17.1.0 + '@commitlint/types': 17.0.0 + import-fresh: 3.3.0 + lodash: 4.17.21 + resolve-from: 5.0.0 + resolve-global: 1.0.0 + dev: true + + /@commitlint/rules/17.0.0: + resolution: {integrity: sha512-45nIy3dERKXWpnwX9HeBzK5SepHwlDxdGBfmedXhL30fmFCkJOdxHyOJsh0+B0RaVsLGT01NELpfzJUmtpDwdQ==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/ensure': 17.0.0 + '@commitlint/message': 17.0.0 + '@commitlint/to-lines': 17.0.0 + '@commitlint/types': 17.0.0 + execa: 5.1.1 + dev: true + + /@commitlint/to-lines/17.0.0: + resolution: {integrity: sha512-nEi4YEz04Rf2upFbpnEorG8iymyH7o9jYIVFBG1QdzebbIFET3ir+8kQvCZuBE5pKCtViE4XBUsRZz139uFrRQ==} + engines: {node: '>=v14'} + dev: true + + /@commitlint/top-level/17.0.0: + resolution: {integrity: sha512-dZrEP1PBJvodNWYPOYiLWf6XZergdksKQaT6i1KSROLdjf5Ai0brLOv5/P+CPxBeoj3vBxK4Ax8H1Pg9t7sHIQ==} + engines: {node: '>=v14'} + dependencies: + find-up: 5.0.0 + dev: true + + /@commitlint/types/17.0.0: + resolution: {integrity: sha512-hBAw6U+SkAT5h47zDMeOu3HSiD0SODw4Aq7rRNh1ceUmL7GyLKYhPbUvlRWqZ65XjBLPHZhFyQlRaPNz8qvUyQ==} + engines: {node: '>=v14'} + dependencies: + chalk: 4.1.2 + dev: true + + /@cspotcode/source-map-support/0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + dev: true + + /@csstools/normalize.css/12.0.0: + resolution: {integrity: sha512-M0qqxAcwCsIVfpFQSlGN5XjXWu8l5JDZN+fPt1LeW5SZexQTgnaEvgXAY+CeygRw0EeppWHi12JxESWiWrB0Sg==} + + /@csstools/postcss-cascade-layers/1.1.1_postcss@8.4.16: + resolution: {integrity: sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + '@csstools/selector-specificity': 2.0.2_pnx64jze6bptzcedy5bidi3zdi + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + + /@csstools/postcss-color-function/1.1.1_postcss@8.4.16: + resolution: {integrity: sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + '@csstools/postcss-progressive-custom-properties': 1.3.0_postcss@8.4.16 + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /@csstools/postcss-font-format-keywords/1.0.1_postcss@8.4.16: + resolution: {integrity: sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /@csstools/postcss-hwb-function/1.0.2_postcss@8.4.16: + resolution: {integrity: sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /@csstools/postcss-ic-unit/1.0.1_postcss@8.4.16: + resolution: {integrity: sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + '@csstools/postcss-progressive-custom-properties': 1.3.0_postcss@8.4.16 + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /@csstools/postcss-is-pseudo-class/2.0.7_postcss@8.4.16: + resolution: {integrity: sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + '@csstools/selector-specificity': 2.0.2_pnx64jze6bptzcedy5bidi3zdi + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + + /@csstools/postcss-nested-calc/1.0.0_postcss@8.4.16: + resolution: {integrity: sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /@csstools/postcss-normalize-display-values/1.0.1_postcss@8.4.16: + resolution: {integrity: sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /@csstools/postcss-oklab-function/1.1.1_postcss@8.4.16: + resolution: {integrity: sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + '@csstools/postcss-progressive-custom-properties': 1.3.0_postcss@8.4.16 + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /@csstools/postcss-progressive-custom-properties/1.3.0_postcss@8.4.16: + resolution: {integrity: sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.3 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /@csstools/postcss-stepped-value-functions/1.0.1_postcss@8.4.16: + resolution: {integrity: sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /@csstools/postcss-text-decoration-shorthand/1.0.0_postcss@8.4.16: + resolution: {integrity: sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /@csstools/postcss-trigonometric-functions/1.0.2_postcss@8.4.16: + resolution: {integrity: sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==} + engines: {node: ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /@csstools/postcss-unset-value/1.0.2_postcss@8.4.16: + resolution: {integrity: sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + postcss: 8.4.16 + + /@csstools/selector-specificity/2.0.2_pnx64jze6bptzcedy5bidi3zdi: + resolution: {integrity: sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + postcss-selector-parser: ^6.0.10 + dependencies: + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + + /@eslint/eslintrc/1.3.2: + resolution: {integrity: sha512-AXYd23w1S/bv3fTs3Lz0vjiYemS08jWkI3hYyS9I1ry+0f+Yjs1wm+sU0BS8qDOPrBIkp4qHYC16I8uVtpLajQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.4.0 + globals: 13.17.0 + ignore: 5.2.0 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + /@fullhuman/postcss-purgecss/4.1.3_postcss@8.4.16: + resolution: {integrity: sha512-jqcsyfvq09VOsMXxJMPLRF6Fhg/NNltzWKnC9qtzva+QKTxerCO4esG6je7hbnmkpZtaDyPTwMBj9bzfWorsrw==} + peerDependencies: + postcss: ^8.0.0 + dependencies: + postcss: 8.4.16 + purgecss: 4.1.3 + dev: true + + /@humanwhocodes/config-array/0.10.4: + resolution: {integrity: sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + /@humanwhocodes/gitignore-to-minimatch/1.0.2: + resolution: {integrity: sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==} + + /@humanwhocodes/module-importer/1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + /@humanwhocodes/object-schema/1.2.1: + resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + + /@hutson/parse-repository-url/3.0.2: + resolution: {integrity: sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==} + engines: {node: '>=6.9.0'} + dev: true + + /@istanbuljs/load-nyc-config/1.1.0: + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + + /@istanbuljs/schema/0.1.3: + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + /@jest/console/27.5.1: + resolution: {integrity: sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/types': 27.5.1 + '@types/node': 16.11.59 + chalk: 4.1.2 + jest-message-util: 27.5.1 + jest-util: 27.5.1 + slash: 3.0.0 + + /@jest/console/28.1.3: + resolution: {integrity: sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/types': 28.1.3 + '@types/node': 16.11.59 + chalk: 4.1.2 + jest-message-util: 28.1.3 + jest-util: 28.1.3 + slash: 3.0.0 + + /@jest/core/27.5.1: + resolution: {integrity: sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/console': 27.5.1 + '@jest/reporters': 27.5.1 + '@jest/test-result': 27.5.1 + '@jest/transform': 27.5.1 + '@jest/types': 27.5.1 + '@types/node': 16.11.59 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.8.1 + exit: 0.1.2 + graceful-fs: 4.2.10 + jest-changed-files: 27.5.1 + jest-config: 27.5.1 + jest-haste-map: 27.5.1 + jest-message-util: 27.5.1 + jest-regex-util: 27.5.1 + jest-resolve: 27.5.1 + jest-resolve-dependencies: 27.5.1 + jest-runner: 27.5.1 + jest-runtime: 27.5.1 + jest-snapshot: 27.5.1 + jest-util: 27.5.1 + jest-validate: 27.5.1 + jest-watcher: 27.5.1 + micromatch: 4.0.5 + rimraf: 3.0.2 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - ts-node + - utf-8-validate + + /@jest/environment/27.5.1: + resolution: {integrity: sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/fake-timers': 27.5.1 + '@jest/types': 27.5.1 + '@types/node': 16.11.59 + jest-mock: 27.5.1 + + /@jest/fake-timers/27.5.1: + resolution: {integrity: sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/types': 27.5.1 + '@sinonjs/fake-timers': 8.1.0 + '@types/node': 16.11.59 + jest-message-util: 27.5.1 + jest-mock: 27.5.1 + jest-util: 27.5.1 + + /@jest/globals/27.5.1: + resolution: {integrity: sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/environment': 27.5.1 + '@jest/types': 27.5.1 + expect: 27.5.1 + + /@jest/reporters/27.5.1: + resolution: {integrity: sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 27.5.1 + '@jest/test-result': 27.5.1 + '@jest/transform': 27.5.1 + '@jest/types': 27.5.1 + '@types/node': 16.11.59 + chalk: 4.1.2 + collect-v8-coverage: 1.0.1 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.10 + istanbul-lib-coverage: 3.2.0 + istanbul-lib-instrument: 5.2.0 + istanbul-lib-report: 3.0.0 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.5 + jest-haste-map: 27.5.1 + jest-resolve: 27.5.1 + jest-util: 27.5.1 + jest-worker: 27.5.1 + slash: 3.0.0 + source-map: 0.6.1 + string-length: 4.0.2 + terminal-link: 2.1.1 + v8-to-istanbul: 8.1.1 + transitivePeerDependencies: + - supports-color + + /@jest/schemas/28.1.3: + resolution: {integrity: sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@sinclair/typebox': 0.24.42 + + /@jest/source-map/27.5.1: + resolution: {integrity: sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + callsites: 3.1.0 + graceful-fs: 4.2.10 + source-map: 0.6.1 + + /@jest/test-result/27.5.1: + resolution: {integrity: sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/console': 27.5.1 + '@jest/types': 27.5.1 + '@types/istanbul-lib-coverage': 2.0.4 + collect-v8-coverage: 1.0.1 + + /@jest/test-result/28.1.3: + resolution: {integrity: sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/console': 28.1.3 + '@jest/types': 28.1.3 + '@types/istanbul-lib-coverage': 2.0.4 + collect-v8-coverage: 1.0.1 + + /@jest/test-sequencer/27.5.1: + resolution: {integrity: sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/test-result': 27.5.1 + graceful-fs: 4.2.10 + jest-haste-map: 27.5.1 + jest-runtime: 27.5.1 + transitivePeerDependencies: + - supports-color + + /@jest/transform/27.5.1: + resolution: {integrity: sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@babel/core': 7.19.1 + '@jest/types': 27.5.1 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 1.8.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.10 + jest-haste-map: 27.5.1 + jest-regex-util: 27.5.1 + jest-util: 27.5.1 + micromatch: 4.0.5 + pirates: 4.0.5 + slash: 3.0.0 + source-map: 0.6.1 + write-file-atomic: 3.0.3 + transitivePeerDependencies: + - supports-color + + /@jest/types/24.9.0: + resolution: {integrity: sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==} + engines: {node: '>= 6'} + dependencies: + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-reports': 1.1.2 + '@types/yargs': 13.0.12 + dev: false + + /@jest/types/27.5.1: + resolution: {integrity: sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-reports': 3.0.1 + '@types/node': 16.11.59 + '@types/yargs': 16.0.4 + chalk: 4.1.2 + + /@jest/types/28.1.3: + resolution: {integrity: sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/schemas': 28.1.3 + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-reports': 3.0.1 + '@types/node': 16.11.59 + '@types/yargs': 17.0.12 + chalk: 4.1.2 + + /@jridgewell/gen-mapping/0.1.1: + resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.14 + + /@jridgewell/gen-mapping/0.3.2: + resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.14 + '@jridgewell/trace-mapping': 0.3.15 + + /@jridgewell/resolve-uri/3.1.0: + resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} + engines: {node: '>=6.0.0'} + + /@jridgewell/set-array/1.1.2: + resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + engines: {node: '>=6.0.0'} + + /@jridgewell/source-map/0.3.2: + resolution: {integrity: sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==} + dependencies: + '@jridgewell/gen-mapping': 0.3.2 + '@jridgewell/trace-mapping': 0.3.15 + + /@jridgewell/sourcemap-codec/1.4.14: + resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} + + /@jridgewell/trace-mapping/0.3.15: + resolution: {integrity: sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==} + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.14 + + /@jridgewell/trace-mapping/0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.14 + dev: true + + /@leichtgewicht/ip-codec/2.0.4: + resolution: {integrity: sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==} + + /@nicolo-ribaudo/eslint-scope-5-internals/5.1.1-v1: + resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} + dependencies: + eslint-scope: 5.1.1 + + /@nodelib/fs.scandir/2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + /@nodelib/fs.stat/2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + /@nodelib/fs.walk/1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.13.0 + + /@pmmmwh/react-refresh-webpack-plugin/0.5.7_prxwy2zxcolvdag5hfkyuqbcze: + resolution: {integrity: sha512-bcKCAzF0DV2IIROp9ZHkRJa6O4jy7NlnHdWL3GmcUxYWNjLXkK5kfELELwEfSP5hXPfVL/qOGMAROuMQb9GG8Q==} + engines: {node: '>= 10.13'} + peerDependencies: + '@types/webpack': 4.x || 5.x + react-refresh: '>=0.10.0 <1.0.0' + sockjs-client: ^1.4.0 + type-fest: '>=0.17.0 <3.0.0' + webpack: '>=4.43.0 <6.0.0' + webpack-dev-server: 3.x || 4.x + webpack-hot-middleware: 2.x + webpack-plugin-serve: 0.x || 1.x + peerDependenciesMeta: + '@types/webpack': + optional: true + sockjs-client: + optional: true + type-fest: + optional: true + webpack-dev-server: + optional: true + webpack-hot-middleware: + optional: true + webpack-plugin-serve: + optional: true + dependencies: + ansi-html-community: 0.0.8 + common-path-prefix: 3.0.0 + core-js-pure: 3.25.2 + error-stack-parser: 2.1.4 + find-up: 5.0.0 + html-entities: 2.3.3 + loader-utils: 2.0.2 + react-refresh: 0.11.0 + schema-utils: 3.1.1 + source-map: 0.7.4 + webpack: 5.74.0 + webpack-dev-server: 4.11.1_webpack@5.74.0 + + /@popperjs/core/2.11.6: + resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==} + + /@react-aria/ssr/3.3.0_react@18.2.0: + resolution: {integrity: sha512-yNqUDuOVZIUGP81R87BJVi/ZUZp/nYOBXbPsRe7oltJOfErQZD+UezMpw4vM2KRz18cURffvmC8tJ6JTeyDtaQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + dependencies: + '@babel/runtime': 7.19.0 + react: 18.2.0 + dev: false + + /@remix-run/router/1.0.0: + resolution: {integrity: sha512-SCR1cxRSMNKjaVYptCzBApPDqGwa3FGdjVHc+rOToocNPHQdIYLZBfv/3f+KvYuXDkUGVIW9IAzmPNZDRL1I4A==} + engines: {node: '>=14'} + dev: false + + /@restart/hooks/0.4.7_react@18.2.0: + resolution: {integrity: sha512-ZbjlEHcG+FQtpDPHd7i4FzNNvJf2enAwZfJbpM8CW7BhmOAbsHpZe3tsHwfQUrBuyrxWqPYp2x5UMnilWcY22A==} + peerDependencies: + react: '>=16.8.0' + dependencies: + dequal: 2.0.3 + react: 18.2.0 + dev: false + + /@restart/ui/1.4.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-5dDj5uDzUgK1iijWPRg6AnxjkHM04XhTQDJirM1h/8tIc7KyLtF9YyjcCpNEn259hPMXswpkfXKNgiag0skPFg==} + peerDependencies: + react: '>=16.14.0' + react-dom: '>=16.14.0' + dependencies: + '@babel/runtime': 7.19.0 + '@popperjs/core': 2.11.6 + '@react-aria/ssr': 3.3.0_react@18.2.0 + '@restart/hooks': 0.4.7_react@18.2.0 + '@types/warning': 3.0.0 + dequal: 2.0.3 + dom-helpers: 5.2.1 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + uncontrollable: 7.2.1_react@18.2.0 + warning: 4.0.3 + dev: false + + /@rollup/plugin-babel/5.3.1_qjhfxcwn2glzcb5646tzyg45bq: + resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} + engines: {node: '>= 10.0.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@types/babel__core': ^7.1.9 + rollup: ^1.20.0||^2.0.0 + peerDependenciesMeta: + '@types/babel__core': + optional: true + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-module-imports': 7.18.6 + '@rollup/pluginutils': 3.1.0_rollup@2.79.0 + rollup: 2.79.0 + + /@rollup/plugin-node-resolve/11.2.1_rollup@2.79.0: + resolution: {integrity: sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==} + engines: {node: '>= 10.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0 + dependencies: + '@rollup/pluginutils': 3.1.0_rollup@2.79.0 + '@types/resolve': 1.17.1 + builtin-modules: 3.3.0 + deepmerge: 4.2.2 + is-module: 1.0.0 + resolve: 1.22.1 + rollup: 2.79.0 + + /@rollup/plugin-replace/2.4.2_rollup@2.79.0: + resolution: {integrity: sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==} + peerDependencies: + rollup: ^1.20.0 || ^2.0.0 + dependencies: + '@rollup/pluginutils': 3.1.0_rollup@2.79.0 + magic-string: 0.25.9 + rollup: 2.79.0 + + /@rollup/pluginutils/3.1.0_rollup@2.79.0: + resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} + engines: {node: '>= 8.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0 + dependencies: + '@types/estree': 0.0.39 + estree-walker: 1.0.1 + picomatch: 2.3.1 + rollup: 2.79.0 + + /@rushstack/eslint-patch/1.2.0: + resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==} + + /@sinclair/typebox/0.24.42: + resolution: {integrity: sha512-d+2AtrHGyWek2u2ITF0lHRIv6Tt7X0dEHW+0rP+5aDCEjC3fiN2RBjrLD0yU0at52BcZbRGxLbAtXiR0hFCjYw==} + + /@sinonjs/commons/1.8.3: + resolution: {integrity: sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==} + dependencies: + type-detect: 4.0.8 + + /@sinonjs/fake-timers/8.1.0: + resolution: {integrity: sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==} + dependencies: + '@sinonjs/commons': 1.8.3 + + /@surma/rollup-plugin-off-main-thread/2.2.3: + resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} + dependencies: + ejs: 3.1.8 + json5: 2.2.1 + magic-string: 0.25.9 + string.prototype.matchall: 4.0.7 + + /@svgr/babel-plugin-add-jsx-attribute/5.4.0: + resolution: {integrity: sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==} + engines: {node: '>=10'} + + /@svgr/babel-plugin-remove-jsx-attribute/5.4.0: + resolution: {integrity: sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==} + engines: {node: '>=10'} + + /@svgr/babel-plugin-remove-jsx-empty-expression/5.0.1: + resolution: {integrity: sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==} + engines: {node: '>=10'} + + /@svgr/babel-plugin-replace-jsx-attribute-value/5.0.1: + resolution: {integrity: sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==} + engines: {node: '>=10'} + + /@svgr/babel-plugin-svg-dynamic-title/5.4.0: + resolution: {integrity: sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==} + engines: {node: '>=10'} + + /@svgr/babel-plugin-svg-em-dimensions/5.4.0: + resolution: {integrity: sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==} + engines: {node: '>=10'} + + /@svgr/babel-plugin-transform-react-native-svg/5.4.0: + resolution: {integrity: sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==} + engines: {node: '>=10'} + + /@svgr/babel-plugin-transform-svg-component/5.5.0: + resolution: {integrity: sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==} + engines: {node: '>=10'} + + /@svgr/babel-preset/5.5.0: + resolution: {integrity: sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==} + engines: {node: '>=10'} + dependencies: + '@svgr/babel-plugin-add-jsx-attribute': 5.4.0 + '@svgr/babel-plugin-remove-jsx-attribute': 5.4.0 + '@svgr/babel-plugin-remove-jsx-empty-expression': 5.0.1 + '@svgr/babel-plugin-replace-jsx-attribute-value': 5.0.1 + '@svgr/babel-plugin-svg-dynamic-title': 5.4.0 + '@svgr/babel-plugin-svg-em-dimensions': 5.4.0 + '@svgr/babel-plugin-transform-react-native-svg': 5.4.0 + '@svgr/babel-plugin-transform-svg-component': 5.5.0 + + /@svgr/core/5.5.0: + resolution: {integrity: sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==} + engines: {node: '>=10'} + dependencies: + '@svgr/plugin-jsx': 5.5.0 + camelcase: 6.3.0 + cosmiconfig: 7.0.1 + transitivePeerDependencies: + - supports-color + + /@svgr/hast-util-to-babel-ast/5.5.0: + resolution: {integrity: sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==} + engines: {node: '>=10'} + dependencies: + '@babel/types': 7.19.0 + + /@svgr/plugin-jsx/5.5.0: + resolution: {integrity: sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==} + engines: {node: '>=10'} + dependencies: + '@babel/core': 7.19.1 + '@svgr/babel-preset': 5.5.0 + '@svgr/hast-util-to-babel-ast': 5.5.0 + svg-parser: 2.0.4 + transitivePeerDependencies: + - supports-color + + /@svgr/plugin-svgo/5.5.0: + resolution: {integrity: sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==} + engines: {node: '>=10'} + dependencies: + cosmiconfig: 7.0.1 + deepmerge: 4.2.2 + svgo: 1.3.2 + + /@svgr/webpack/5.5.0: + resolution: {integrity: sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==} + engines: {node: '>=10'} + dependencies: + '@babel/core': 7.19.1 + '@babel/plugin-transform-react-constant-elements': 7.18.12_@babel+core@7.19.1 + '@babel/preset-env': 7.19.1_@babel+core@7.19.1 + '@babel/preset-react': 7.18.6_@babel+core@7.19.1 + '@svgr/core': 5.5.0 + '@svgr/plugin-jsx': 5.5.0 + '@svgr/plugin-svgo': 5.5.0 + loader-utils: 2.0.2 + transitivePeerDependencies: + - supports-color + + /@testing-library/dom/8.18.1: + resolution: {integrity: sha512-oEvsm2B/WtcHKE+IcEeeCqNU/ltFGaVyGbpcm4g/2ytuT49jrlH9x5qRKL/H3A6yfM4YAbSbC0ceT5+9CEXnLg==} + engines: {node: '>=12'} + dependencies: + '@babel/code-frame': 7.18.6 + '@babel/runtime': 7.19.0 + '@types/aria-query': 4.2.2 + aria-query: 5.0.2 + chalk: 4.1.2 + dom-accessibility-api: 0.5.14 + lz-string: 1.4.4 + pretty-format: 27.5.1 + dev: true + + /@testing-library/jest-dom/4.2.4: + resolution: {integrity: sha512-j31Bn0rQo12fhCWOUWy9fl7wtqkp7In/YP2p5ZFyRuiiB9Qs3g+hS4gAmDWONbAHcRmVooNJ5eOHQDCOmUFXHg==} + engines: {node: '>=8', npm: '>=6'} + dependencies: + '@babel/runtime': 7.19.0 + chalk: 2.4.2 + css: 2.2.4 + css.escape: 1.5.1 + jest-diff: 24.9.0 + jest-matcher-utils: 24.9.0 + lodash: 4.17.21 + pretty-format: 24.9.0 + redent: 3.0.0 + dev: false + + /@testing-library/react/13.4.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==} + engines: {node: '>=12'} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + dependencies: + '@babel/runtime': 7.19.0 + '@testing-library/dom': 8.18.1 + '@types/react-dom': 18.0.6 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: true + + /@testing-library/user-event/13.5.0_znccgeejomvff3jrsk3ljovfpu: + resolution: {integrity: sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==} + engines: {node: '>=10', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + dependencies: + '@babel/runtime': 7.19.0 + '@testing-library/dom': 8.18.1 + dev: true + + /@tootallnate/once/1.1.2: + resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} + engines: {node: '>= 6'} + + /@trysound/sax/0.2.0: + resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} + engines: {node: '>=10.13.0'} + + /@tsconfig/node10/1.0.9: + resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + dev: true + + /@tsconfig/node12/1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + dev: true + + /@tsconfig/node14/1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + dev: true + + /@tsconfig/node16/1.0.3: + resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==} + dev: true + + /@types/aria-query/4.2.2: + resolution: {integrity: sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==} + dev: true + + /@types/babel__core/7.1.19: + resolution: {integrity: sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==} + dependencies: + '@babel/parser': 7.19.1 + '@babel/types': 7.19.0 + '@types/babel__generator': 7.6.4 + '@types/babel__template': 7.4.1 + '@types/babel__traverse': 7.18.1 + + /@types/babel__generator/7.6.4: + resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} + dependencies: + '@babel/types': 7.19.0 + + /@types/babel__template/7.4.1: + resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} + dependencies: + '@babel/parser': 7.19.1 + '@babel/types': 7.19.0 + + /@types/babel__traverse/7.18.1: + resolution: {integrity: sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA==} + dependencies: + '@babel/types': 7.19.0 + + /@types/body-parser/1.19.2: + resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} + dependencies: + '@types/connect': 3.4.35 + '@types/node': 16.11.59 + + /@types/bonjour/3.5.10: + resolution: {integrity: sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==} + dependencies: + '@types/node': 16.11.59 + + /@types/connect-history-api-fallback/1.3.5: + resolution: {integrity: sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==} + dependencies: + '@types/express-serve-static-core': 4.17.31 + '@types/node': 16.11.59 + + /@types/connect/3.4.35: + resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} + dependencies: + '@types/node': 16.11.59 + + /@types/eslint-scope/3.7.4: + resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==} + dependencies: + '@types/eslint': 8.4.6 + '@types/estree': 0.0.51 + + /@types/eslint/8.4.6: + resolution: {integrity: sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==} + dependencies: + '@types/estree': 1.0.0 + '@types/json-schema': 7.0.11 + + /@types/estree/0.0.39: + resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} + + /@types/estree/0.0.51: + resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==} + + /@types/estree/1.0.0: + resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} + + /@types/express-serve-static-core/4.17.31: + resolution: {integrity: sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==} + dependencies: + '@types/node': 16.11.59 + '@types/qs': 6.9.7 + '@types/range-parser': 1.2.4 + + /@types/express/4.17.14: + resolution: {integrity: sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==} + dependencies: + '@types/body-parser': 1.19.2 + '@types/express-serve-static-core': 4.17.31 + '@types/qs': 6.9.7 + '@types/serve-static': 1.15.0 + + /@types/graceful-fs/4.1.5: + resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==} + dependencies: + '@types/node': 16.11.59 + + /@types/html-minifier-terser/6.1.0: + resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==} + + /@types/http-proxy/1.17.9: + resolution: {integrity: sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==} + dependencies: + '@types/node': 16.11.59 + + /@types/istanbul-lib-coverage/2.0.4: + resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} + + /@types/istanbul-lib-report/3.0.0: + resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==} + dependencies: + '@types/istanbul-lib-coverage': 2.0.4 + + /@types/istanbul-reports/1.1.2: + resolution: {integrity: sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==} + dependencies: + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-lib-report': 3.0.0 + dev: false + + /@types/istanbul-reports/3.0.1: + resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==} + dependencies: + '@types/istanbul-lib-report': 3.0.0 + + /@types/jest/27.5.2: + resolution: {integrity: sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==} + dependencies: + jest-matcher-utils: 27.5.1 + pretty-format: 27.5.1 + dev: true + + /@types/js-cookie/2.2.7: + resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==} + dev: false + + /@types/json-schema/7.0.11: + resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} + + /@types/json5/0.0.29: + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + /@types/lodash/4.14.185: + resolution: {integrity: sha512-evMDG1bC4rgQg4ku9tKpuMh5iBNEwNa3tf9zRHdP1qlv+1WUg44xat4IxCE14gIpZRGUUWAx2VhItCZc25NfMA==} + dev: true + + /@types/marked/4.0.7: + resolution: {integrity: sha512-eEAhnz21CwvKVW+YvRvcTuFKNU9CV1qH+opcgVK3pIMI6YZzDm6gc8o2vHjldFk6MGKt5pueSB7IOpvpx5Qekw==} + dev: true + + /@types/mime/3.0.1: + resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==} + + /@types/minimist/1.2.2: + resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} + dev: true + + /@types/node/14.18.29: + resolution: {integrity: sha512-LhF+9fbIX4iPzhsRLpK5H7iPdvW8L4IwGciXQIOEcuF62+9nw/VQVsOViAOOGxY3OlOKGLFv0sWwJXdwQeTn6A==} + dev: true + + /@types/node/16.11.59: + resolution: {integrity: sha512-6u+36Dj3aDzhfBVUf/mfmc92OEdzQ2kx2jcXGdigfl70E/neV21ZHE6UCz4MDzTRcVqGAM27fk+DLXvyDsn3Jw==} + + /@types/normalize-package-data/2.4.1: + resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} + dev: true + + /@types/parse-json/4.0.0: + resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} + + /@types/prettier/2.7.0: + resolution: {integrity: sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==} + + /@types/prop-types/15.7.5: + resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} + + /@types/q/1.5.5: + resolution: {integrity: sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==} + + /@types/qs/6.9.7: + resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} + + /@types/range-parser/1.2.4: + resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} + + /@types/react-dom/18.0.6: + resolution: {integrity: sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==} + dependencies: + '@types/react': 18.0.20 + dev: true + + /@types/react-helmet/6.1.5: + resolution: {integrity: sha512-/ICuy7OHZxR0YCAZLNg9r7I9aijWUWvxaPR6uTuyxe8tAj5RL4Sw1+R6NhXUtOsarkGYPmaHdBDvuXh2DIN/uA==} + dependencies: + '@types/react': 18.0.20 + dev: true + + /@types/react-transition-group/4.4.5: + resolution: {integrity: sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==} + dependencies: + '@types/react': 18.0.20 + dev: false + + /@types/react/18.0.20: + resolution: {integrity: sha512-MWul1teSPxujEHVwZl4a5HxQ9vVNsjTchVA+xRqv/VYGCuKGAU6UhfrTdF5aBefwD1BHUD8i/zq+O/vyCm/FrA==} + dependencies: + '@types/prop-types': 15.7.5 + '@types/scheduler': 0.16.2 + csstype: 3.1.1 + + /@types/resolve/1.17.1: + resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} + dependencies: + '@types/node': 16.11.59 + + /@types/retry/0.12.0: + resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + + /@types/scheduler/0.16.2: + resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} + + /@types/serve-index/1.9.1: + resolution: {integrity: sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==} + dependencies: + '@types/express': 4.17.14 + + /@types/serve-static/1.15.0: + resolution: {integrity: sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==} + dependencies: + '@types/mime': 3.0.1 + '@types/node': 16.11.59 + + /@types/sockjs/0.3.33: + resolution: {integrity: sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==} + dependencies: + '@types/node': 16.11.59 + + /@types/stack-utils/2.0.1: + resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} + + /@types/trusted-types/2.0.2: + resolution: {integrity: sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==} + + /@types/warning/3.0.0: + resolution: {integrity: sha512-t/Tvs5qR47OLOr+4E9ckN8AmP2Tf16gWq+/qA4iUGS/OOyHVO8wv2vjJuX8SNOUTJyWb+2t7wJm6cXILFnOROA==} + dev: false + + /@types/ws/8.5.3: + resolution: {integrity: sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==} + dependencies: + '@types/node': 16.11.59 + + /@types/yargs-parser/21.0.0: + resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} + + /@types/yargs/13.0.12: + resolution: {integrity: sha512-qCxJE1qgz2y0hA4pIxjBR+PelCH0U5CK1XJXFwCNqfmliatKp47UCXXE9Dyk1OXBDLvsCF57TqQEJaeLfDYEOQ==} + dependencies: + '@types/yargs-parser': 21.0.0 + dev: false + + /@types/yargs/16.0.4: + resolution: {integrity: sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==} + dependencies: + '@types/yargs-parser': 21.0.0 + + /@types/yargs/17.0.12: + resolution: {integrity: sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ==} + dependencies: + '@types/yargs-parser': 21.0.0 + + /@typescript-eslint/eslint-plugin/5.38.0_wsb62dxj2oqwgas4kadjymcmry: + resolution: {integrity: sha512-GgHi/GNuUbTOeoJiEANi0oI6fF3gBQc3bGFYj40nnAPCbhrtEDf2rjBmefFadweBmO1Du1YovHeDP2h5JLhtTQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/parser': 5.38.0_irgkl5vooow2ydyo6aokmferha + '@typescript-eslint/scope-manager': 5.38.0 + '@typescript-eslint/type-utils': 5.38.0_irgkl5vooow2ydyo6aokmferha + '@typescript-eslint/utils': 5.38.0_irgkl5vooow2ydyo6aokmferha + debug: 4.3.4 + eslint: 8.23.1 + ignore: 5.2.0 + regexpp: 3.2.0 + semver: 7.3.7 + tsutils: 3.21.0_typescript@4.8.3 + typescript: 4.8.3 + transitivePeerDependencies: + - supports-color + + /@typescript-eslint/experimental-utils/5.38.0_irgkl5vooow2ydyo6aokmferha: + resolution: {integrity: sha512-kzXBRfvGlicgGk4CYuRUqKvwc2s3wHXNssUWWJU18bhMRxriFm3BZWyQ6vEHBRpEIMKB6b7MIQHO+9lYlts19w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@typescript-eslint/utils': 5.38.0_irgkl5vooow2ydyo6aokmferha + eslint: 8.23.1 + transitivePeerDependencies: + - supports-color + - typescript + + /@typescript-eslint/parser/5.38.0_irgkl5vooow2ydyo6aokmferha: + resolution: {integrity: sha512-/F63giJGLDr0ms1Cr8utDAxP2SPiglaD6V+pCOcG35P2jCqdfR7uuEhz1GIC3oy4hkUF8xA1XSXmd9hOh/a5EA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 5.38.0 + '@typescript-eslint/types': 5.38.0 + '@typescript-eslint/typescript-estree': 5.38.0_typescript@4.8.3 + debug: 4.3.4 + eslint: 8.23.1 + typescript: 4.8.3 + transitivePeerDependencies: + - supports-color + + /@typescript-eslint/scope-manager/5.38.0: + resolution: {integrity: sha512-ByhHIuNyKD9giwkkLqzezZ9y5bALW8VNY6xXcP+VxoH4JBDKjU5WNnsiD4HJdglHECdV+lyaxhvQjTUbRboiTA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.38.0 + '@typescript-eslint/visitor-keys': 5.38.0 + + /@typescript-eslint/type-utils/5.38.0_irgkl5vooow2ydyo6aokmferha: + resolution: {integrity: sha512-iZq5USgybUcj/lfnbuelJ0j3K9dbs1I3RICAJY9NZZpDgBYXmuUlYQGzftpQA9wC8cKgtS6DASTvF3HrXwwozA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 5.38.0_typescript@4.8.3 + '@typescript-eslint/utils': 5.38.0_irgkl5vooow2ydyo6aokmferha + debug: 4.3.4 + eslint: 8.23.1 + tsutils: 3.21.0_typescript@4.8.3 + typescript: 4.8.3 + transitivePeerDependencies: + - supports-color + + /@typescript-eslint/types/5.38.0: + resolution: {integrity: sha512-HHu4yMjJ7i3Cb+8NUuRCdOGu2VMkfmKyIJsOr9PfkBVYLYrtMCK/Ap50Rpov+iKpxDTfnqvDbuPLgBE5FwUNfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + /@typescript-eslint/typescript-estree/5.38.0_typescript@4.8.3: + resolution: {integrity: sha512-6P0RuphkR+UuV7Avv7MU3hFoWaGcrgOdi8eTe1NwhMp2/GjUJoODBTRWzlHpZh6lFOaPmSvgxGlROa0Sg5Zbyg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.38.0 + '@typescript-eslint/visitor-keys': 5.38.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.3.7 + tsutils: 3.21.0_typescript@4.8.3 + typescript: 4.8.3 + transitivePeerDependencies: + - supports-color + + /@typescript-eslint/utils/5.38.0_irgkl5vooow2ydyo6aokmferha: + resolution: {integrity: sha512-6sdeYaBgk9Fh7N2unEXGz+D+som2QCQGPAf1SxrkEr+Z32gMreQ0rparXTNGRRfYUWk/JzbGdcM8NSSd6oqnTA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@types/json-schema': 7.0.11 + '@typescript-eslint/scope-manager': 5.38.0 + '@typescript-eslint/types': 5.38.0 + '@typescript-eslint/typescript-estree': 5.38.0_typescript@4.8.3 + eslint: 8.23.1 + eslint-scope: 5.1.1 + eslint-utils: 3.0.0_eslint@8.23.1 + transitivePeerDependencies: + - supports-color + - typescript + + /@typescript-eslint/visitor-keys/5.38.0: + resolution: {integrity: sha512-MxnrdIyArnTi+XyFLR+kt/uNAcdOnmT+879os7qDRI+EYySR4crXJq9BXPfRzzLGq0wgxkwidrCJ9WCAoacm1w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.38.0 + eslint-visitor-keys: 3.3.0 + + /@webassemblyjs/ast/1.11.1: + resolution: {integrity: sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==} + dependencies: + '@webassemblyjs/helper-numbers': 1.11.1 + '@webassemblyjs/helper-wasm-bytecode': 1.11.1 + + /@webassemblyjs/floating-point-hex-parser/1.11.1: + resolution: {integrity: sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==} + + /@webassemblyjs/helper-api-error/1.11.1: + resolution: {integrity: sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==} + + /@webassemblyjs/helper-buffer/1.11.1: + resolution: {integrity: sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==} + + /@webassemblyjs/helper-numbers/1.11.1: + resolution: {integrity: sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==} + dependencies: + '@webassemblyjs/floating-point-hex-parser': 1.11.1 + '@webassemblyjs/helper-api-error': 1.11.1 + '@xtuc/long': 4.2.2 + + /@webassemblyjs/helper-wasm-bytecode/1.11.1: + resolution: {integrity: sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==} + + /@webassemblyjs/helper-wasm-section/1.11.1: + resolution: {integrity: sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==} + dependencies: + '@webassemblyjs/ast': 1.11.1 + '@webassemblyjs/helper-buffer': 1.11.1 + '@webassemblyjs/helper-wasm-bytecode': 1.11.1 + '@webassemblyjs/wasm-gen': 1.11.1 + + /@webassemblyjs/ieee754/1.11.1: + resolution: {integrity: sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==} + dependencies: + '@xtuc/ieee754': 1.2.0 + + /@webassemblyjs/leb128/1.11.1: + resolution: {integrity: sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==} + dependencies: + '@xtuc/long': 4.2.2 + + /@webassemblyjs/utf8/1.11.1: + resolution: {integrity: sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==} + + /@webassemblyjs/wasm-edit/1.11.1: + resolution: {integrity: sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==} + dependencies: + '@webassemblyjs/ast': 1.11.1 + '@webassemblyjs/helper-buffer': 1.11.1 + '@webassemblyjs/helper-wasm-bytecode': 1.11.1 + '@webassemblyjs/helper-wasm-section': 1.11.1 + '@webassemblyjs/wasm-gen': 1.11.1 + '@webassemblyjs/wasm-opt': 1.11.1 + '@webassemblyjs/wasm-parser': 1.11.1 + '@webassemblyjs/wast-printer': 1.11.1 + + /@webassemblyjs/wasm-gen/1.11.1: + resolution: {integrity: sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==} + dependencies: + '@webassemblyjs/ast': 1.11.1 + '@webassemblyjs/helper-wasm-bytecode': 1.11.1 + '@webassemblyjs/ieee754': 1.11.1 + '@webassemblyjs/leb128': 1.11.1 + '@webassemblyjs/utf8': 1.11.1 + + /@webassemblyjs/wasm-opt/1.11.1: + resolution: {integrity: sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==} + dependencies: + '@webassemblyjs/ast': 1.11.1 + '@webassemblyjs/helper-buffer': 1.11.1 + '@webassemblyjs/wasm-gen': 1.11.1 + '@webassemblyjs/wasm-parser': 1.11.1 + + /@webassemblyjs/wasm-parser/1.11.1: + resolution: {integrity: sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==} + dependencies: + '@webassemblyjs/ast': 1.11.1 + '@webassemblyjs/helper-api-error': 1.11.1 + '@webassemblyjs/helper-wasm-bytecode': 1.11.1 + '@webassemblyjs/ieee754': 1.11.1 + '@webassemblyjs/leb128': 1.11.1 + '@webassemblyjs/utf8': 1.11.1 + + /@webassemblyjs/wast-printer/1.11.1: + resolution: {integrity: sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==} + dependencies: + '@webassemblyjs/ast': 1.11.1 + '@xtuc/long': 4.2.2 + + /@xtuc/ieee754/1.2.0: + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + + /@xtuc/long/4.2.2: + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + + /JSONStream/1.3.5: + resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} + hasBin: true + dependencies: + jsonparse: 1.3.1 + through: 2.3.8 + dev: true + + /abab/2.0.6: + resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} + + /accepts/1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + /acorn-globals/6.0.0: + resolution: {integrity: sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==} + dependencies: + acorn: 7.4.1 + acorn-walk: 7.2.0 + + /acorn-import-assertions/1.8.0_acorn@8.8.0: + resolution: {integrity: sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==} + peerDependencies: + acorn: ^8 + dependencies: + acorn: 8.8.0 + + /acorn-jsx/5.3.2_acorn@8.8.0: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.8.0 + + /acorn-node/1.8.2: + resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==} + dependencies: + acorn: 7.4.1 + acorn-walk: 7.2.0 + xtend: 4.0.2 + + /acorn-walk/7.2.0: + resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} + engines: {node: '>=0.4.0'} + + /acorn-walk/8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} + dev: true + + /acorn/7.4.1: + resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} + engines: {node: '>=0.4.0'} + hasBin: true + + /acorn/8.8.0: + resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==} + engines: {node: '>=0.4.0'} + hasBin: true + + /add-stream/1.0.0: + resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} + dev: true + + /address/1.2.1: + resolution: {integrity: sha512-B+6bi5D34+fDYENiH5qOlA0cV2rAGKuWZ9LeyUUehbXy8e0VS9e498yO0Jeeh+iM+6KbfudHTFjXw2MmJD4QRA==} + engines: {node: '>= 10.0.0'} + + /adjust-sourcemap-loader/4.0.0: + resolution: {integrity: sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==} + engines: {node: '>=8.9'} + dependencies: + loader-utils: 2.0.2 + regex-parser: 2.2.11 + + /agent-base/6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + + /aggregate-error/3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + dev: true + + /ahooks-v3-count/1.0.0: + resolution: {integrity: sha512-V7uUvAwnimu6eh/PED4mCDjE7tokeZQLKlxg9lCTMPhN+NjsSbtdacByVlR1oluXQzD3MOw55wylDmQo4+S9ZQ==} + dev: false + + /ahooks/3.7.1_react@18.2.0: + resolution: {integrity: sha512-9fooKjhScNyJaIPnlWd13LkY1gQYqv3BqwSA9ynHg1ZUtDqAICuCRoedV97ylrEL6QqI4zeq3bO3lQxkfWVNcg==} + engines: {node: '>=8.0.0'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@types/js-cookie': 2.2.7 + ahooks-v3-count: 1.0.0 + dayjs: 1.11.5 + intersection-observer: 0.12.2 + js-cookie: 2.2.1 + lodash: 4.17.21 + react: 18.2.0 + resize-observer-polyfill: 1.5.1 + screenfull: 5.2.0 + dev: false + + /ajv-formats/2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependenciesMeta: + ajv: + optional: true + dependencies: + ajv: 8.11.0 + + /ajv-keywords/3.5.2_ajv@6.12.6: + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 + dependencies: + ajv: 6.12.6 + + /ajv-keywords/5.1.0_ajv@8.11.0: + resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} + peerDependencies: + ajv: ^8.8.2 + dependencies: + ajv: 8.11.0 + fast-deep-equal: 3.1.3 + + /ajv/6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + /ajv/8.11.0: + resolution: {integrity: sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==} + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + /ansi-escapes/4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.21.3 + + /ansi-html-community/0.0.8: + resolution: {integrity: sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==} + engines: {'0': node >= 0.8.0} + hasBin: true + + /ansi-regex/4.1.1: + resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} + engines: {node: '>=6'} + dev: false + + /ansi-regex/5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + /ansi-regex/6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + + /ansi-styles/3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + + /ansi-styles/4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + + /ansi-styles/5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + /ansi-styles/6.1.1: + resolution: {integrity: sha512-qDOv24WjnYuL+wbwHdlsYZFy+cgPtrYw0Tn7GLORicQp9BkQLzrgI3Pm4VyR9ERZ41YTn7KlMPuL1n05WdZvmg==} + engines: {node: '>=12'} + dev: true + + /anymatch/3.1.2: + resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + /arg/4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + dev: true + + /arg/5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + /argparse/1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + dependencies: + sprintf-js: 1.0.3 + + /argparse/2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + /aria-query/4.2.2: + resolution: {integrity: sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==} + engines: {node: '>=6.0'} + dependencies: + '@babel/runtime': 7.19.0 + '@babel/runtime-corejs3': 7.19.1 + + /aria-query/5.0.2: + resolution: {integrity: sha512-eigU3vhqSO+Z8BKDnVLN/ompjhf3pYzecKXz8+whRy+9gZu8n1TCGfwzQUUPnqdHl9ax1Hr9031orZ+UOEYr7Q==} + engines: {node: '>=6.0'} + dev: true + + /array-flatten/1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + /array-flatten/2.1.2: + resolution: {integrity: sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==} + + /array-ify/1.0.0: + resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + dev: true + + /array-includes/3.1.5: + resolution: {integrity: sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.2 + get-intrinsic: 1.1.3 + is-string: 1.0.7 + + /array-union/2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + /array.prototype.flat/1.3.0: + resolution: {integrity: sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.2 + es-shim-unscopables: 1.0.0 + + /array.prototype.flatmap/1.3.0: + resolution: {integrity: sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.2 + es-shim-unscopables: 1.0.0 + + /array.prototype.reduce/1.0.4: + resolution: {integrity: sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.2 + es-array-method-boxes-properly: 1.0.0 + is-string: 1.0.7 + + /arrify/1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + dev: true + + /asap/2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + + /ast-types-flow/0.0.7: + resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==} + + /astral-regex/2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + dev: true + + /async/3.2.4: + resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} + + /asynckit/0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + /at-least-node/1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + + /atob/2.1.2: + resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} + engines: {node: '>= 4.5.0'} + hasBin: true + dev: false + + /autoprefixer/10.4.12_postcss@8.4.16: + resolution: {integrity: sha512-WrCGV9/b97Pa+jtwf5UGaRjgQIg7OK3D06GnoYoZNcG1Xb8Gt3EfuKjlhh9i/VtT16g6PYjZ69jdJ2g8FxSC4Q==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + dependencies: + browserslist: 4.21.4 + caniuse-lite: 1.0.30001408 + fraction.js: 4.2.0 + normalize-range: 0.1.2 + picocolors: 1.0.0 + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /axe-core/4.4.3: + resolution: {integrity: sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==} + engines: {node: '>=4'} + + /axios/0.27.2: + resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} + dependencies: + follow-redirects: 1.15.2 + form-data: 4.0.0 + transitivePeerDependencies: + - debug + dev: false + + /axobject-query/2.2.0: + resolution: {integrity: sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==} + + /babel-jest/27.5.1_@babel+core@7.19.1: + resolution: {integrity: sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + dependencies: + '@babel/core': 7.19.1 + '@jest/transform': 27.5.1 + '@jest/types': 27.5.1 + '@types/babel__core': 7.1.19 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 27.5.1_@babel+core@7.19.1 + chalk: 4.1.2 + graceful-fs: 4.2.10 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + /babel-loader/8.2.5_rhsdbzevgb5tizdhlla5jsbgyu: + resolution: {integrity: sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==} + engines: {node: '>= 8.9'} + peerDependencies: + '@babel/core': ^7.0.0 + webpack: '>=2' + dependencies: + '@babel/core': 7.19.1 + find-cache-dir: 3.3.2 + loader-utils: 2.0.2 + make-dir: 3.1.0 + schema-utils: 2.7.1 + webpack: 5.74.0 + + /babel-plugin-dynamic-import-node/2.3.3: + resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==} + dependencies: + object.assign: 4.1.4 + + /babel-plugin-istanbul/6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + dependencies: + '@babel/helper-plugin-utils': 7.19.0 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.0 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + /babel-plugin-jest-hoist/27.5.1: + resolution: {integrity: sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@babel/template': 7.18.10 + '@babel/types': 7.19.0 + '@types/babel__core': 7.1.19 + '@types/babel__traverse': 7.18.1 + + /babel-plugin-macros/3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + dependencies: + '@babel/runtime': 7.19.0 + cosmiconfig: 7.0.1 + resolve: 1.22.1 + + /babel-plugin-named-asset-import/0.3.8_@babel+core@7.19.1: + resolution: {integrity: sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==} + peerDependencies: + '@babel/core': ^7.1.0 + dependencies: + '@babel/core': 7.19.1 + + /babel-plugin-polyfill-corejs2/0.3.3_@babel+core@7.19.1: + resolution: {integrity: sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.19.1 + '@babel/core': 7.19.1 + '@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.19.1 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + + /babel-plugin-polyfill-corejs3/0.6.0_@babel+core@7.19.1: + resolution: {integrity: sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.19.1 + core-js-compat: 3.25.2 + transitivePeerDependencies: + - supports-color + + /babel-plugin-polyfill-regenerator/0.4.1_@babel+core@7.19.1: + resolution: {integrity: sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.1 + '@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.19.1 + transitivePeerDependencies: + - supports-color + + /babel-plugin-transform-react-remove-prop-types/0.4.24: + resolution: {integrity: sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==} + + /babel-preset-current-node-syntax/1.0.1_@babel+core@7.19.1: + resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.19.1 + '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.19.1 + '@babel/plugin-syntax-bigint': 7.8.3_@babel+core@7.19.1 + '@babel/plugin-syntax-class-properties': 7.12.13_@babel+core@7.19.1 + '@babel/plugin-syntax-import-meta': 7.10.4_@babel+core@7.19.1 + '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.19.1 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.19.1 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.19.1 + '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.19.1 + '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.19.1 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.19.1 + '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.19.1 + '@babel/plugin-syntax-top-level-await': 7.14.5_@babel+core@7.19.1 + + /babel-preset-jest/27.5.1_@babel+core@7.19.1: + resolution: {integrity: sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.19.1 + babel-plugin-jest-hoist: 27.5.1 + babel-preset-current-node-syntax: 1.0.1_@babel+core@7.19.1 + + /babel-preset-react-app/10.0.1: + resolution: {integrity: sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg==} + dependencies: + '@babel/core': 7.19.1 + '@babel/plugin-proposal-class-properties': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-proposal-decorators': 7.19.1_@babel+core@7.19.1 + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-proposal-numeric-separator': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-proposal-optional-chaining': 7.18.9_@babel+core@7.19.1 + '@babel/plugin-proposal-private-methods': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-proposal-private-property-in-object': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-flow-strip-types': 7.19.0_@babel+core@7.19.1 + '@babel/plugin-transform-react-display-name': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-runtime': 7.19.1_@babel+core@7.19.1 + '@babel/preset-env': 7.19.1_@babel+core@7.19.1 + '@babel/preset-react': 7.18.6_@babel+core@7.19.1 + '@babel/preset-typescript': 7.18.6_@babel+core@7.19.1 + '@babel/runtime': 7.19.0 + babel-plugin-macros: 3.1.0 + babel-plugin-transform-react-remove-prop-types: 0.4.24 + transitivePeerDependencies: + - supports-color + + /balanced-match/1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + /base64-js/1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: true + + /batch/0.6.1: + resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} + + /bfj/7.0.2: + resolution: {integrity: sha512-+e/UqUzwmzJamNF50tBV6tZPTORow7gQ96iFow+8b562OdMpEK0BcJEq2OSPEDmAbSMBQ7PKZ87ubFkgxpYWgw==} + engines: {node: '>= 8.0.0'} + dependencies: + bluebird: 3.7.2 + check-types: 11.1.2 + hoopy: 0.1.4 + tryer: 1.0.1 + + /big.js/5.2.2: + resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} + + /binary-extensions/2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + + /bl/4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.0 + dev: true + + /bluebird/3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + + /body-parser/1.20.0: + resolution: {integrity: sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + bytes: 3.1.2 + content-type: 1.0.4 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.10.3 + raw-body: 2.5.1 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + /bonjour-service/1.0.14: + resolution: {integrity: sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ==} + dependencies: + array-flatten: 2.1.2 + dns-equal: 1.0.0 + fast-deep-equal: 3.1.3 + multicast-dns: 7.2.5 + + /boolbase/1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + /bootstrap-icons/1.9.1: + resolution: {integrity: sha512-d4ZkO30MIkAhQ2nNRJqKXJVEQorALGbLWTuRxyCTJF96lRIV6imcgMehWGJUiJMJhglN0o2tqLIeDnMdiQEE9g==} + dev: false + + /bootstrap/5.2.1_@popperjs+core@2.11.6: + resolution: {integrity: sha512-UQi3v2NpVPEi1n35dmRRzBJFlgvWHYwyem6yHhuT6afYF+sziEt46McRbT//kVXZ7b1YUYEVGdXEH74Nx3xzGA==} + peerDependencies: + '@popperjs/core': ^2.11.6 + dependencies: + '@popperjs/core': 2.11.6 + dev: false + + /brace-expansion/1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + /brace-expansion/2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + + /braces/3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + + /browser-process-hrtime/1.0.0: + resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==} + + /browserslist/4.21.4: + resolution: {integrity: sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + caniuse-lite: 1.0.30001408 + electron-to-chromium: 1.4.256 + node-releases: 2.0.6 + update-browserslist-db: 1.0.9_browserslist@4.21.4 + + /bser/2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + dependencies: + node-int64: 0.4.0 + + /buffer-from/1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + /buffer/5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: true + + /builtin-modules/3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} + engines: {node: '>=6'} + + /builtins/5.0.1: + resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} + dependencies: + semver: 7.3.7 + dev: true + + /bytes/3.0.0: + resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} + engines: {node: '>= 0.8'} + + /bytes/3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + /cachedir/2.3.0: + resolution: {integrity: sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==} + engines: {node: '>=6'} + dev: true + + /call-bind/1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.1.3 + + /callsites/3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + /camel-case/4.1.2: + resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} + dependencies: + pascal-case: 3.1.2 + tslib: 2.4.0 + + /camelcase-css/2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + /camelcase-keys/6.2.2: + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} + dependencies: + camelcase: 5.3.1 + map-obj: 4.3.0 + quick-lru: 4.0.1 + dev: true + + /camelcase/5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + /camelcase/6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + /caniuse-api/3.0.0: + resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} + dependencies: + browserslist: 4.21.4 + caniuse-lite: 1.0.30001408 + lodash.memoize: 4.1.2 + lodash.uniq: 4.5.0 + + /caniuse-lite/1.0.30001408: + resolution: {integrity: sha512-DdUCktgMSM+1ndk9EFMZcavsGszV7zxV9O7MtOHniTa/iyAIwJCF0dFVBdU9SijJbfh29hC9bCs07wu8pjnGJQ==} + + /case-sensitive-paths-webpack-plugin/2.4.0: + resolution: {integrity: sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==} + engines: {node: '>=4'} + + /chalk/2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + /chalk/4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + /char-regex/1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + /char-regex/2.0.1: + resolution: {integrity: sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==} + engines: {node: '>=12.20'} + + /chardet/0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + dev: true + + /check-types/11.1.2: + resolution: {integrity: sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ==} + + /chokidar/3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.2 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 + + /chrome-trace-event/1.0.3: + resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==} + engines: {node: '>=6.0'} + + /ci-info/3.4.0: + resolution: {integrity: sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==} + + /cjs-module-lexer/1.2.2: + resolution: {integrity: sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==} + + /classnames/2.3.2: + resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==} + dev: false + + /clean-css/5.3.1: + resolution: {integrity: sha512-lCr8OHhiWCTw4v8POJovCoh4T7I9U11yVsPjMWWnnMmp9ZowCxyad1Pathle/9HjaDp+fdQKjO9fQydE6RHTZg==} + engines: {node: '>= 10.0'} + dependencies: + source-map: 0.6.1 + + /clean-stack/2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + dev: true + + /cli-cursor/3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + dependencies: + restore-cursor: 3.1.0 + dev: true + + /cli-spinners/2.7.0: + resolution: {integrity: sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==} + engines: {node: '>=6'} + dev: true + + /cli-truncate/2.1.0: + resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} + engines: {node: '>=8'} + dependencies: + slice-ansi: 3.0.0 + string-width: 4.2.3 + dev: true + + /cli-truncate/3.1.0: + resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + slice-ansi: 5.0.0 + string-width: 5.1.2 + dev: true + + /cli-width/3.0.0: + resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} + engines: {node: '>= 10'} + dev: true + + /cliui/7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + /clone/1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + dev: true + + /co/4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + /coa/2.0.2: + resolution: {integrity: sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==} + engines: {node: '>= 4.0'} + dependencies: + '@types/q': 1.5.5 + chalk: 2.4.2 + q: 1.5.1 + + /codemirror/5.65.0: + resolution: {integrity: sha512-gWEnHKEcz1Hyz7fsQWpK7P0sPI2/kSkRX2tc7DFA6TmZuDN75x/1ejnH/Pn8adYKrLEA1V2ww6L00GudHZbSKw==} + dev: false + + /collect-v8-coverage/1.0.1: + resolution: {integrity: sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==} + + /color-convert/1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + + /color-convert/2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + + /color-name/1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + /color-name/1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + /colord/2.9.3: + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + + /colorette/2.0.19: + resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} + + /combined-stream/1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + + /commander/2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + /commander/4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + dev: true + + /commander/7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + /commander/8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + + /commander/9.4.0: + resolution: {integrity: sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==} + engines: {node: ^12.20.0 || >=14} + dev: true + + /commitizen/4.2.5: + resolution: {integrity: sha512-9sXju8Qrz1B4Tw7kC5KhnvwYQN88qs2zbiB8oyMsnXZyJ24PPGiNM3nHr73d32dnE3i8VJEXddBFIbOgYSEXtQ==} + engines: {node: '>= 12'} + hasBin: true + dependencies: + cachedir: 2.3.0 + cz-conventional-changelog: 3.3.0 + dedent: 0.7.0 + detect-indent: 6.1.0 + find-node-modules: 2.1.3 + find-root: 1.1.0 + fs-extra: 9.1.0 + glob: 7.2.3 + inquirer: 8.2.4 + is-utf8: 0.2.1 + lodash: 4.17.21 + minimist: 1.2.6 + strip-bom: 4.0.0 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + dev: true + + /common-path-prefix/3.0.0: + resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} + + /common-tags/1.8.2: + resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} + engines: {node: '>=4.0.0'} + + /commondir/1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + + /compare-func/2.0.0: + resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + dependencies: + array-ify: 1.0.0 + dot-prop: 5.3.0 + dev: true + + /compressible/2.0.18: + resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + + /compression/1.7.4: + resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==} + engines: {node: '>= 0.8.0'} + dependencies: + accepts: 1.3.8 + bytes: 3.0.0 + compressible: 2.0.18 + debug: 2.6.9 + on-headers: 1.0.2 + safe-buffer: 5.1.2 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + /concat-map/0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + /confusing-browser-globals/1.0.11: + resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==} + + /connect-history-api-fallback/2.0.0: + resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==} + engines: {node: '>=0.8'} + + /content-disposition/0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + dependencies: + safe-buffer: 5.2.1 + + /content-type/1.0.4: + resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==} + engines: {node: '>= 0.6'} + + /conventional-changelog-angular/5.0.13: + resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==} + engines: {node: '>=10'} + dependencies: + compare-func: 2.0.0 + q: 1.5.1 + dev: true + + /conventional-changelog-atom/2.0.8: + resolution: {integrity: sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==} + engines: {node: '>=10'} + dependencies: + q: 1.5.1 + dev: true + + /conventional-changelog-cli/2.2.2: + resolution: {integrity: sha512-8grMV5Jo8S0kP3yoMeJxV2P5R6VJOqK72IiSV9t/4H5r/HiRqEBQ83bYGuz4Yzfdj4bjaAEhZN/FFbsFXr5bOA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + add-stream: 1.0.0 + conventional-changelog: 3.1.25 + lodash: 4.17.21 + meow: 8.1.2 + tempfile: 3.0.0 + dev: true + + /conventional-changelog-codemirror/2.0.8: + resolution: {integrity: sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==} + engines: {node: '>=10'} + dependencies: + q: 1.5.1 + dev: true + + /conventional-changelog-conventionalcommits/4.6.3: + resolution: {integrity: sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==} + engines: {node: '>=10'} + dependencies: + compare-func: 2.0.0 + lodash: 4.17.21 + q: 1.5.1 + dev: true + + /conventional-changelog-conventionalcommits/5.0.0: + resolution: {integrity: sha512-lCDbA+ZqVFQGUj7h9QBKoIpLhl8iihkO0nCTyRNzuXtcd7ubODpYB04IFy31JloiJgG0Uovu8ot8oxRzn7Nwtw==} + engines: {node: '>=10'} + dependencies: + compare-func: 2.0.0 + lodash: 4.17.21 + q: 1.5.1 + dev: true + + /conventional-changelog-core/4.2.4: + resolution: {integrity: sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==} + engines: {node: '>=10'} + dependencies: + add-stream: 1.0.0 + conventional-changelog-writer: 5.0.1 + conventional-commits-parser: 3.2.4 + dateformat: 3.0.3 + get-pkg-repo: 4.2.1 + git-raw-commits: 2.0.11 + git-remote-origin-url: 2.0.0 + git-semver-tags: 4.1.1 + lodash: 4.17.21 + normalize-package-data: 3.0.3 + q: 1.5.1 + read-pkg: 3.0.0 + read-pkg-up: 3.0.0 + through2: 4.0.2 + dev: true + + /conventional-changelog-ember/2.0.9: + resolution: {integrity: sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==} + engines: {node: '>=10'} + dependencies: + q: 1.5.1 + dev: true + + /conventional-changelog-eslint/3.0.9: + resolution: {integrity: sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==} + engines: {node: '>=10'} + dependencies: + q: 1.5.1 + dev: true + + /conventional-changelog-express/2.0.6: + resolution: {integrity: sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==} + engines: {node: '>=10'} + dependencies: + q: 1.5.1 + dev: true + + /conventional-changelog-jquery/3.0.11: + resolution: {integrity: sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==} + engines: {node: '>=10'} + dependencies: + q: 1.5.1 + dev: true + + /conventional-changelog-jshint/2.0.9: + resolution: {integrity: sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==} + engines: {node: '>=10'} + dependencies: + compare-func: 2.0.0 + q: 1.5.1 + dev: true + + /conventional-changelog-preset-loader/2.3.4: + resolution: {integrity: sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==} + engines: {node: '>=10'} + dev: true + + /conventional-changelog-writer/5.0.1: + resolution: {integrity: sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==} + engines: {node: '>=10'} + hasBin: true + dependencies: + conventional-commits-filter: 2.0.7 + dateformat: 3.0.3 + handlebars: 4.7.7 + json-stringify-safe: 5.0.1 + lodash: 4.17.21 + meow: 8.1.2 + semver: 6.3.0 + split: 1.0.1 + through2: 4.0.2 + dev: true + + /conventional-changelog/3.1.25: + resolution: {integrity: sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ==} + engines: {node: '>=10'} + dependencies: + conventional-changelog-angular: 5.0.13 + conventional-changelog-atom: 2.0.8 + conventional-changelog-codemirror: 2.0.8 + conventional-changelog-conventionalcommits: 4.6.3 + conventional-changelog-core: 4.2.4 + conventional-changelog-ember: 2.0.9 + conventional-changelog-eslint: 3.0.9 + conventional-changelog-express: 2.0.6 + conventional-changelog-jquery: 3.0.11 + conventional-changelog-jshint: 2.0.9 + conventional-changelog-preset-loader: 2.3.4 + dev: true + + /conventional-commit-types/3.0.0: + resolution: {integrity: sha512-SmmCYnOniSsAa9GqWOeLqc179lfr5TRu5b4QFDkbsrJ5TZjPJx85wtOr3zn+1dbeNiXDKGPbZ72IKbPhLXh/Lg==} + dev: true + + /conventional-commits-filter/2.0.7: + resolution: {integrity: sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==} + engines: {node: '>=10'} + dependencies: + lodash.ismatch: 4.4.0 + modify-values: 1.0.1 + dev: true + + /conventional-commits-parser/3.2.4: + resolution: {integrity: sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==} + engines: {node: '>=10'} + hasBin: true + dependencies: + is-text-path: 1.0.1 + JSONStream: 1.3.5 + lodash: 4.17.21 + meow: 8.1.2 + split2: 3.2.2 + through2: 4.0.2 + dev: true + + /convert-source-map/1.8.0: + resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==} + dependencies: + safe-buffer: 5.1.2 + + /cookie-signature/1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + + /cookie/0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + + /copy-to-clipboard/3.3.2: + resolution: {integrity: sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg==} + dependencies: + toggle-selection: 1.0.6 + dev: false + + /core-js-compat/3.25.2: + resolution: {integrity: sha512-TxfyECD4smdn3/CjWxczVtJqVLEEC2up7/82t7vC0AzNogr+4nQ8vyF7abxAuTXWvjTClSbvGhU0RgqA4ToQaQ==} + dependencies: + browserslist: 4.21.4 + + /core-js-pure/3.25.2: + resolution: {integrity: sha512-ItD7YpW1cUB4jaqFLZXe1AXkyqIxz6GqPnsDV4uF4hVcWh/WAGIqSqw5p0/WdsILM0Xht9s3Koyw05R3K6RtiA==} + requiresBuild: true + + /core-js/3.25.2: + resolution: {integrity: sha512-YB4IAT1bjEfxTJ1XYy11hJAKskO+qmhuDBM8/guIfMz4JvdsAQAqvyb97zXX7JgSrfPLG5mRGFWJwJD39ruq2A==} + requiresBuild: true + + /core-util-is/1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + /cosmiconfig-typescript-loader/4.1.0_3owiowz3ujipd4k6pbqn3n7oui: + resolution: {integrity: sha512-HbWIuR5O+XO5Oj9SZ5bzgrD4nN+rfhrm2PMb0FVx+t+XIvC45n8F0oTNnztXtspWGw0i2IzHaUWFD5LzV1JB4A==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@types/node': '*' + cosmiconfig: '>=7' + ts-node: '>=10' + typescript: '>=3' + dependencies: + '@types/node': 14.18.29 + cosmiconfig: 7.0.1 + ts-node: 10.9.1_ck2axrxkiif44rdbzjywaqjysa + typescript: 4.8.3 + dev: true + + /cosmiconfig/6.0.0: + resolution: {integrity: sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==} + engines: {node: '>=8'} + dependencies: + '@types/parse-json': 4.0.0 + import-fresh: 3.3.0 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + + /cosmiconfig/7.0.1: + resolution: {integrity: sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==} + engines: {node: '>=10'} + dependencies: + '@types/parse-json': 4.0.0 + import-fresh: 3.3.0 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + + /create-require/1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true + + /cross-fetch/3.1.5: + resolution: {integrity: sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==} + dependencies: + node-fetch: 2.6.7 + transitivePeerDependencies: + - encoding + dev: false + + /cross-spawn/7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + /crypto-random-string/2.0.0: + resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} + engines: {node: '>=8'} + + /css-blank-pseudo/3.0.3_postcss@8.4.16: + resolution: {integrity: sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==} + engines: {node: ^12 || ^14 || >=16} + hasBin: true + peerDependencies: + postcss: ^8.4 + dependencies: + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + + /css-declaration-sorter/6.3.1_postcss@8.4.16: + resolution: {integrity: sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==} + engines: {node: ^10 || ^12 || >=14} + peerDependencies: + postcss: ^8.0.9 + dependencies: + postcss: 8.4.16 + + /css-has-pseudo/3.0.4_postcss@8.4.16: + resolution: {integrity: sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==} + engines: {node: ^12 || ^14 || >=16} + hasBin: true + peerDependencies: + postcss: ^8.4 + dependencies: + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + + /css-loader/6.7.1_webpack@5.74.0: + resolution: {integrity: sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==} + engines: {node: '>= 12.13.0'} + peerDependencies: + webpack: ^5.0.0 + dependencies: + icss-utils: 5.1.0_postcss@8.4.16 + postcss: 8.4.16 + postcss-modules-extract-imports: 3.0.0_postcss@8.4.16 + postcss-modules-local-by-default: 4.0.0_postcss@8.4.16 + postcss-modules-scope: 3.0.0_postcss@8.4.16 + postcss-modules-values: 4.0.0_postcss@8.4.16 + postcss-value-parser: 4.2.0 + semver: 7.3.7 + webpack: 5.74.0 + + /css-minimizer-webpack-plugin/3.4.1_webpack@5.74.0: + resolution: {integrity: sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==} + engines: {node: '>= 12.13.0'} + peerDependencies: + '@parcel/css': '*' + clean-css: '*' + csso: '*' + esbuild: '*' + webpack: ^5.0.0 + peerDependenciesMeta: + '@parcel/css': + optional: true + clean-css: + optional: true + csso: + optional: true + esbuild: + optional: true + dependencies: + cssnano: 5.1.13_postcss@8.4.16 + jest-worker: 27.5.1 + postcss: 8.4.16 + schema-utils: 4.0.0 + serialize-javascript: 6.0.0 + source-map: 0.6.1 + webpack: 5.74.0 + + /css-prefers-color-scheme/6.0.3_postcss@8.4.16: + resolution: {integrity: sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==} + engines: {node: ^12 || ^14 || >=16} + hasBin: true + peerDependencies: + postcss: ^8.4 + dependencies: + postcss: 8.4.16 + + /css-select-base-adapter/0.1.1: + resolution: {integrity: sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==} + + /css-select/2.1.0: + resolution: {integrity: sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==} + dependencies: + boolbase: 1.0.0 + css-what: 3.4.2 + domutils: 1.7.0 + nth-check: 1.0.2 + + /css-select/4.3.0: + resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 4.3.1 + domutils: 2.8.0 + nth-check: 2.1.1 + + /css-tree/1.0.0-alpha.37: + resolution: {integrity: sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==} + engines: {node: '>=8.0.0'} + dependencies: + mdn-data: 2.0.4 + source-map: 0.6.1 + + /css-tree/1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + + /css-what/3.4.2: + resolution: {integrity: sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==} + engines: {node: '>= 6'} + + /css-what/6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + + /css.escape/1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + dev: false + + /css/2.2.4: + resolution: {integrity: sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==} + dependencies: + inherits: 2.0.4 + source-map: 0.6.1 + source-map-resolve: 0.5.3 + urix: 0.1.0 + dev: false + + /cssdb/7.0.1: + resolution: {integrity: sha512-pT3nzyGM78poCKLAEy2zWIVX2hikq6dIrjuZzLV98MumBg+xMTNYfHx7paUlfiRTgg91O/vR889CIf+qiv79Rw==} + + /cssesc/3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + /cssnano-preset-default/5.2.12_postcss@8.4.16: + resolution: {integrity: sha512-OyCBTZi+PXgylz9HAA5kHyoYhfGcYdwFmyaJzWnzxuGRtnMw/kR6ilW9XzlzlRAtB6PLT/r+prYgkef7hngFew==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + css-declaration-sorter: 6.3.1_postcss@8.4.16 + cssnano-utils: 3.1.0_postcss@8.4.16 + postcss: 8.4.16 + postcss-calc: 8.2.4_postcss@8.4.16 + postcss-colormin: 5.3.0_postcss@8.4.16 + postcss-convert-values: 5.1.2_postcss@8.4.16 + postcss-discard-comments: 5.1.2_postcss@8.4.16 + postcss-discard-duplicates: 5.1.0_postcss@8.4.16 + postcss-discard-empty: 5.1.1_postcss@8.4.16 + postcss-discard-overridden: 5.1.0_postcss@8.4.16 + postcss-merge-longhand: 5.1.6_postcss@8.4.16 + postcss-merge-rules: 5.1.2_postcss@8.4.16 + postcss-minify-font-values: 5.1.0_postcss@8.4.16 + postcss-minify-gradients: 5.1.1_postcss@8.4.16 + postcss-minify-params: 5.1.3_postcss@8.4.16 + postcss-minify-selectors: 5.2.1_postcss@8.4.16 + postcss-normalize-charset: 5.1.0_postcss@8.4.16 + postcss-normalize-display-values: 5.1.0_postcss@8.4.16 + postcss-normalize-positions: 5.1.1_postcss@8.4.16 + postcss-normalize-repeat-style: 5.1.1_postcss@8.4.16 + postcss-normalize-string: 5.1.0_postcss@8.4.16 + postcss-normalize-timing-functions: 5.1.0_postcss@8.4.16 + postcss-normalize-unicode: 5.1.0_postcss@8.4.16 + postcss-normalize-url: 5.1.0_postcss@8.4.16 + postcss-normalize-whitespace: 5.1.1_postcss@8.4.16 + postcss-ordered-values: 5.1.3_postcss@8.4.16 + postcss-reduce-initial: 5.1.0_postcss@8.4.16 + postcss-reduce-transforms: 5.1.0_postcss@8.4.16 + postcss-svgo: 5.1.0_postcss@8.4.16 + postcss-unique-selectors: 5.1.1_postcss@8.4.16 + + /cssnano-utils/3.1.0_postcss@8.4.16: + resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.16 + + /cssnano/5.1.13_postcss@8.4.16: + resolution: {integrity: sha512-S2SL2ekdEz6w6a2epXn4CmMKU4K3KpcyXLKfAYc9UQQqJRkD/2eLUG0vJ3Db/9OvO5GuAdgXw3pFbR6abqghDQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + cssnano-preset-default: 5.2.12_postcss@8.4.16 + lilconfig: 2.0.6 + postcss: 8.4.16 + yaml: 1.10.2 + + /csso/4.2.0: + resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} + engines: {node: '>=8.0.0'} + dependencies: + css-tree: 1.1.3 + + /cssom/0.3.8: + resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} + + /cssom/0.4.4: + resolution: {integrity: sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==} + + /cssstyle/2.3.0: + resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} + engines: {node: '>=8'} + dependencies: + cssom: 0.3.8 + + /csstype/3.1.1: + resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} + + /customize-cra/1.0.0: + resolution: {integrity: sha512-DbtaLuy59224U+xCiukkxSq8clq++MOtJ1Et7LED1fLszWe88EoblEYFBJ895sB1mC6B4uu3xPT/IjClELhMbA==} + dependencies: + lodash.flow: 3.5.0 + dev: true + + /cz-conventional-changelog/3.3.0: + resolution: {integrity: sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw==} + engines: {node: '>= 10'} + dependencies: + chalk: 2.4.2 + commitizen: 4.2.5 + conventional-commit-types: 3.0.0 + lodash.map: 4.6.0 + longest: 2.0.1 + word-wrap: 1.2.3 + optionalDependencies: + '@commitlint/load': 17.1.2 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + dev: true + + /d3-array/1.2.4: + resolution: {integrity: sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==} + dev: false + + /d3-array/3.2.0: + resolution: {integrity: sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==} + engines: {node: '>=12'} + dependencies: + internmap: 2.0.3 + dev: false + + /d3-axis/1.0.12: + resolution: {integrity: sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==} + dev: false + + /d3-axis/3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + dev: false + + /d3-brush/1.1.6: + resolution: {integrity: sha512-7RW+w7HfMCPyZLifTz/UnJmI5kdkXtpCbombUSs8xniAyo0vIbrDzDwUJB6eJOgl9u5DQOt2TQlYumxzD1SvYA==} + dependencies: + d3-dispatch: 1.0.6 + d3-drag: 1.2.5 + d3-interpolate: 1.4.0 + d3-selection: 1.4.2 + d3-transition: 1.3.2 + dev: false + + /d3-brush/3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1_d3-selection@3.0.0 + dev: false + + /d3-chord/1.0.6: + resolution: {integrity: sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==} + dependencies: + d3-array: 1.2.4 + d3-path: 1.0.9 + dev: false + + /d3-chord/3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + dependencies: + d3-path: 3.0.1 + dev: false + + /d3-collection/1.0.7: + resolution: {integrity: sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==} + dev: false + + /d3-color/1.4.1: + resolution: {integrity: sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==} + dev: false + + /d3-color/3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + dev: false + + /d3-contour/1.3.2: + resolution: {integrity: sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==} + dependencies: + d3-array: 1.2.4 + dev: false + + /d3-contour/4.0.0: + resolution: {integrity: sha512-7aQo0QHUTu/Ko3cP9YK9yUTxtoDEiDGwnBHyLxG5M4vqlBkO/uixMRele3nfsfj6UXOcuReVpVXzAboGraYIJw==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.0 + dev: false + + /d3-delaunay/6.0.2: + resolution: {integrity: sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==} + engines: {node: '>=12'} + dependencies: + delaunator: 5.0.0 + dev: false + + /d3-dispatch/1.0.6: + resolution: {integrity: sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==} + dev: false + + /d3-dispatch/3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + dev: false + + /d3-drag/1.2.5: + resolution: {integrity: sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==} + dependencies: + d3-dispatch: 1.0.6 + d3-selection: 1.4.2 + dev: false + + /d3-drag/3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + dev: false + + /d3-dsv/1.2.0: + resolution: {integrity: sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==} + hasBin: true + dependencies: + commander: 2.20.3 + iconv-lite: 0.4.24 + rw: 1.3.3 + dev: false + + /d3-dsv/3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + dev: false + + /d3-ease/1.0.7: + resolution: {integrity: sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==} + dev: false + + /d3-ease/3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + dev: false + + /d3-fetch/1.2.0: + resolution: {integrity: sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA==} + dependencies: + d3-dsv: 1.2.0 + dev: false + + /d3-fetch/3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + dependencies: + d3-dsv: 3.0.1 + dev: false + + /d3-force/1.2.1: + resolution: {integrity: sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==} + dependencies: + d3-collection: 1.0.7 + d3-dispatch: 1.0.6 + d3-quadtree: 1.0.7 + d3-timer: 1.0.10 + dev: false + + /d3-force/3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + dev: false + + /d3-format/1.4.5: + resolution: {integrity: sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==} + dev: false + + /d3-format/3.1.0: + resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + engines: {node: '>=12'} + dev: false + + /d3-geo/1.12.1: + resolution: {integrity: sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==} + dependencies: + d3-array: 1.2.4 + dev: false + + /d3-geo/3.0.1: + resolution: {integrity: sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.0 + dev: false + + /d3-hierarchy/1.1.9: + resolution: {integrity: sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==} + dev: false + + /d3-hierarchy/3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + dev: false + + /d3-interpolate/1.4.0: + resolution: {integrity: sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==} + dependencies: + d3-color: 1.4.1 + dev: false + + /d3-interpolate/3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + dependencies: + d3-color: 3.1.0 + dev: false + + /d3-path/1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + dev: false + + /d3-path/3.0.1: + resolution: {integrity: sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==} + engines: {node: '>=12'} + dev: false + + /d3-polygon/1.0.6: + resolution: {integrity: sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==} + dev: false + + /d3-polygon/3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + dev: false + + /d3-quadtree/1.0.7: + resolution: {integrity: sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==} + dev: false + + /d3-quadtree/3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + dev: false + + /d3-random/1.1.2: + resolution: {integrity: sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==} + dev: false + + /d3-random/3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + dev: false + + /d3-scale-chromatic/1.5.0: + resolution: {integrity: sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==} + dependencies: + d3-color: 1.4.1 + d3-interpolate: 1.4.0 + dev: false + + /d3-scale-chromatic/3.0.0: + resolution: {integrity: sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==} + engines: {node: '>=12'} + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + dev: false + + /d3-scale/2.2.2: + resolution: {integrity: sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==} + dependencies: + d3-array: 1.2.4 + d3-collection: 1.0.7 + d3-format: 1.4.5 + d3-interpolate: 1.4.0 + d3-time: 1.1.0 + d3-time-format: 2.3.0 + dev: false + + /d3-scale/4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.0 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.0.0 + d3-time-format: 4.1.0 + dev: false + + /d3-selection/1.4.2: + resolution: {integrity: sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==} + dev: false + + /d3-selection/3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + dev: false + + /d3-shape/1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + dependencies: + d3-path: 1.0.9 + dev: false + + /d3-shape/3.1.0: + resolution: {integrity: sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==} + engines: {node: '>=12'} + dependencies: + d3-path: 3.0.1 + dev: false + + /d3-time-format/2.3.0: + resolution: {integrity: sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==} + dependencies: + d3-time: 1.1.0 + dev: false + + /d3-time-format/4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + dependencies: + d3-time: 3.0.0 + dev: false + + /d3-time/1.1.0: + resolution: {integrity: sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==} + dev: false + + /d3-time/3.0.0: + resolution: {integrity: sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.0 + dev: false + + /d3-timer/1.0.10: + resolution: {integrity: sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==} + dev: false + + /d3-timer/3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + dev: false + + /d3-transition/1.3.2: + resolution: {integrity: sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==} + dependencies: + d3-color: 1.4.1 + d3-dispatch: 1.0.6 + d3-ease: 1.0.7 + d3-interpolate: 1.4.0 + d3-selection: 1.4.2 + d3-timer: 1.0.10 + dev: false + + /d3-transition/3.0.1_d3-selection@3.0.0: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + dev: false + + /d3-voronoi/1.1.4: + resolution: {integrity: sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==} + dev: false + + /d3-zoom/1.8.3: + resolution: {integrity: sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==} + dependencies: + d3-dispatch: 1.0.6 + d3-drag: 1.2.5 + d3-interpolate: 1.4.0 + d3-selection: 1.4.2 + d3-transition: 1.3.2 + dev: false + + /d3-zoom/3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1_d3-selection@3.0.0 + dev: false + + /d3/5.16.0: + resolution: {integrity: sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==} + dependencies: + d3-array: 1.2.4 + d3-axis: 1.0.12 + d3-brush: 1.1.6 + d3-chord: 1.0.6 + d3-collection: 1.0.7 + d3-color: 1.4.1 + d3-contour: 1.3.2 + d3-dispatch: 1.0.6 + d3-drag: 1.2.5 + d3-dsv: 1.2.0 + d3-ease: 1.0.7 + d3-fetch: 1.2.0 + d3-force: 1.2.1 + d3-format: 1.4.5 + d3-geo: 1.12.1 + d3-hierarchy: 1.1.9 + d3-interpolate: 1.4.0 + d3-path: 1.0.9 + d3-polygon: 1.0.6 + d3-quadtree: 1.0.7 + d3-random: 1.1.2 + d3-scale: 2.2.2 + d3-scale-chromatic: 1.5.0 + d3-selection: 1.4.2 + d3-shape: 1.3.7 + d3-time: 1.1.0 + d3-time-format: 2.3.0 + d3-timer: 1.0.10 + d3-transition: 1.3.2 + d3-voronoi: 1.1.4 + d3-zoom: 1.8.3 + dev: false + + /d3/7.6.1: + resolution: {integrity: sha512-txMTdIHFbcpLx+8a0IFhZsbp+PfBBPt8yfbmukZTQFroKuFqIwqswF0qE5JXWefylaAVpSXFoKm3yP+jpNLFLw==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.0 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.0 + d3-delaunay: 6.0.2 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.0 + d3-geo: 3.0.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.0.1 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.0.0 + d3-selection: 3.0.0 + d3-shape: 3.1.0 + d3-time: 3.0.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1_d3-selection@3.0.0 + d3-zoom: 3.0.0 + dev: false + + /dagre-d3/0.6.4: + resolution: {integrity: sha512-e/6jXeCP7/ptlAM48clmX4xTZc5Ek6T6kagS7Oz2HrYSdqcLZFLqpAfh7ldbZRFfxCZVyh61NEPR08UQRVxJzQ==} + dependencies: + d3: 5.16.0 + dagre: 0.8.5 + graphlib: 2.1.8 + lodash: 4.17.21 + dev: false + + /dagre/0.8.5: + resolution: {integrity: sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==} + dependencies: + graphlib: 2.1.8 + lodash: 4.17.21 + dev: false + + /damerau-levenshtein/1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + + /dargs/7.0.0: + resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} + engines: {node: '>=8'} + dev: true + + /data-urls/2.0.0: + resolution: {integrity: sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==} + engines: {node: '>=10'} + dependencies: + abab: 2.0.6 + whatwg-mimetype: 2.3.0 + whatwg-url: 8.7.0 + + /dateformat/3.0.3: + resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} + dev: true + + /dayjs/1.11.5: + resolution: {integrity: sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==} + dev: false + + /debug/2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.0.0 + + /debug/3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + + /debug/4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + + /decamelize-keys/1.1.0: + resolution: {integrity: sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==} + engines: {node: '>=0.10.0'} + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + dev: true + + /decamelize/1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + dev: true + + /decimal.js/10.4.1: + resolution: {integrity: sha512-F29o+vci4DodHYT9UrR5IEbfBw9pE5eSapIJdTqXK5+6hq+t8VRxwQyKlW2i+KDKFkkJQRvFyI/QXD83h8LyQw==} + + /decode-uri-component/0.2.0: + resolution: {integrity: sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==} + engines: {node: '>=0.10'} + dev: false + + /dedent/0.7.0: + resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} + + /deep-is/0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + /deepmerge/4.2.2: + resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} + engines: {node: '>=0.10.0'} + + /default-gateway/6.0.3: + resolution: {integrity: sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==} + engines: {node: '>= 10'} + dependencies: + execa: 5.1.1 + + /defaults/1.0.3: + resolution: {integrity: sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==} + dependencies: + clone: 1.0.4 + dev: true + + /define-lazy-prop/2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + + /define-properties/1.1.4: + resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} + engines: {node: '>= 0.4'} + dependencies: + has-property-descriptors: 1.0.0 + object-keys: 1.1.1 + + /defined/1.0.0: + resolution: {integrity: sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==} + + /delaunator/5.0.0: + resolution: {integrity: sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==} + dependencies: + robust-predicates: 3.0.1 + dev: false + + /delayed-stream/1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + /depd/1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + + /depd/2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + /dequal/2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + dev: false + + /destroy/1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + /detect-file/1.0.0: + resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} + engines: {node: '>=0.10.0'} + dev: true + + /detect-indent/6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + dev: true + + /detect-newline/3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + /detect-node/2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + + /detect-port-alt/1.1.6: + resolution: {integrity: sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==} + engines: {node: '>= 4.2.1'} + hasBin: true + dependencies: + address: 1.2.1 + debug: 2.6.9 + transitivePeerDependencies: + - supports-color + + /detective/5.2.1: + resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==} + engines: {node: '>=0.8.0'} + hasBin: true + dependencies: + acorn-node: 1.8.2 + defined: 1.0.0 + minimist: 1.2.6 + + /didyoumean/1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + /diff-sequences/24.9.0: + resolution: {integrity: sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==} + engines: {node: '>= 6'} + dev: false + + /diff-sequences/27.5.1: + resolution: {integrity: sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + /diff/4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dev: true + + /dir-glob/3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + + /dlv/1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + /dns-equal/1.0.0: + resolution: {integrity: sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==} + + /dns-packet/5.4.0: + resolution: {integrity: sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==} + engines: {node: '>=6'} + dependencies: + '@leichtgewicht/ip-codec': 2.0.4 + + /doctrine/2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + dependencies: + esutils: 2.0.3 + + /doctrine/3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dependencies: + esutils: 2.0.3 + + /dom-accessibility-api/0.5.14: + resolution: {integrity: sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==} + dev: true + + /dom-converter/0.2.0: + resolution: {integrity: sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==} + dependencies: + utila: 0.4.0 + + /dom-helpers/5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dependencies: + '@babel/runtime': 7.19.0 + csstype: 3.1.1 + dev: false + + /dom-serializer/0.2.2: + resolution: {integrity: sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==} + dependencies: + domelementtype: 2.3.0 + entities: 2.2.0 + + /dom-serializer/1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + + /domelementtype/1.3.1: + resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==} + + /domelementtype/2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + /domexception/2.0.1: + resolution: {integrity: sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==} + engines: {node: '>=8'} + dependencies: + webidl-conversions: 5.0.0 + + /domhandler/4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + + /dompurify/2.4.0: + resolution: {integrity: sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA==} + dev: false + + /domutils/1.7.0: + resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==} + dependencies: + dom-serializer: 0.2.2 + domelementtype: 1.3.1 + + /domutils/2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + + /dot-case/3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + dependencies: + no-case: 3.0.4 + tslib: 2.4.0 + + /dot-prop/5.3.0: + resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + engines: {node: '>=8'} + dependencies: + is-obj: 2.0.0 + dev: true + + /dotenv-expand/5.1.0: + resolution: {integrity: sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==} + + /dotenv/10.0.0: + resolution: {integrity: sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==} + engines: {node: '>=10'} + + /duplexer/0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + + /eastasianwidth/0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: true + + /ee-first/1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + /ejs/3.1.8: + resolution: {integrity: sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==} + engines: {node: '>=0.10.0'} + hasBin: true + dependencies: + jake: 10.8.5 + + /electron-to-chromium/1.4.256: + resolution: {integrity: sha512-x+JnqyluoJv8I0U9gVe+Sk2st8vF0CzMt78SXxuoWCooLLY2k5VerIBdpvG7ql6GKI4dzNnPjmqgDJ76EdaAKw==} + + /emittery/0.10.2: + resolution: {integrity: sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==} + engines: {node: '>=12'} + + /emittery/0.8.1: + resolution: {integrity: sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==} + engines: {node: '>=10'} + + /emoji-regex/8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + /emoji-regex/9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + /emojis-list/3.0.0: + resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} + engines: {node: '>= 4'} + + /encodeurl/1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + /enhanced-resolve/5.10.0: + resolution: {integrity: sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==} + engines: {node: '>=10.13.0'} + dependencies: + graceful-fs: 4.2.10 + tapable: 2.2.1 + + /entities/2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + + /env-cmd/10.1.0: + resolution: {integrity: sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==} + engines: {node: '>=8.0.0'} + hasBin: true + dependencies: + commander: 4.1.1 + cross-spawn: 7.0.3 + dev: true + + /error-ex/1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + dependencies: + is-arrayish: 0.2.1 + + /error-stack-parser/2.1.4: + resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + dependencies: + stackframe: 1.3.4 + + /es-abstract/1.20.2: + resolution: {integrity: sha512-XxXQuVNrySBNlEkTYJoDNFe5+s2yIOpzq80sUHEdPdQr0S5nTLz4ZPPPswNIpKseDDUS5yghX1gfLIHQZ1iNuQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + es-to-primitive: 1.2.1 + function-bind: 1.1.1 + function.prototype.name: 1.1.5 + get-intrinsic: 1.1.3 + get-symbol-description: 1.0.0 + has: 1.0.3 + has-property-descriptors: 1.0.0 + has-symbols: 1.0.3 + internal-slot: 1.0.3 + is-callable: 1.2.6 + is-negative-zero: 2.0.2 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + is-string: 1.0.7 + is-weakref: 1.0.2 + object-inspect: 1.12.2 + object-keys: 1.1.1 + object.assign: 4.1.4 + regexp.prototype.flags: 1.4.3 + string.prototype.trimend: 1.0.5 + string.prototype.trimstart: 1.0.5 + unbox-primitive: 1.0.2 + + /es-array-method-boxes-properly/1.0.0: + resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} + + /es-module-lexer/0.9.3: + resolution: {integrity: sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==} + + /es-shim-unscopables/1.0.0: + resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} + dependencies: + has: 1.0.3 + + /es-to-primitive/1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.6 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + + /escalade/3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + + /escape-html/1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + /escape-string-regexp/1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + /escape-string-regexp/2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + /escape-string-regexp/4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + /escodegen/2.0.0: + resolution: {integrity: sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==} + engines: {node: '>=6.0'} + hasBin: true + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionator: 0.8.3 + optionalDependencies: + source-map: 0.6.1 + + /eslint-config-airbnb-base/15.0.0_hdzsmr7kawaomymueo2tso6fjq: + resolution: {integrity: sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==} + engines: {node: ^10.12.0 || >=12.0.0} + peerDependencies: + eslint: ^7.32.0 || ^8.2.0 + eslint-plugin-import: ^2.25.2 + dependencies: + confusing-browser-globals: 1.0.11 + eslint: 8.23.1 + eslint-plugin-import: 2.26.0_cxqatnnjiq7ozd2bkspxnuicdq + object.assign: 4.1.4 + object.entries: 1.1.5 + semver: 6.3.0 + dev: true + + /eslint-config-airbnb-typescript/17.0.0_j57hrpt2hfp47otngkwtnuyxpa: + resolution: {integrity: sha512-elNiuzD0kPAPTXjFWg+lE24nMdHMtuxgYoD30OyMD6yrW1AhFZPAg27VX7d3tzOErw+dgJTNWfRSDqEcXb4V0g==} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^5.13.0 + '@typescript-eslint/parser': ^5.0.0 + eslint: ^7.32.0 || ^8.2.0 + eslint-plugin-import: ^2.25.3 + dependencies: + '@typescript-eslint/eslint-plugin': 5.38.0_wsb62dxj2oqwgas4kadjymcmry + '@typescript-eslint/parser': 5.38.0_irgkl5vooow2ydyo6aokmferha + eslint: 8.23.1 + eslint-config-airbnb-base: 15.0.0_hdzsmr7kawaomymueo2tso6fjq + eslint-plugin-import: 2.26.0_cxqatnnjiq7ozd2bkspxnuicdq + dev: true + + /eslint-config-airbnb/19.0.4_4zstfqq5uopk5xuvotejlnl36y: + resolution: {integrity: sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==} + engines: {node: ^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^7.32.0 || ^8.2.0 + eslint-plugin-import: ^2.25.3 + eslint-plugin-jsx-a11y: ^6.5.1 + eslint-plugin-react: ^7.28.0 + eslint-plugin-react-hooks: ^4.3.0 + dependencies: + eslint: 8.23.1 + eslint-config-airbnb-base: 15.0.0_hdzsmr7kawaomymueo2tso6fjq + eslint-plugin-import: 2.26.0_cxqatnnjiq7ozd2bkspxnuicdq + eslint-plugin-jsx-a11y: 6.6.1_eslint@8.23.1 + eslint-plugin-react: 7.31.8_eslint@8.23.1 + eslint-plugin-react-hooks: 4.6.0_eslint@8.23.1 + object.assign: 4.1.4 + object.entries: 1.1.5 + dev: true + + /eslint-config-prettier/8.5.0_eslint@8.23.1: + resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + dependencies: + eslint: 8.23.1 + dev: true + + /eslint-config-react-app/7.0.1_ep5hkfurrjf46kbnkcej3benz4: + resolution: {integrity: sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==} + engines: {node: '>=14.0.0'} + peerDependencies: + eslint: ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@babel/core': 7.19.1 + '@babel/eslint-parser': 7.19.1_zdglor7vg7osicr5spasq6cc5a + '@rushstack/eslint-patch': 1.2.0 + '@typescript-eslint/eslint-plugin': 5.38.0_wsb62dxj2oqwgas4kadjymcmry + '@typescript-eslint/parser': 5.38.0_irgkl5vooow2ydyo6aokmferha + babel-preset-react-app: 10.0.1 + confusing-browser-globals: 1.0.11 + eslint: 8.23.1 + eslint-plugin-flowtype: 8.0.3_yb7llsfoad77a2vx2os7pgkvmi + eslint-plugin-import: 2.26.0_cxqatnnjiq7ozd2bkspxnuicdq + eslint-plugin-jest: 25.7.0_hpujes4m5fznz335nz2hgbshme + eslint-plugin-jsx-a11y: 6.6.1_eslint@8.23.1 + eslint-plugin-react: 7.31.8_eslint@8.23.1 + eslint-plugin-react-hooks: 4.6.0_eslint@8.23.1 + eslint-plugin-testing-library: 5.6.4_irgkl5vooow2ydyo6aokmferha + typescript: 4.8.3 + transitivePeerDependencies: + - '@babel/plugin-syntax-flow' + - '@babel/plugin-transform-react-jsx' + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - jest + - supports-color + + /eslint-config-standard-with-typescript/22.0.0_fsqc7gnfr7ufpr4slszrtm5abq: + resolution: {integrity: sha512-VA36U7UlFpwULvkdnh6MQj5GAV2Q+tT68ALLAwJP0ZuNXU2m0wX07uxX4qyLRdHgSzH4QJ73CveKBuSOYvh7vQ==} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^5.0.0 + eslint: ^8.0.1 + eslint-plugin-import: ^2.25.2 + eslint-plugin-n: ^15.0.0 + eslint-plugin-promise: ^6.0.0 + typescript: '*' + dependencies: + '@typescript-eslint/eslint-plugin': 5.38.0_wsb62dxj2oqwgas4kadjymcmry + '@typescript-eslint/parser': 5.38.0_irgkl5vooow2ydyo6aokmferha + eslint: 8.23.1 + eslint-config-standard: 17.0.0_4nulviyjkaspo7v2xlghuwxbf4 + eslint-plugin-import: 2.26.0_cxqatnnjiq7ozd2bkspxnuicdq + eslint-plugin-n: 15.2.5_eslint@8.23.1 + eslint-plugin-promise: 6.0.1_eslint@8.23.1 + typescript: 4.8.3 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-config-standard/17.0.0_4nulviyjkaspo7v2xlghuwxbf4: + resolution: {integrity: sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==} + peerDependencies: + eslint: ^8.0.1 + eslint-plugin-import: ^2.25.2 + eslint-plugin-n: ^15.0.0 + eslint-plugin-promise: ^6.0.0 + dependencies: + eslint: 8.23.1 + eslint-plugin-import: 2.26.0_cxqatnnjiq7ozd2bkspxnuicdq + eslint-plugin-n: 15.2.5_eslint@8.23.1 + eslint-plugin-promise: 6.0.1_eslint@8.23.1 + dev: true + + /eslint-import-resolver-node/0.3.6: + resolution: {integrity: sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==} + dependencies: + debug: 3.2.7 + resolve: 1.22.1 + transitivePeerDependencies: + - supports-color + + /eslint-module-utils/2.7.4_p4kveujfv4nmzmj4mix5hvnxlm: + resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 5.38.0_irgkl5vooow2ydyo6aokmferha + debug: 3.2.7 + eslint: 8.23.1 + eslint-import-resolver-node: 0.3.6 + transitivePeerDependencies: + - supports-color + + /eslint-plugin-es/4.1.0_eslint@8.23.1: + resolution: {integrity: sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==} + engines: {node: '>=8.10.0'} + peerDependencies: + eslint: '>=4.19.1' + dependencies: + eslint: 8.23.1 + eslint-utils: 2.1.0 + regexpp: 3.2.0 + dev: true + + /eslint-plugin-flowtype/8.0.3_yb7llsfoad77a2vx2os7pgkvmi: + resolution: {integrity: sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@babel/plugin-syntax-flow': ^7.14.5 + '@babel/plugin-transform-react-jsx': ^7.14.9 + eslint: ^8.1.0 + dependencies: + '@babel/plugin-syntax-flow': 7.18.6_@babel+core@7.19.1 + '@babel/plugin-transform-react-jsx': 7.19.0_@babel+core@7.19.1 + eslint: 8.23.1 + lodash: 4.17.21 + string-natural-compare: 3.0.1 + + /eslint-plugin-import/2.26.0_cxqatnnjiq7ozd2bkspxnuicdq: + resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + dependencies: + '@typescript-eslint/parser': 5.38.0_irgkl5vooow2ydyo6aokmferha + array-includes: 3.1.5 + array.prototype.flat: 1.3.0 + debug: 2.6.9 + doctrine: 2.1.0 + eslint: 8.23.1 + eslint-import-resolver-node: 0.3.6 + eslint-module-utils: 2.7.4_p4kveujfv4nmzmj4mix5hvnxlm + has: 1.0.3 + is-core-module: 2.10.0 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.values: 1.1.5 + resolve: 1.22.1 + tsconfig-paths: 3.14.1 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + /eslint-plugin-jest/25.7.0_hpujes4m5fznz335nz2hgbshme: + resolution: {integrity: sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^4.0.0 || ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + jest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + jest: + optional: true + dependencies: + '@typescript-eslint/eslint-plugin': 5.38.0_wsb62dxj2oqwgas4kadjymcmry + '@typescript-eslint/experimental-utils': 5.38.0_irgkl5vooow2ydyo6aokmferha + eslint: 8.23.1 + jest: 27.5.1 + transitivePeerDependencies: + - supports-color + - typescript + + /eslint-plugin-jsx-a11y/6.6.1_eslint@8.23.1: + resolution: {integrity: sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + dependencies: + '@babel/runtime': 7.19.0 + aria-query: 4.2.2 + array-includes: 3.1.5 + ast-types-flow: 0.0.7 + axe-core: 4.4.3 + axobject-query: 2.2.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 8.23.1 + has: 1.0.3 + jsx-ast-utils: 3.3.3 + language-tags: 1.0.5 + minimatch: 3.1.2 + semver: 6.3.0 + + /eslint-plugin-n/15.2.5_eslint@8.23.1: + resolution: {integrity: sha512-8+BYsqiyZfpu6NXmdLOXVUfk8IocpCjpd8nMRRH0A9ulrcemhb2VI9RSJMEy5udx++A/YcVPD11zT8hpFq368g==} + engines: {node: '>=12.22.0'} + peerDependencies: + eslint: '>=7.0.0' + dependencies: + builtins: 5.0.1 + eslint: 8.23.1 + eslint-plugin-es: 4.1.0_eslint@8.23.1 + eslint-utils: 3.0.0_eslint@8.23.1 + ignore: 5.2.0 + is-core-module: 2.10.0 + minimatch: 3.1.2 + resolve: 1.22.1 + semver: 7.3.7 + dev: true + + /eslint-plugin-prettier/4.2.1_cabrci5exjdaojcvd6xoxgeowu: + resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + eslint: '>=7.28.0' + eslint-config-prettier: '*' + prettier: '>=2.0.0' + peerDependenciesMeta: + eslint-config-prettier: + optional: true + dependencies: + eslint: 8.23.1 + eslint-config-prettier: 8.5.0_eslint@8.23.1 + prettier: 2.7.1 + prettier-linter-helpers: 1.0.0 + dev: true + + /eslint-plugin-promise/6.0.1_eslint@8.23.1: + resolution: {integrity: sha512-uM4Tgo5u3UWQiroOyDEsYcVMOo7re3zmno0IZmB5auxoaQNIceAbXEkSt8RNrKtaYehARHG06pYK6K1JhtP0Zw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + dependencies: + eslint: 8.23.1 + dev: true + + /eslint-plugin-react-hooks/4.6.0_eslint@8.23.1: + resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + dependencies: + eslint: 8.23.1 + + /eslint-plugin-react/7.31.8_eslint@8.23.1: + resolution: {integrity: sha512-5lBTZmgQmARLLSYiwI71tiGVTLUuqXantZM6vlSY39OaDSV0M7+32K5DnLkmFrwTe+Ksz0ffuLUC91RUviVZfw==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + dependencies: + array-includes: 3.1.5 + array.prototype.flatmap: 1.3.0 + doctrine: 2.1.0 + eslint: 8.23.1 + estraverse: 5.3.0 + jsx-ast-utils: 3.3.3 + minimatch: 3.1.2 + object.entries: 1.1.5 + object.fromentries: 2.0.5 + object.hasown: 1.1.1 + object.values: 1.1.5 + prop-types: 15.8.1 + resolve: 2.0.0-next.4 + semver: 6.3.0 + string.prototype.matchall: 4.0.7 + + /eslint-plugin-testing-library/5.6.4_irgkl5vooow2ydyo6aokmferha: + resolution: {integrity: sha512-0oW3tC5NNT2WexmJ3848a/utawOymw4ibl3/NkwywndVAz2hT9+ab70imA7ccg3RaScQgMvJT60OL00hpmJvrg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0, npm: '>=6'} + peerDependencies: + eslint: ^7.5.0 || ^8.0.0 + dependencies: + '@typescript-eslint/utils': 5.38.0_irgkl5vooow2ydyo6aokmferha + eslint: 8.23.1 + transitivePeerDependencies: + - supports-color + - typescript + + /eslint-scope/5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + /eslint-scope/7.1.1: + resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + /eslint-utils/2.1.0: + resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} + engines: {node: '>=6'} + dependencies: + eslint-visitor-keys: 1.3.0 + dev: true + + /eslint-utils/3.0.0_eslint@8.23.1: + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + dependencies: + eslint: 8.23.1 + eslint-visitor-keys: 2.1.0 + + /eslint-visitor-keys/1.3.0: + resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} + engines: {node: '>=4'} + dev: true + + /eslint-visitor-keys/2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + + /eslint-visitor-keys/3.3.0: + resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + /eslint-webpack-plugin/3.2.0_cnsurwdbw57xgwxuf5k544xt5e: + resolution: {integrity: sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==} + engines: {node: '>= 12.13.0'} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + webpack: ^5.0.0 + dependencies: + '@types/eslint': 8.4.6 + eslint: 8.23.1 + jest-worker: 28.1.3 + micromatch: 4.0.5 + normalize-path: 3.0.0 + schema-utils: 4.0.0 + webpack: 5.74.0 + + /eslint/8.23.1: + resolution: {integrity: sha512-w7C1IXCc6fNqjpuYd0yPlcTKKmHlHHktRkzmBPZ+7cvNBQuiNjx0xaMTjAJGCafJhQkrFJooREv0CtrVzmHwqg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint/eslintrc': 1.3.2 + '@humanwhocodes/config-array': 0.10.4 + '@humanwhocodes/gitignore-to-minimatch': 1.0.2 + '@humanwhocodes/module-importer': 1.0.1 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.1.1 + eslint-utils: 3.0.0_eslint@8.23.1 + eslint-visitor-keys: 3.3.0 + espree: 9.4.0 + esquery: 1.4.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.17.0 + globby: 11.1.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.0 + import-fresh: 3.3.0 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + js-sdsl: 4.1.4 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.1 + regexpp: 3.2.0 + strip-ansi: 6.0.1 + strip-json-comments: 3.1.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + /espree/9.4.0: + resolution: {integrity: sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.8.0 + acorn-jsx: 5.3.2_acorn@8.8.0 + eslint-visitor-keys: 3.3.0 + + /esprima/4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + /esquery/1.4.0: + resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + + /esrecurse/4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.3.0 + + /estraverse/4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + /estraverse/5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + /estree-walker/1.0.1: + resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} + + /esutils/2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + /etag/1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + /eventemitter3/4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + /events/3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + /execa/5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + /execa/6.1.0: + resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 3.0.1 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.1.0 + onetime: 6.0.0 + signal-exit: 3.0.7 + strip-final-newline: 3.0.0 + dev: true + + /exit/0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + + /expand-tilde/2.0.2: + resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} + engines: {node: '>=0.10.0'} + dependencies: + homedir-polyfill: 1.0.3 + dev: true + + /expect/27.5.1: + resolution: {integrity: sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/types': 27.5.1 + jest-get-type: 27.5.1 + jest-matcher-utils: 27.5.1 + jest-message-util: 27.5.1 + + /express/4.18.1: + resolution: {integrity: sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==} + engines: {node: '>= 0.10.0'} + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.0 + content-disposition: 0.5.4 + content-type: 1.0.4 + cookie: 0.5.0 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.2.0 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.1 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.7 + proxy-addr: 2.0.7 + qs: 6.10.3 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.18.0 + serve-static: 1.15.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + /external-editor/3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + dev: true + + /fast-deep-equal/3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + /fast-diff/1.2.0: + resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==} + dev: true + + /fast-glob/3.2.12: + resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + + /fast-json-stable-stringify/2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + /fast-levenshtein/2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + /fastq/1.13.0: + resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} + dependencies: + reusify: 1.0.4 + + /faye-websocket/0.11.4: + resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} + engines: {node: '>=0.8.0'} + dependencies: + websocket-driver: 0.7.4 + + /fb-watchman/2.0.1: + resolution: {integrity: sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==} + dependencies: + bser: 2.1.1 + + /figures/3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} + dependencies: + escape-string-regexp: 1.0.5 + dev: true + + /file-entry-cache/6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flat-cache: 3.0.4 + + /file-loader/6.2.0_webpack@5.74.0: + resolution: {integrity: sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==} + engines: {node: '>= 10.13.0'} + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + dependencies: + loader-utils: 2.0.2 + schema-utils: 3.1.1 + webpack: 5.74.0 + + /filelist/1.0.4: + resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + dependencies: + minimatch: 5.1.0 + + /filesize/8.0.7: + resolution: {integrity: sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==} + engines: {node: '>= 0.4.0'} + + /fill-range/7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + + /finalhandler/1.2.0: + resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + engines: {node: '>= 0.8'} + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + /find-cache-dir/3.3.2: + resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} + engines: {node: '>=8'} + dependencies: + commondir: 1.0.1 + make-dir: 3.1.0 + pkg-dir: 4.2.0 + + /find-node-modules/2.1.3: + resolution: {integrity: sha512-UC2I2+nx1ZuOBclWVNdcnbDR5dlrOdVb7xNjmT/lHE+LsgztWks3dG7boJ37yTS/venXw84B/mAW9uHVoC5QRg==} + dependencies: + findup-sync: 4.0.0 + merge: 2.1.1 + dev: true + + /find-root/1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + dev: true + + /find-up/2.1.0: + resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} + engines: {node: '>=4'} + dependencies: + locate-path: 2.0.0 + dev: true + + /find-up/3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + engines: {node: '>=6'} + dependencies: + locate-path: 3.0.0 + + /find-up/4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + /find-up/5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + /findup-sync/4.0.0: + resolution: {integrity: sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==} + engines: {node: '>= 8'} + dependencies: + detect-file: 1.0.0 + is-glob: 4.0.3 + micromatch: 4.0.5 + resolve-dir: 1.0.1 + dev: true + + /flat-cache/3.0.4: + resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flatted: 3.2.7 + rimraf: 3.0.2 + + /flatted/3.2.7: + resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} + + /follow-redirects/1.15.2: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + /fork-ts-checker-webpack-plugin/6.5.2_npfwkgbcmgrbevrxnqgustqabe: + resolution: {integrity: sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA==} + engines: {node: '>=10', yarn: '>=1.0.0'} + peerDependencies: + eslint: '>= 6' + typescript: '>= 2.7' + vue-template-compiler: '*' + webpack: '>= 4' + peerDependenciesMeta: + eslint: + optional: true + vue-template-compiler: + optional: true + dependencies: + '@babel/code-frame': 7.18.6 + '@types/json-schema': 7.0.11 + chalk: 4.1.2 + chokidar: 3.5.3 + cosmiconfig: 6.0.0 + deepmerge: 4.2.2 + eslint: 8.23.1 + fs-extra: 9.1.0 + glob: 7.2.3 + memfs: 3.4.7 + minimatch: 3.1.2 + schema-utils: 2.7.0 + semver: 7.3.7 + tapable: 1.1.3 + typescript: 4.8.3 + webpack: 5.74.0 + + /form-data/3.0.1: + resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + /form-data/4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + + /forwarded/0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + /fraction.js/4.2.0: + resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} + + /fresh/0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + /fs-extra/10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + dependencies: + graceful-fs: 4.2.10 + jsonfile: 6.1.0 + universalify: 2.0.0 + + /fs-extra/9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.10 + jsonfile: 6.1.0 + universalify: 2.0.0 + + /fs-monkey/1.0.3: + resolution: {integrity: sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==} + + /fs.realpath/1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + /fsevents/2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + optional: true + + /function-bind/1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + + /function.prototype.name/1.1.5: + resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.2 + functions-have-names: 1.2.3 + + /functions-have-names/1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + /gensync/1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + /get-caller-file/2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + /get-intrinsic/1.1.3: + resolution: {integrity: sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==} + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-symbols: 1.0.3 + + /get-own-enumerable-property-symbols/3.0.2: + resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} + + /get-package-type/0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + /get-pkg-repo/4.2.1: + resolution: {integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==} + engines: {node: '>=6.9.0'} + hasBin: true + dependencies: + '@hutson/parse-repository-url': 3.0.2 + hosted-git-info: 4.1.0 + through2: 2.0.5 + yargs: 16.2.0 + dev: true + + /get-stream/6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + /get-symbol-description/1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.3 + + /git-raw-commits/2.0.11: + resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} + engines: {node: '>=10'} + hasBin: true + dependencies: + dargs: 7.0.0 + lodash: 4.17.21 + meow: 8.1.2 + split2: 3.2.2 + through2: 4.0.2 + dev: true + + /git-remote-origin-url/2.0.0: + resolution: {integrity: sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==} + engines: {node: '>=4'} + dependencies: + gitconfiglocal: 1.0.0 + pify: 2.3.0 + dev: true + + /git-semver-tags/4.1.1: + resolution: {integrity: sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + meow: 8.1.2 + semver: 6.3.0 + dev: true + + /gitconfiglocal/1.0.0: + resolution: {integrity: sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==} + dependencies: + ini: 1.3.8 + dev: true + + /glob-parent/5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + + /glob-parent/6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + + /glob-to-regexp/0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + + /glob/7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + /global-dirs/0.1.1: + resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} + engines: {node: '>=4'} + dependencies: + ini: 1.3.8 + dev: true + + /global-modules/1.0.0: + resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==} + engines: {node: '>=0.10.0'} + dependencies: + global-prefix: 1.0.2 + is-windows: 1.0.2 + resolve-dir: 1.0.1 + dev: true + + /global-modules/2.0.0: + resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} + engines: {node: '>=6'} + dependencies: + global-prefix: 3.0.0 + + /global-prefix/1.0.2: + resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==} + engines: {node: '>=0.10.0'} + dependencies: + expand-tilde: 2.0.2 + homedir-polyfill: 1.0.3 + ini: 1.3.8 + is-windows: 1.0.2 + which: 1.3.1 + dev: true + + /global-prefix/3.0.0: + resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} + engines: {node: '>=6'} + dependencies: + ini: 1.3.8 + kind-of: 6.0.3 + which: 1.3.1 + + /globals/11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + /globals/13.17.0: + resolution: {integrity: sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + + /globby/11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.2.12 + ignore: 5.2.0 + merge2: 1.4.1 + slash: 3.0.0 + + /graceful-fs/4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + + /grapheme-splitter/1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + + /graphlib/2.1.8: + resolution: {integrity: sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==} + dependencies: + lodash: 4.17.21 + dev: false + + /gzip-size/6.0.0: + resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} + engines: {node: '>=10'} + dependencies: + duplexer: 0.1.2 + + /handle-thing/2.0.1: + resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} + + /handlebars/4.7.7: + resolution: {integrity: sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==} + engines: {node: '>=0.4.7'} + hasBin: true + dependencies: + minimist: 1.2.6 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.17.1 + dev: true + + /hard-rejection/2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} + dev: true + + /harmony-reflect/1.6.2: + resolution: {integrity: sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==} + + /has-bigints/1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + + /has-flag/3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + /has-flag/4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + /has-property-descriptors/1.0.0: + resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} + dependencies: + get-intrinsic: 1.1.3 + + /has-symbols/1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + + /has-tostringtag/1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + + /has/1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + + /he/1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + /highlight.js/11.6.0: + resolution: {integrity: sha512-ig1eqDzJaB0pqEvlPVIpSSyMaO92bH1N2rJpLMN/nX396wTpDA4Eq0uK+7I/2XG17pFaaKE0kjV/XPeGt7Evjw==} + engines: {node: '>=12.0.0'} + dev: false + + /homedir-polyfill/1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} + dependencies: + parse-passwd: 1.0.0 + dev: true + + /hoopy/0.1.4: + resolution: {integrity: sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==} + engines: {node: '>= 6.0.0'} + + /hosted-git-info/2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + dev: true + + /hosted-git-info/4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + dependencies: + lru-cache: 6.0.0 + dev: true + + /hpack.js/2.1.6: + resolution: {integrity: sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==} + dependencies: + inherits: 2.0.4 + obuf: 1.1.2 + readable-stream: 2.3.7 + wbuf: 1.7.3 + + /html-encoding-sniffer/2.0.1: + resolution: {integrity: sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==} + engines: {node: '>=10'} + dependencies: + whatwg-encoding: 1.0.5 + + /html-entities/2.3.3: + resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==} + + /html-escaper/2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + /html-minifier-terser/6.1.0: + resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==} + engines: {node: '>=12'} + hasBin: true + dependencies: + camel-case: 4.1.2 + clean-css: 5.3.1 + commander: 8.3.0 + he: 1.2.0 + param-case: 3.0.4 + relateurl: 0.2.7 + terser: 5.15.0 + + /html-parse-stringify/3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + dependencies: + void-elements: 3.1.0 + dev: false + + /html-webpack-plugin/5.5.0_webpack@5.74.0: + resolution: {integrity: sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==} + engines: {node: '>=10.13.0'} + peerDependencies: + webpack: ^5.20.0 + dependencies: + '@types/html-minifier-terser': 6.1.0 + html-minifier-terser: 6.1.0 + lodash: 4.17.21 + pretty-error: 4.0.0 + tapable: 2.2.1 + webpack: 5.74.0 + + /htmlparser2/6.1.0: + resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==} + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + domutils: 2.8.0 + entities: 2.2.0 + + /http-deceiver/1.2.7: + resolution: {integrity: sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==} + + /http-errors/1.6.3: + resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} + engines: {node: '>= 0.6'} + dependencies: + depd: 1.1.2 + inherits: 2.0.3 + setprototypeof: 1.1.0 + statuses: 1.5.0 + + /http-errors/2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + /http-parser-js/0.5.8: + resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==} + + /http-proxy-agent/4.0.1: + resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} + engines: {node: '>= 6'} + dependencies: + '@tootallnate/once': 1.1.2 + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + + /http-proxy-middleware/2.0.6_@types+express@4.17.14: + resolution: {integrity: sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/express': ^4.17.13 + peerDependenciesMeta: + '@types/express': + optional: true + dependencies: + '@types/express': 4.17.14 + '@types/http-proxy': 1.17.9 + http-proxy: 1.18.1 + is-glob: 4.0.3 + is-plain-obj: 3.0.0 + micromatch: 4.0.5 + transitivePeerDependencies: + - debug + + /http-proxy/1.18.1: + resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} + engines: {node: '>=8.0.0'} + dependencies: + eventemitter3: 4.0.7 + follow-redirects: 1.15.2 + requires-port: 1.0.0 + transitivePeerDependencies: + - debug + + /https-proxy-agent/5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + + /human-signals/2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + /human-signals/3.0.1: + resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==} + engines: {node: '>=12.20.0'} + dev: true + + /husky/8.0.1: + resolution: {integrity: sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==} + engines: {node: '>=14'} + hasBin: true + dev: true + + /i18next-chained-backend/3.1.0: + resolution: {integrity: sha512-ltWy0fPMrtXjq0rSLo7s4ZF92LGvVsO47hhV3czaZXzMzhpFva6LVKMyDT7x82R8vAyB7VAeyGLBvMiW4W543A==} + dependencies: + '@babel/runtime': 7.19.0 + dev: false + + /i18next-http-backend/1.4.4: + resolution: {integrity: sha512-M4gLPe6JKZ2p1UmE6t4rzWV/sAxgrLThW7ztXAsTpFwFqXoyzhTzX8eYxVv9KjpCQh4K9nwxnEjEi+74C4Thbg==} + dependencies: + cross-fetch: 3.1.5 + transitivePeerDependencies: + - encoding + dev: false + + /i18next-localstorage-backend/3.1.3: + resolution: {integrity: sha512-tx8dxQTEsTnRC654IrXPFr94c3NH7bIVHGKHnGvbgefpLz13/uFT5ITsmhqhg/gOza0TIj8e5jTsGnQytIhh+A==} + dependencies: + '@babel/runtime': 7.19.0 + dev: false + + /i18next/21.9.2: + resolution: {integrity: sha512-00fVrLQOwy45nm3OtC9l1WiLK3nJlIYSljgCt0qzTaAy65aciMdRy9GsuW+a2AtKtdg9/njUGfRH30LRupV7ZQ==} + dependencies: + '@babel/runtime': 7.19.0 + dev: false + + /iconv-lite/0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + + /iconv-lite/0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + + /icss-utils/5.1.0_postcss@8.4.16: + resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + dependencies: + postcss: 8.4.16 + + /idb/7.0.2: + resolution: {integrity: sha512-jjKrT1EnyZewQ/gCBb/eyiYrhGzws2FeY92Yx8qT9S9GeQAmo4JFVIiWRIfKW/6Ob9A+UDAOW9j9jn58fy2HIg==} + + /identity-obj-proxy/3.0.0: + resolution: {integrity: sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==} + engines: {node: '>=4'} + dependencies: + harmony-reflect: 1.6.2 + + /ieee754/1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: true + + /ignore/5.2.0: + resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} + engines: {node: '>= 4'} + + /immer/9.0.15: + resolution: {integrity: sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ==} + + /immutable/4.1.0: + resolution: {integrity: sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==} + + /import-fresh/3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + /import-local/3.1.0: + resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} + engines: {node: '>=8'} + hasBin: true + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + /imurmurhash/0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + /indent-string/4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + /inflight/1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + /inherits/2.0.3: + resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} + + /inherits/2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + /ini/1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + /inquirer/8.2.4: + resolution: {integrity: sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==} + engines: {node: '>=12.0.0'} + dependencies: + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-width: 3.0.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.21 + mute-stream: 0.0.8 + ora: 5.4.1 + run-async: 2.4.1 + rxjs: 7.5.6 + string-width: 4.2.3 + strip-ansi: 6.0.1 + through: 2.3.8 + wrap-ansi: 7.0.0 + dev: true + + /internal-slot/1.0.3: + resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.1.3 + has: 1.0.3 + side-channel: 1.0.4 + + /internmap/2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + dev: false + + /intersection-observer/0.12.2: + resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==} + dev: false + + /invariant/2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + dependencies: + loose-envify: 1.4.0 + dev: false + + /ipaddr.js/1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + /ipaddr.js/2.0.1: + resolution: {integrity: sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==} + engines: {node: '>= 10'} + + /is-arrayish/0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + /is-bigint/1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.2 + + /is-binary-path/2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + + /is-boolean-object/1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + + /is-callable/1.2.6: + resolution: {integrity: sha512-krO72EO2NptOGAX2KYyqbP9vYMlNAXdB53rq6f8LXY6RY7JdSR/3BD6wLUlPHSAesmY9vstNrjvqGaCiRK/91Q==} + engines: {node: '>= 0.4'} + + /is-core-module/2.10.0: + resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==} + dependencies: + has: 1.0.3 + + /is-date-object/1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + + /is-docker/2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + /is-extglob/2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + /is-fullwidth-code-point/3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + /is-fullwidth-code-point/4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + dev: true + + /is-generator-fn/2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + + /is-glob/4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + + /is-interactive/1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + dev: true + + /is-module/1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + + /is-negative-zero/2.0.2: + resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + engines: {node: '>= 0.4'} + + /is-number-object/1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + + /is-number/7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + /is-obj/1.0.1: + resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} + engines: {node: '>=0.10.0'} + + /is-obj/2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + dev: true + + /is-plain-obj/1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + dev: true + + /is-plain-obj/3.0.0: + resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==} + engines: {node: '>=10'} + + /is-potential-custom-element-name/1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + /is-regex/1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + + /is-regexp/1.0.0: + resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} + engines: {node: '>=0.10.0'} + + /is-root/2.1.0: + resolution: {integrity: sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==} + engines: {node: '>=6'} + + /is-shared-array-buffer/1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + dependencies: + call-bind: 1.0.2 + + /is-stream/2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + /is-stream/3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + + /is-string/1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + + /is-symbol/1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + + /is-text-path/1.0.1: + resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} + engines: {node: '>=0.10.0'} + dependencies: + text-extensions: 1.9.0 + dev: true + + /is-typedarray/1.0.0: + resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + + /is-unicode-supported/0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + dev: true + + /is-utf8/0.2.1: + resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==} + dev: true + + /is-weakref/1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + dependencies: + call-bind: 1.0.2 + + /is-windows/1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + dev: true + + /is-wsl/2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + dependencies: + is-docker: 2.2.1 + + /isarray/1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + /isexe/2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + /istanbul-lib-coverage/3.2.0: + resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} + engines: {node: '>=8'} + + /istanbul-lib-instrument/5.2.0: + resolution: {integrity: sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==} + engines: {node: '>=8'} + dependencies: + '@babel/core': 7.19.1 + '@babel/parser': 7.19.1 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.0 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + + /istanbul-lib-report/3.0.0: + resolution: {integrity: sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==} + engines: {node: '>=8'} + dependencies: + istanbul-lib-coverage: 3.2.0 + make-dir: 3.1.0 + supports-color: 7.2.0 + + /istanbul-lib-source-maps/4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + dependencies: + debug: 4.3.4 + istanbul-lib-coverage: 3.2.0 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + /istanbul-reports/3.1.5: + resolution: {integrity: sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==} + engines: {node: '>=8'} + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.0 + + /jake/10.8.5: + resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + async: 3.2.4 + chalk: 4.1.2 + filelist: 1.0.4 + minimatch: 3.1.2 + + /jest-changed-files/27.5.1: + resolution: {integrity: sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/types': 27.5.1 + execa: 5.1.1 + throat: 6.0.1 + + /jest-circus/27.5.1: + resolution: {integrity: sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/environment': 27.5.1 + '@jest/test-result': 27.5.1 + '@jest/types': 27.5.1 + '@types/node': 16.11.59 + chalk: 4.1.2 + co: 4.6.0 + dedent: 0.7.0 + expect: 27.5.1 + is-generator-fn: 2.1.0 + jest-each: 27.5.1 + jest-matcher-utils: 27.5.1 + jest-message-util: 27.5.1 + jest-runtime: 27.5.1 + jest-snapshot: 27.5.1 + jest-util: 27.5.1 + pretty-format: 27.5.1 + slash: 3.0.0 + stack-utils: 2.0.5 + throat: 6.0.1 + transitivePeerDependencies: + - supports-color + + /jest-cli/27.5.1: + resolution: {integrity: sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 27.5.1 + '@jest/test-result': 27.5.1 + '@jest/types': 27.5.1 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.10 + import-local: 3.1.0 + jest-config: 27.5.1 + jest-util: 27.5.1 + jest-validate: 27.5.1 + prompts: 2.4.2 + yargs: 16.2.0 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - ts-node + - utf-8-validate + + /jest-config/27.5.1: + resolution: {integrity: sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + peerDependencies: + ts-node: '>=9.0.0' + peerDependenciesMeta: + ts-node: + optional: true + dependencies: + '@babel/core': 7.19.1 + '@jest/test-sequencer': 27.5.1 + '@jest/types': 27.5.1 + babel-jest: 27.5.1_@babel+core@7.19.1 + chalk: 4.1.2 + ci-info: 3.4.0 + deepmerge: 4.2.2 + glob: 7.2.3 + graceful-fs: 4.2.10 + jest-circus: 27.5.1 + jest-environment-jsdom: 27.5.1 + jest-environment-node: 27.5.1 + jest-get-type: 27.5.1 + jest-jasmine2: 27.5.1 + jest-regex-util: 27.5.1 + jest-resolve: 27.5.1 + jest-runner: 27.5.1 + jest-util: 27.5.1 + jest-validate: 27.5.1 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 27.5.1 + slash: 3.0.0 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - utf-8-validate + + /jest-diff/24.9.0: + resolution: {integrity: sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==} + engines: {node: '>= 6'} + dependencies: + chalk: 2.4.2 + diff-sequences: 24.9.0 + jest-get-type: 24.9.0 + pretty-format: 24.9.0 + dev: false + + /jest-diff/27.5.1: + resolution: {integrity: sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + chalk: 4.1.2 + diff-sequences: 27.5.1 + jest-get-type: 27.5.1 + pretty-format: 27.5.1 + + /jest-docblock/27.5.1: + resolution: {integrity: sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + detect-newline: 3.1.0 + + /jest-each/27.5.1: + resolution: {integrity: sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/types': 27.5.1 + chalk: 4.1.2 + jest-get-type: 27.5.1 + jest-util: 27.5.1 + pretty-format: 27.5.1 + + /jest-environment-jsdom/27.5.1: + resolution: {integrity: sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/environment': 27.5.1 + '@jest/fake-timers': 27.5.1 + '@jest/types': 27.5.1 + '@types/node': 16.11.59 + jest-mock: 27.5.1 + jest-util: 27.5.1 + jsdom: 16.7.0 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - utf-8-validate + + /jest-environment-node/27.5.1: + resolution: {integrity: sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/environment': 27.5.1 + '@jest/fake-timers': 27.5.1 + '@jest/types': 27.5.1 + '@types/node': 16.11.59 + jest-mock: 27.5.1 + jest-util: 27.5.1 + + /jest-get-type/24.9.0: + resolution: {integrity: sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==} + engines: {node: '>= 6'} + dev: false + + /jest-get-type/27.5.1: + resolution: {integrity: sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + /jest-haste-map/27.5.1: + resolution: {integrity: sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/types': 27.5.1 + '@types/graceful-fs': 4.1.5 + '@types/node': 16.11.59 + anymatch: 3.1.2 + fb-watchman: 2.0.1 + graceful-fs: 4.2.10 + jest-regex-util: 27.5.1 + jest-serializer: 27.5.1 + jest-util: 27.5.1 + jest-worker: 27.5.1 + micromatch: 4.0.5 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.2 + + /jest-jasmine2/27.5.1: + resolution: {integrity: sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/environment': 27.5.1 + '@jest/source-map': 27.5.1 + '@jest/test-result': 27.5.1 + '@jest/types': 27.5.1 + '@types/node': 16.11.59 + chalk: 4.1.2 + co: 4.6.0 + expect: 27.5.1 + is-generator-fn: 2.1.0 + jest-each: 27.5.1 + jest-matcher-utils: 27.5.1 + jest-message-util: 27.5.1 + jest-runtime: 27.5.1 + jest-snapshot: 27.5.1 + jest-util: 27.5.1 + pretty-format: 27.5.1 + throat: 6.0.1 + transitivePeerDependencies: + - supports-color + + /jest-leak-detector/27.5.1: + resolution: {integrity: sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + jest-get-type: 27.5.1 + pretty-format: 27.5.1 + + /jest-matcher-utils/24.9.0: + resolution: {integrity: sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==} + engines: {node: '>= 6'} + dependencies: + chalk: 2.4.2 + jest-diff: 24.9.0 + jest-get-type: 24.9.0 + pretty-format: 24.9.0 + dev: false + + /jest-matcher-utils/27.5.1: + resolution: {integrity: sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + chalk: 4.1.2 + jest-diff: 27.5.1 + jest-get-type: 27.5.1 + pretty-format: 27.5.1 + + /jest-message-util/27.5.1: + resolution: {integrity: sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@babel/code-frame': 7.18.6 + '@jest/types': 27.5.1 + '@types/stack-utils': 2.0.1 + chalk: 4.1.2 + graceful-fs: 4.2.10 + micromatch: 4.0.5 + pretty-format: 27.5.1 + slash: 3.0.0 + stack-utils: 2.0.5 + + /jest-message-util/28.1.3: + resolution: {integrity: sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@babel/code-frame': 7.18.6 + '@jest/types': 28.1.3 + '@types/stack-utils': 2.0.1 + chalk: 4.1.2 + graceful-fs: 4.2.10 + micromatch: 4.0.5 + pretty-format: 28.1.3 + slash: 3.0.0 + stack-utils: 2.0.5 + + /jest-mock/27.5.1: + resolution: {integrity: sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/types': 27.5.1 + '@types/node': 16.11.59 + + /jest-pnp-resolver/1.2.2_jest-resolve@27.5.1: + resolution: {integrity: sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + dependencies: + jest-resolve: 27.5.1 + + /jest-regex-util/27.5.1: + resolution: {integrity: sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + /jest-regex-util/28.0.2: + resolution: {integrity: sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + + /jest-resolve-dependencies/27.5.1: + resolution: {integrity: sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/types': 27.5.1 + jest-regex-util: 27.5.1 + jest-snapshot: 27.5.1 + transitivePeerDependencies: + - supports-color + + /jest-resolve/27.5.1: + resolution: {integrity: sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/types': 27.5.1 + chalk: 4.1.2 + graceful-fs: 4.2.10 + jest-haste-map: 27.5.1 + jest-pnp-resolver: 1.2.2_jest-resolve@27.5.1 + jest-util: 27.5.1 + jest-validate: 27.5.1 + resolve: 1.22.1 + resolve.exports: 1.1.0 + slash: 3.0.0 + + /jest-runner/27.5.1: + resolution: {integrity: sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/console': 27.5.1 + '@jest/environment': 27.5.1 + '@jest/test-result': 27.5.1 + '@jest/transform': 27.5.1 + '@jest/types': 27.5.1 + '@types/node': 16.11.59 + chalk: 4.1.2 + emittery: 0.8.1 + graceful-fs: 4.2.10 + jest-docblock: 27.5.1 + jest-environment-jsdom: 27.5.1 + jest-environment-node: 27.5.1 + jest-haste-map: 27.5.1 + jest-leak-detector: 27.5.1 + jest-message-util: 27.5.1 + jest-resolve: 27.5.1 + jest-runtime: 27.5.1 + jest-util: 27.5.1 + jest-worker: 27.5.1 + source-map-support: 0.5.21 + throat: 6.0.1 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - utf-8-validate + + /jest-runtime/27.5.1: + resolution: {integrity: sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/environment': 27.5.1 + '@jest/fake-timers': 27.5.1 + '@jest/globals': 27.5.1 + '@jest/source-map': 27.5.1 + '@jest/test-result': 27.5.1 + '@jest/transform': 27.5.1 + '@jest/types': 27.5.1 + chalk: 4.1.2 + cjs-module-lexer: 1.2.2 + collect-v8-coverage: 1.0.1 + execa: 5.1.1 + glob: 7.2.3 + graceful-fs: 4.2.10 + jest-haste-map: 27.5.1 + jest-message-util: 27.5.1 + jest-mock: 27.5.1 + jest-regex-util: 27.5.1 + jest-resolve: 27.5.1 + jest-snapshot: 27.5.1 + jest-util: 27.5.1 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + /jest-serializer/27.5.1: + resolution: {integrity: sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@types/node': 16.11.59 + graceful-fs: 4.2.10 + + /jest-snapshot/27.5.1: + resolution: {integrity: sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@babel/core': 7.19.1 + '@babel/generator': 7.19.0 + '@babel/plugin-syntax-typescript': 7.18.6_@babel+core@7.19.1 + '@babel/traverse': 7.19.1 + '@babel/types': 7.19.0 + '@jest/transform': 27.5.1 + '@jest/types': 27.5.1 + '@types/babel__traverse': 7.18.1 + '@types/prettier': 2.7.0 + babel-preset-current-node-syntax: 1.0.1_@babel+core@7.19.1 + chalk: 4.1.2 + expect: 27.5.1 + graceful-fs: 4.2.10 + jest-diff: 27.5.1 + jest-get-type: 27.5.1 + jest-haste-map: 27.5.1 + jest-matcher-utils: 27.5.1 + jest-message-util: 27.5.1 + jest-util: 27.5.1 + natural-compare: 1.4.0 + pretty-format: 27.5.1 + semver: 7.3.7 + transitivePeerDependencies: + - supports-color + + /jest-util/27.5.1: + resolution: {integrity: sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/types': 27.5.1 + '@types/node': 16.11.59 + chalk: 4.1.2 + ci-info: 3.4.0 + graceful-fs: 4.2.10 + picomatch: 2.3.1 + + /jest-util/28.1.3: + resolution: {integrity: sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/types': 28.1.3 + '@types/node': 16.11.59 + chalk: 4.1.2 + ci-info: 3.4.0 + graceful-fs: 4.2.10 + picomatch: 2.3.1 + + /jest-validate/27.5.1: + resolution: {integrity: sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/types': 27.5.1 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 27.5.1 + leven: 3.1.0 + pretty-format: 27.5.1 + + /jest-watch-typeahead/1.1.0_jest@27.5.1: + resolution: {integrity: sha512-Va5nLSJTN7YFtC2jd+7wsoe1pNe5K4ShLux/E5iHEwlB9AxaxmggY7to9KUqKojhaJw3aXqt5WAb4jGPOolpEw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + jest: ^27.0.0 || ^28.0.0 + dependencies: + ansi-escapes: 4.3.2 + chalk: 4.1.2 + jest: 27.5.1 + jest-regex-util: 28.0.2 + jest-watcher: 28.1.3 + slash: 4.0.0 + string-length: 5.0.1 + strip-ansi: 7.0.1 + + /jest-watcher/27.5.1: + resolution: {integrity: sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + '@jest/test-result': 27.5.1 + '@jest/types': 27.5.1 + '@types/node': 16.11.59 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + jest-util: 27.5.1 + string-length: 4.0.2 + + /jest-watcher/28.1.3: + resolution: {integrity: sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/test-result': 28.1.3 + '@jest/types': 28.1.3 + '@types/node': 16.11.59 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.10.2 + jest-util: 28.1.3 + string-length: 4.0.2 + + /jest-worker/26.6.2: + resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} + engines: {node: '>= 10.13.0'} + dependencies: + '@types/node': 16.11.59 + merge-stream: 2.0.0 + supports-color: 7.2.0 + + /jest-worker/27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} + dependencies: + '@types/node': 16.11.59 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + /jest-worker/28.1.3: + resolution: {integrity: sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@types/node': 16.11.59 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + /jest/27.5.1: + resolution: {integrity: sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 27.5.1 + import-local: 3.1.0 + jest-cli: 27.5.1 + transitivePeerDependencies: + - bufferutil + - canvas + - supports-color + - ts-node + - utf-8-validate + + /js-cookie/2.2.1: + resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==} + dev: false + + /js-sdsl/4.1.4: + resolution: {integrity: sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==} + + /js-tokens/4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + /js-yaml/3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + /js-yaml/4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + + /jsdom/16.7.0: + resolution: {integrity: sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==} + engines: {node: '>=10'} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + dependencies: + abab: 2.0.6 + acorn: 8.8.0 + acorn-globals: 6.0.0 + cssom: 0.4.4 + cssstyle: 2.3.0 + data-urls: 2.0.0 + decimal.js: 10.4.1 + domexception: 2.0.1 + escodegen: 2.0.0 + form-data: 3.0.1 + html-encoding-sniffer: 2.0.1 + http-proxy-agent: 4.0.1 + https-proxy-agent: 5.0.1 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.2 + parse5: 6.0.1 + saxes: 5.0.1 + symbol-tree: 3.2.4 + tough-cookie: 4.1.2 + w3c-hr-time: 1.0.2 + w3c-xmlserializer: 2.0.0 + webidl-conversions: 6.1.0 + whatwg-encoding: 1.0.5 + whatwg-mimetype: 2.3.0 + whatwg-url: 8.7.0 + ws: 7.5.9 + xml-name-validator: 3.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + /jsesc/0.5.0: + resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} + hasBin: true + + /jsesc/2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + + /json-parse-better-errors/1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + dev: true + + /json-parse-even-better-errors/2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + /json-schema-traverse/0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + /json-schema-traverse/1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + /json-schema/0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + + /json-stable-stringify-without-jsonify/1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + /json-stringify-safe/5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + dev: true + + /json5/1.0.1: + resolution: {integrity: sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==} + hasBin: true + dependencies: + minimist: 1.2.6 + + /json5/2.2.1: + resolution: {integrity: sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==} + engines: {node: '>=6'} + hasBin: true + + /jsonfile/6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + dependencies: + universalify: 2.0.0 + optionalDependencies: + graceful-fs: 4.2.10 + + /jsonp/0.2.1: + resolution: {integrity: sha512-pfog5gdDxPdV4eP7Kg87M8/bHgshlZ5pybl+yKxAnCZ5O7lCIn7Ixydj03wOlnDQesky2BPyA91SQ+5Y/mNwzw==} + dependencies: + debug: 2.6.9 + transitivePeerDependencies: + - supports-color + dev: false + + /jsonparse/1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} + dev: true + + /jsonpointer/5.0.1: + resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} + engines: {node: '>=0.10.0'} + + /jsx-ast-utils/3.3.3: + resolution: {integrity: sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==} + engines: {node: '>=4.0'} + dependencies: + array-includes: 3.1.5 + object.assign: 4.1.4 + + /katex/0.16.2: + resolution: {integrity: sha512-70DJdQAyh9EMsthw3AaQlDyFf54X7nWEUIa5W+rq8XOpEk//w5Th7/8SqFqpvi/KZ2t6MHUj4f9wLmztBmAYQA==} + hasBin: true + dependencies: + commander: 8.3.0 + dev: false + + /khroma/2.0.0: + resolution: {integrity: sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g==} + dev: false + + /kind-of/6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + /kleur/3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + /klona/2.0.5: + resolution: {integrity: sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==} + engines: {node: '>= 8'} + + /language-subtag-registry/0.3.22: + resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} + + /language-tags/1.0.5: + resolution: {integrity: sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==} + dependencies: + language-subtag-registry: 0.3.22 + + /leven/3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + /levn/0.3.0: + resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.1.2 + type-check: 0.3.2 + + /levn/0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + /lilconfig/2.0.5: + resolution: {integrity: sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==} + engines: {node: '>=10'} + dev: true + + /lilconfig/2.0.6: + resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} + engines: {node: '>=10'} + + /lines-and-columns/1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + /lint-staged/13.0.3: + resolution: {integrity: sha512-9hmrwSCFroTSYLjflGI8Uk+GWAwMB4OlpU4bMJEAT5d/llQwtYKoim4bLOyLCuWFAhWEupE0vkIFqtw/WIsPug==} + engines: {node: ^14.13.1 || >=16.0.0} + hasBin: true + dependencies: + cli-truncate: 3.1.0 + colorette: 2.0.19 + commander: 9.4.0 + debug: 4.3.4 + execa: 6.1.0 + lilconfig: 2.0.5 + listr2: 4.0.5 + micromatch: 4.0.5 + normalize-path: 3.0.0 + object-inspect: 1.12.2 + pidtree: 0.6.0 + string-argv: 0.3.1 + yaml: 2.1.1 + transitivePeerDependencies: + - enquirer + - supports-color + dev: true + + /listr2/4.0.5: + resolution: {integrity: sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==} + engines: {node: '>=12'} + peerDependencies: + enquirer: '>= 2.3.0 < 3' + peerDependenciesMeta: + enquirer: + optional: true + dependencies: + cli-truncate: 2.1.0 + colorette: 2.0.19 + log-update: 4.0.0 + p-map: 4.0.0 + rfdc: 1.3.0 + rxjs: 7.5.6 + through: 2.3.8 + wrap-ansi: 7.0.0 + dev: true + + /load-json-file/4.0.0: + resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} + engines: {node: '>=4'} + dependencies: + graceful-fs: 4.2.10 + parse-json: 4.0.0 + pify: 3.0.0 + strip-bom: 3.0.0 + dev: true + + /loader-runner/4.3.0: + resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} + engines: {node: '>=6.11.5'} + + /loader-utils/2.0.2: + resolution: {integrity: sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==} + engines: {node: '>=8.9.0'} + dependencies: + big.js: 5.2.2 + emojis-list: 3.0.0 + json5: 2.2.1 + + /loader-utils/3.2.0: + resolution: {integrity: sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ==} + engines: {node: '>= 12.13.0'} + + /locate-path/2.0.0: + resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} + engines: {node: '>=4'} + dependencies: + p-locate: 2.0.0 + path-exists: 3.0.0 + dev: true + + /locate-path/3.0.0: + resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} + engines: {node: '>=6'} + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + + /locate-path/5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + dependencies: + p-locate: 4.1.0 + + /locate-path/6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + + /lodash.debounce/4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + /lodash.flow/3.5.0: + resolution: {integrity: sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==} + dev: true + + /lodash.ismatch/4.4.0: + resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==} + dev: true + + /lodash.map/4.6.0: + resolution: {integrity: sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==} + dev: true + + /lodash.memoize/4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + + /lodash.merge/4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + /lodash.sortby/4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + + /lodash.uniq/4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + + /lodash/4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + /log-symbols/4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + dev: true + + /log-update/4.0.0: + resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} + engines: {node: '>=10'} + dependencies: + ansi-escapes: 4.3.2 + cli-cursor: 3.1.0 + slice-ansi: 4.0.0 + wrap-ansi: 6.2.0 + dev: true + + /longest/2.0.1: + resolution: {integrity: sha512-Ajzxb8CM6WAnFjgiloPsI3bF+WCxcvhdIG3KNA2KN962+tdBsHcuQ4k4qX/EcS/2CRkcc0iAkR956Nib6aXU/Q==} + engines: {node: '>=0.10.0'} + dev: true + + /loose-envify/1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + dependencies: + js-tokens: 4.0.0 + + /lower-case/2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + dependencies: + tslib: 2.4.0 + + /lru-cache/6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + + /lz-string/1.4.4: + resolution: {integrity: sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==} + hasBin: true + dev: true + + /magic-string/0.25.9: + resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} + dependencies: + sourcemap-codec: 1.4.8 + + /make-dir/3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + dependencies: + semver: 6.3.0 + + /make-error/1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true + + /makeerror/1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + dependencies: + tmpl: 1.0.5 + + /map-obj/1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + dev: true + + /map-obj/4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} + dev: true + + /marked/4.1.0: + resolution: {integrity: sha512-+Z6KDjSPa6/723PQYyc1axYZpYYpDnECDaU6hkaf5gqBieBkMKYReL5hteF2QizhlMbgbo8umXl/clZ67+GlsA==} + engines: {node: '>= 12'} + hasBin: true + dev: false + + /mdn-data/2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + + /mdn-data/2.0.4: + resolution: {integrity: sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==} + + /media-typer/0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + /memfs/3.4.7: + resolution: {integrity: sha512-ygaiUSNalBX85388uskeCyhSAoOSgzBbtVCr9jA2RROssFL9Q19/ZXFqS+2Th2sr1ewNIWgFdLzLC3Yl1Zv+lw==} + engines: {node: '>= 4.0.0'} + dependencies: + fs-monkey: 1.0.3 + + /meow/8.1.2: + resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} + engines: {node: '>=10'} + dependencies: + '@types/minimist': 1.2.2 + camelcase-keys: 6.2.2 + decamelize-keys: 1.1.0 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 7.0.1 + redent: 3.0.0 + trim-newlines: 3.0.1 + type-fest: 0.18.1 + yargs-parser: 20.2.9 + dev: true + + /merge-descriptors/1.0.1: + resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + + /merge-stream/2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + /merge/2.1.1: + resolution: {integrity: sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==} + dev: true + + /merge2/1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + /mermaid/9.1.7: + resolution: {integrity: sha512-MRVHXy5FLjnUQUG7YS3UN9jEN6FXCJbFCXVGJQjVIbiR6Vhw0j/6pLIjqsiah9xoHmQU6DEaKOvB3S1g/1nBPA==} + dependencies: + '@braintree/sanitize-url': 6.0.0 + d3: 7.6.1 + dagre: 0.8.5 + dagre-d3: 0.6.4 + dompurify: 2.4.0 + graphlib: 2.1.8 + khroma: 2.0.0 + moment-mini: 2.24.0 + stylis: 4.1.2 + dev: false + + /methods/1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + /micromatch/4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + + /mime-db/1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + /mime-types/2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + + /mime/1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + /mimic-fn/2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + /mimic-fn/4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + dev: true + + /min-indent/1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + /mini-css-extract-plugin/2.6.1_webpack@5.74.0: + resolution: {integrity: sha512-wd+SD57/K6DiV7jIR34P+s3uckTRuQvx0tKPcvjFlrEylk6P4mQ2KSWk1hblj1Kxaqok7LogKOieygXqBczNlg==} + engines: {node: '>= 12.13.0'} + peerDependencies: + webpack: ^5.0.0 + dependencies: + schema-utils: 4.0.0 + webpack: 5.74.0 + + /minimalistic-assert/1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + + /minimatch/3.0.4: + resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==} + dependencies: + brace-expansion: 1.1.11 + + /minimatch/3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + + /minimatch/5.1.0: + resolution: {integrity: sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + + /minimist-options/4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 + dev: true + + /minimist/1.2.6: + resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} + + /mkdirp/0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + dependencies: + minimist: 1.2.6 + + /modify-values/1.0.1: + resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} + engines: {node: '>=0.10.0'} + dev: true + + /moment-mini/2.24.0: + resolution: {integrity: sha512-9ARkWHBs+6YJIvrIp0Ik5tyTTtP9PoV0Ssu2Ocq5y9v8+NOOpWiRshAp8c4rZVWTOe+157on/5G+zj5pwIQFEQ==} + dev: false + + /ms/2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + /ms/2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + /ms/2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + /multicast-dns/7.2.5: + resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} + hasBin: true + dependencies: + dns-packet: 5.4.0 + thunky: 1.1.0 + + /mute-stream/0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + dev: true + + /nanoid/3.3.4: + resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + /natural-compare/1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + /negotiator/0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + /neo-async/2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + /next-share/0.18.1_lbqamd2wfmenkveygahn4wdfcq: + resolution: {integrity: sha512-M7+J8ShJxVHQymKDq7NQCtcCemWL1bMQ3QNtu7rEr1ouDO4wzlhHeLG5vldfsI/JhUwnrOKYAhJ/UL26QpL+zg==} + engines: {node: '>=8', npm: '>=5'} + peerDependencies: + react: '>=17.0.0' + react-dom: '>=17.0.0' + react-scripts: '>=4.0.0' + dependencies: + jsonp: 0.2.1 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + react-scripts: 5.0.1_r727nmttzgvwuocpb6eyxi2m5i + transitivePeerDependencies: + - supports-color + dev: false + + /no-case/3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + dependencies: + lower-case: 2.0.2 + tslib: 2.4.0 + + /node-fetch/2.6.7: + resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: false + + /node-forge/1.3.1: + resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} + engines: {node: '>= 6.13.0'} + + /node-int64/0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + /node-releases/2.0.6: + resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==} + + /normalize-package-data/2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.1 + semver: 5.7.1 + validate-npm-package-license: 3.0.4 + dev: true + + /normalize-package-data/3.0.3: + resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} + engines: {node: '>=10'} + dependencies: + hosted-git-info: 4.1.0 + is-core-module: 2.10.0 + semver: 7.3.7 + validate-npm-package-license: 3.0.4 + dev: true + + /normalize-path/3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + /normalize-range/0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + + /normalize-url/6.1.0: + resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} + engines: {node: '>=10'} + + /npm-run-path/4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + dependencies: + path-key: 3.1.1 + + /npm-run-path/5.1.0: + resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-key: 4.0.0 + dev: true + + /nth-check/1.0.2: + resolution: {integrity: sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==} + dependencies: + boolbase: 1.0.0 + + /nth-check/2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + dependencies: + boolbase: 1.0.0 + + /nwsapi/2.2.2: + resolution: {integrity: sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==} + + /object-assign/4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + /object-hash/3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + /object-inspect/1.12.2: + resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} + + /object-keys/1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + /object.assign/4.1.4: + resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + has-symbols: 1.0.3 + object-keys: 1.1.1 + + /object.entries/1.1.5: + resolution: {integrity: sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.2 + + /object.fromentries/2.0.5: + resolution: {integrity: sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.2 + + /object.getownpropertydescriptors/2.1.4: + resolution: {integrity: sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==} + engines: {node: '>= 0.8'} + dependencies: + array.prototype.reduce: 1.0.4 + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.2 + + /object.hasown/1.1.1: + resolution: {integrity: sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==} + dependencies: + define-properties: 1.1.4 + es-abstract: 1.20.2 + + /object.values/1.1.5: + resolution: {integrity: sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.2 + + /obuf/1.1.2: + resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + + /on-finished/2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + dependencies: + ee-first: 1.1.1 + + /on-headers/1.0.2: + resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} + engines: {node: '>= 0.8'} + + /once/1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + + /onetime/5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + dependencies: + mimic-fn: 2.1.0 + + /onetime/6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + dependencies: + mimic-fn: 4.0.0 + dev: true + + /open/8.4.0: + resolution: {integrity: sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==} + engines: {node: '>=12'} + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + + /optionator/0.8.3: + resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==} + engines: {node: '>= 0.8.0'} + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.3.0 + prelude-ls: 1.1.2 + type-check: 0.3.2 + word-wrap: 1.2.3 + + /optionator/0.9.1: + resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} + engines: {node: '>= 0.8.0'} + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.3 + + /ora/5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.7.0 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + dev: true + + /os-tmpdir/1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + dev: true + + /p-limit/1.3.0: + resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} + engines: {node: '>=4'} + dependencies: + p-try: 1.0.0 + dev: true + + /p-limit/2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + dependencies: + p-try: 2.2.0 + + /p-limit/3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + + /p-locate/2.0.0: + resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} + engines: {node: '>=4'} + dependencies: + p-limit: 1.3.0 + dev: true + + /p-locate/3.0.0: + resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} + engines: {node: '>=6'} + dependencies: + p-limit: 2.3.0 + + /p-locate/4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + dependencies: + p-limit: 2.3.0 + + /p-locate/5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + + /p-map/4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + dependencies: + aggregate-error: 3.1.0 + dev: true + + /p-retry/4.6.2: + resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} + engines: {node: '>=8'} + dependencies: + '@types/retry': 0.12.0 + retry: 0.13.1 + + /p-try/1.0.0: + resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} + engines: {node: '>=4'} + dev: true + + /p-try/2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + /param-case/3.0.4: + resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} + dependencies: + dot-case: 3.0.4 + tslib: 2.4.0 + + /parent-module/1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + + /parse-json/4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + dependencies: + error-ex: 1.3.2 + json-parse-better-errors: 1.0.2 + dev: true + + /parse-json/5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + dependencies: + '@babel/code-frame': 7.18.6 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + /parse-passwd/1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} + dev: true + + /parse5/6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + + /parseurl/1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + /pascal-case/3.1.2: + resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + dependencies: + no-case: 3.0.4 + tslib: 2.4.0 + + /path-exists/3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + + /path-exists/4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + /path-is-absolute/1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + /path-key/3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + /path-key/4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + dev: true + + /path-parse/1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + /path-to-regexp/0.1.7: + resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + + /path-type/3.0.0: + resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} + engines: {node: '>=4'} + dependencies: + pify: 3.0.0 + dev: true + + /path-type/4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + /performance-now/2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + + /picocolors/0.2.1: + resolution: {integrity: sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==} + + /picocolors/1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + + /picomatch/2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + /pidtree/0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + dev: true + + /pify/2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + /pify/3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + dev: true + + /pirates/4.0.5: + resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} + engines: {node: '>= 6'} + + /pkg-dir/4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + dependencies: + find-up: 4.1.0 + + /pkg-up/3.1.0: + resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} + engines: {node: '>=8'} + dependencies: + find-up: 3.0.0 + + /postcss-attribute-case-insensitive/5.0.2_postcss@8.4.16: + resolution: {integrity: sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + + /postcss-browser-comments/4.0.0_yroec54rl3ndwvbunmnefp5nvy: + resolution: {integrity: sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==} + engines: {node: '>=8'} + peerDependencies: + browserslist: '>=4' + postcss: '>=8' + dependencies: + browserslist: 4.21.4 + postcss: 8.4.16 + + /postcss-calc/8.2.4_postcss@8.4.16: + resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} + peerDependencies: + postcss: ^8.2.2 + dependencies: + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + postcss-value-parser: 4.2.0 + + /postcss-clamp/4.1.0_postcss@8.4.16: + resolution: {integrity: sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==} + engines: {node: '>=7.6.0'} + peerDependencies: + postcss: ^8.4.6 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-color-functional-notation/4.2.4_postcss@8.4.16: + resolution: {integrity: sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-color-hex-alpha/8.0.4_postcss@8.4.16: + resolution: {integrity: sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.4 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-color-rebeccapurple/7.1.1_postcss@8.4.16: + resolution: {integrity: sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-colormin/5.3.0_postcss@8.4.16: + resolution: {integrity: sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.4 + caniuse-api: 3.0.0 + colord: 2.9.3 + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-convert-values/5.1.2_postcss@8.4.16: + resolution: {integrity: sha512-c6Hzc4GAv95B7suy4udszX9Zy4ETyMCgFPUDtWjdFTKH1SE9eFY/jEpHSwTH1QPuwxHpWslhckUQWbNRM4ho5g==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.4 + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-custom-media/8.0.2_postcss@8.4.16: + resolution: {integrity: sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.3 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-custom-properties/12.1.9_postcss@8.4.16: + resolution: {integrity: sha512-/E7PRvK8DAVljBbeWrcEQJPG72jaImxF3vvCNFwv9cC8CzigVoNIpeyfnJzphnN3Fd8/auBf5wvkw6W9MfmTyg==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-custom-selectors/6.0.3_postcss@8.4.16: + resolution: {integrity: sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.3 + dependencies: + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + + /postcss-dir-pseudo-class/6.0.5_postcss@8.4.16: + resolution: {integrity: sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + + /postcss-discard-comments/5.1.2_postcss@8.4.16: + resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.16 + + /postcss-discard-duplicates/5.1.0_postcss@8.4.16: + resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.16 + + /postcss-discard-empty/5.1.1_postcss@8.4.16: + resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.16 + + /postcss-discard-overridden/5.1.0_postcss@8.4.16: + resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.16 + + /postcss-double-position-gradients/3.1.2_postcss@8.4.16: + resolution: {integrity: sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + '@csstools/postcss-progressive-custom-properties': 1.3.0_postcss@8.4.16 + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-env-function/4.0.6_postcss@8.4.16: + resolution: {integrity: sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.4 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-flexbugs-fixes/5.0.2_postcss@8.4.16: + resolution: {integrity: sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==} + peerDependencies: + postcss: ^8.1.4 + dependencies: + postcss: 8.4.16 + + /postcss-focus-visible/6.0.4_postcss@8.4.16: + resolution: {integrity: sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.4 + dependencies: + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + + /postcss-focus-within/5.0.4_postcss@8.4.16: + resolution: {integrity: sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.4 + dependencies: + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + + /postcss-font-variant/5.0.0_postcss@8.4.16: + resolution: {integrity: sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==} + peerDependencies: + postcss: ^8.1.0 + dependencies: + postcss: 8.4.16 + + /postcss-gap-properties/3.0.5_postcss@8.4.16: + resolution: {integrity: sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + postcss: 8.4.16 + + /postcss-image-set-function/4.0.7_postcss@8.4.16: + resolution: {integrity: sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-import/14.1.0_postcss@8.4.16: + resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} + engines: {node: '>=10.0.0'} + peerDependencies: + postcss: ^8.0.0 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.1 + + /postcss-initial/4.0.1_postcss@8.4.16: + resolution: {integrity: sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==} + peerDependencies: + postcss: ^8.0.0 + dependencies: + postcss: 8.4.16 + + /postcss-js/4.0.0_postcss@8.4.16: + resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.3.3 + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.16 + + /postcss-lab-function/4.2.1_postcss@8.4.16: + resolution: {integrity: sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + '@csstools/postcss-progressive-custom-properties': 1.3.0_postcss@8.4.16 + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-load-config/3.1.4_postcss@8.4.16: + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 2.0.6 + postcss: 8.4.16 + yaml: 1.10.2 + + /postcss-loader/6.2.1_qjv4cptcpse3y5hrjkrbb7drda: + resolution: {integrity: sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==} + engines: {node: '>= 12.13.0'} + peerDependencies: + postcss: ^7.0.0 || ^8.0.1 + webpack: ^5.0.0 + dependencies: + cosmiconfig: 7.0.1 + klona: 2.0.5 + postcss: 8.4.16 + semver: 7.3.7 + webpack: 5.74.0 + + /postcss-logical/5.0.4_postcss@8.4.16: + resolution: {integrity: sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.4 + dependencies: + postcss: 8.4.16 + + /postcss-media-minmax/5.0.0_postcss@8.4.16: + resolution: {integrity: sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + postcss: ^8.1.0 + dependencies: + postcss: 8.4.16 + + /postcss-merge-longhand/5.1.6_postcss@8.4.16: + resolution: {integrity: sha512-6C/UGF/3T5OE2CEbOuX7iNO63dnvqhGZeUnKkDeifebY0XqkkvrctYSZurpNE902LDf2yKwwPFgotnfSoPhQiw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + stylehacks: 5.1.0_postcss@8.4.16 + + /postcss-merge-rules/5.1.2_postcss@8.4.16: + resolution: {integrity: sha512-zKMUlnw+zYCWoPN6yhPjtcEdlJaMUZ0WyVcxTAmw3lkkN/NDMRkOkiuctQEoWAOvH7twaxUUdvBWl0d4+hifRQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.4 + caniuse-api: 3.0.0 + cssnano-utils: 3.1.0_postcss@8.4.16 + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + + /postcss-minify-font-values/5.1.0_postcss@8.4.16: + resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-minify-gradients/5.1.1_postcss@8.4.16: + resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + colord: 2.9.3 + cssnano-utils: 3.1.0_postcss@8.4.16 + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-minify-params/5.1.3_postcss@8.4.16: + resolution: {integrity: sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.4 + cssnano-utils: 3.1.0_postcss@8.4.16 + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-minify-selectors/5.2.1_postcss@8.4.16: + resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + + /postcss-modules-extract-imports/3.0.0_postcss@8.4.16: + resolution: {integrity: sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + dependencies: + postcss: 8.4.16 + + /postcss-modules-local-by-default/4.0.0_postcss@8.4.16: + resolution: {integrity: sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + dependencies: + icss-utils: 5.1.0_postcss@8.4.16 + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + postcss-value-parser: 4.2.0 + + /postcss-modules-scope/3.0.0_postcss@8.4.16: + resolution: {integrity: sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + dependencies: + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + + /postcss-modules-values/4.0.0_postcss@8.4.16: + resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + dependencies: + icss-utils: 5.1.0_postcss@8.4.16 + postcss: 8.4.16 + + /postcss-nested/5.0.6_postcss@8.4.16: + resolution: {integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + dependencies: + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + + /postcss-nesting/10.2.0_postcss@8.4.16: + resolution: {integrity: sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + '@csstools/selector-specificity': 2.0.2_pnx64jze6bptzcedy5bidi3zdi + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + + /postcss-normalize-charset/5.1.0_postcss@8.4.16: + resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.16 + + /postcss-normalize-display-values/5.1.0_postcss@8.4.16: + resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-normalize-positions/5.1.1_postcss@8.4.16: + resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-normalize-repeat-style/5.1.1_postcss@8.4.16: + resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-normalize-string/5.1.0_postcss@8.4.16: + resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-normalize-timing-functions/5.1.0_postcss@8.4.16: + resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-normalize-unicode/5.1.0_postcss@8.4.16: + resolution: {integrity: sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.4 + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-normalize-url/5.1.0_postcss@8.4.16: + resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + normalize-url: 6.1.0 + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-normalize-whitespace/5.1.1_postcss@8.4.16: + resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-normalize/10.0.1_yroec54rl3ndwvbunmnefp5nvy: + resolution: {integrity: sha512-+5w18/rDev5mqERcG3W5GZNMJa1eoYYNGo8gB7tEwaos0ajk3ZXAI4mHGcNT47NE+ZnZD1pEpUOFLvltIwmeJA==} + engines: {node: '>= 12'} + peerDependencies: + browserslist: '>= 4' + postcss: '>= 8' + dependencies: + '@csstools/normalize.css': 12.0.0 + browserslist: 4.21.4 + postcss: 8.4.16 + postcss-browser-comments: 4.0.0_yroec54rl3ndwvbunmnefp5nvy + sanitize.css: 13.0.0 + + /postcss-opacity-percentage/1.1.2: + resolution: {integrity: sha512-lyUfF7miG+yewZ8EAk9XUBIlrHyUE6fijnesuz+Mj5zrIHIEw6KcIZSOk/elVMqzLvREmXB83Zi/5QpNRYd47w==} + engines: {node: ^12 || ^14 || >=16} + + /postcss-ordered-values/5.1.3_postcss@8.4.16: + resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + cssnano-utils: 3.1.0_postcss@8.4.16 + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-overflow-shorthand/3.0.4_postcss@8.4.16: + resolution: {integrity: sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-page-break/3.0.4_postcss@8.4.16: + resolution: {integrity: sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==} + peerDependencies: + postcss: ^8 + dependencies: + postcss: 8.4.16 + + /postcss-place/7.0.5_postcss@8.4.16: + resolution: {integrity: sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-preset-env/7.8.2_postcss@8.4.16: + resolution: {integrity: sha512-rSMUEaOCnovKnwc5LvBDHUDzpGP+nrUeWZGWt9M72fBvckCi45JmnJigUr4QG4zZeOHmOCNCZnd2LKDvP++ZuQ==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + '@csstools/postcss-cascade-layers': 1.1.1_postcss@8.4.16 + '@csstools/postcss-color-function': 1.1.1_postcss@8.4.16 + '@csstools/postcss-font-format-keywords': 1.0.1_postcss@8.4.16 + '@csstools/postcss-hwb-function': 1.0.2_postcss@8.4.16 + '@csstools/postcss-ic-unit': 1.0.1_postcss@8.4.16 + '@csstools/postcss-is-pseudo-class': 2.0.7_postcss@8.4.16 + '@csstools/postcss-nested-calc': 1.0.0_postcss@8.4.16 + '@csstools/postcss-normalize-display-values': 1.0.1_postcss@8.4.16 + '@csstools/postcss-oklab-function': 1.1.1_postcss@8.4.16 + '@csstools/postcss-progressive-custom-properties': 1.3.0_postcss@8.4.16 + '@csstools/postcss-stepped-value-functions': 1.0.1_postcss@8.4.16 + '@csstools/postcss-text-decoration-shorthand': 1.0.0_postcss@8.4.16 + '@csstools/postcss-trigonometric-functions': 1.0.2_postcss@8.4.16 + '@csstools/postcss-unset-value': 1.0.2_postcss@8.4.16 + autoprefixer: 10.4.12_postcss@8.4.16 + browserslist: 4.21.4 + css-blank-pseudo: 3.0.3_postcss@8.4.16 + css-has-pseudo: 3.0.4_postcss@8.4.16 + css-prefers-color-scheme: 6.0.3_postcss@8.4.16 + cssdb: 7.0.1 + postcss: 8.4.16 + postcss-attribute-case-insensitive: 5.0.2_postcss@8.4.16 + postcss-clamp: 4.1.0_postcss@8.4.16 + postcss-color-functional-notation: 4.2.4_postcss@8.4.16 + postcss-color-hex-alpha: 8.0.4_postcss@8.4.16 + postcss-color-rebeccapurple: 7.1.1_postcss@8.4.16 + postcss-custom-media: 8.0.2_postcss@8.4.16 + postcss-custom-properties: 12.1.9_postcss@8.4.16 + postcss-custom-selectors: 6.0.3_postcss@8.4.16 + postcss-dir-pseudo-class: 6.0.5_postcss@8.4.16 + postcss-double-position-gradients: 3.1.2_postcss@8.4.16 + postcss-env-function: 4.0.6_postcss@8.4.16 + postcss-focus-visible: 6.0.4_postcss@8.4.16 + postcss-focus-within: 5.0.4_postcss@8.4.16 + postcss-font-variant: 5.0.0_postcss@8.4.16 + postcss-gap-properties: 3.0.5_postcss@8.4.16 + postcss-image-set-function: 4.0.7_postcss@8.4.16 + postcss-initial: 4.0.1_postcss@8.4.16 + postcss-lab-function: 4.2.1_postcss@8.4.16 + postcss-logical: 5.0.4_postcss@8.4.16 + postcss-media-minmax: 5.0.0_postcss@8.4.16 + postcss-nesting: 10.2.0_postcss@8.4.16 + postcss-opacity-percentage: 1.1.2 + postcss-overflow-shorthand: 3.0.4_postcss@8.4.16 + postcss-page-break: 3.0.4_postcss@8.4.16 + postcss-place: 7.0.5_postcss@8.4.16 + postcss-pseudo-class-any-link: 7.1.6_postcss@8.4.16 + postcss-replace-overflow-wrap: 4.0.0_postcss@8.4.16 + postcss-selector-not: 6.0.1_postcss@8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-pseudo-class-any-link/7.1.6_postcss@8.4.16: + resolution: {integrity: sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + + /postcss-reduce-initial/5.1.0_postcss@8.4.16: + resolution: {integrity: sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.4 + caniuse-api: 3.0.0 + postcss: 8.4.16 + + /postcss-reduce-transforms/5.1.0_postcss@8.4.16: + resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + + /postcss-replace-overflow-wrap/4.0.0_postcss@8.4.16: + resolution: {integrity: sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==} + peerDependencies: + postcss: ^8.0.3 + dependencies: + postcss: 8.4.16 + + /postcss-selector-not/6.0.1_postcss@8.4.16: + resolution: {integrity: sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==} + engines: {node: ^12 || ^14 || >=16} + peerDependencies: + postcss: ^8.2 + dependencies: + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + + /postcss-selector-parser/6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + /postcss-svgo/5.1.0_postcss@8.4.16: + resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.16 + postcss-value-parser: 4.2.0 + svgo: 2.8.0 + + /postcss-unique-selectors/5.1.1_postcss@8.4.16: + resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + + /postcss-value-parser/4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + /postcss/7.0.39: + resolution: {integrity: sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==} + engines: {node: '>=6.0.0'} + dependencies: + picocolors: 0.2.1 + source-map: 0.6.1 + + /postcss/8.4.16: + resolution: {integrity: sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + + /prelude-ls/1.1.2: + resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} + engines: {node: '>= 0.8.0'} + + /prelude-ls/1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + /prettier-linter-helpers/1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + dependencies: + fast-diff: 1.2.0 + dev: true + + /prettier/2.7.1: + resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + + /pretty-bytes/5.6.0: + resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} + engines: {node: '>=6'} + + /pretty-error/4.0.0: + resolution: {integrity: sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==} + dependencies: + lodash: 4.17.21 + renderkid: 3.0.0 + + /pretty-format/24.9.0: + resolution: {integrity: sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==} + engines: {node: '>= 6'} + dependencies: + '@jest/types': 24.9.0 + ansi-regex: 4.1.1 + ansi-styles: 3.2.1 + react-is: 16.13.1 + dev: false + + /pretty-format/27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + /pretty-format/28.1.3: + resolution: {integrity: sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/schemas': 28.1.3 + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 18.2.0 + + /process-nextick-args/2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + /promise/8.2.0: + resolution: {integrity: sha512-+CMAlLHqwRYwBMXKCP+o8ns7DN+xHDUiI+0nArsiJ9y+kJVPLFxEaSw6Ha9s9H0tftxg2Yzl25wqj9G7m5wLZg==} + dependencies: + asap: 2.0.6 + + /prompts/2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + /prop-types-extra/1.1.1_react@18.2.0: + resolution: {integrity: sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==} + peerDependencies: + react: '>=0.14.0' + dependencies: + react: 18.2.0 + react-is: 16.13.1 + warning: 4.0.3 + dev: false + + /prop-types/15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + /proxy-addr/2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + /psl/1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + + /punycode/2.1.1: + resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} + engines: {node: '>=6'} + + /purgecss-webpack-plugin/4.1.3: + resolution: {integrity: sha512-1OHS0WE935w66FjaFSlV06ycmn3/A8a6Q+iVUmmCYAujQ1HPdX+psMXUhASEW0uF1PYEpOlhMc5ApigVqYK08g==} + dependencies: + purgecss: 4.1.3 + webpack: 5.74.0 + webpack-sources: 3.2.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + - webpack-cli + dev: true + + /purgecss/4.1.3: + resolution: {integrity: sha512-99cKy4s+VZoXnPxaoM23e5ABcP851nC2y2GROkkjS8eJaJtlciGavd7iYAw2V84WeBqggZ12l8ef44G99HmTaw==} + hasBin: true + dependencies: + commander: 8.3.0 + glob: 7.2.3 + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + dev: true + + /q/1.5.1: + resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} + engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + + /qs/6.10.3: + resolution: {integrity: sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + + /qs/6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: false + + /querystringify/2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + + /queue-microtask/1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + /quick-lru/4.0.1: + resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} + engines: {node: '>=8'} + dev: true + + /quick-lru/5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + + /raf/3.4.1: + resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==} + dependencies: + performance-now: 2.1.0 + + /randombytes/2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + dependencies: + safe-buffer: 5.2.1 + + /range-parser/1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + /raw-body/2.5.1: + resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + /react-app-polyfill/3.0.0: + resolution: {integrity: sha512-sZ41cxiU5llIB003yxxQBYrARBqe0repqPTTYBTmMqTz9szeBbE37BehCE891NZsmdZqqP+xWKdT3eo3vOzN8w==} + engines: {node: '>=14'} + dependencies: + core-js: 3.25.2 + object-assign: 4.1.1 + promise: 8.2.0 + raf: 3.4.1 + regenerator-runtime: 0.13.9 + whatwg-fetch: 3.6.2 + + /react-app-rewired/2.2.1_react-scripts@5.0.1: + resolution: {integrity: sha512-uFQWTErXeLDrMzOJHKp0h8P1z0LV9HzPGsJ6adOtGlA/B9WfT6Shh4j2tLTTGlXOfiVx6w6iWpp7SOC5pvk+gA==} + hasBin: true + peerDependencies: + react-scripts: '>=2.1.3' + dependencies: + react-scripts: 5.0.1_r727nmttzgvwuocpb6eyxi2m5i + semver: 5.7.1 + dev: true + + /react-bootstrap/2.5.0_7ey2zzynotv32rpkwno45fsx4e: + resolution: {integrity: sha512-j/aLR+okzbYk61TM3eDOU1NqOqnUdwyVrF+ojoCRUxPdzc2R0xXvqyRsjSoyRoCo7n82Fs/LWjPCin/QJNdwvA==} + peerDependencies: + '@types/react': '>=16.14.8' + react: '>=16.14.0' + react-dom: '>=16.14.0' + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.19.0 + '@restart/hooks': 0.4.7_react@18.2.0 + '@restart/ui': 1.4.0_biqbaboplfbrettd7655fr4n2y + '@types/react': 18.0.20 + '@types/react-transition-group': 4.4.5 + classnames: 2.3.2 + dom-helpers: 5.2.1 + invariant: 2.2.4 + prop-types: 15.8.1 + prop-types-extra: 1.1.1_react@18.2.0 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + react-transition-group: 4.4.5_biqbaboplfbrettd7655fr4n2y + uncontrollable: 7.2.1_react@18.2.0 + warning: 4.0.3 + dev: false + + /react-dev-utils/12.0.1_npfwkgbcmgrbevrxnqgustqabe: + resolution: {integrity: sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==} + engines: {node: '>=14'} + dependencies: + '@babel/code-frame': 7.18.6 + address: 1.2.1 + browserslist: 4.21.4 + chalk: 4.1.2 + cross-spawn: 7.0.3 + detect-port-alt: 1.1.6 + escape-string-regexp: 4.0.0 + filesize: 8.0.7 + find-up: 5.0.0 + fork-ts-checker-webpack-plugin: 6.5.2_npfwkgbcmgrbevrxnqgustqabe + global-modules: 2.0.0 + globby: 11.1.0 + gzip-size: 6.0.0 + immer: 9.0.15 + is-root: 2.1.0 + loader-utils: 3.2.0 + open: 8.4.0 + pkg-up: 3.1.0 + prompts: 2.4.2 + react-error-overlay: 6.0.11 + recursive-readdir: 2.2.2 + shell-quote: 1.7.3 + strip-ansi: 6.0.1 + text-table: 0.2.0 + typescript: 4.8.3 + webpack: 5.74.0 + transitivePeerDependencies: + - eslint + - supports-color + - typescript + - vue-template-compiler + - webpack + + /react-dom/18.2.0_react@18.2.0: + resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + peerDependencies: + react: ^18.2.0 + dependencies: + loose-envify: 1.4.0 + react: 18.2.0 + scheduler: 0.23.0 + + /react-error-overlay/6.0.11: + resolution: {integrity: sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==} + + /react-fast-compare/3.2.0: + resolution: {integrity: sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==} + dev: false + + /react-helmet/6.1.0_react@18.2.0: + resolution: {integrity: sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==} + peerDependencies: + react: '>=16.3.0' + dependencies: + object-assign: 4.1.1 + prop-types: 15.8.1 + react: 18.2.0 + react-fast-compare: 3.2.0 + react-side-effect: 2.1.2_react@18.2.0 + dev: false + + /react-i18next/11.18.6_ulhmqqxshznzmtuvahdi5nasbq: + resolution: {integrity: sha512-yHb2F9BiT0lqoQDt8loZ5gWP331GwctHz9tYQ8A2EIEUu+CcEdjBLQWli1USG3RdWQt3W+jqQLg/d4rrQR96LA==} + peerDependencies: + i18next: '>= 19.0.0' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + dependencies: + '@babel/runtime': 7.19.0 + html-parse-stringify: 3.0.1 + i18next: 21.9.2 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /react-is/16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + /react-is/17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + /react-is/18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + + /react-lifecycles-compat/3.0.4: + resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} + dev: false + + /react-refresh/0.11.0: + resolution: {integrity: sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==} + engines: {node: '>=0.10.0'} + + /react-router-dom/6.4.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-4Aw1xmXKeleYYQ3x0Lcl2undHR6yMjXZjd9DKZd53SGOYqirrUThyUb0wwAX5VZAyvSuzjNJmZlJ3rR9+/vzqg==} + engines: {node: '>=14'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + dependencies: + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + react-router: 6.4.0_react@18.2.0 + dev: false + + /react-router/6.4.0_react@18.2.0: + resolution: {integrity: sha512-B+5bEXFlgR1XUdHYR6P94g299SjrfCBMmEDJNcFbpAyRH1j1748yt9NdDhW3++nw1lk3zQJ6aOO66zUx3KlTZg==} + engines: {node: '>=14'} + peerDependencies: + react: '>=16.8' + dependencies: + '@remix-run/router': 1.0.0 + react: 18.2.0 + dev: false + + /react-scripts/5.0.1_r727nmttzgvwuocpb6eyxi2m5i: + resolution: {integrity: sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==} + engines: {node: '>=14.0.0'} + hasBin: true + peerDependencies: + react: '>= 16' + typescript: ^3.2.1 || ^4 + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@babel/core': 7.19.1 + '@pmmmwh/react-refresh-webpack-plugin': 0.5.7_prxwy2zxcolvdag5hfkyuqbcze + '@svgr/webpack': 5.5.0 + babel-jest: 27.5.1_@babel+core@7.19.1 + babel-loader: 8.2.5_rhsdbzevgb5tizdhlla5jsbgyu + babel-plugin-named-asset-import: 0.3.8_@babel+core@7.19.1 + babel-preset-react-app: 10.0.1 + bfj: 7.0.2 + browserslist: 4.21.4 + camelcase: 6.3.0 + case-sensitive-paths-webpack-plugin: 2.4.0 + css-loader: 6.7.1_webpack@5.74.0 + css-minimizer-webpack-plugin: 3.4.1_webpack@5.74.0 + dotenv: 10.0.0 + dotenv-expand: 5.1.0 + eslint: 8.23.1 + eslint-config-react-app: 7.0.1_ep5hkfurrjf46kbnkcej3benz4 + eslint-webpack-plugin: 3.2.0_cnsurwdbw57xgwxuf5k544xt5e + file-loader: 6.2.0_webpack@5.74.0 + fs-extra: 10.1.0 + html-webpack-plugin: 5.5.0_webpack@5.74.0 + identity-obj-proxy: 3.0.0 + jest: 27.5.1 + jest-resolve: 27.5.1 + jest-watch-typeahead: 1.1.0_jest@27.5.1 + mini-css-extract-plugin: 2.6.1_webpack@5.74.0 + postcss: 8.4.16 + postcss-flexbugs-fixes: 5.0.2_postcss@8.4.16 + postcss-loader: 6.2.1_qjv4cptcpse3y5hrjkrbb7drda + postcss-normalize: 10.0.1_yroec54rl3ndwvbunmnefp5nvy + postcss-preset-env: 7.8.2_postcss@8.4.16 + prompts: 2.4.2 + react: 18.2.0 + react-app-polyfill: 3.0.0 + react-dev-utils: 12.0.1_npfwkgbcmgrbevrxnqgustqabe + react-refresh: 0.11.0 + resolve: 1.22.1 + resolve-url-loader: 4.0.0 + sass-loader: 12.6.0_sass@1.54.9+webpack@5.74.0 + semver: 7.3.7 + source-map-loader: 3.0.1_webpack@5.74.0 + style-loader: 3.3.1_webpack@5.74.0 + tailwindcss: 3.1.8 + terser-webpack-plugin: 5.3.6_webpack@5.74.0 + typescript: 4.8.3 + webpack: 5.74.0 + webpack-dev-server: 4.11.1_webpack@5.74.0 + webpack-manifest-plugin: 4.1.1_webpack@5.74.0 + workbox-webpack-plugin: 6.5.4_webpack@5.74.0 + optionalDependencies: + fsevents: 2.3.2 + transitivePeerDependencies: + - '@babel/plugin-syntax-flow' + - '@babel/plugin-transform-react-jsx' + - '@parcel/css' + - '@swc/core' + - '@types/babel__core' + - '@types/webpack' + - bufferutil + - canvas + - clean-css + - csso + - debug + - esbuild + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - fibers + - node-notifier + - node-sass + - rework + - rework-visit + - sass + - sass-embedded + - sockjs-client + - supports-color + - ts-node + - type-fest + - uglify-js + - utf-8-validate + - vue-template-compiler + - webpack-cli + - webpack-hot-middleware + - webpack-plugin-serve + + /react-side-effect/2.1.2_react@18.2.0: + resolution: {integrity: sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==} + peerDependencies: + react: ^16.3.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + + /react-transition-group/4.4.5_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + dependencies: + '@babel/runtime': 7.19.0 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /react/18.2.0: + resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + engines: {node: '>=0.10.0'} + dependencies: + loose-envify: 1.4.0 + + /read-cache/1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + dependencies: + pify: 2.3.0 + + /read-pkg-up/3.0.0: + resolution: {integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==} + engines: {node: '>=4'} + dependencies: + find-up: 2.1.0 + read-pkg: 3.0.0 + dev: true + + /read-pkg-up/7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} + dependencies: + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 + dev: true + + /read-pkg/3.0.0: + resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} + engines: {node: '>=4'} + dependencies: + load-json-file: 4.0.0 + normalize-package-data: 2.5.0 + path-type: 3.0.0 + dev: true + + /read-pkg/5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} + dependencies: + '@types/normalize-package-data': 2.4.1 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 + dev: true + + /readable-stream/2.3.7: + resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + /readable-stream/3.6.0: + resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + /readdirp/3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + + /recursive-readdir/2.2.2: + resolution: {integrity: sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==} + engines: {node: '>=0.10.0'} + dependencies: + minimatch: 3.0.4 + + /redent/3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + /regenerate-unicode-properties/10.1.0: + resolution: {integrity: sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==} + engines: {node: '>=4'} + dependencies: + regenerate: 1.4.2 + + /regenerate/1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + + /regenerator-runtime/0.13.9: + resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==} + + /regenerator-transform/0.15.0: + resolution: {integrity: sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==} + dependencies: + '@babel/runtime': 7.19.0 + + /regex-parser/2.2.11: + resolution: {integrity: sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==} + + /regexp.prototype.flags/1.4.3: + resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + functions-have-names: 1.2.3 + + /regexpp/3.2.0: + resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} + engines: {node: '>=8'} + + /regexpu-core/5.2.1: + resolution: {integrity: sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ==} + engines: {node: '>=4'} + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.1.0 + regjsgen: 0.7.1 + regjsparser: 0.9.1 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.0.0 + + /regjsgen/0.7.1: + resolution: {integrity: sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==} + + /regjsparser/0.9.1: + resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} + hasBin: true + dependencies: + jsesc: 0.5.0 + + /relateurl/0.2.7: + resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} + engines: {node: '>= 0.10'} + + /renderkid/3.0.0: + resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==} + dependencies: + css-select: 4.3.0 + dom-converter: 0.2.0 + htmlparser2: 6.1.0 + lodash: 4.17.21 + strip-ansi: 6.0.1 + + /require-directory/2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + /require-from-string/2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + /requires-port/1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + /resize-observer-polyfill/1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + dev: false + + /resolve-cwd/3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + dependencies: + resolve-from: 5.0.0 + + /resolve-dir/1.0.1: + resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==} + engines: {node: '>=0.10.0'} + dependencies: + expand-tilde: 2.0.2 + global-modules: 1.0.0 + dev: true + + /resolve-from/4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + /resolve-from/5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + /resolve-global/1.0.0: + resolution: {integrity: sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==} + engines: {node: '>=8'} + dependencies: + global-dirs: 0.1.1 + dev: true + + /resolve-url-loader/4.0.0: + resolution: {integrity: sha512-05VEMczVREcbtT7Bz+C+96eUO5HDNvdthIiMB34t7FcF8ehcu4wC0sSgPUubs3XW2Q3CNLJk/BJrCU9wVRymiA==} + engines: {node: '>=8.9'} + peerDependencies: + rework: 1.0.1 + rework-visit: 1.0.0 + peerDependenciesMeta: + rework: + optional: true + rework-visit: + optional: true + dependencies: + adjust-sourcemap-loader: 4.0.0 + convert-source-map: 1.8.0 + loader-utils: 2.0.2 + postcss: 7.0.39 + source-map: 0.6.1 + + /resolve-url/0.2.1: + resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==} + deprecated: https://github.com/lydell/resolve-url#deprecated + dev: false + + /resolve.exports/1.1.0: + resolution: {integrity: sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==} + engines: {node: '>=10'} + + /resolve/1.22.1: + resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} + hasBin: true + dependencies: + is-core-module: 2.10.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + /resolve/2.0.0-next.4: + resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} + hasBin: true + dependencies: + is-core-module: 2.10.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + /restore-cursor/3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + dev: true + + /retry/0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + + /reusify/1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + /rfdc/1.3.0: + resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} + dev: true + + /rimraf/3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + + /robust-predicates/3.0.1: + resolution: {integrity: sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==} + dev: false + + /rollup-plugin-terser/7.0.2_rollup@2.79.0: + resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==} + peerDependencies: + rollup: ^2.0.0 + dependencies: + '@babel/code-frame': 7.18.6 + jest-worker: 26.6.2 + rollup: 2.79.0 + serialize-javascript: 4.0.0 + terser: 5.15.0 + + /rollup/2.79.0: + resolution: {integrity: sha512-x4KsrCgwQ7ZJPcFA/SUu6QVcYlO7uRLfLAy0DSA4NS2eG8japdbpM50ToH7z4iObodRYOJ0soneF0iaQRJ6zhA==} + engines: {node: '>=10.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.2 + + /run-async/2.4.1: + resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} + engines: {node: '>=0.12.0'} + dev: true + + /run-parallel/1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + + /rw/1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + dev: false + + /rxjs/7.5.6: + resolution: {integrity: sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==} + dependencies: + tslib: 2.4.0 + dev: true + + /safe-buffer/5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + /safe-buffer/5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + /safer-buffer/2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + /sanitize.css/13.0.0: + resolution: {integrity: sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==} + + /sass-loader/12.6.0_sass@1.54.9+webpack@5.74.0: + resolution: {integrity: sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==} + engines: {node: '>= 12.13.0'} + peerDependencies: + fibers: '>= 3.1.0' + node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + sass: ^1.3.0 + sass-embedded: '*' + webpack: ^5.0.0 + peerDependenciesMeta: + fibers: + optional: true + node-sass: + optional: true + sass: + optional: true + sass-embedded: + optional: true + dependencies: + klona: 2.0.5 + neo-async: 2.6.2 + sass: 1.54.9 + webpack: 5.74.0 + + /sass/1.54.9: + resolution: {integrity: sha512-xb1hjASzEH+0L0WI9oFjqhRi51t/gagWnxLiwUNMltA0Ab6jIDkAacgKiGYKM9Jhy109osM7woEEai6SXeJo5Q==} + engines: {node: '>=12.0.0'} + hasBin: true + dependencies: + chokidar: 3.5.3 + immutable: 4.1.0 + source-map-js: 1.0.2 + + /sax/1.2.4: + resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} + + /saxes/5.0.1: + resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} + engines: {node: '>=10'} + dependencies: + xmlchars: 2.2.0 + + /scheduler/0.23.0: + resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} + dependencies: + loose-envify: 1.4.0 + + /schema-utils/2.7.0: + resolution: {integrity: sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==} + engines: {node: '>= 8.9.0'} + dependencies: + '@types/json-schema': 7.0.11 + ajv: 6.12.6 + ajv-keywords: 3.5.2_ajv@6.12.6 + + /schema-utils/2.7.1: + resolution: {integrity: sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==} + engines: {node: '>= 8.9.0'} + dependencies: + '@types/json-schema': 7.0.11 + ajv: 6.12.6 + ajv-keywords: 3.5.2_ajv@6.12.6 + + /schema-utils/3.1.1: + resolution: {integrity: sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==} + engines: {node: '>= 10.13.0'} + dependencies: + '@types/json-schema': 7.0.11 + ajv: 6.12.6 + ajv-keywords: 3.5.2_ajv@6.12.6 + + /schema-utils/4.0.0: + resolution: {integrity: sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==} + engines: {node: '>= 12.13.0'} + dependencies: + '@types/json-schema': 7.0.11 + ajv: 8.11.0 + ajv-formats: 2.1.1 + ajv-keywords: 5.1.0_ajv@8.11.0 + + /screenfull/5.2.0: + resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==} + engines: {node: '>=0.10.0'} + dev: false + + /select-hose/2.0.0: + resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==} + + /selfsigned/2.1.1: + resolution: {integrity: sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==} + engines: {node: '>=10'} + dependencies: + node-forge: 1.3.1 + + /semver/5.7.1: + resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} + hasBin: true + dev: true + + /semver/6.3.0: + resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} + hasBin: true + + /semver/7.3.7: + resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + + /send/0.18.0: + resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + engines: {node: '>= 0.8.0'} + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + + /serialize-javascript/4.0.0: + resolution: {integrity: sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==} + dependencies: + randombytes: 2.1.0 + + /serialize-javascript/6.0.0: + resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} + dependencies: + randombytes: 2.1.0 + + /serve-index/1.9.1: + resolution: {integrity: sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==} + engines: {node: '>= 0.8.0'} + dependencies: + accepts: 1.3.8 + batch: 0.6.1 + debug: 2.6.9 + escape-html: 1.0.3 + http-errors: 1.6.3 + mime-types: 2.1.35 + parseurl: 1.3.3 + transitivePeerDependencies: + - supports-color + + /serve-static/1.15.0: + resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} + engines: {node: '>= 0.8.0'} + dependencies: + encodeurl: 1.0.2 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.18.0 + transitivePeerDependencies: + - supports-color + + /setprototypeof/1.1.0: + resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} + + /setprototypeof/1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + /shebang-command/2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + + /shebang-regex/3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + /shell-quote/1.7.3: + resolution: {integrity: sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==} + + /side-channel/1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.3 + object-inspect: 1.12.2 + + /signal-exit/3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + /sisteransi/1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + /slash/3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + /slash/4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} + + /slice-ansi/3.0.0: + resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + dev: true + + /slice-ansi/4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + dev: true + + /slice-ansi/5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.1.1 + is-fullwidth-code-point: 4.0.0 + dev: true + + /sockjs/0.3.24: + resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==} + dependencies: + faye-websocket: 0.11.4 + uuid: 8.3.2 + websocket-driver: 0.7.4 + + /source-list-map/2.0.1: + resolution: {integrity: sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==} + + /source-map-js/1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + + /source-map-loader/3.0.1_webpack@5.74.0: + resolution: {integrity: sha512-Vp1UsfyPvgujKQzi4pyDiTOnE3E4H+yHvkVRN3c/9PJmQS4CQJExvcDvaX/D+RV+xQben9HJ56jMJS3CgUeWyA==} + engines: {node: '>= 12.13.0'} + peerDependencies: + webpack: ^5.0.0 + dependencies: + abab: 2.0.6 + iconv-lite: 0.6.3 + source-map-js: 1.0.2 + webpack: 5.74.0 + + /source-map-resolve/0.5.3: + resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} + deprecated: See https://github.com/lydell/source-map-resolve#deprecated + dependencies: + atob: 2.1.2 + decode-uri-component: 0.2.0 + resolve-url: 0.2.1 + source-map-url: 0.4.1 + urix: 0.1.0 + dev: false + + /source-map-support/0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + /source-map-url/0.4.1: + resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==} + deprecated: See https://github.com/lydell/source-map-url#deprecated + dev: false + + /source-map/0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + /source-map/0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + + /source-map/0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + dependencies: + whatwg-url: 7.1.0 + + /sourcemap-codec/1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + + /spdx-correct/3.1.1: + resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.12 + dev: true + + /spdx-exceptions/2.3.0: + resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + dev: true + + /spdx-expression-parse/3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + dependencies: + spdx-exceptions: 2.3.0 + spdx-license-ids: 3.0.12 + dev: true + + /spdx-license-ids/3.0.12: + resolution: {integrity: sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==} + dev: true + + /spdy-transport/3.0.0: + resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==} + dependencies: + debug: 4.3.4 + detect-node: 2.1.0 + hpack.js: 2.1.6 + obuf: 1.1.2 + readable-stream: 3.6.0 + wbuf: 1.7.3 + transitivePeerDependencies: + - supports-color + + /spdy/4.0.2: + resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==} + engines: {node: '>=6.0.0'} + dependencies: + debug: 4.3.4 + handle-thing: 2.0.1 + http-deceiver: 1.2.7 + select-hose: 2.0.0 + spdy-transport: 3.0.0 + transitivePeerDependencies: + - supports-color + + /split/1.0.1: + resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} + dependencies: + through: 2.3.8 + dev: true + + /split2/3.2.2: + resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} + dependencies: + readable-stream: 3.6.0 + dev: true + + /sprintf-js/1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + /stable/0.1.8: + resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} + deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' + + /stack-utils/2.0.5: + resolution: {integrity: sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==} + engines: {node: '>=10'} + dependencies: + escape-string-regexp: 2.0.0 + + /stackframe/1.3.4: + resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} + + /statuses/1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + /statuses/2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + /string-argv/0.3.1: + resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} + engines: {node: '>=0.6.19'} + dev: true + + /string-length/4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + /string-length/5.0.1: + resolution: {integrity: sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==} + engines: {node: '>=12.20'} + dependencies: + char-regex: 2.0.1 + strip-ansi: 7.0.1 + + /string-natural-compare/3.0.1: + resolution: {integrity: sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==} + + /string-width/4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + /string-width/5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.0.1 + dev: true + + /string.prototype.matchall/4.0.7: + resolution: {integrity: sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.2 + get-intrinsic: 1.1.3 + has-symbols: 1.0.3 + internal-slot: 1.0.3 + regexp.prototype.flags: 1.4.3 + side-channel: 1.0.4 + + /string.prototype.trimend/1.0.5: + resolution: {integrity: sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.2 + + /string.prototype.trimstart/1.0.5: + resolution: {integrity: sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.2 + + /string_decoder/1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + dependencies: + safe-buffer: 5.1.2 + + /string_decoder/1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + + /stringify-object/3.3.0: + resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} + engines: {node: '>=4'} + dependencies: + get-own-enumerable-property-symbols: 3.0.2 + is-obj: 1.0.1 + is-regexp: 1.0.0 + + /strip-ansi/6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + + /strip-ansi/7.0.1: + resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 + + /strip-bom/3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + /strip-bom/4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + /strip-comments/2.0.1: + resolution: {integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==} + engines: {node: '>=10'} + + /strip-final-newline/2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + /strip-final-newline/3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + dev: true + + /strip-indent/3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + dependencies: + min-indent: 1.0.1 + + /strip-json-comments/3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + /style-loader/3.3.1_webpack@5.74.0: + resolution: {integrity: sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==} + engines: {node: '>= 12.13.0'} + peerDependencies: + webpack: ^5.0.0 + dependencies: + webpack: 5.74.0 + + /stylehacks/5.1.0_postcss@8.4.16: + resolution: {integrity: sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.4 + postcss: 8.4.16 + postcss-selector-parser: 6.0.10 + + /stylis/4.1.2: + resolution: {integrity: sha512-Nn2CCrG2ZaFziDxaZPN43CXqn+j7tcdjPFCkRBkFue8QYXC2HdEwnw5TCBo4yQZ2WxKYeSi0fdoOrtEqgDrXbA==} + dev: false + + /supports-color/5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + + /supports-color/7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + + /supports-color/8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + dependencies: + has-flag: 4.0.0 + + /supports-hyperlinks/2.3.0: + resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + + /supports-preserve-symlinks-flag/1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + /svg-parser/2.0.4: + resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} + + /svgo/1.3.2: + resolution: {integrity: sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==} + engines: {node: '>=4.0.0'} + deprecated: This SVGO version is no longer supported. Upgrade to v2.x.x. + hasBin: true + dependencies: + chalk: 2.4.2 + coa: 2.0.2 + css-select: 2.1.0 + css-select-base-adapter: 0.1.1 + css-tree: 1.0.0-alpha.37 + csso: 4.2.0 + js-yaml: 3.14.1 + mkdirp: 0.5.6 + object.values: 1.1.5 + sax: 1.2.4 + stable: 0.1.8 + unquote: 1.1.1 + util.promisify: 1.0.1 + + /svgo/2.8.0: + resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} + engines: {node: '>=10.13.0'} + hasBin: true + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 4.3.0 + css-tree: 1.1.3 + csso: 4.2.0 + picocolors: 1.0.0 + stable: 0.1.8 + + /swr/1.3.0_react@18.2.0: + resolution: {integrity: sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + + /symbol-tree/3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + /tailwindcss/3.1.8: + resolution: {integrity: sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==} + engines: {node: '>=12.13.0'} + hasBin: true + dependencies: + arg: 5.0.2 + chokidar: 3.5.3 + color-name: 1.1.4 + detective: 5.2.1 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.2.12 + glob-parent: 6.0.2 + is-glob: 4.0.3 + lilconfig: 2.0.6 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.0.0 + postcss: 8.4.16 + postcss-import: 14.1.0_postcss@8.4.16 + postcss-js: 4.0.0_postcss@8.4.16 + postcss-load-config: 3.1.4_postcss@8.4.16 + postcss-nested: 5.0.6_postcss@8.4.16 + postcss-selector-parser: 6.0.10 + postcss-value-parser: 4.2.0 + quick-lru: 5.1.1 + resolve: 1.22.1 + transitivePeerDependencies: + - ts-node + + /tapable/1.1.3: + resolution: {integrity: sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==} + engines: {node: '>=6'} + + /tapable/2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + + /temp-dir/2.0.0: + resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} + engines: {node: '>=8'} + + /tempfile/3.0.0: + resolution: {integrity: sha512-uNFCg478XovRi85iD42egu+eSFUmmka750Jy7L5tfHI5hQKKtbPnxaSaXAbBqCDYrw3wx4tXjKwci4/QmsZJxw==} + engines: {node: '>=8'} + dependencies: + temp-dir: 2.0.0 + uuid: 3.4.0 + dev: true + + /tempy/0.6.0: + resolution: {integrity: sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==} + engines: {node: '>=10'} + dependencies: + is-stream: 2.0.1 + temp-dir: 2.0.0 + type-fest: 0.16.0 + unique-string: 2.0.0 + + /terminal-link/2.1.1: + resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} + engines: {node: '>=8'} + dependencies: + ansi-escapes: 4.3.2 + supports-hyperlinks: 2.3.0 + + /terser-webpack-plugin/5.3.6_webpack@5.74.0: + resolution: {integrity: sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + dependencies: + '@jridgewell/trace-mapping': 0.3.15 + jest-worker: 27.5.1 + schema-utils: 3.1.1 + serialize-javascript: 6.0.0 + terser: 5.15.0 + webpack: 5.74.0 + + /terser/5.15.0: + resolution: {integrity: sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + '@jridgewell/source-map': 0.3.2 + acorn: 8.8.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + /test-exclude/6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + + /text-extensions/1.9.0: + resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} + engines: {node: '>=0.10'} + dev: true + + /text-table/0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + /throat/6.0.1: + resolution: {integrity: sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==} + + /through/2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + dev: true + + /through2/2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + dependencies: + readable-stream: 2.3.7 + xtend: 4.0.2 + dev: true + + /through2/4.0.2: + resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + dependencies: + readable-stream: 3.6.0 + dev: true + + /thunky/1.1.0: + resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==} + + /tmp/0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + dependencies: + os-tmpdir: 1.0.2 + dev: true + + /tmpl/1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + /to-fast-properties/2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + /to-regex-range/5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + + /toggle-selection/1.0.6: + resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} + dev: false + + /toidentifier/1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + /tough-cookie/4.1.2: + resolution: {integrity: sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==} + engines: {node: '>=6'} + dependencies: + psl: 1.9.0 + punycode: 2.1.1 + universalify: 0.2.0 + url-parse: 1.5.10 + + /tr46/0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: false + + /tr46/1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + dependencies: + punycode: 2.1.1 + + /tr46/2.1.0: + resolution: {integrity: sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==} + engines: {node: '>=8'} + dependencies: + punycode: 2.1.1 + + /trim-newlines/3.0.1: + resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} + engines: {node: '>=8'} + dev: true + + /tryer/1.0.1: + resolution: {integrity: sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==} + + /ts-node/10.9.1_ck2axrxkiif44rdbzjywaqjysa: + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.3 + '@types/node': 14.18.29 + acorn: 8.8.0 + acorn-walk: 8.2.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.8.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + + /tsconfig-paths-webpack-plugin/4.0.0: + resolution: {integrity: sha512-fw/7265mIWukrSHd0i+wSwx64kYUSAKPfxRDksjKIYTxSAp9W9/xcZVBF4Kl0eqQd5eBpAQ/oQrc5RyM/0c1GQ==} + engines: {node: '>=10.13.0'} + dependencies: + chalk: 4.1.2 + enhanced-resolve: 5.10.0 + tsconfig-paths: 4.1.0 + dev: true + + /tsconfig-paths/3.14.1: + resolution: {integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==} + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.1 + minimist: 1.2.6 + strip-bom: 3.0.0 + + /tsconfig-paths/4.1.0: + resolution: {integrity: sha512-AHx4Euop/dXFC+Vx589alFba8QItjF+8hf8LtmuiCwHyI4rHXQtOOENaM8kvYf5fR0dRChy3wzWIZ9WbB7FWow==} + engines: {node: '>=6'} + dependencies: + json5: 2.2.1 + minimist: 1.2.6 + strip-bom: 3.0.0 + dev: true + + /tslib/1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + /tslib/2.4.0: + resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} + + /tsutils/3.21.0_typescript@4.8.3: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 4.8.3 + + /type-check/0.3.2: + resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.1.2 + + /type-check/0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + + /type-detect/4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + /type-fest/0.16.0: + resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} + engines: {node: '>=10'} + + /type-fest/0.18.1: + resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} + engines: {node: '>=10'} + dev: true + + /type-fest/0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + /type-fest/0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + /type-fest/0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + dev: true + + /type-fest/0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + dev: true + + /type-is/1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + /typedarray-to-buffer/3.1.5: + resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} + dependencies: + is-typedarray: 1.0.0 + + /typescript/4.8.3: + resolution: {integrity: sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==} + engines: {node: '>=4.2.0'} + hasBin: true + + /uglify-js/3.17.1: + resolution: {integrity: sha512-+juFBsLLw7AqMaqJ0GFvlsGZwdQfI2ooKQB39PSBgMnMakcFosi9O8jCwE+2/2nMNcc0z63r9mwjoDG8zr+q0Q==} + engines: {node: '>=0.8.0'} + hasBin: true + requiresBuild: true + dev: true + optional: true + + /unbox-primitive/1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + dependencies: + call-bind: 1.0.2 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + + /uncontrollable/7.2.1_react@18.2.0: + resolution: {integrity: sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==} + peerDependencies: + react: '>=15.0.0' + dependencies: + '@babel/runtime': 7.19.0 + '@types/react': 18.0.20 + invariant: 2.2.4 + react: 18.2.0 + react-lifecycles-compat: 3.0.4 + dev: false + + /unicode-canonical-property-names-ecmascript/2.0.0: + resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} + engines: {node: '>=4'} + + /unicode-match-property-ecmascript/2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.0 + unicode-property-aliases-ecmascript: 2.1.0 + + /unicode-match-property-value-ecmascript/2.0.0: + resolution: {integrity: sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==} + engines: {node: '>=4'} + + /unicode-property-aliases-ecmascript/2.1.0: + resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} + engines: {node: '>=4'} + + /unique-string/2.0.0: + resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} + engines: {node: '>=8'} + dependencies: + crypto-random-string: 2.0.0 + + /universalify/0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + + /universalify/2.0.0: + resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} + engines: {node: '>= 10.0.0'} + + /unpipe/1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + /unquote/1.1.1: + resolution: {integrity: sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==} + + /upath/1.2.0: + resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==} + engines: {node: '>=4'} + + /update-browserslist-db/1.0.9_browserslist@4.21.4: + resolution: {integrity: sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + dependencies: + browserslist: 4.21.4 + escalade: 3.1.1 + picocolors: 1.0.0 + + /uri-js/4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.1.1 + + /urix/0.1.0: + resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==} + deprecated: Please see https://github.com/lydell/urix#deprecated + dev: false + + /url-parse/1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + + /use-sync-external-store/1.2.0_react@18.2.0: + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + + /util-deprecate/1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + /util.promisify/1.0.1: + resolution: {integrity: sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==} + dependencies: + define-properties: 1.1.4 + es-abstract: 1.20.2 + has-symbols: 1.0.3 + object.getownpropertydescriptors: 2.1.4 + + /utila/0.4.0: + resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==} + + /utils-merge/1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + /uuid/3.4.0: + resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} + deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + hasBin: true + dev: true + + /uuid/8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + + /v8-compile-cache-lib/3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + dev: true + + /v8-to-istanbul/8.1.1: + resolution: {integrity: sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==} + engines: {node: '>=10.12.0'} + dependencies: + '@types/istanbul-lib-coverage': 2.0.4 + convert-source-map: 1.8.0 + source-map: 0.7.4 + + /validate-npm-package-license/3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + dependencies: + spdx-correct: 3.1.1 + spdx-expression-parse: 3.0.1 + dev: true + + /vary/1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + /void-elements/3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + dev: false + + /w3c-hr-time/1.0.2: + resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==} + dependencies: + browser-process-hrtime: 1.0.0 + + /w3c-xmlserializer/2.0.0: + resolution: {integrity: sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==} + engines: {node: '>=10'} + dependencies: + xml-name-validator: 3.0.0 + + /walker/1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + dependencies: + makeerror: 1.0.12 + + /warning/4.0.3: + resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} + dependencies: + loose-envify: 1.4.0 + dev: false + + /watchpack/2.4.0: + resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} + engines: {node: '>=10.13.0'} + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.10 + + /wbuf/1.7.3: + resolution: {integrity: sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==} + dependencies: + minimalistic-assert: 1.0.1 + + /wcwidth/1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + dependencies: + defaults: 1.0.3 + dev: true + + /web-vitals/2.1.4: + resolution: {integrity: sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==} + dev: true + + /webidl-conversions/3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: false + + /webidl-conversions/4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + + /webidl-conversions/5.0.0: + resolution: {integrity: sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==} + engines: {node: '>=8'} + + /webidl-conversions/6.1.0: + resolution: {integrity: sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==} + engines: {node: '>=10.4'} + + /webpack-dev-middleware/5.3.3_webpack@5.74.0: + resolution: {integrity: sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==} + engines: {node: '>= 12.13.0'} + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + dependencies: + colorette: 2.0.19 + memfs: 3.4.7 + mime-types: 2.1.35 + range-parser: 1.2.1 + schema-utils: 4.0.0 + webpack: 5.74.0 + + /webpack-dev-server/4.11.1_webpack@5.74.0: + resolution: {integrity: sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==} + engines: {node: '>= 12.13.0'} + hasBin: true + peerDependencies: + webpack: ^4.37.0 || ^5.0.0 + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + dependencies: + '@types/bonjour': 3.5.10 + '@types/connect-history-api-fallback': 1.3.5 + '@types/express': 4.17.14 + '@types/serve-index': 1.9.1 + '@types/serve-static': 1.15.0 + '@types/sockjs': 0.3.33 + '@types/ws': 8.5.3 + ansi-html-community: 0.0.8 + bonjour-service: 1.0.14 + chokidar: 3.5.3 + colorette: 2.0.19 + compression: 1.7.4 + connect-history-api-fallback: 2.0.0 + default-gateway: 6.0.3 + express: 4.18.1 + graceful-fs: 4.2.10 + html-entities: 2.3.3 + http-proxy-middleware: 2.0.6_@types+express@4.17.14 + ipaddr.js: 2.0.1 + open: 8.4.0 + p-retry: 4.6.2 + rimraf: 3.0.2 + schema-utils: 4.0.0 + selfsigned: 2.1.1 + serve-index: 1.9.1 + sockjs: 0.3.24 + spdy: 4.0.2 + webpack: 5.74.0 + webpack-dev-middleware: 5.3.3_webpack@5.74.0 + ws: 8.8.1 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + + /webpack-manifest-plugin/4.1.1_webpack@5.74.0: + resolution: {integrity: sha512-YXUAwxtfKIJIKkhg03MKuiFAD72PlrqCiwdwO4VEXdRO5V0ORCNwaOwAZawPZalCbmH9kBDmXnNeQOw+BIEiow==} + engines: {node: '>=12.22.0'} + peerDependencies: + webpack: ^4.44.2 || ^5.47.0 + dependencies: + tapable: 2.2.1 + webpack: 5.74.0 + webpack-sources: 2.3.1 + + /webpack-sources/1.4.3: + resolution: {integrity: sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==} + dependencies: + source-list-map: 2.0.1 + source-map: 0.6.1 + + /webpack-sources/2.3.1: + resolution: {integrity: sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==} + engines: {node: '>=10.13.0'} + dependencies: + source-list-map: 2.0.1 + source-map: 0.6.1 + + /webpack-sources/3.2.3: + resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} + engines: {node: '>=10.13.0'} + + /webpack/5.74.0: + resolution: {integrity: sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + dependencies: + '@types/eslint-scope': 3.7.4 + '@types/estree': 0.0.51 + '@webassemblyjs/ast': 1.11.1 + '@webassemblyjs/wasm-edit': 1.11.1 + '@webassemblyjs/wasm-parser': 1.11.1 + acorn: 8.8.0 + acorn-import-assertions: 1.8.0_acorn@8.8.0 + browserslist: 4.21.4 + chrome-trace-event: 1.0.3 + enhanced-resolve: 5.10.0 + es-module-lexer: 0.9.3 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.10 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.1.1 + tapable: 2.2.1 + terser-webpack-plugin: 5.3.6_webpack@5.74.0 + watchpack: 2.4.0 + webpack-sources: 3.2.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + + /websocket-driver/0.7.4: + resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} + engines: {node: '>=0.8.0'} + dependencies: + http-parser-js: 0.5.8 + safe-buffer: 5.2.1 + websocket-extensions: 0.1.4 + + /websocket-extensions/0.1.4: + resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} + engines: {node: '>=0.8.0'} + + /whatwg-encoding/1.0.5: + resolution: {integrity: sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==} + dependencies: + iconv-lite: 0.4.24 + + /whatwg-fetch/3.6.2: + resolution: {integrity: sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==} + + /whatwg-mimetype/2.3.0: + resolution: {integrity: sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==} + + /whatwg-url/5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: false + + /whatwg-url/7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + + /whatwg-url/8.7.0: + resolution: {integrity: sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==} + engines: {node: '>=10'} + dependencies: + lodash: 4.17.21 + tr46: 2.1.0 + webidl-conversions: 6.1.0 + + /which-boxed-primitive/1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + + /which/1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + dependencies: + isexe: 2.0.0 + + /which/2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + + /word-wrap/1.2.3: + resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} + engines: {node: '>=0.10.0'} + + /wordwrap/1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + dev: true + + /workbox-background-sync/6.5.4: + resolution: {integrity: sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g==} + dependencies: + idb: 7.0.2 + workbox-core: 6.5.4 + + /workbox-broadcast-update/6.5.4: + resolution: {integrity: sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw==} + dependencies: + workbox-core: 6.5.4 + + /workbox-build/6.5.4: + resolution: {integrity: sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA==} + engines: {node: '>=10.0.0'} + dependencies: + '@apideck/better-ajv-errors': 0.3.6_ajv@8.11.0 + '@babel/core': 7.19.1 + '@babel/preset-env': 7.19.1_@babel+core@7.19.1 + '@babel/runtime': 7.19.0 + '@rollup/plugin-babel': 5.3.1_qjhfxcwn2glzcb5646tzyg45bq + '@rollup/plugin-node-resolve': 11.2.1_rollup@2.79.0 + '@rollup/plugin-replace': 2.4.2_rollup@2.79.0 + '@surma/rollup-plugin-off-main-thread': 2.2.3 + ajv: 8.11.0 + common-tags: 1.8.2 + fast-json-stable-stringify: 2.1.0 + fs-extra: 9.1.0 + glob: 7.2.3 + lodash: 4.17.21 + pretty-bytes: 5.6.0 + rollup: 2.79.0 + rollup-plugin-terser: 7.0.2_rollup@2.79.0 + source-map: 0.8.0-beta.0 + stringify-object: 3.3.0 + strip-comments: 2.0.1 + tempy: 0.6.0 + upath: 1.2.0 + workbox-background-sync: 6.5.4 + workbox-broadcast-update: 6.5.4 + workbox-cacheable-response: 6.5.4 + workbox-core: 6.5.4 + workbox-expiration: 6.5.4 + workbox-google-analytics: 6.5.4 + workbox-navigation-preload: 6.5.4 + workbox-precaching: 6.5.4 + workbox-range-requests: 6.5.4 + workbox-recipes: 6.5.4 + workbox-routing: 6.5.4 + workbox-strategies: 6.5.4 + workbox-streams: 6.5.4 + workbox-sw: 6.5.4 + workbox-window: 6.5.4 + transitivePeerDependencies: + - '@types/babel__core' + - supports-color + + /workbox-cacheable-response/6.5.4: + resolution: {integrity: sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug==} + dependencies: + workbox-core: 6.5.4 + + /workbox-core/6.5.4: + resolution: {integrity: sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==} + + /workbox-expiration/6.5.4: + resolution: {integrity: sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ==} + dependencies: + idb: 7.0.2 + workbox-core: 6.5.4 + + /workbox-google-analytics/6.5.4: + resolution: {integrity: sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg==} + dependencies: + workbox-background-sync: 6.5.4 + workbox-core: 6.5.4 + workbox-routing: 6.5.4 + workbox-strategies: 6.5.4 + + /workbox-navigation-preload/6.5.4: + resolution: {integrity: sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng==} + dependencies: + workbox-core: 6.5.4 + + /workbox-precaching/6.5.4: + resolution: {integrity: sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg==} + dependencies: + workbox-core: 6.5.4 + workbox-routing: 6.5.4 + workbox-strategies: 6.5.4 + + /workbox-range-requests/6.5.4: + resolution: {integrity: sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg==} + dependencies: + workbox-core: 6.5.4 + + /workbox-recipes/6.5.4: + resolution: {integrity: sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA==} + dependencies: + workbox-cacheable-response: 6.5.4 + workbox-core: 6.5.4 + workbox-expiration: 6.5.4 + workbox-precaching: 6.5.4 + workbox-routing: 6.5.4 + workbox-strategies: 6.5.4 + + /workbox-routing/6.5.4: + resolution: {integrity: sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg==} + dependencies: + workbox-core: 6.5.4 + + /workbox-strategies/6.5.4: + resolution: {integrity: sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw==} + dependencies: + workbox-core: 6.5.4 + + /workbox-streams/6.5.4: + resolution: {integrity: sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg==} + dependencies: + workbox-core: 6.5.4 + workbox-routing: 6.5.4 + + /workbox-sw/6.5.4: + resolution: {integrity: sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA==} + + /workbox-webpack-plugin/6.5.4_webpack@5.74.0: + resolution: {integrity: sha512-LmWm/zoaahe0EGmMTrSLUi+BjyR3cdGEfU3fS6PN1zKFYbqAKuQ+Oy/27e4VSXsyIwAw8+QDfk1XHNGtZu9nQg==} + engines: {node: '>=10.0.0'} + peerDependencies: + webpack: ^4.4.0 || ^5.9.0 + dependencies: + fast-json-stable-stringify: 2.1.0 + pretty-bytes: 5.6.0 + upath: 1.2.0 + webpack: 5.74.0 + webpack-sources: 1.4.3 + workbox-build: 6.5.4 + transitivePeerDependencies: + - '@types/babel__core' + - supports-color + + /workbox-window/6.5.4: + resolution: {integrity: sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==} + dependencies: + '@types/trusted-types': 2.0.2 + workbox-core: 6.5.4 + + /wrap-ansi/6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrap-ansi/7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + /wrappy/1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + /write-file-atomic/3.0.3: + resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} + dependencies: + imurmurhash: 0.1.4 + is-typedarray: 1.0.0 + signal-exit: 3.0.7 + typedarray-to-buffer: 3.1.5 + + /ws/7.5.9: + resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + /ws/8.8.1: + resolution: {integrity: sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + /xml-name-validator/3.0.0: + resolution: {integrity: sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==} + + /xmlchars/2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + /xtend/4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + /y18n/5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + /yallist/4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + /yaml/1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + /yaml/2.1.1: + resolution: {integrity: sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==} + engines: {node: '>= 14'} + dev: true + + /yargs-parser/20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + /yargs-parser/21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + dev: true + + /yargs/16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + dependencies: + cliui: 7.0.4 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + + /yargs/17.5.1: + resolution: {integrity: sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==} + engines: {node: '>=12'} + dependencies: + cliui: 7.0.4 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + dev: true + + /yn/3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + dev: true + + /yocto-queue/0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + /zustand/4.1.1_react@18.2.0: + resolution: {integrity: sha512-h4F3WMqsZgvvaE0n3lThx4MM81Ls9xebjvrABNzf5+jb3/03YjNTSgZXeyrvXDArMeV9untvWXRw1tY+ntPYbA==} + engines: {node: '>=12.7.0'} + peerDependencies: + immer: '>=9.0' + react: '>=16.8' + peerDependenciesMeta: + immer: + optional: true + react: + optional: true + dependencies: + react: 18.2.0 + use-sync-external-store: 1.2.0_react@18.2.0 + dev: false diff --git a/ui/public/favicon.ico b/ui/public/favicon.ico new file mode 100644 index 00000000..a11777cc Binary files /dev/null and b/ui/public/favicon.ico differ diff --git a/ui/public/index.html b/ui/public/index.html new file mode 100644 index 00000000..bff92c3f --- /dev/null +++ b/ui/public/index.html @@ -0,0 +1,39 @@ + + + + + + + + + + + + Answer + + + +
+ + + diff --git a/ui/public/logo192.png b/ui/public/logo192.png new file mode 100644 index 00000000..fc44b0a3 Binary files /dev/null and b/ui/public/logo192.png differ diff --git a/ui/public/logo512.png b/ui/public/logo512.png new file mode 100644 index 00000000..a4e47a65 Binary files /dev/null and b/ui/public/logo512.png differ diff --git a/ui/public/manifest.json b/ui/public/manifest.json new file mode 100644 index 00000000..080d6c77 --- /dev/null +++ b/ui/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/ui/public/robots.txt b/ui/public/robots.txt new file mode 100644 index 00000000..e9e57dc4 --- /dev/null +++ b/ui/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/ui/src/App.css b/ui/src/App.css new file mode 100644 index 00000000..e69de29b diff --git a/ui/src/App.test.tsx b/ui/src/App.test.tsx new file mode 100644 index 00000000..74d156c5 --- /dev/null +++ b/ui/src/App.test.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +import { render, screen } from '@testing-library/react'; + +import App from './App'; + +test('renders learn react link', () => { + render(); + const linkElement = screen.getByText(/learn react/i); + expect(linkElement).toBeInTheDocument(); +}); diff --git a/ui/src/App.tsx b/ui/src/App.tsx new file mode 100644 index 00000000..5d4f6925 --- /dev/null +++ b/ui/src/App.tsx @@ -0,0 +1,9 @@ +import { RouterProvider } from 'react-router-dom'; + +import router from '@/router'; + +function App() { + return ; +} + +export default App; diff --git a/ui/src/assets/images/default-avatar.svg b/ui/src/assets/images/default-avatar.svg new file mode 100644 index 00000000..08e0a60b --- /dev/null +++ b/ui/src/assets/images/default-avatar.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/src/assets/images/icon-editor-toolbar.png b/ui/src/assets/images/icon-editor-toolbar.png new file mode 100644 index 00000000..a2fbe68c Binary files /dev/null and b/ui/src/assets/images/icon-editor-toolbar.png differ diff --git a/ui/src/common/index.ts b/ui/src/common/index.ts new file mode 100644 index 00000000..cdbff5b3 --- /dev/null +++ b/ui/src/common/index.ts @@ -0,0 +1,36 @@ +export const LOGIN_NEED_BACK = [ + '/users/login', + '/users/register', + '/users/account-recovery', + '/users/password-reset', +]; + +export const ADMIN_LIST_STATUS = { + // normal; + 1: { + variant: 'success', + name: 'normal', + }, + // closed; + 2: { + variant: 'warning', + name: 'closed', + }, + // deleted + 10: { + variant: 'danger', + name: 'deleted', + }, + normal: { + variant: 'success', + name: 'normal', + }, + closed: { + variant: 'warning', + name: 'closed', + }, + deleted: { + variant: 'danger', + name: 'deleted', + }, +}; diff --git a/ui/src/common/interface.ts b/ui/src/common/interface.ts new file mode 100644 index 00000000..1d205691 --- /dev/null +++ b/ui/src/common/interface.ts @@ -0,0 +1,9 @@ +export interface FormValue { + value: T; + isInvalid: boolean; + errorMsg: string; +} + +export interface FormDataType { + [prop: string]: FormValue; +} diff --git a/ui/src/components/AccordionNav/index.tsx b/ui/src/components/AccordionNav/index.tsx new file mode 100644 index 00000000..ccc14d44 --- /dev/null +++ b/ui/src/components/AccordionNav/index.tsx @@ -0,0 +1,111 @@ +import React, { FC } from 'react'; +import { Accordion, Badge, Button, Stack } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; +import { useNavigate, useMatch } from 'react-router-dom'; + +import { useAccordionButton } from 'react-bootstrap/AccordionButton'; + +import { Icon } from '@answer/components'; + +function MenuNode({ menu, callback, activeKey, isLeaf = false }) { + const { t } = useTranslation('translation', { keyPrefix: 'admin.nav_menus' }); + const accordionClick = useAccordionButton(menu.name); + const menuOnClick = (evt) => { + evt.preventDefault(); + evt.stopPropagation(); + if (!isLeaf) { + accordionClick(evt); + } + if (typeof callback === 'function') { + callback(menu); + } + }; + + let menuCls = 'text-start text-dark text-nowrap shadow-none bg-body border-0'; + let menuVariant = 'light'; + if (activeKey === menu.name) { + menuCls = 'text-start text-white text-nowrap shadow-none'; + menuVariant = 'primary'; + } + return ( + + ); +} + +interface AccordionProps { + menus: any[]; +} +const AccordionNav: FC = ({ menus }) => { + const navigate = useNavigate(); + let activeKey = menus[0].name; + const pathMatch = useMatch('/admin/*'); + const splat = pathMatch && pathMatch.params['*']; + if (splat) { + activeKey = splat; + } + const menuClick = (clickedMenu) => { + const menuKey = clickedMenu.name; + if (Array.isArray(clickedMenu.child) && clickedMenu.child.length) { + return; + } + if (activeKey !== menuKey) { + const routePath = `/admin/${menuKey}`; + navigate(routePath); + } + }; + + let defaultOpenKey; + menus.forEach((li) => { + if (Array.isArray(li.child) && li.child.length) { + const matchedChild = li.child.find((el) => { + return el.name === activeKey; + }); + if (matchedChild) { + defaultOpenKey = li.name; + } + } + }); + + return ( + + + {menus.map((li) => { + return ( + + + {Array.isArray(li.child) ? ( + + + {li.child?.map((leaf) => { + return ( + + ); + })} + + + ) : null} + + ); + })} + + + ); +}; + +export default AccordionNav; diff --git a/ui/src/components/Actions/index.tsx b/ui/src/components/Actions/index.tsx new file mode 100644 index 00000000..ccfa04cc --- /dev/null +++ b/ui/src/components/Actions/index.tsx @@ -0,0 +1,133 @@ +import { memo, FC, useState, useEffect } from 'react'; +import { Button, ButtonGroup } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; + +import classNames from 'classnames'; + +import { Icon } from '@answer/components'; +import { bookmark, postVote } from '@answer/services/api'; +import { isLogin } from '@answer/utils'; +import { userInfoStore } from '@answer/stores'; +import { useToast } from '@answer/hooks'; + +interface Props { + className?: string; + data: { + id: string; + votesCount: number; + isLike: boolean; + isHate: boolean; + hideCollect?: boolean; + collected: boolean; + collectCount: number; + username: string; + }; +} + +const Index: FC = ({ className, data }) => { + const [votes, setVotes] = useState(0); + const [like, setLike] = useState(false); + const [hate, setHated] = useState(false); + const [bookmarkState, setBookmark] = useState({ + state: data?.collected, + count: data?.collectCount, + }); + const { username = '' } = userInfoStore((state) => state.user); + const toast = useToast(); + const { t } = useTranslation(); + useEffect(() => { + if (data) { + setVotes(data.votesCount); + setLike(data.isLike); + setHated(data.isHate); + setBookmark({ + state: data?.collected, + count: data?.collectCount, + }); + } + }, [data]); + + const handleVote = (type: 'up' | 'down') => { + if (!isLogin(true)) { + return; + } + + if (data.username === username) { + toast.onShow({ + msg: t('cannot_vote_for_self'), + variant: 'danger', + }); + return; + } + const isCancel = (type === 'up' && like) || (type === 'down' && hate); + postVote( + { + object_id: data?.id, + is_cancel: isCancel, + }, + type, + ) + .then((res) => { + setVotes(res.votes); + setLike(res.vote_status === 'vote_up'); + setHated(res.vote_status === 'vote_down'); + }) + .catch((err) => { + const errMsg = err?.value; + if (errMsg) { + toast.onShow({ + msg: errMsg, + variant: 'danger', + }); + } + }); + }; + + const handleBookmark = () => { + if (!isLogin(true)) { + return; + } + bookmark({ + group_id: '0', + object_id: data?.id, + }).then((res) => { + setBookmark({ + state: res.switch, + count: res.object_collection_count, + }); + }); + }; + + return ( +
+ + + + + + {!data?.hideCollect && ( + + )} +
+ ); +}; + +export default memo(Index); diff --git a/ui/src/components/AdminHeader/index.tsx b/ui/src/components/AdminHeader/index.tsx new file mode 100644 index 00000000..cb5d6401 --- /dev/null +++ b/ui/src/components/AdminHeader/index.tsx @@ -0,0 +1,23 @@ +import { FC, memo } from 'react'; +import { Container } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; +import { useMatch } from 'react-router-dom'; + +const Index: FC = () => { + const { t } = useTranslation('translation', { + keyPrefix: 'admin.admin_header', + }); + const adminPathMatch = useMatch('/admin/*'); + if (!adminPathMatch) { + return null; + } + return ( +
+ +
{t('title')}
+
+
+ ); +}; + +export default memo(Index); diff --git a/ui/src/components/Avatar/index.tsx b/ui/src/components/Avatar/index.tsx new file mode 100644 index 00000000..08242715 --- /dev/null +++ b/ui/src/components/Avatar/index.tsx @@ -0,0 +1,26 @@ +import { memo, FC } from 'react'; + +import classNames from 'classnames'; + +import DefaultAvatar from '@/assets/images/default-avatar.svg'; + +interface IProps { + /** avatar url */ + avatar: string; + size: string; + className?: string; +} + +const Index: FC = ({ avatar, size, className }) => { + return ( + + ); +}; + +export default memo(Index); diff --git a/ui/src/components/BaseUserCard/index.tsx b/ui/src/components/BaseUserCard/index.tsx new file mode 100644 index 00000000..a5278ebc --- /dev/null +++ b/ui/src/components/BaseUserCard/index.tsx @@ -0,0 +1,30 @@ +import { memo, FC } from 'react'; +import { Link } from 'react-router-dom'; + +import { Avatar } from '@answer/components'; + +interface Props { + data: any; + avatarSize?: string; + className?: string; +} + +const Index: FC = ({ + data, + avatarSize = '20px', + className = 'fs-14', +}) => { + return ( +
+ + + + + {data?.display_name} + + {data?.rank} +
+ ); +}; + +export default memo(Index); diff --git a/ui/src/components/Comment/components/ActionBar/index.tsx b/ui/src/components/Comment/components/ActionBar/index.tsx new file mode 100644 index 00000000..a980fd3e --- /dev/null +++ b/ui/src/components/Comment/components/ActionBar/index.tsx @@ -0,0 +1,66 @@ +import { memo } from 'react'; +import { Button } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; + +import classNames from 'classnames'; + +import { Icon, FormatTime } from '@answer/components'; + +const ActionBar = ({ + nickName, + username, + createdAt, + isVote, + voteCount = 0, + memberActions, + onReply, + onVote, + onAction, +}) => { + const { t } = useTranslation('translation', { keyPrefix: 'comment' }); + + return ( +
+
+ {nickName} + · + + + +
+
+ {memberActions.map((action, index) => { + return ( + + ); + })} +
+
+ ); +}; + +export default memo(ActionBar); diff --git a/ui/src/components/Comment/components/Form/index.tsx b/ui/src/components/Comment/components/Form/index.tsx new file mode 100644 index 00000000..a18847d5 --- /dev/null +++ b/ui/src/components/Comment/components/Form/index.tsx @@ -0,0 +1,69 @@ +import { useState, useEffect, memo } from 'react'; +import { Button } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; + +import classNames from 'classnames'; + +import { TextArea, Mentions } from '@answer/components'; +import { usePageUsers } from '@answer/hooks'; + +const Form = ({ + className = '', + value: initialValue = '', + onSendReply, + type = '', + onCancel, + mode, +}) => { + const [value, setValue] = useState(''); + const pageUsers = usePageUsers(); + const { t } = useTranslation('translation', { keyPrefix: 'comment' }); + + useEffect(() => { + if (!initialValue) { + return; + } + setValue(initialValue); + }, [initialValue]); + + const handleChange = (e) => { + setValue(e.target.value); + }; + + return ( +
+
+ +