diff --git a/.dockerignore b/.dockerignore index cadb3e8..c297da2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,8 +1,17 @@ * + +# Backend !internal !cmd !pkg !main.go !go.mod !go.sum -!LICENSE \ No newline at end of file + +# Dashboard +!dashboard/* +dashboard/node_modules + +# Other +!LICENSE +!docker/* diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..42763fb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +max_line_length = 80 +trim_trailing_whitespace = true + +[*.md] +max_line_length = 0 +trim_trailing_whitespace = false + +[COMMIT_EDITMSG] +max_line_length = 0 \ No newline at end of file diff --git a/.env.example b/.env.example index 7050bb8..6bbea22 100644 --- a/.env.example +++ b/.env.example @@ -4,3 +4,6 @@ SERVER_PORT=9000 LOG_LEVEL: debug LOG_FORMAT: text + +AWS_REGION=eu-west-1 +AWS_BUCKET_NAME=my-bucket \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c487453..dcababc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,39 +1,122 @@ name: CI -permissions: - contents: read - pull-requests: read - on: + workflow_dispatch: {} + pull_request: {} + release: + types: [published] push: - branches: [master] - pull_request: - branches: [master] + branches: + - master + - develop + - release/* + - feature/* + - hotfix/* jobs: test: - runs-on: self-hosted + runs-on: default steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 + with: + go-version: 1.23 + + - name: Cache Go Modules + uses: actions/cache@v3 with: - go-version: 1.19 + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go-${{ matrix.go-version }}- - name: Test run: go test -v ./... lint: name: lint - runs-on: self-hosted + runs-on: default steps: - name: Checkout Code - uses: actions/checkout@v2 + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.23 - name: golangci-lint - uses: golangci/golangci-lint-action@v2 + uses: golangci/golangci-lint-action@v6 with: version: latest github-token: ${{secrets.GITHUB_TOKEN}} only-new-issues: true + + build: + name: Build 🏗 + runs-on: default + needs: [lint, test] + concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-build + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Generate Docker Metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ inputs.image_name }} + tags: | + # Raw sha of the commit + type=raw,value={{sha}} + + # Branch name with and without the sha suffix + type=raw,value={{branch}},enable=${{ github.event_name == 'push' }} + type=raw,value={{branch}}-{{sha}},enable=${{ github.event_name == 'push' }} + + # Branch name with and without the sha suffix and unix timestamp milliseconds + type=raw,value={{branch}}-{{date 'x'}},enable=${{ github.event_name == 'push' }} + type=raw,value={{branch}}-{{sha}}-{{date 'x'}},enable=${{ github.event_name == 'push' }} + + # PR number with and without the sha suffix + type=raw,value=${{github.head_ref}},enable=${{ github.event_name == 'pull_request' }} + type=raw,value=${{github.head_ref}}-{{sha}},enable=${{ github.event_name == 'pull_request' }} + + # Release tag + type=semver,pattern={{version}},enable=${{ github.event_name == 'release' }} + type=raw,value={{tag}},enable=${{ github.event_name == 'release' }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Registry + uses: docker/login-action@v3 + with: + registry: docker.wiseflow.io + username: ${{ secrets.HARBOR_USERNAME }} + password: ${{ secrets.HARBOR_TOKEN }} + + - name: Build and Push + uses: docker/build-push-action@v5 + with: + provenance: false + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=build + cache-to: type=gha,mode=max,scope=build + platforms: ${{ inputs.platforms }} + + - name: Write summary + run: | + echo "# Image tags" >> $GITHUB_STEP_SUMMARY + for tag in ${{ join(fromJSON(steps.meta.outputs.json).tags, ' ') }} + do + echo "- $tag" >> $GITHUB_STEP_SUMMARY + done diff --git a/.gitignore b/.gitignore index d20a3f0..4575231 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ .env .parrot.yaml +.vite/ \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml index e3a2b06..c847018 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,13 +1,8 @@ linters: - enable-all: true - disable: - - wsl - - gochecknoglobals - - exhaustivestruct - - wrapcheck - - # Deprecated - - scopelint - - golint - - maligned - - interfacer + disable: [] +run: + tests: false +issues: + exclude-files: + - ".*\\.pb\\.go$" + - ".*\\_mock\\.go$" diff --git a/.ship-it b/.ship-it deleted file mode 100644 index a44af5d..0000000 --- a/.ship-it +++ /dev/null @@ -1 +0,0 @@ -targetBranch: master diff --git a/Dockerfile b/Dockerfile index b400dd8..5475d47 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,32 +1,43 @@ ############################ -# STEP 1 build base +# Backend build ############################ -FROM golang:1.19-alpine3.17 as build-base -RUN apk add --update --no-cache git ca-certificates build-base +FROM golang:1.23-alpine3.20 AS backend-builder WORKDIR /build -ENV GO111MODULE=on + COPY go.mod . COPY go.sum . RUN go mod download -x +COPY . . +RUN GOOS=linux GOARCH=amd64 go build -o /build/bin/parrot main.go + ############################ -# STEP 2 image base +# Frontend build ############################ -FROM alpine:3.17 as image-base +FROM node:20-alpine3.20 AS frotnend-builder WORKDIR /app -COPY --from=build-base /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ -ENTRYPOINT ["/app/parrot", "serve"] -############################ -# STEP 3 build executable -############################ -FROM build-base AS builder -COPY . . -RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o /build/bin/parrot main.go +RUN npm install -g pnpm + +COPY ./dashboard/package.json . +COPY ./dashboard/pnpm-lock.yaml . +RUN pnpm install --frozen-lockfile + +COPY ./dashboard /app +RUN pnpm build + ############################ -# STEP 4 Finalize image +# Finalize image ############################ -FROM image-base +FROM nginx:1.27.2-alpine3.20 AS image-base +WORKDIR / +ENTRYPOINT ["/bin/sh", "/entrypoint.sh"] + COPY LICENSE . -COPY --from=builder /build/bin/parrot parrot \ No newline at end of file + +COPY ./docker/nginx.conf /etc/nginx +COPY ./docker/entrypoint.sh / + +COPY --from=backend-builder /build/bin/parrot /usr/local/bin/parrot +COPY --from=frotnend-builder /app/dist /usr/share/nginx/html diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 0d12033..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,39 +0,0 @@ -#!groovy -@Library('utils') -import jenkinslib.Utilities -import jenkinslib.PodBuilder -def utils = new Utilities(this, false) -def podbuilder = new PodBuilder(this, false) - -properties([ - parameters([ - string(name: 'docker tags', defaultValue: utils.tagsFromBranch(env.BRANCH_NAME).join(","), description: "Tag the docker image with these tags. (separated by commas)") - ]) -]) - -podbuilder.Golang(){ - def tags = params["docker tags"].tokenize(",") - stage('Checkout'){ - tags.push((checkout(scm)).GIT_COMMIT.substring(0,7)) - } - - container('golang'){ - stage('Test'){ - sh "go test ./..." - } - } - - container('dind'){ - stage('Build'){ - withCredentials([usernamePassword(credentialsId: "jenkins-harbor-registry", usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]){ - sh "docker login --username='$USERNAME' --password='$PASSWORD' docker.wiseflow.io" - } - sh "docker build . -t " + utils.dockertags(tags).join(" -t ") - } - stage('Push'){ - utils.dockertags(tags).each { - tag -> sh "docker push $tag" - } - } - } -} \ No newline at end of file diff --git a/README.md b/README.md index 2c081ea..7cc0a81 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ The server can be configured with a yaml configuration file specified with the ` | key | description | type | default | | ------------------------------ | ---------------------------------------------------------------------------- | -------- | ---------------------------- | -| server.port | port for the main http server | int | `80` | +| server.port.public | port for the public http server | int | `80` | +| server.port.private | port for the private http server | int | `81` | | server.gracePeriod | grace period for the http server to shutdown | duration | `10s` | | log.level | log level | string | `info` | | log.format | format of the log. Can be "text" or "json" | string | `json` | @@ -54,6 +55,9 @@ The server can be configured with a yaml configuration file specified with the ` | prometheus.path | expose prometheus metrics under path | string | `/metrics` | | prometheus.port | port to expose the prometheus metrics under | int | `9090` | | api.token | secret token to authenticating against poeditor | string | +| aws.region | AWS Region | string | `eu-west-1` | +| aws.bucket | AWS bucket | string | + # API specification diff --git a/chart/parrot/.helmignore b/chart/parrot/.helmignore deleted file mode 100644 index 0e8a0eb..0000000 --- a/chart/parrot/.helmignore +++ /dev/null @@ -1,23 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ diff --git a/chart/parrot/Chart.yaml b/chart/parrot/Chart.yaml deleted file mode 100644 index 13f5437..0000000 --- a/chart/parrot/Chart.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -name: parrot -description: A Helm chart for Kubernetes - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.2 - -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Versions are not expected to -# follow Semantic Versioning. They should reflect the version the application is using. -appVersion: 0.2.0 - -icon: https://github.com/UNIwise/parrot/raw/master/assets/parrot.svg diff --git a/chart/parrot/templates/NOTES.txt b/chart/parrot/templates/NOTES.txt deleted file mode 100644 index 9bf4c3b..0000000 --- a/chart/parrot/templates/NOTES.txt +++ /dev/null @@ -1,21 +0,0 @@ -1. Get the application URL by running these commands: -{{- if .Values.ingress.enabled }} -{{- range $host := .Values.ingress.hosts }} - {{- range .paths }} - http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }} - {{- end }} -{{- end }} -{{- else if contains "NodePort" .Values.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "parrot.fullname" . }}) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - echo http://$NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" .Values.service.type }} - NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "parrot.fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "parrot.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") - echo http://$SERVICE_IP:{{ .Values.service.port }} -{{- else if contains "ClusterIP" .Values.service.type }} - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "parrot.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") - echo "Visit http://127.0.0.1:8080 to use your application" - kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80 -{{- end }} diff --git a/chart/parrot/templates/_helpers.tpl b/chart/parrot/templates/_helpers.tpl deleted file mode 100644 index f685410..0000000 --- a/chart/parrot/templates/_helpers.tpl +++ /dev/null @@ -1,62 +0,0 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "parrot.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 "parrot.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 "parrot.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "parrot.labels" -}} -helm.sh/chart: {{ include "parrot.chart" . }} -{{ include "parrot.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "parrot.selectorLabels" -}} -app.kubernetes.io/name: {{ include "parrot.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Create the name of the service account to use -*/}} -{{- define "parrot.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include "parrot.fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} -{{- end }} diff --git a/chart/parrot/templates/deployment.yaml b/chart/parrot/templates/deployment.yaml deleted file mode 100644 index bc58014..0000000 --- a/chart/parrot/templates/deployment.yaml +++ /dev/null @@ -1,77 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "parrot.fullname" . }} - labels: - {{- include "parrot.labels" . | nindent 4 }} -spec: -{{- if not .Values.autoscaling.enabled }} - replicas: {{ .Values.replicaCount }} -{{- end }} - selector: - matchLabels: - {{- include "parrot.selectorLabels" . | nindent 6 }} - template: - metadata: - annotations: - checksum/config: {{ toJson .Values.config | sha256sum }} - {{- with .Values.podAnnotations }} - {{- toYaml . | nindent 8 }} - {{- end }} - labels: - {{- include "parrot.selectorLabels" . | nindent 8 }} - spec: - volumes: - - name: config - secret: - secretName: {{ include "parrot.fullname" . }} - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include "parrot.serviceAccountName" . }} - securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} - containers: - - name: {{ .Chart.Name }} - securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - args: - - --config=/config/config.yaml - volumeMounts: - - name: config - mountPath: /config/config.yaml - subPath: config.yaml - ports: - - name: http - containerPort: {{ default 80 .Values.config.server.port }} - protocol: TCP - {{- if .Values.config.prometheus.enabled }} - - name: prometheus - containerPort: {{ default 9090 .Values.config.prometheus.port }} - protocol: TCP - {{- end }} - livenessProbe: - httpGet: - path: /health - port: http - readinessProbe: - httpGet: - path: /health - port: http - resources: - {{- toYaml .Values.resources | nindent 12 }} - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} diff --git a/chart/parrot/templates/hpa.yaml b/chart/parrot/templates/hpa.yaml deleted file mode 100644 index 1df58a0..0000000 --- a/chart/parrot/templates/hpa.yaml +++ /dev/null @@ -1,28 +0,0 @@ -{{- if .Values.autoscaling.enabled }} -apiVersion: autoscaling/v2beta1 -kind: HorizontalPodAutoscaler -metadata: - name: {{ include "parrot.fullname" . }} - labels: - {{- include "parrot.labels" . | nindent 4 }} -spec: - scaleTargetRef: - apiVersion: apps/v1 - kind: Deployment - name: {{ include "parrot.fullname" . }} - minReplicas: {{ .Values.autoscaling.minReplicas }} - maxReplicas: {{ .Values.autoscaling.maxReplicas }} - metrics: - {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} - - type: Resource - resource: - name: cpu - targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} - {{- end }} - {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} - - type: Resource - resource: - name: memory - targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} - {{- end }} -{{- end }} diff --git a/chart/parrot/templates/ingress.yaml b/chart/parrot/templates/ingress.yaml deleted file mode 100644 index 775be0c..0000000 --- a/chart/parrot/templates/ingress.yaml +++ /dev/null @@ -1,55 +0,0 @@ -{{- if .Values.ingress.enabled -}} -{{- $fullName := include "parrot.fullname" . -}} -{{- $svcPort := .Values.service.port -}} -{{- if semverCompare ">=1.22-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullName }} - labels: - {{- include "parrot.labels" . | nindent 4 }} - {{- with .Values.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: -{{- if semverCompare ">=1.22-0" .Capabilities.KubeVersion.GitVersion }} - ingressClassName: {{ .Values.ingress.ingressClassName }} -{{- end }} - {{- if .Values.ingress.tls }} - tls: - {{- range .Values.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} - {{- end }} - rules: - {{- range .Values.ingress.hosts }} - - host: {{ .host | quote }} - http: - paths: - {{- range .paths }} - - path: {{ . }} - {{- if semverCompare ">=1.22-0" $.Capabilities.KubeVersion.GitVersion }} - pathType: ImplementationSpecific - backend: - service: - name: {{ $fullName }} - port: - number: {{ $svcPort }} - {{- else }} - backend: - serviceName: {{ $fullName }} - servicePort: {{ $svcPort }} - {{- end }} - {{- end }} - {{- end }} - {{- end }} diff --git a/chart/parrot/templates/secret.yaml b/chart/parrot/templates/secret.yaml deleted file mode 100644 index 955236b..0000000 --- a/chart/parrot/templates/secret.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "parrot.fullname" . }} - labels: - {{- include "parrot.labels" . | nindent 4 }} -type: Opaque -data: - config.yaml: {{ toYaml .Values.config | b64enc }} - \ No newline at end of file diff --git a/chart/parrot/templates/service.yaml b/chart/parrot/templates/service.yaml deleted file mode 100644 index 78c96da..0000000 --- a/chart/parrot/templates/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ include "parrot.fullname" . }} - labels: - {{- include "parrot.labels" . | nindent 4 }} -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.port }} - targetPort: http - protocol: TCP - name: http - selector: - {{- include "parrot.selectorLabels" . | nindent 4 }} diff --git a/chart/parrot/templates/serviceaccount.yaml b/chart/parrot/templates/serviceaccount.yaml deleted file mode 100644 index 9bd16c7..0000000 --- a/chart/parrot/templates/serviceaccount.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{- if .Values.serviceAccount.create -}} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "parrot.serviceAccountName" . }} - labels: - {{- include "parrot.labels" . | nindent 4 }} - {{- with .Values.serviceAccount.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -{{- end }} diff --git a/chart/parrot/templates/tests/test-connection.yaml b/chart/parrot/templates/tests/test-connection.yaml deleted file mode 100644 index 34f36f3..0000000 --- a/chart/parrot/templates/tests/test-connection.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: "{{ include "parrot.fullname" . }}-test-connection" - labels: - {{- include "parrot.labels" . | nindent 4 }} - annotations: - "helm.sh/hook": test-success -spec: - containers: - - name: wget - image: busybox - command: ['wget'] - args: ['{{ include "parrot.fullname" . }}:{{ .Values.service.port }}'] - restartPolicy: Never diff --git a/chart/parrot/values.yaml b/chart/parrot/values.yaml deleted file mode 100644 index aac47b3..0000000 --- a/chart/parrot/values.yaml +++ /dev/null @@ -1,110 +0,0 @@ -# Default values for parrot. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - -replicaCount: 1 - -image: - repository: docker.wiseflow.io/k8s/parrot - pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart appVersion. - tag: "" - -imagePullSecrets: [] -nameOverride: "" -fullnameOverride: "" - -serviceAccount: - # Specifies whether a service account should be created - create: true - # Annotations to add to the service account - annotations: {} - # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template - name: "" - -podAnnotations: {} - -podSecurityContext: {} - # fsGroup: 2000 - -securityContext: {} - # capabilities: - # drop: - # - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true - # runAsUser: 1000 - -service: - type: ClusterIP - port: 80 - -ingress: - enabled: false - annotations: {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - hosts: [] - # - host: chart-example.local - # paths: [] - tls: [] - # - secretName: chart-example-tls - # hosts: - # - chart-example.local - -resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - -autoscaling: - enabled: false - minReplicas: 1 - maxReplicas: 100 - targetCPUUtilizationPercentage: 80 - # targetMemoryUtilizationPercentage: 80 - -nodeSelector: {} - -tolerations: [] - -affinity: {} - -config: - server: - port: 80 - gracePeriod: 10s - log: - level: info - format: json - cache: - type: filesystem - ttl: 1h - renewalThreshold: 30m - # filesystem: - # dir: - # redis: - # mode: - # address: - # username: - # password: - # maxRetries: - # db: - # sentinel: - # master: - # addresses: - # password: - prometheus: - enabled: true - path: /metrics - port: 9090 - api: - token: REDACTED diff --git a/cmd/root.go b/cmd/root.go index 496fbca..14e4352 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -76,10 +76,14 @@ func initConfig() { viper.SetConfigName(".parrot") } - godotenv.Load(".env") + // nolint + godotenv.Load(".env") // we don't care about the error here + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) viper.AutomaticEnv() // read in environment variables that match // If a config file is found, read it in. + // we don't care about the error here + // nolint viper.ReadInConfig() } diff --git a/cmd/serve.go b/cmd/serve.go index feb9591..eea9615 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -31,13 +31,16 @@ import ( "github.com/uniwise/parrot/internal/cache" "github.com/uniwise/parrot/internal/metrics" "github.com/uniwise/parrot/internal/project" - "github.com/uniwise/parrot/internal/rest" + privateRest "github.com/uniwise/parrot/internal/rest/v1/private" + publicRest "github.com/uniwise/parrot/internal/rest/v1/public" + "github.com/uniwise/parrot/internal/storage" "github.com/uniwise/parrot/pkg/poedit" ) const ( - confServerPort = "server.port" - confServerGrace = "server.gracePeriod" + confServerPortPublic = "server.port.public" + confServerPortPrivate = "server.port.private" + confServerGrace = "server.gracePeriod" confLogLevel = "log.level" confLogFormat = "log.format" @@ -61,6 +64,9 @@ const ( confPrometheusPort = "prometheus.port" confAPIToken = "api.token" + + confAWSRegion = "aws.region" + confAWSBucket = "aws.bucket" ) // serveCmd represents the serve command @@ -75,24 +81,50 @@ by caching exports from poeditor`, cacheInstance, err := instantiateCache(logger.WithField("subsystem", "cache")) if err != nil { - logger.Fatal(err) + logger.WithError(err).Fatal("Could not instantiate cache") } cli := poedit.NewClient(viper.GetString(confAPIToken), http.DefaultClient) - svc := project.NewService(cli, cacheInstance, viper.GetDuration(confCacheRenewalThreshold), logrus.NewEntry(logger)) + s3Config := &storage.S3StorageConfig{ + Region: viper.GetString(confAWSRegion), + Bucket: viper.GetString(confAWSBucket), + } - server, err := rest.NewServer(logrus.NewEntry(logger), svc, viper.GetBool(confPrometheusEnabled)) + s3Client, err := storage.NewS3Client(context.Background(), *s3Config) if err != nil { - logger.Fatal(err) + logger.WithError(err).Fatal("Could not instantiate S3 client") } - port := viper.GetInt(confServerPort) + storageService := storage.NewService(context.Background(), s3Client) + + svc := project.NewService(cli, storageService, cacheInstance, viper.GetDuration(confCacheRenewalThreshold), logrus.NewEntry(logger)) - logger.Infof("Server listening at :%d", port) + publicServer, err := publicRest.NewServer(logrus.NewEntry(logger), svc) + if err != nil { + logger.WithError(err).Fatal("Could not instantiate public server") + } + + publicPort := viper.GetInt(confServerPortPublic) + + logger.Infof("Public server listening at :%d", publicPort) go func() { - if err := server.Start(port); err != nil && !errors.Is(err, http.ErrServerClosed) { - logger.Fatal("shutting down server") + if err := publicServer.Start(publicPort); err != nil && !errors.Is(err, http.ErrServerClosed) { + logger.Fatal("shutting down public server") + } + }() + + privateServer, err := privateRest.NewServer(logrus.NewEntry(logger), svc, viper.GetBool(confPrometheusEnabled)) + if err != nil { + logger.WithError(err).Fatal("Could not instantiate private server") + } + + privatePort := viper.GetInt(confServerPortPrivate) + + logger.Infof("Private server listening at :%d", privatePort) + go func() { + if err := privateServer.Start(privatePort); err != nil && !errors.Is(err, http.ErrServerClosed) { + logger.Fatal("shutting down private server") } }() @@ -108,7 +140,10 @@ by caching exports from poeditor`, <-quit ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration(confServerGrace)) defer cancel() - if err := server.Shutdown(ctx); err != nil { + if err := privateServer.Shutdown(ctx); err != nil { + logger.Fatal(err) + } + if err := publicServer.Shutdown(ctx); err != nil { logger.Fatal(err) } }, @@ -121,7 +156,8 @@ func init() { cDir = "/tmp" } - viper.SetDefault(confServerPort, 80) + viper.SetDefault(confServerPortPublic, 8000) + viper.SetDefault(confServerPortPrivate, 8001) viper.SetDefault(confServerGrace, time.Second*10) viper.SetDefault(confLogLevel, "info") @@ -138,6 +174,7 @@ func init() { viper.SetDefault(confPrometheusEnabled, true) viper.SetDefault(confPrometheusPort, 9090) viper.SetDefault(confPrometheusPath, "/metrics") + viper.SetDefault(confAWSRegion, "eu-west-1") rootCmd.AddCommand(serveCmd) } @@ -161,6 +198,12 @@ func instantiateLogger() *logrus.Logger { logger.Warnf("Did not understand log format '%s'. Defaulting to json format", viper.GetString(confLogFormat)) logger.SetFormatter(&logrus.JSONFormatter{}) } + + logger.WithFields(logrus.Fields{ + "level": viper.GetString(confLogLevel), + "format": viper.GetString(confLogFormat), + }).Info("Logger initialized") + return logger } diff --git a/dashboard/.editorconfig b/dashboard/.editorconfig new file mode 100644 index 0000000..f173d64 --- /dev/null +++ b/dashboard/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +max_line_length = 80 +trim_trailing_whitespace = true + +[*.md] +max_line_length = 0 +trim_trailing_whitespace = false + +[COMMIT_EDITMSG] +max_line_length = 0 diff --git a/dashboard/.eslintrc.cjs b/dashboard/.eslintrc.cjs new file mode 100644 index 0000000..d6c9537 --- /dev/null +++ b/dashboard/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/dashboard/.gitignore b/dashboard/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/dashboard/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/dashboard/README.md b/dashboard/README.md new file mode 100644 index 0000000..f54e4d1 --- /dev/null +++ b/dashboard/README.md @@ -0,0 +1,29 @@ +# Parrot versioning dashboard + +Visualized dashboard that was developed in order to have overview of our translated projects in one table. We can choose and open any project and see available translation versions, add new one or delete some. + +## Available Scripts + +In the project directory, you can run: + +### `pnpm start` + +Runs the app in development mode. +Open [http://localhost:5174](http://localhost:5174) to view it in the browser. + +The page will reload if you make edits, and you will see any lint errors in the console. + +### `pnpm start:mocked` + +Runs the app like `pnpm start`, but it sets the `VITE_MOCKED` variable to true to run mocked Axios requests. + +### `pnpm lint` + +Runs ESLint on the project with the rules specified in .eslintrc.json. The linter should be satisfied at all times! + +### `pnpm build` + +Builds the app for production to the `build` folder. +It optimizes the build for the best performance. The build is minified, and the filenames include hashes. Your app is ready to be deployed. + +For more information on building with Vite, refer to the [Vite documentation](https://vitejs.dev/guide/build.html). diff --git a/dashboard/index.html b/dashboard/index.html new file mode 100644 index 0000000..4df099b --- /dev/null +++ b/dashboard/index.html @@ -0,0 +1,13 @@ + + + + + + + Parrot + + +
+ + + diff --git a/dashboard/package.json b/dashboard/package.json new file mode 100644 index 0000000..4b08570 --- /dev/null +++ b/dashboard/package.json @@ -0,0 +1,46 @@ +{ + "name": "dashboard", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "start": "vite", + "start:mocked": "VITE_MOCKED=true vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0" + }, + "dependencies": { + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", + "@fontsource/inter": "^5.0.18", + "@mui/icons-material": "^5.15.20", + "@mui/joy": "5.0.0-beta.36", + "@mui/material": "^5.15.20", + "@tanstack/react-query": "^5.45.1", + "axios": "^1.7.2", + "http-status-codes": "^2.3.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router": "^6.24.0", + "react-router-dom": "^6.24.0" + }, + "devDependencies": { + "@tanstack/eslint-plugin-query": "^5.43.1", + "@types/node": "^20.14.10", + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react": "^4.2.1", + "axios-logger": "^2.8.1", + "axios-mock-adapter": "^1.22.0", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "jsdom": "^24.1.0", + "prettier": "^3.3.2", + "sass": "^1.77.8", + "typescript": "^5.2.2", + "vite": "^5.2.0" + } +} diff --git a/dashboard/pnpm-lock.yaml b/dashboard/pnpm-lock.yaml new file mode 100644 index 0000000..d3dfdf7 --- /dev/null +++ b/dashboard/pnpm-lock.yaml @@ -0,0 +1,3523 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@emotion/react': + specifier: ^11.11.4 + version: 11.11.4(@types/react@18.3.3)(react@18.3.1) + '@emotion/styled': + specifier: ^11.11.5 + version: 11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + '@fontsource/inter': + specifier: ^5.0.18 + version: 5.0.18 + '@mui/icons-material': + specifier: ^5.15.20 + version: 5.15.20(@mui/material@5.15.20(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + '@mui/joy': + specifier: 5.0.0-beta.36 + version: 5.0.0-beta.36(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/material': + specifier: ^5.15.20 + version: 5.15.20(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/react-query': + specifier: ^5.45.1 + version: 5.45.1(react@18.3.1) + axios: + specifier: ^1.7.2 + version: 1.7.2 + http-status-codes: + specifier: ^2.3.0 + version: 2.3.0 + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + react-router: + specifier: ^6.24.0 + version: 6.24.0(react@18.3.1) + react-router-dom: + specifier: ^6.24.0 + version: 6.24.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + devDependencies: + '@tanstack/eslint-plugin-query': + specifier: ^5.43.1 + version: 5.43.1(eslint@8.57.0)(typescript@5.4.5) + '@types/node': + specifier: ^20.14.10 + version: 20.14.10 + '@types/react': + specifier: ^18.2.66 + version: 18.3.3 + '@types/react-dom': + specifier: ^18.2.22 + version: 18.3.0 + '@typescript-eslint/eslint-plugin': + specifier: ^7.2.0 + version: 7.12.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': + specifier: ^7.2.0 + version: 7.12.0(eslint@8.57.0)(typescript@5.4.5) + '@vitejs/plugin-react': + specifier: ^4.2.1 + version: 4.3.1(vite@5.2.13(@types/node@20.14.10)(sass@1.77.8)) + axios-logger: + specifier: ^2.8.1 + version: 2.8.1 + axios-mock-adapter: + specifier: ^1.22.0 + version: 1.22.0(axios@1.7.2) + eslint: + specifier: ^8.57.0 + version: 8.57.0 + eslint-plugin-react-hooks: + specifier: ^4.6.0 + version: 4.6.2(eslint@8.57.0) + eslint-plugin-react-refresh: + specifier: ^0.4.6 + version: 0.4.7(eslint@8.57.0) + jsdom: + specifier: ^24.1.0 + version: 24.1.0 + prettier: + specifier: ^3.3.2 + version: 3.3.2 + sass: + specifier: ^1.77.8 + version: 1.77.8 + typescript: + specifier: ^5.2.2 + version: 5.4.5 + vite: + specifier: ^5.2.0 + version: 5.2.13(@types/node@20.14.10)(sass@1.77.8) + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.24.7': + resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.24.7': + resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.24.7': + resolution: {integrity: sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.24.7': + resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.24.7': + resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-environment-visitor@7.24.7': + resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-function-name@7.24.7': + resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-hoist-variables@7.24.7': + resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.24.7': + resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.24.7': + resolution: {integrity: sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.24.7': + resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-simple-access@7.24.7': + resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-split-export-declaration@7.24.7': + resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.24.7': + resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.24.7': + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.24.7': + resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.24.7': + resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==} + engines: {node: '>=6.9.0'} + + '@babel/highlight@7.24.7': + resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.24.7': + resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.24.7': + resolution: {integrity: sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.24.7': + resolution: {integrity: sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.24.7': + resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.24.7': + resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.24.7': + resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.24.7': + resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} + engines: {node: '>=6.9.0'} + + '@emotion/babel-plugin@11.11.0': + resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==} + + '@emotion/cache@11.11.0': + resolution: {integrity: sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==} + + '@emotion/hash@0.9.1': + resolution: {integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==} + + '@emotion/is-prop-valid@1.2.2': + resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==} + + '@emotion/memoize@0.8.1': + resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} + + '@emotion/react@11.11.4': + resolution: {integrity: sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==} + peerDependencies: + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/serialize@1.1.4': + resolution: {integrity: sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==} + + '@emotion/sheet@1.2.2': + resolution: {integrity: sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==} + + '@emotion/styled@11.11.5': + resolution: {integrity: sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==} + peerDependencies: + '@emotion/react': ^11.0.0-rc.0 + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/unitless@0.8.1': + resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} + + '@emotion/use-insertion-effect-with-fallbacks@1.0.1': + resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==} + peerDependencies: + react: '>=16.8.0' + + '@emotion/utils@1.2.1': + resolution: {integrity: sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==} + + '@emotion/weak-memoize@0.3.1': + resolution: {integrity: sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==} + + '@esbuild/aix-ppc64@0.20.2': + resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.20.2': + resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.20.2': + resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.20.2': + resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.20.2': + resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.20.2': + resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.20.2': + resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.20.2': + resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.20.2': + resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.20.2': + resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.20.2': + resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.20.2': + resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.20.2': + resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.20.2': + resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.20.2': + resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.20.2': + resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.20.2': + resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.20.2': + resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.20.2': + resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.20.2': + resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.20.2': + resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.20.2': + resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.20.2': + resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.4.0': + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.10.1': + resolution: {integrity: sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.0': + resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@floating-ui/core@1.6.2': + resolution: {integrity: sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg==} + + '@floating-ui/dom@1.6.5': + resolution: {integrity: sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw==} + + '@floating-ui/react-dom@2.1.0': + resolution: {integrity: sha512-lNzj5EQmEKn5FFKc04+zasr09h/uX8RtJRNj5gUXsSQIXHVWTVh+hVAg1vOMCexkX8EgvemMvIFpQfkosnVNyA==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.2': + resolution: {integrity: sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==} + + '@fontsource/inter@5.0.18': + resolution: {integrity: sha512-YCsoYPTcs713sI7tLtxaPrIhXAXvEetGg5Ry02ivA8qUOb3fQHojbK/X9HLD5OOKvFUNR2Ynkwb1kR1hVKQHpw==} + + '@humanwhocodes/config-array@0.11.14': + resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} + engines: {node: '>=10.10.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.4.15': + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@mui/base@5.0.0-beta.40': + resolution: {integrity: sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/base@5.0.0-beta.42': + resolution: {integrity: sha512-fWRiUJVCHCPF+mxd5drn08bY2qRw3jj5f1SSQdUXmaJ/yKpk23ys8MgLO2KGVTRtbks/+ctRfgffGPbXifj0Ug==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/core-downloads-tracker@5.15.20': + resolution: {integrity: sha512-DoL2ppgldL16utL8nNyj/P12f8mCNdx/Hb/AJnX9rLY4b52hCMIx1kH83pbXQ6uMy6n54M3StmEbvSGoj2OFuA==} + + '@mui/core-downloads-tracker@6.0.0-dev.240424162023-9968b4889d': + resolution: {integrity: sha512-doh3M3U7HUGSBIWGe1yvesSbfDguMRjP0N09ogWSBM2hovXAlgULhMgcRTepAZLLwfRxFII0bCohq6B9NqoKuw==} + + '@mui/icons-material@5.15.20': + resolution: {integrity: sha512-oGcKmCuHaYbAAoLN67WKSXtHmEgyWcJToT1uRtmPyxMj9N5uqwc/mRtEnst4Wj/eGr+zYH2FiZQ79v9k7kSk1Q==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@mui/material': ^5.0.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/joy@5.0.0-beta.36': + resolution: {integrity: sha512-fFW8jqA/6JcnjjSUgiFJ17bbvMxwFU+daT5Ohpi1qYrDub0XYj1+8UYDUgGCLsv8XNe50AkbnqidtAlMpq7Glg==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + + '@mui/material@5.15.20': + resolution: {integrity: sha512-tVq3l4qoXx/NxUgIx/x3lZiPn/5xDbdTE8VrLczNpfblLYZzlrbxA7kb9mI8NoBF6+w9WE9IrxWnKK5KlPI2bg==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + + '@mui/private-theming@5.15.20': + resolution: {integrity: sha512-BK8F94AIqSrnaPYXf2KAOjGZJgWfvqAVQ2gVR3EryvQFtuBnG6RwodxrCvd3B48VuMy6Wsk897+lQMUxJyk+6g==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/private-theming@6.0.0-dev.20240529-082515-213b5e33ab': + resolution: {integrity: sha512-5AL+3TUSWaW2E9g+wIaD8jXHxFzqDInhNa3kNWJk2ZEqN2yG5msJLFr2BMp5/ev/x6V3AzTIIktkAV6qnlhOig==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/styled-engine@5.15.14': + resolution: {integrity: sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.4.1 + '@emotion/styled': ^11.3.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + + '@mui/styled-engine@6.0.0-dev.20240529-082515-213b5e33ab': + resolution: {integrity: sha512-+vpJBmaOPwp5fTjcin/MZ5umeqYAJD4RXxj0Ey+5KBp+j+7aXQBW5ZgehIZtM2eCg/VfUIfiVsB+i3SMFrfdFw==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.4.1 + '@emotion/styled': ^11.3.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + + '@mui/system@5.15.20': + resolution: {integrity: sha512-LoMq4IlAAhxzL2VNUDBTQxAb4chnBe8JvRINVNDiMtHE2PiPOoHlhOPutSxEbaL5mkECPVWSv6p8JEV+uykwIA==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + + '@mui/system@6.0.0-dev.240424162023-9968b4889d': + resolution: {integrity: sha512-Y3yCFUHN1xMK62hJJBqzZb1YQvHNaHc7JUX01eU6QTPojtIbGMF2jCOP/EQw77/byahNbxeLoAIQx10F0IR3Rw==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + + '@mui/types@7.2.14': + resolution: {integrity: sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/utils@5.15.20': + resolution: {integrity: sha512-mAbYx0sovrnpAu1zHc3MDIhPqL8RPVC5W5xcO1b7PiSCJPtckIZmBkp8hefamAvUiAV8gpfMOM6Zb+eSisbI2A==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/utils@6.0.0-dev.20240529-082515-213b5e33ab': + resolution: {integrity: sha512-jyNcB0drDhYcoq5MHNTiEc63GfVE1GZK+CVUd8tlLzk5q631RPYJ5jONSOszLiUOXBmI8Uu1SBJUwrG3j2YG2A==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@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'} + + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + + '@remix-run/router@1.17.0': + resolution: {integrity: sha512-2D6XaHEVvkCn682XBnipbJjgZUU7xjLtA4dGJRBVUKpEaDYOZMENZoZjAOSb7qirxt5RupjzZxz4fK2FO+EFPw==} + engines: {node: '>=14.0.0'} + + '@rollup/rollup-android-arm-eabi@4.18.0': + resolution: {integrity: sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.18.0': + resolution: {integrity: sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.18.0': + resolution: {integrity: sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.18.0': + resolution: {integrity: sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-linux-arm-gnueabihf@4.18.0': + resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.18.0': + resolution: {integrity: sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.18.0': + resolution: {integrity: sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.18.0': + resolution: {integrity: sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': + resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.18.0': + resolution: {integrity: sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.18.0': + resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.18.0': + resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.18.0': + resolution: {integrity: sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.18.0': + resolution: {integrity: sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.18.0': + resolution: {integrity: sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.18.0': + resolution: {integrity: sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==} + cpu: [x64] + os: [win32] + + '@tanstack/eslint-plugin-query@5.43.1': + resolution: {integrity: sha512-5WZmkny6u/lSjzUpgnvn+vnA1KtIa7umNZYLqCg9TZK0lmz9SRP6Hnui1PI279eisDy/O+1yD0MfEHTJWlQGVw==} + peerDependencies: + eslint: ^8 || ^9 + + '@tanstack/query-core@5.45.0': + resolution: {integrity: sha512-RVfIZQmFUTdjhSAAblvueimfngYyfN6HlwaJUPK71PKd7yi43Vs1S/rdimmZedPWX/WGppcq/U1HOj7O7FwYxw==} + + '@tanstack/react-query@5.45.1': + resolution: {integrity: sha512-mYYfJujKg2kxmkRRjA6nn4YKG3ITsKuH22f1kteJ5IuVQqgKUgbaSQfYwVP0gBS05mhwxO03HVpD0t7BMN7WOA==} + peerDependencies: + react: ^18.0.0 + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.6': + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + + '@types/estree@1.0.5': + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + + '@types/node@20.14.10': + resolution: {integrity: sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==} + + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + + '@types/prop-types@15.7.12': + resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} + + '@types/react-dom@18.3.0': + resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + + '@types/react-transition-group@4.4.10': + resolution: {integrity: sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==} + + '@types/react@18.3.3': + resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} + + '@typescript-eslint/eslint-plugin@7.12.0': + resolution: {integrity: sha512-7F91fcbuDf/d3S8o21+r3ZncGIke/+eWk0EpO21LXhDfLahriZF9CGj4fbAetEjlaBdjdSm9a6VeXbpbT6Z40Q==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@7.12.0': + resolution: {integrity: sha512-dm/J2UDY3oV3TKius2OUZIFHsomQmpHtsV0FTh1WO8EKgHLQ1QCADUqscPgTpU+ih1e21FQSRjXckHn3txn6kQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@7.12.0': + resolution: {integrity: sha512-itF1pTnN6F3unPak+kutH9raIkL3lhH1YRPGgt7QQOh43DQKVJXmWkpb+vpc/TiDHs6RSd9CTbDsc/Y+Ygq7kg==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/scope-manager@8.0.0-alpha.28': + resolution: {integrity: sha512-Iq8QFmJ2DH2tx7jfOraMZM1Y1axRfWh4t29JXRgbzvgiDQ2uHRHcaXqTulqsZXzJ0+vERNvNkOIPcQYGsNeGVQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@7.12.0': + resolution: {integrity: sha512-lib96tyRtMhLxwauDWUp/uW3FMhLA6D0rJ8T7HmH7x23Gk1Gwwu8UZ94NMXBvOELn6flSPiBrCKlehkiXyaqwA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@7.12.0': + resolution: {integrity: sha512-o+0Te6eWp2ppKY3mLCU+YA9pVJxhUJE15FV7kxuD9jgwIAa+w/ycGJBMrYDTpVGUM/tgpa9SeMOugSabWFq7bg==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/types@8.0.0-alpha.28': + resolution: {integrity: sha512-HYg+e0EWVShx0FEX0MAjDinYLmd+wD6nGMpbaddB1iACYwqaJFbf7vw0l+hdLTJvQC6UY8ndRkaEsL68QEoIZQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@7.12.0': + resolution: {integrity: sha512-5bwqLsWBULv1h6pn7cMW5dXX/Y2amRqLaKqsASVwbBHMZSnHqE/HN4vT4fE0aFsiwxYvr98kqOWh1a8ZKXalCQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/typescript-estree@8.0.0-alpha.28': + resolution: {integrity: sha512-I/5ODd4XJ+TO0XrKwDaB4tVGVi6kz2LAlN3WPd7mZVVtW21HHByCILRhOF9RbC69gJQ/TGHFpWCmAcsq2RZisg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@7.12.0': + resolution: {integrity: sha512-Y6hhwxwDx41HNpjuYswYp6gDbkiZ8Hin9Bf5aJQn1bpTs3afYY4GX+MPYxma8jtoIV2GRwTM/UJm/2uGCVv+DQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + + '@typescript-eslint/utils@8.0.0-alpha.28': + resolution: {integrity: sha512-PnIz94+nbyjJisMI+KZqXMfw0wfIHvbyh0MGEx2M314wqm6SUWcxB5I8zduGQgJbRB0YFnboPS+MeSlBYPWrBQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + + '@typescript-eslint/visitor-keys@7.12.0': + resolution: {integrity: sha512-uZk7DevrQLL3vSnfFl5bj4sL75qC9D6EdjemIdbtkuUmIheWpuiiylSY01JxJE7+zGrOWDZrp1WxOuDntvKrHQ==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/visitor-keys@8.0.0-alpha.28': + resolution: {integrity: sha512-+ewAOeKDycydKMlnfmW8zAURTA8PR5Csyvxy6PJt4XRYjoquode9/eWaMt9Sp4Rz1FGMSVU9KxDRR83ASH/xkQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@ungap/structured-clone@1.2.0': + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + + '@vitejs/plugin-react@4.3.1': + resolution: {integrity: sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.11.3: + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.1: + resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} + engines: {node: '>= 14'} + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios-logger@2.8.1: + resolution: {integrity: sha512-Bbl7XRR/Rkxg2Owv/kRgAZ/0qf8kMPLc08LtiUcGCWV5RmoI7vHr+eee6SUc8jRi2nE5KWShziCVh35C1SBEaw==} + + axios-mock-adapter@1.22.0: + resolution: {integrity: sha512-dmI0KbkyAhntUR05YY96qg2H6gg0XMl2+qTW0xmYg6Up+BFBAJYRLROMXRdDEL06/Wqwa0TJThAYvFtSFdRCZw==} + peerDependencies: + axios: '>= 0.17.0' + + axios@1.7.2: + resolution: {integrity: sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==} + + babel-plugin-macros@3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.23.1: + resolution: {integrity: sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001632: + resolution: {integrity: sha512-udx3o7yHJfUxMLkGohMlVHCvFvWmirKh9JAH/d7WOLPetlH+LTL5cocMZ0t7oZx/mdlOWXti97xLZWc8uURRHg==} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + cssstyle@4.0.1: + resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==} + engines: {node: '>=18'} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + + dateformat@3.0.3: + resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} + + debug@4.3.5: + resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + + electron-to-chromium@1.4.796: + resolution: {integrity: sha512-NglN/xprcM+SHD2XCli4oC6bWe6kHoytcyLKCWXmRL854F0qhPhaYgUswUsglnPxYaNQIg2uMY4BvaomIf3kLA==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + esbuild@0.20.2: + resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@4.6.2: + resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + + eslint-plugin-react-refresh@0.4.7: + resolution: {integrity: sha512-yrj+KInFmwuQS2UQcg1SF83ha1tuHC1jMQbRNyuWtlEzzKRDgAl7L4Yp4NlDUZTZNlWvHEzOtJhMi40R7JxcSw==} + peerDependencies: + eslint: '>=7' + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.0: + resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + 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.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-root@1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + + follow-redirects@1.15.6: + resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + 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'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + http-status-codes@2.3.0: + resolution: {integrity: sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==} + + https-proxy-agent@7.0.5: + resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} + engines: {node: '>= 14'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + engines: {node: '>= 4'} + + immutable@4.3.7: + resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-buffer@2.0.5: + resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} + engines: {node: '>=4'} + + is-core-module@2.14.0: + resolution: {integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsdom@24.1.0: + resolution: {integrity: sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + 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-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.7: + resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + engines: {node: '>=8.6'} + + 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'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.4: + resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.14: + resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + nwsapi@2.2.12: + resolution: {integrity: sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse5@7.1.2: + resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + + 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-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.3.2: + resolution: {integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==} + engines: {node: '>=14'} + hasBin: true + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + + react-router-dom@6.24.0: + resolution: {integrity: sha512-960sKuau6/yEwS8e+NVEidYQb1hNjAYM327gjEyXlc6r3Skf2vtwuJ2l7lssdegD2YjoKG5l8MsVyeTDlVeY8g==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + react-router@6.24.0: + resolution: {integrity: sha512-sQrgJ5bXk7vbcC4BxQxeNa5UmboFm35we1AFK0VvQaz9g0LzxEIuLOhHIoZ8rnu9BO21ishGeL9no1WB76W/eg==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + + react-transition-group@4.4.5: + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup@4.18.0: + resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + rrweb-cssom@0.6.0: + resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} + + rrweb-cssom@0.7.1: + resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sass@1.77.8: + resolution: {integrity: sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + stylis@4.2.0: + resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + 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'} + + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + + tr46@5.0.0: + resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} + engines: {node: '>=18'} + + ts-api-utils@1.3.0: + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + typescript@5.4.5: + resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + + update-browserslist-db@1.0.16: + resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + + vite@5.2.13: + resolution: {integrity: sha512-SSq1noJfY9pR3I1TUENL3rQYDQCFqgD+lM6fTRAM8Nv6Lsg5hDLaXkjETVeBt+7vZBCMoibD+6IWnT2mJ+Zb/A==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.0.0: + resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==} + engines: {node: '>=18'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + 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@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@babel/code-frame@7.24.7': + dependencies: + '@babel/highlight': 7.24.7 + picocolors: 1.0.1 + + '@babel/compat-data@7.24.7': {} + + '@babel/core@7.24.7': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.24.7 + '@babel/helper-compilation-targets': 7.24.7 + '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) + '@babel/helpers': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/template': 7.24.7 + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + convert-source-map: 2.0.0 + debug: 4.3.5 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.24.7': + dependencies: + '@babel/types': 7.24.7 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 2.5.2 + + '@babel/helper-compilation-targets@7.24.7': + dependencies: + '@babel/compat-data': 7.24.7 + '@babel/helper-validator-option': 7.24.7 + browserslist: 4.23.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-environment-visitor@7.24.7': + dependencies: + '@babel/types': 7.24.7 + + '@babel/helper-function-name@7.24.7': + dependencies: + '@babel/template': 7.24.7 + '@babel/types': 7.24.7 + + '@babel/helper-hoist-variables@7.24.7': + dependencies: + '@babel/types': 7.24.7 + + '@babel/helper-module-imports@7.24.7': + dependencies: + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-module-imports': 7.24.7 + '@babel/helper-simple-access': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.24.7': {} + + '@babel/helper-simple-access@7.24.7': + dependencies: + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-split-export-declaration@7.24.7': + dependencies: + '@babel/types': 7.24.7 + + '@babel/helper-string-parser@7.24.7': {} + + '@babel/helper-validator-identifier@7.24.7': {} + + '@babel/helper-validator-option@7.24.7': {} + + '@babel/helpers@7.24.7': + dependencies: + '@babel/template': 7.24.7 + '@babel/types': 7.24.7 + + '@babel/highlight@7.24.7': + dependencies: + '@babel/helper-validator-identifier': 7.24.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.0.1 + + '@babel/parser@7.24.7': + dependencies: + '@babel/types': 7.24.7 + + '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + + '@babel/runtime@7.24.7': + dependencies: + regenerator-runtime: 0.14.1 + + '@babel/template@7.24.7': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + + '@babel/traverse@7.24.7': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.24.7 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-function-name': 7.24.7 + '@babel/helper-hoist-variables': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + debug: 4.3.5 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.24.7': + dependencies: + '@babel/helper-string-parser': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 + + '@emotion/babel-plugin@11.11.0': + dependencies: + '@babel/helper-module-imports': 7.24.7 + '@babel/runtime': 7.24.7 + '@emotion/hash': 0.9.1 + '@emotion/memoize': 0.8.1 + '@emotion/serialize': 1.1.4 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.9.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.2.0 + transitivePeerDependencies: + - supports-color + + '@emotion/cache@11.11.0': + dependencies: + '@emotion/memoize': 0.8.1 + '@emotion/sheet': 1.2.2 + '@emotion/utils': 1.2.1 + '@emotion/weak-memoize': 0.3.1 + stylis: 4.2.0 + + '@emotion/hash@0.9.1': {} + + '@emotion/is-prop-valid@1.2.2': + dependencies: + '@emotion/memoize': 0.8.1 + + '@emotion/memoize@0.8.1': {} + + '@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@emotion/babel-plugin': 11.11.0 + '@emotion/cache': 11.11.0 + '@emotion/serialize': 1.1.4 + '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.3.1) + '@emotion/utils': 1.2.1 + '@emotion/weak-memoize': 0.3.1 + hoist-non-react-statics: 3.3.2 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + transitivePeerDependencies: + - supports-color + + '@emotion/serialize@1.1.4': + dependencies: + '@emotion/hash': 0.9.1 + '@emotion/memoize': 0.8.1 + '@emotion/unitless': 0.8.1 + '@emotion/utils': 1.2.1 + csstype: 3.1.3 + + '@emotion/sheet@1.2.2': {} + + '@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@emotion/babel-plugin': 11.11.0 + '@emotion/is-prop-valid': 1.2.2 + '@emotion/react': 11.11.4(@types/react@18.3.3)(react@18.3.1) + '@emotion/serialize': 1.1.4 + '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.3.1) + '@emotion/utils': 1.2.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + transitivePeerDependencies: + - supports-color + + '@emotion/unitless@0.8.1': {} + + '@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@emotion/utils@1.2.1': {} + + '@emotion/weak-memoize@0.3.1': {} + + '@esbuild/aix-ppc64@0.20.2': + optional: true + + '@esbuild/android-arm64@0.20.2': + optional: true + + '@esbuild/android-arm@0.20.2': + optional: true + + '@esbuild/android-x64@0.20.2': + optional: true + + '@esbuild/darwin-arm64@0.20.2': + optional: true + + '@esbuild/darwin-x64@0.20.2': + optional: true + + '@esbuild/freebsd-arm64@0.20.2': + optional: true + + '@esbuild/freebsd-x64@0.20.2': + optional: true + + '@esbuild/linux-arm64@0.20.2': + optional: true + + '@esbuild/linux-arm@0.20.2': + optional: true + + '@esbuild/linux-ia32@0.20.2': + optional: true + + '@esbuild/linux-loong64@0.20.2': + optional: true + + '@esbuild/linux-mips64el@0.20.2': + optional: true + + '@esbuild/linux-ppc64@0.20.2': + optional: true + + '@esbuild/linux-riscv64@0.20.2': + optional: true + + '@esbuild/linux-s390x@0.20.2': + optional: true + + '@esbuild/linux-x64@0.20.2': + optional: true + + '@esbuild/netbsd-x64@0.20.2': + optional: true + + '@esbuild/openbsd-x64@0.20.2': + optional: true + + '@esbuild/sunos-x64@0.20.2': + optional: true + + '@esbuild/win32-arm64@0.20.2': + optional: true + + '@esbuild/win32-ia32@0.20.2': + optional: true + + '@esbuild/win32-x64@0.20.2': + optional: true + + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': + dependencies: + eslint: 8.57.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.10.1': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.3.5 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.0': {} + + '@floating-ui/core@1.6.2': + dependencies: + '@floating-ui/utils': 0.2.2 + + '@floating-ui/dom@1.6.5': + dependencies: + '@floating-ui/core': 1.6.2 + '@floating-ui/utils': 0.2.2 + + '@floating-ui/react-dom@2.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/dom': 1.6.5 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@floating-ui/utils@0.2.2': {} + + '@fontsource/inter@5.0.18': {} + + '@humanwhocodes/config-array@0.11.14': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.3.5 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.4.15': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + + '@mui/base@5.0.0-beta.40(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@floating-ui/react-dom': 2.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/types': 7.2.14(@types/react@18.3.3) + '@mui/utils': 5.15.20(@types/react@18.3.3)(react@18.3.1) + '@popperjs/core': 2.11.8 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + + '@mui/base@5.0.0-beta.42(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@floating-ui/react-dom': 2.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/types': 7.2.14(@types/react@18.3.3) + '@mui/utils': 6.0.0-dev.20240529-082515-213b5e33ab(@types/react@18.3.3)(react@18.3.1) + '@popperjs/core': 2.11.8 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + + '@mui/core-downloads-tracker@5.15.20': {} + + '@mui/core-downloads-tracker@6.0.0-dev.240424162023-9968b4889d': {} + + '@mui/icons-material@5.15.20(@mui/material@5.15.20(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@mui/material': 5.15.20(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + + '@mui/joy@5.0.0-beta.36(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@mui/base': 5.0.0-beta.42(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/core-downloads-tracker': 6.0.0-dev.240424162023-9968b4889d + '@mui/system': 6.0.0-dev.240424162023-9968b4889d(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + '@mui/types': 7.2.14(@types/react@18.3.3) + '@mui/utils': 6.0.0-dev.20240529-082515-213b5e33ab(@types/react@18.3.3)(react@18.3.1) + clsx: 2.1.1 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@emotion/react': 11.11.4(@types/react@18.3.3)(react@18.3.1) + '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + '@types/react': 18.3.3 + + '@mui/material@5.15.20(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@mui/base': 5.0.0-beta.40(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/core-downloads-tracker': 5.15.20 + '@mui/system': 5.15.20(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + '@mui/types': 7.2.14(@types/react@18.3.3) + '@mui/utils': 5.15.20(@types/react@18.3.3)(react@18.3.1) + '@types/react-transition-group': 4.4.10 + clsx: 2.1.1 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 18.3.1 + react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + optionalDependencies: + '@emotion/react': 11.11.4(@types/react@18.3.3)(react@18.3.1) + '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + '@types/react': 18.3.3 + + '@mui/private-theming@5.15.20(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@mui/utils': 5.15.20(@types/react@18.3.3)(react@18.3.1) + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + + '@mui/private-theming@6.0.0-dev.20240529-082515-213b5e33ab(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@mui/utils': 6.0.0-dev.20240529-082515-213b5e33ab(@types/react@18.3.3)(react@18.3.1) + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + + '@mui/styled-engine@5.15.14(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@emotion/cache': 11.11.0 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@emotion/react': 11.11.4(@types/react@18.3.3)(react@18.3.1) + '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + + '@mui/styled-engine@6.0.0-dev.20240529-082515-213b5e33ab(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@emotion/cache': 11.11.0 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@emotion/react': 11.11.4(@types/react@18.3.3)(react@18.3.1) + '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + + '@mui/system@5.15.20(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@mui/private-theming': 5.15.20(@types/react@18.3.3)(react@18.3.1) + '@mui/styled-engine': 5.15.14(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(react@18.3.1) + '@mui/types': 7.2.14(@types/react@18.3.3) + '@mui/utils': 5.15.20(@types/react@18.3.3)(react@18.3.1) + clsx: 2.1.1 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@emotion/react': 11.11.4(@types/react@18.3.3)(react@18.3.1) + '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + '@types/react': 18.3.3 + + '@mui/system@6.0.0-dev.240424162023-9968b4889d(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@mui/private-theming': 6.0.0-dev.20240529-082515-213b5e33ab(@types/react@18.3.3)(react@18.3.1) + '@mui/styled-engine': 6.0.0-dev.20240529-082515-213b5e33ab(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@emotion/styled@11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1))(react@18.3.1) + '@mui/types': 7.2.14(@types/react@18.3.3) + '@mui/utils': 6.0.0-dev.20240529-082515-213b5e33ab(@types/react@18.3.3)(react@18.3.1) + clsx: 2.1.1 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@emotion/react': 11.11.4(@types/react@18.3.3)(react@18.3.1) + '@emotion/styled': 11.11.5(@emotion/react@11.11.4(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) + '@types/react': 18.3.3 + + '@mui/types@7.2.14(@types/react@18.3.3)': + optionalDependencies: + '@types/react': 18.3.3 + + '@mui/utils@5.15.20(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@types/prop-types': 15.7.12 + prop-types: 15.8.1 + react: 18.3.1 + react-is: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + + '@mui/utils@6.0.0-dev.20240529-082515-213b5e33ab(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.7 + '@types/prop-types': 15.7.12 + prop-types: 15.8.1 + react: 18.3.1 + react-is: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@popperjs/core@2.11.8': {} + + '@remix-run/router@1.17.0': {} + + '@rollup/rollup-android-arm-eabi@4.18.0': + optional: true + + '@rollup/rollup-android-arm64@4.18.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.18.0': + optional: true + + '@rollup/rollup-darwin-x64@4.18.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.18.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.18.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.18.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.18.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.18.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.18.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.18.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.18.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.18.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.18.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.18.0': + optional: true + + '@tanstack/eslint-plugin-query@5.43.1(eslint@8.57.0)(typescript@5.4.5)': + dependencies: + '@typescript-eslint/utils': 8.0.0-alpha.28(eslint@8.57.0)(typescript@5.4.5) + eslint: 8.57.0 + transitivePeerDependencies: + - supports-color + - typescript + + '@tanstack/query-core@5.45.0': {} + + '@tanstack/react-query@5.45.1(react@18.3.1)': + dependencies: + '@tanstack/query-core': 5.45.0 + react: 18.3.1 + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 + + '@types/babel__generator@7.6.8': + dependencies: + '@babel/types': 7.24.7 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + + '@types/babel__traverse@7.20.6': + dependencies: + '@babel/types': 7.24.7 + + '@types/estree@1.0.5': {} + + '@types/node@20.14.10': + dependencies: + undici-types: 5.26.5 + + '@types/parse-json@4.0.2': {} + + '@types/prop-types@15.7.12': {} + + '@types/react-dom@18.3.0': + dependencies: + '@types/react': 18.3.3 + + '@types/react-transition-group@4.4.10': + dependencies: + '@types/react': 18.3.3 + + '@types/react@18.3.3': + dependencies: + '@types/prop-types': 15.7.12 + csstype: 3.1.3 + + '@typescript-eslint/eslint-plugin@7.12.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': + dependencies: + '@eslint-community/regexpp': 4.10.1 + '@typescript-eslint/parser': 7.12.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/scope-manager': 7.12.0 + '@typescript-eslint/type-utils': 7.12.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/utils': 7.12.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.12.0 + eslint: 8.57.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5)': + dependencies: + '@typescript-eslint/scope-manager': 7.12.0 + '@typescript-eslint/types': 7.12.0 + '@typescript-eslint/typescript-estree': 7.12.0(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.12.0 + debug: 4.3.5 + eslint: 8.57.0 + optionalDependencies: + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@7.12.0': + dependencies: + '@typescript-eslint/types': 7.12.0 + '@typescript-eslint/visitor-keys': 7.12.0 + + '@typescript-eslint/scope-manager@8.0.0-alpha.28': + dependencies: + '@typescript-eslint/types': 8.0.0-alpha.28 + '@typescript-eslint/visitor-keys': 8.0.0-alpha.28 + + '@typescript-eslint/type-utils@7.12.0(eslint@8.57.0)(typescript@5.4.5)': + dependencies: + '@typescript-eslint/typescript-estree': 7.12.0(typescript@5.4.5) + '@typescript-eslint/utils': 7.12.0(eslint@8.57.0)(typescript@5.4.5) + debug: 4.3.5 + eslint: 8.57.0 + ts-api-utils: 1.3.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@7.12.0': {} + + '@typescript-eslint/types@8.0.0-alpha.28': {} + + '@typescript-eslint/typescript-estree@7.12.0(typescript@5.4.5)': + dependencies: + '@typescript-eslint/types': 7.12.0 + '@typescript-eslint/visitor-keys': 7.12.0 + debug: 4.3.5 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.4 + semver: 7.6.3 + ts-api-utils: 1.3.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@8.0.0-alpha.28(typescript@5.4.5)': + dependencies: + '@typescript-eslint/types': 8.0.0-alpha.28 + '@typescript-eslint/visitor-keys': 8.0.0-alpha.28 + debug: 4.3.5 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.4 + semver: 7.6.3 + ts-api-utils: 1.3.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@7.12.0(eslint@8.57.0)(typescript@5.4.5)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@typescript-eslint/scope-manager': 7.12.0 + '@typescript-eslint/types': 7.12.0 + '@typescript-eslint/typescript-estree': 7.12.0(typescript@5.4.5) + eslint: 8.57.0 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/utils@8.0.0-alpha.28(eslint@8.57.0)(typescript@5.4.5)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@typescript-eslint/scope-manager': 8.0.0-alpha.28 + '@typescript-eslint/types': 8.0.0-alpha.28 + '@typescript-eslint/typescript-estree': 8.0.0-alpha.28(typescript@5.4.5) + eslint: 8.57.0 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@7.12.0': + dependencies: + '@typescript-eslint/types': 7.12.0 + eslint-visitor-keys: 3.4.3 + + '@typescript-eslint/visitor-keys@8.0.0-alpha.28': + dependencies: + '@typescript-eslint/types': 8.0.0-alpha.28 + eslint-visitor-keys: 3.4.3 + + '@ungap/structured-clone@1.2.0': {} + + '@vitejs/plugin-react@4.3.1(vite@5.2.13(@types/node@20.14.10)(sass@1.77.8))': + dependencies: + '@babel/core': 7.24.7 + '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.24.7) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 5.2.13(@types/node@20.14.10)(sass@1.77.8) + transitivePeerDependencies: + - supports-color + + acorn-jsx@5.3.2(acorn@8.11.3): + dependencies: + acorn: 8.11.3 + + acorn@8.11.3: {} + + agent-base@7.1.1: + dependencies: + debug: 4.3.5 + transitivePeerDependencies: + - supports-color + + ajv@6.12.6: + 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 + + ansi-regex@5.0.1: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + argparse@2.0.1: {} + + array-union@2.1.0: {} + + asynckit@0.4.0: {} + + axios-logger@2.8.1: + dependencies: + chalk: 4.1.2 + dateformat: 3.0.3 + + axios-mock-adapter@1.22.0(axios@1.7.2): + dependencies: + axios: 1.7.2 + fast-deep-equal: 3.1.3 + is-buffer: 2.0.5 + + axios@1.7.2: + dependencies: + follow-redirects: 1.15.6 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + babel-plugin-macros@3.1.0: + dependencies: + '@babel/runtime': 7.24.7 + cosmiconfig: 7.1.0 + resolve: 1.22.8 + + balanced-match@1.0.2: {} + + binary-extensions@2.3.0: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.23.1: + dependencies: + caniuse-lite: 1.0.30001632 + electron-to-chromium: 1.4.796 + node-releases: 2.0.14 + update-browserslist-db: 1.0.16(browserslist@4.23.1) + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001632: {} + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + 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.3 + + clsx@2.1.1: {} + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + concat-map@0.0.1: {} + + convert-source-map@1.9.0: {} + + convert-source-map@2.0.0: {} + + cosmiconfig@7.1.0: + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.0 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssstyle@4.0.1: + dependencies: + rrweb-cssom: 0.6.0 + + csstype@3.1.3: {} + + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + + dateformat@3.0.3: {} + + debug@4.3.5: + dependencies: + ms: 2.1.2 + + decimal.js@10.4.3: {} + + deep-is@0.1.4: {} + + delayed-stream@1.0.0: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dom-helpers@5.2.1: + dependencies: + '@babel/runtime': 7.24.7 + csstype: 3.1.3 + + electron-to-chromium@1.4.796: {} + + entities@4.5.0: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + esbuild@0.20.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.20.2 + '@esbuild/android-arm': 0.20.2 + '@esbuild/android-arm64': 0.20.2 + '@esbuild/android-x64': 0.20.2 + '@esbuild/darwin-arm64': 0.20.2 + '@esbuild/darwin-x64': 0.20.2 + '@esbuild/freebsd-arm64': 0.20.2 + '@esbuild/freebsd-x64': 0.20.2 + '@esbuild/linux-arm': 0.20.2 + '@esbuild/linux-arm64': 0.20.2 + '@esbuild/linux-ia32': 0.20.2 + '@esbuild/linux-loong64': 0.20.2 + '@esbuild/linux-mips64el': 0.20.2 + '@esbuild/linux-ppc64': 0.20.2 + '@esbuild/linux-riscv64': 0.20.2 + '@esbuild/linux-s390x': 0.20.2 + '@esbuild/linux-x64': 0.20.2 + '@esbuild/netbsd-x64': 0.20.2 + '@esbuild/openbsd-x64': 0.20.2 + '@esbuild/sunos-x64': 0.20.2 + '@esbuild/win32-arm64': 0.20.2 + '@esbuild/win32-ia32': 0.20.2 + '@esbuild/win32-x64': 0.20.2 + + escalade@3.1.2: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@4.6.2(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + + eslint-plugin-react-refresh@0.4.7(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.0: + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@eslint-community/regexpp': 4.10.1 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.0 + '@humanwhocodes/config-array': 0.11.14 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.5 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.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.24.0 + graphemer: 1.4.0 + ignore: 5.3.1 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + 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.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.11.3 + acorn-jsx: 5.3.2(acorn@8.11.3) + eslint-visitor-keys: 3.4.3 + + esquery@1.5.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.2: + 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.7 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-root@1.1.0: {} + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.3.1: {} + + follow-redirects@1.15.6: {} + + form-data@4.0.0: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@7.2.3: + 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 + + globals@11.12.0: {} + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.1 + merge2: 1.4.1 + slash: 3.0.0 + + graphemer@1.4.0: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.1 + debug: 4.3.5 + transitivePeerDependencies: + - supports-color + + http-status-codes@2.3.0: {} + + https-proxy-agent@7.0.5: + dependencies: + agent-base: 7.1.1 + debug: 4.3.5 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.1: {} + + immutable@4.3.7: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + is-arrayish@0.2.1: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-buffer@2.0.5: {} + + is-core-module@2.14.0: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + is-potential-custom-element-name@1.0.1: {} + + isexe@2.0.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsdom@24.1.0: + dependencies: + cssstyle: 4.0.1 + data-urls: 5.0.0 + decimal.js: 10.4.3 + form-data: 4.0.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.5 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.12 + parse5: 7.1.2 + rrweb-cssom: 0.7.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.4 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + ws: 8.18.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jsesc@2.5.2: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lines-and-columns@1.2.4: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + merge2@1.4.1: {} + + micromatch@4.0.7: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.4: + dependencies: + brace-expansion: 2.0.1 + + ms@2.1.2: {} + + nanoid@3.3.7: {} + + natural-compare@1.4.0: {} + + node-releases@2.0.14: {} + + normalize-path@3.0.0: {} + + nwsapi@2.2.12: {} + + object-assign@4.1.1: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + optionator@0.9.4: + 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.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.24.7 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse5@7.1.2: + dependencies: + entities: 4.5.0 + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-type@4.0.0: {} + + picocolors@1.0.1: {} + + picomatch@2.3.1: {} + + postcss@8.4.38: + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.1 + source-map-js: 1.2.0 + + prelude-ls@1.2.1: {} + + prettier@3.3.2: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + proxy-from-env@1.1.0: {} + + psl@1.9.0: {} + + punycode@2.3.1: {} + + querystringify@2.2.0: {} + + queue-microtask@1.2.3: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-is@16.13.1: {} + + react-is@18.3.1: {} + + react-refresh@0.14.2: {} + + react-router-dom@6.24.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@remix-run/router': 1.17.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 6.24.0(react@18.3.1) + + react-router@6.24.0(react@18.3.1): + dependencies: + '@remix-run/router': 1.17.0 + react: 18.3.1 + + react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.24.7 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + regenerator-runtime@0.14.1: {} + + requires-port@1.0.0: {} + + resolve-from@4.0.0: {} + + resolve@1.22.8: + dependencies: + is-core-module: 2.14.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.0.4: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup@4.18.0: + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.18.0 + '@rollup/rollup-android-arm64': 4.18.0 + '@rollup/rollup-darwin-arm64': 4.18.0 + '@rollup/rollup-darwin-x64': 4.18.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.18.0 + '@rollup/rollup-linux-arm-musleabihf': 4.18.0 + '@rollup/rollup-linux-arm64-gnu': 4.18.0 + '@rollup/rollup-linux-arm64-musl': 4.18.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.18.0 + '@rollup/rollup-linux-riscv64-gnu': 4.18.0 + '@rollup/rollup-linux-s390x-gnu': 4.18.0 + '@rollup/rollup-linux-x64-gnu': 4.18.0 + '@rollup/rollup-linux-x64-musl': 4.18.0 + '@rollup/rollup-win32-arm64-msvc': 4.18.0 + '@rollup/rollup-win32-ia32-msvc': 4.18.0 + '@rollup/rollup-win32-x64-msvc': 4.18.0 + fsevents: 2.3.3 + + rrweb-cssom@0.6.0: {} + + rrweb-cssom@0.7.1: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safer-buffer@2.1.2: {} + + sass@1.77.8: + dependencies: + chokidar: 3.6.0 + immutable: 4.3.7 + source-map-js: 1.2.0 + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + semver@6.3.1: {} + + semver@7.6.3: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + slash@3.0.0: {} + + source-map-js@1.2.0: {} + + source-map@0.5.7: {} + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-json-comments@3.1.1: {} + + stylis@4.2.0: {} + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + symbol-tree@3.2.4: {} + + text-table@0.2.0: {} + + to-fast-properties@2.0.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tough-cookie@4.1.4: + dependencies: + psl: 1.9.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + + tr46@5.0.0: + dependencies: + punycode: 2.3.1 + + ts-api-utils@1.3.0(typescript@5.4.5): + dependencies: + typescript: 5.4.5 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.20.2: {} + + typescript@5.4.5: {} + + undici-types@5.26.5: {} + + universalify@0.2.0: {} + + update-browserslist-db@1.0.16(browserslist@4.23.1): + dependencies: + browserslist: 4.23.1 + escalade: 3.1.2 + picocolors: 1.0.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + + vite@5.2.13(@types/node@20.14.10)(sass@1.77.8): + dependencies: + esbuild: 0.20.2 + postcss: 8.4.38 + rollup: 4.18.0 + optionalDependencies: + '@types/node': 20.14.10 + fsevents: 2.3.3 + sass: 1.77.8 + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@7.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.0.0: + dependencies: + tr46: 5.0.0 + webidl-conversions: 7.0.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrappy@1.0.2: {} + + ws@8.18.0: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + yallist@3.1.1: {} + + yaml@1.10.2: {} + + yocto-queue@0.1.0: {} diff --git a/dashboard/public/parrot_logo.png b/dashboard/public/parrot_logo.png new file mode 100644 index 0000000..ecd865f Binary files /dev/null and b/dashboard/public/parrot_logo.png differ diff --git a/dashboard/src/api/client/axios.ts b/dashboard/src/api/client/axios.ts new file mode 100644 index 0000000..ce0399d --- /dev/null +++ b/dashboard/src/api/client/axios.ts @@ -0,0 +1,22 @@ +import axios from "axios"; +import AxiosMockAdapter from "axios-mock-adapter"; +import registerLoggerInterceptor from "../interceptors/logger"; + +const isMocked = import.meta.env.VITE_MOCKED === "true"; + +// Mock axios instance +const mockInstance = axios.create(); +registerLoggerInterceptor(mockInstance); + +export const mock = new AxiosMockAdapter(mockInstance, { + delayResponse: 200, +}); + +// Real axios instance +const realInstance = axios.create(); + +if (isMocked) { + console.debug("Using mocked axios client"); +} + +export default isMocked ? mockInstance : realInstance; diff --git a/dashboard/src/api/client/index.ts b/dashboard/src/api/client/index.ts new file mode 100644 index 0000000..be701f1 --- /dev/null +++ b/dashboard/src/api/client/index.ts @@ -0,0 +1,5 @@ +export * from "./axios"; +export * from "./query"; + +import client from "./axios"; +export default client; diff --git a/dashboard/src/api/client/query.tsx b/dashboard/src/api/client/query.tsx new file mode 100644 index 0000000..fb305bb --- /dev/null +++ b/dashboard/src/api/client/query.tsx @@ -0,0 +1,14 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { FC, ReactNode } from "react"; + +const queryClient = new QueryClient(); + +const ReactQueryClientProvider: FC<{ children: ReactNode }> = ({ + children, +}) => { + return ( + {children} + ); +}; + +export { ReactQueryClientProvider }; diff --git a/dashboard/src/api/hooks/useDeleteVersion.ts b/dashboard/src/api/hooks/useDeleteVersion.ts new file mode 100644 index 0000000..d105729 --- /dev/null +++ b/dashboard/src/api/hooks/useDeleteVersion.ts @@ -0,0 +1,29 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { GetVersionsResponse } from "../../interfaces/versions"; +import client from "../client"; +import "../mocks/useDeleteVersion.mock"; + +const deleteVersion = async (projectId: number, versionId: number) => { + const response = await client.delete( + `/api/v1/projects/${projectId}/versions/${versionId}`, + ); + + return response.data; +}; + +export const useDeleteVersion = (projectId: number, versionId: number) => { + const queryClient = useQueryClient(); + + return useMutation({ + onSuccess: async () => { + await queryClient.invalidateQueries( + { + queryKey: ["api", "projects", projectId, "versions"], + exact: true, + }, + { throwOnError: true }, + ); + }, + mutationFn: () => deleteVersion(projectId, versionId), + }); +}; diff --git a/dashboard/src/api/hooks/useGetPageParams.ts b/dashboard/src/api/hooks/useGetPageParams.ts new file mode 100644 index 0000000..a55c390 --- /dev/null +++ b/dashboard/src/api/hooks/useGetPageParams.ts @@ -0,0 +1,11 @@ +import { useParams } from "react-router-dom"; + +export const useGetPageParams = () => { + const { projectId } = useParams<{ + projectId: string; + }>(); + + return { + projectId: projectId ? parseInt(projectId) : undefined, + }; +}; diff --git a/dashboard/src/api/hooks/useGetProject.ts b/dashboard/src/api/hooks/useGetProject.ts new file mode 100644 index 0000000..87616b8 --- /dev/null +++ b/dashboard/src/api/hooks/useGetProject.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { Project } from "../../interfaces/projects"; +import client from "../client"; +import "../mocks/useGetProject.mock"; + +const getProject = async (projectId?: number) => { + const response = await client.get(`/api/v1/projects/${projectId}`); + + return response.data; +}; + +export const useGetProject = (projectId?: number) => { + return useQuery({ + queryKey: ["api", "projects", projectId], + queryFn: () => getProject(projectId), + }); +}; diff --git a/dashboard/src/api/hooks/useGetProjects.ts b/dashboard/src/api/hooks/useGetProjects.ts new file mode 100644 index 0000000..8befc39 --- /dev/null +++ b/dashboard/src/api/hooks/useGetProjects.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { GetProjectsResponse } from "../../interfaces/projects"; +import client from "../client"; +import "../mocks/useGetProjects.mock"; + +const getProjects = async () => { + const response = await client.get(`/api/v1/projects`); + + return response.data; +}; + +export const useGetProjects = () => { + return useQuery({ + queryKey: ["api", "projects"], + queryFn: () => getProjects(), + }); +}; diff --git a/dashboard/src/api/hooks/useGetVersions.ts b/dashboard/src/api/hooks/useGetVersions.ts new file mode 100644 index 0000000..9074695 --- /dev/null +++ b/dashboard/src/api/hooks/useGetVersions.ts @@ -0,0 +1,19 @@ +import { useQuery } from "@tanstack/react-query"; +import { GetVersionsResponse } from "../../interfaces/versions"; +import client from "../client"; +import "../mocks/useGetVersions.mock"; + +const getVersions = async (projectId?: number) => { + const response = await client.get( + `/api/v1/projects/${projectId}/versions`, + ); + + return response.data; +}; + +export const useGetVersions = (projectId?: number) => { + return useQuery({ + queryKey: ["api", "projects", projectId, "versions"], + queryFn: () => getVersions(projectId), + }); +}; diff --git a/dashboard/src/api/hooks/usePostVersion.ts b/dashboard/src/api/hooks/usePostVersion.ts new file mode 100644 index 0000000..e296987 --- /dev/null +++ b/dashboard/src/api/hooks/usePostVersion.ts @@ -0,0 +1,32 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { postProjectRequest } from "../../interfaces/projects"; +import { GetVersionsResponse } from "../../interfaces/versions"; +import client from "../client"; +import "../mocks/usePostVersion.mock"; + +const postVersion = async (projectId: number, request: postProjectRequest) => { + const response = await client.post( + `/api/v1/projects/${projectId}/versions`, + request, + ); + + return response.data; +}; + +export const usePostVersion = (projectId: number) => { + const queryClient = useQueryClient(); + + return useMutation({ + onSuccess: async () => { + await queryClient.invalidateQueries( + { + queryKey: ["api", "projects", projectId, "versions"], + exact: true, + }, + { throwOnError: true }, + ); + }, + mutationFn: (request: postProjectRequest) => + postVersion(projectId, request), + }); +}; diff --git a/dashboard/src/api/interceptors/logger.ts b/dashboard/src/api/interceptors/logger.ts new file mode 100644 index 0000000..489663c --- /dev/null +++ b/dashboard/src/api/interceptors/logger.ts @@ -0,0 +1,15 @@ +import { AxiosInstance } from "axios"; +import * as AxiosLogger from "axios-logger"; + +function registerLoggerInterceptor(inst: AxiosInstance) { + inst.interceptors.request.use((request) => { + return AxiosLogger.requestLogger(request, { + prefixText: "Mocked", + dateFormat: "HH:MM:ss", + headers: false, + data: false, + }); + }); +} + +export default registerLoggerInterceptor; diff --git a/dashboard/src/api/mocks/useDeleteVersion.mock.ts b/dashboard/src/api/mocks/useDeleteVersion.mock.ts new file mode 100644 index 0000000..a90961a --- /dev/null +++ b/dashboard/src/api/mocks/useDeleteVersion.mock.ts @@ -0,0 +1,14 @@ +import { mock } from "../client"; +import { mockedVersionsResponse } from "./useGetVersions.mock"; + +mock.onDelete(/^\/api\/v1\/projects\/\d+\/versions\/\d+$/).reply((req) => { + const versionId = parseInt(req.url!.split("/")[6]); + + const versionIndex = mockedVersionsResponse.versions.findIndex( + (version) => version.id === versionId, + ); + + mockedVersionsResponse.versions.splice(versionIndex, 1); + + return [204]; +}); diff --git a/dashboard/src/api/mocks/useGetProject.mock.ts b/dashboard/src/api/mocks/useGetProject.mock.ts new file mode 100644 index 0000000..5d0b321 --- /dev/null +++ b/dashboard/src/api/mocks/useGetProject.mock.ts @@ -0,0 +1,12 @@ +import { mock } from "../client"; +import { mockedProjectsResponse } from "./useGetProjects.mock"; + +mock.onGet(/^\/api\/v1\/projects\/\d+$/).reply((req) => { + const projectId = parseInt(req.url!.split("/")[4]); + + const project = mockedProjectsResponse.projects.find( + (project) => project.id === projectId, + ); + + return [200, project]; +}); diff --git a/dashboard/src/api/mocks/useGetProjects.mock.ts b/dashboard/src/api/mocks/useGetProjects.mock.ts new file mode 100644 index 0000000..595cf01 --- /dev/null +++ b/dashboard/src/api/mocks/useGetProjects.mock.ts @@ -0,0 +1,405 @@ +import { GetProjectsResponse } from "../../interfaces/projects"; +import { mock } from "../client"; + +export const mockedProjectsResponse: GetProjectsResponse = { + projects: [ + { + id: 1, + name: "Project 1", + numberOfVersions: 3, + createdAt: "2021-09-01T00:00:00.000Z", + }, + { + id: 2, + name: "Project 2", + numberOfVersions: 1, + createdAt: "2021-09-02T00:00:00.000Z", + }, + { + id: 3, + name: "Project 3", + numberOfVersions: 2, + createdAt: "2021-09-03T00:00:00.000Z", + }, + { + id: 4, + name: "Project 4", + numberOfVersions: 2, + createdAt: "2021-09-04T00:00:00.000Z", + }, + { + id: 5, + name: "Project 5", + numberOfVersions: 4, + createdAt: "2021-09-05T00:00:00.000Z", + }, + { + id: 6, + name: "Project 6", + numberOfVersions: 2, + createdAt: "2021-09-06T00:00:00.000Z", + }, + { + id: 7, + name: "Project 7", + numberOfVersions: 3, + createdAt: "2021-09-07T00:00:00.000Z", + }, + { + id: 8, + name: "Project 8", + numberOfVersions: 1, + createdAt: "2021-09-08T00:00:00.000Z", + }, + { + id: 9, + name: "Project 9", + numberOfVersions: 2, + createdAt: "2021-09-09T00:00:00.000Z", + }, + { + id: 10, + name: "Project 10", + numberOfVersions: 3, + createdAt: "2021-09-10T00:00:00.000Z", + }, + { + id: 11, + name: "Project 11", + numberOfVersions: 1, + createdAt: "2021-09-11T00:00:00.000Z", + }, + { + id: 12, + name: "Project 12", + numberOfVersions: 2, + createdAt: "2021-09-12T00:00:00.000Z", + }, + { + id: 13, + name: "Project 13", + numberOfVersions: 2, + createdAt: "2021-09-13T00:00:00.000Z", + }, + { + id: 14, + name: "Project 14", + numberOfVersions: 3, + createdAt: "2021-09-14T00:00:00.000Z", + }, + { + id: 15, + name: "Project 15", + numberOfVersions: 1, + createdAt: "2021-09-15T00:00:00.000Z", + }, + { + id: 16, + name: "Project 16", + numberOfVersions: 2, + createdAt: "2021-09-16T00:00:00.000Z", + }, + { + id: 17, + name: "Project 17", + numberOfVersions: 2, + createdAt: "2021-09-17T00:00:00.000Z", + }, + { + id: 18, + name: "Project 18", + numberOfVersions: 3, + createdAt: "2021-09-18T00:00:00.000Z", + }, + { + id: 19, + name: "Project 19", + numberOfVersions: 1, + createdAt: "2021-09-19T00:00:00.000Z", + }, + { + id: 20, + name: "Project 20", + numberOfVersions: 2, + createdAt: "2021-09-20T00:00:00.000Z", + }, + { + id: 21, + name: "Project 21", + numberOfVersions: 3, + createdAt: "2021-09-21T00:00:00.000Z", + }, + { + id: 22, + name: "Project 22", + numberOfVersions: 1, + createdAt: "2021-09-22T00:00:00.000Z", + }, + { + id: 23, + name: "Project 23", + numberOfVersions: 2, + createdAt: "2021-09-23T00:00:00.000Z", + }, + { + id: 24, + name: "Project 24", + numberOfVersions: 2, + createdAt: "2021-09-24T00:00:00.000Z", + }, + { + id: 25, + name: "Project 25", + numberOfVersions: 3, + createdAt: "2021-09-25T00:00:00.000Z", + }, + { + id: 26, + name: "Project 26", + numberOfVersions: 1, + createdAt: "2021-09-26T00:00:00.000Z", + }, + { + id: 27, + name: "Project 27", + numberOfVersions: 2, + createdAt: "2021-09-27T00:00:00.000Z", + }, + { + id: 28, + name: "Project 28", + numberOfVersions: 2, + createdAt: "2021-09-28T00:00:00.000Z", + }, + { + id: 29, + name: "Project 29", + numberOfVersions: 3, + createdAt: "2021-09-29T00:00:00.000Z", + }, + { + id: 30, + name: "Project 30", + numberOfVersions: 1, + createdAt: "2021-09-30T00:00:00.000Z", + }, + { + id: 31, + name: "Project 31", + numberOfVersions: 2, + createdAt: "2021-10-01T00:00:00.000Z", + }, + { + id: 32, + name: "Project 32", + numberOfVersions: 2, + createdAt: "2021-10-02T00:00:00.000Z", + }, + { + id: 33, + name: "Project 33", + numberOfVersions: 3, + createdAt: "2021-10-03T00:00:00.000Z", + }, + { + id: 34, + name: "Project 34", + numberOfVersions: 1, + createdAt: "2021-10-04T00:00:00.000Z", + }, + { + id: 35, + name: "Project 35", + numberOfVersions: 2, + createdAt: "2021-10-05T00:00:00.000Z", + }, + { + id: 36, + name: "Project 36", + numberOfVersions: 2, + createdAt: "2021-10-06T00:00:00.000Z", + }, + { + id: 37, + name: "Project 37", + numberOfVersions: 3, + createdAt: "2021-10-07T00:00:00.000Z", + }, + { + id: 38, + name: "Project 38", + numberOfVersions: 1, + createdAt: "2021-10-08T00:00:00.000Z", + }, + { + id: 39, + name: "Project 39", + numberOfVersions: 2, + createdAt: "2021-10-09T00:00:00.000Z", + }, + { + id: 40, + name: "Project 40", + numberOfVersions: 2, + createdAt: "2021-10-10T00:00:00.000Z", + }, + { + id: 41, + name: "Project 41", + numberOfVersions: 3, + createdAt: "2021-10-11T00:00:00.000Z", + }, + { + id: 42, + name: "Project 42", + numberOfVersions: 1, + createdAt: "2021-10-12T00:00:00.000Z", + }, + { + id: 43, + name: "Project 43", + numberOfVersions: 2, + createdAt: "2021-10-13T00:00:00.000Z", + }, + { + id: 44, + name: "Project 44", + numberOfVersions: 2, + createdAt: "2021-10-14T00:00:00.000Z", + }, + { + id: 45, + name: "Project 45", + numberOfVersions: 3, + createdAt: "2021-10-15T00:00:00.000Z", + }, + { + id: 46, + name: "Project 46", + numberOfVersions: 1, + createdAt: "2021-10-16T00:00:00.000Z", + }, + { + id: 47, + name: "Project 47", + numberOfVersions: 2, + createdAt: "2021-10-17T00:00:00.000Z", + }, + { + id: 48, + name: "Project 48", + numberOfVersions: 2, + createdAt: "2021-10-18T00:00:00.000Z", + }, + { + id: 49, + name: "Project 49", + numberOfVersions: 3, + createdAt: "2021-10-19T00:00:00.000Z", + }, + { + id: 50, + name: "Project 50", + numberOfVersions: 1, + createdAt: "2021-10-20T00:00:00.000Z", + }, + { + id: 51, + name: "Project 51", + numberOfVersions: 2, + createdAt: "2021-10-21T00:00:00.000Z", + }, + { + id: 52, + name: "Project 52", + numberOfVersions: 2, + createdAt: "2021-10-22T00:00:00.000Z", + }, + { + id: 53, + name: "Project 53", + numberOfVersions: 3, + createdAt: "2021-10-23T00:00:00.000Z", + }, + { + id: 54, + name: "Project 54", + numberOfVersions: 1, + createdAt: "2021-10-24T00:00:00.000Z", + }, + { + id: 55, + name: "Project 55", + numberOfVersions: 2, + createdAt: "2021-10-25T00:00:00.000Z", + }, + { + id: 56, + name: "Project 56", + numberOfVersions: 2, + createdAt: "2021-10-26T00:00:00.000Z", + }, + { + id: 57, + name: "Project 57", + numberOfVersions: 3, + createdAt: "2021-10-27T00:00:00.000Z", + }, + { + id: 58, + name: "Project 58", + numberOfVersions: 1, + createdAt: "2021-10-28T00:00:00.000Z", + }, + { + id: 59, + name: "Project 59", + numberOfVersions: 2, + createdAt: "2021-10-29T00:00:00.000Z", + }, + { + id: 60, + name: "Project 60", + numberOfVersions: 2, + createdAt: "2021-10-30T00:00:00.000Z", + }, + { + id: 61, + name: "Project 61", + numberOfVersions: 3, + createdAt: "2021-10-31T00:00:00.000Z", + }, + { + id: 62, + name: "Project 62", + numberOfVersions: 1, + createdAt: "2021-11-01T00:00:00.000Z", + }, + { + id: 63, + name: "Project 63", + numberOfVersions: 2, + createdAt: "2021-11-02T00:00:00.000Z", + }, + { + id: 64, + name: "Project 64", + numberOfVersions: 2, + createdAt: "2021-11-03T00:00:00.000Z", + }, + { + id: 65, + name: "Project 65", + numberOfVersions: 3, + createdAt: "2021-11-04T00:00:00.000Z", + }, + { + id: 66, + name: "Project 66", + numberOfVersions: 1, + createdAt: "2021-11-05T00:00:00.000Z", + }, + ], +}; + +mock.onGet(/^\/api\/v1\/projects$/).reply(200, mockedProjectsResponse); diff --git a/dashboard/src/api/mocks/useGetVersions.mock.ts b/dashboard/src/api/mocks/useGetVersions.mock.ts new file mode 100644 index 0000000..28b7536 --- /dev/null +++ b/dashboard/src/api/mocks/useGetVersions.mock.ts @@ -0,0 +1,136 @@ +import { GetVersionsResponse } from "../../interfaces/versions"; +import { mock } from "../client"; + +export const mockedVersionsResponse: GetVersionsResponse = { + versions: [ + { + id: 1, + name: "Version 1", + createdAt: "2021-09-01T00:00:00.000Z", + }, + { + id: 2, + name: "Version 2", + createdAt: "2021-09-02T00:00:00.000Z", + }, + { + id: 3, + name: "Version 3", + createdAt: "2021-09-03T00:00:00.000Z", + }, + { + id: 4, + name: "Version 4", + createdAt: "2021-09-04T00:00:00.000Z", + }, + { + id: 5, + name: "Version 5", + createdAt: "2021-09-05T00:00:00.000Z", + }, + { + id: 6, + name: "Version 6", + createdAt: "2021-09-06T00:00:00.000Z", + }, + { + id: 7, + name: "Version 7", + createdAt: "2021-09-07T00:00:00.000Z", + }, + { + id: 8, + name: "Version 8", + createdAt: "2021-09-08T00:00:00.000Z", + }, + { + id: 9, + name: "Version 9", + createdAt: "2021-09-09T00:00:00.000Z", + }, + { + id: 10, + name: "Version 10", + createdAt: "2021-09-10T00:00:00.000Z", + }, + { + id: 11, + name: "Version 11", + createdAt: "2021-09-11T00:00:00.000Z", + }, + { + id: 12, + name: "Version 12", + createdAt: "2021-09-12T00:00:00.000Z", + }, + { + id: 13, + name: "Version 13", + createdAt: "2021-09-13T00:00:00.000Z", + }, + { + id: 14, + name: "Version 14", + createdAt: "2021-09-14T00:00:00.000Z", + }, + { + id: 15, + name: "Version 15", + createdAt: "2021-09-15T00:00:00.000Z", + }, + { + id: 16, + name: "Version 16", + createdAt: "2021-09-16T00:00:00.000Z", + }, + { + id: 17, + name: "Version 17", + createdAt: "2021-09-17T00:00:00.000Z", + }, + { + id: 18, + name: "Version 18", + createdAt: "2021-09-18T00:00:00.000Z", + }, + { + id: 19, + name: "Version 19", + createdAt: "2021-09-19T00:00:00.000Z", + }, + { + id: 20, + name: "Version 20", + createdAt: "2021-09-20T00:00:00.000Z", + }, + { + id: 21, + name: "Version 21", + createdAt: "2021-09-21T00:00:00.000Z", + }, + { + id: 22, + name: "Version 22", + createdAt: "2021-09-22T00:00:00.000Z", + }, + { + id: 23, + name: "Version 23", + createdAt: "2021-09-23T00:00:00.000Z", + }, + { + id: 24, + name: "Version 24", + createdAt: "2021-09-24T00:00:00.000Z", + }, + { + id: 25, + name: "Version 25", + createdAt: "2021-09-25T00:00:00.000Z", + }, + ], +}; + +mock + .onGet(/^\/api\/v1\/projects\/\d+\/versions$/) + .reply(200, mockedVersionsResponse); diff --git a/dashboard/src/api/mocks/usePostVersion.mock.ts b/dashboard/src/api/mocks/usePostVersion.mock.ts new file mode 100644 index 0000000..cc06020 --- /dev/null +++ b/dashboard/src/api/mocks/usePostVersion.mock.ts @@ -0,0 +1,17 @@ +import { Version } from "../../interfaces/versions"; +import { mock } from "../client"; +import { mockedVersionsResponse } from "./useGetVersions.mock"; + +mock.onPost(/^\/api\/v1\/projects\/\d+\/versions$/).reply((req) => { + const request = JSON.parse(req.data); + + const newVersion: Version = { + id: Math.floor(Math.random() * 1000), + name: request.name, + createdAt: new Date().toISOString(), + }; + + mockedVersionsResponse.versions.push(newVersion); + + return [201, newVersion]; +}); diff --git a/dashboard/src/assets/slap.mp3 b/dashboard/src/assets/slap.mp3 new file mode 100644 index 0000000..917a6ec Binary files /dev/null and b/dashboard/src/assets/slap.mp3 differ diff --git a/dashboard/src/components/ColorSchemeToggle.tsx b/dashboard/src/components/ColorSchemeToggle.tsx new file mode 100644 index 0000000..dcfd2f5 --- /dev/null +++ b/dashboard/src/components/ColorSchemeToggle.tsx @@ -0,0 +1,66 @@ +import DarkModeRoundedIcon from "@mui/icons-material/DarkModeRounded"; +import LightModeIcon from "@mui/icons-material/LightMode"; +import IconButton, { IconButtonProps } from "@mui/joy/IconButton"; +import { styled, useColorScheme } from "@mui/joy/styles"; +import { useEffect, useState } from "react"; + +enum ColorMode { + LIGHT = "light", + DARK = "dark", +} + +const StyledColorSchemeToggle = styled(IconButton)({ + borderRadius: "12%", + '&[data-mode="dark"]': { + "& > *:first-of-type": { display: "none" }, + "& > *:last-child": { display: "initial" }, + }, + '&[data-mode="light"]': { + "& > *:first-of-type": { display: "initial" }, + "& > *:last-child": { display: "none" }, + }, +}); + +export const ColorSchemeToggle = (props: IconButtonProps) => { + const { onClick, ...other } = props; + const { mode, setMode } = useColorScheme(); + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + if (!mounted) { + return ( + + ); + } + + return ( + { + if (mode === ColorMode.LIGHT) { + setMode(ColorMode.DARK); + } else { + setMode(ColorMode.LIGHT); + } + onClick?.(event); + }} + data-mode={mode} + > + + + + ); +}; diff --git a/dashboard/src/components/Header.tsx b/dashboard/src/components/Header.tsx new file mode 100644 index 0000000..1a2722e --- /dev/null +++ b/dashboard/src/components/Header.tsx @@ -0,0 +1,133 @@ +import styled from "@emotion/styled"; +import { Button, Sheet, Stack, Typography } from "@mui/joy"; +import { FC, useEffect, useRef, useState } from "react"; +import { Link } from "react-router-dom"; +import soundEffect from "../assets/slap.mp3"; +import { ColorSchemeToggle } from "./ColorSchemeToggle"; + +const rainbowGradient = `linear-gradient( + to right, + rgba(255, 0, 0, 0.7), + rgba(255, 165, 0, 0.7), + rgba(255, 255, 0, 0.7), + rgba(0, 128, 0, 0.7), + rgba(0, 0, 255, 0.7), + rgba(128, 0, 128, 0.7) +)`; + +const AnimatedSheet = styled(Sheet, { + shouldForwardProp: (prop) => prop !== "isblue" && prop !== "isanimating", +})<{ + isblue: boolean; + isanimating: boolean; +}>` + transition: background 0.5s linear; + background-size: 100% 100%; + background-position: ${(props) => + props.isblue ? "left bottom" : "right bottom"}; + background-color: ${(props) => + props.isanimating + ? `linear-gradient(to right, blue 50%, ${rainbowGradient} 50%)` + : props.isblue + ? "#4393E4" + : rainbowGradient}; + background-image: ${(props) => + props.isanimating + ? `linear-gradient(to right, blue 50%, ${rainbowGradient} 50%)` + : props.isblue + ? "#4393E4" + : rainbowGradient}; +`; + +type HeaderProps = { + items?: { + name: string; + to?: string; + }[]; +}; + +export const Header: FC = ({ items }) => { + const [isBlue, setIsBlue] = useState(true); + const [isAnimating, setIsAnimating] = useState(false); + const audioRef = useRef(null); + + const toggleBackground = () => { + setIsAnimating(true); + if (audioRef.current) { + audioRef.current.play(); + } + }; + + useEffect(() => { + if (isAnimating) { + const timer = setTimeout(() => { + setIsBlue((prev) => !prev); + setIsAnimating(false); + }, 1000); + return () => clearTimeout(timer); + } + }, [isAnimating]); + + return ( + + + {items?.map((item, index) => ( + <> + {index > 0 && ( + + / + + )} + + + {item.name} + + + ))} + + +
+
+
+ ); +}; diff --git a/dashboard/src/components/ManageVersionModal.tsx b/dashboard/src/components/ManageVersionModal.tsx new file mode 100644 index 0000000..4dff724 --- /dev/null +++ b/dashboard/src/components/ManageVersionModal.tsx @@ -0,0 +1,137 @@ +import { Delete } from "@mui/icons-material"; +import Add from "@mui/icons-material/Add"; +import { FormLabel } from "@mui/joy"; +import Button from "@mui/joy/Button"; +import DialogContent from "@mui/joy/DialogContent"; +import DialogTitle from "@mui/joy/DialogTitle"; +import FormControl from "@mui/joy/FormControl"; +import Input from "@mui/joy/Input"; +import Modal from "@mui/joy/Modal"; +import ModalDialog from "@mui/joy/ModalDialog"; +import Stack from "@mui/joy/Stack"; +import { ChangeEvent, FC, FormEvent, useState } from "react"; +import { useDeleteVersion } from "../api/hooks/useDeleteVersion"; +import { usePostVersion } from "../api/hooks/usePostVersion"; + +type ManageVersionModalProps = { + projectId: number; + versionId?: number; + versionName?: string; +}; + +export const ManageVersionModal: FC = ({ + projectId, + versionId, + versionName, +}) => { + const [open, setOpen] = useState(false); + const [newProjectVersion, setNewProjectVersion] = useState(""); + const [validProjectVersion, setValidProjectVersion] = useState(false); + + const [processing, setProcessing] = useState(false); + + const { mutateAsync: deleteVersion } = useDeleteVersion( + projectId, + versionId!, + ); + const { mutateAsync: postNewVersion } = usePostVersion(projectId); + + const onProjectNameChange = (event: ChangeEvent) => { + const valid = /^[a-zA-Z0-9.-_]*$/.test(event.target.value); + setValidProjectVersion(valid); + + setNewProjectVersion(event.target.value); + }; + + const onSubmit = async (event: FormEvent) => { + event.preventDefault(); + + try { + setProcessing(true); + + if (versionId) { + await deleteVersion(); + } else { + await postNewVersion({ name: newProjectVersion }); + } + } finally { + setProcessing(false); + setOpen(false); + } + }; + + return ( + <> + {versionId ? ( + + ) : ( + + )} + + setOpen(false)}> + +
+ {versionId ? ( + + Delete version + + Are you sure you want to delete {versionName} version? + + + + + ) : ( + + New version + + + Choose a tag for the new version. Creating a new version will + take a couple of minutes. + + + + Version tag + + + + + + )} +
+
+
+ + ); +}; diff --git a/dashboard/src/components/Placeholder.tsx b/dashboard/src/components/Placeholder.tsx new file mode 100644 index 0000000..1188114 --- /dev/null +++ b/dashboard/src/components/Placeholder.tsx @@ -0,0 +1,22 @@ +import { Box, CircularProgress } from "@mui/joy"; +import { FC } from "react"; + +export const Placeholder: FC = () => { + return ( + + + + + + + + ); +}; diff --git a/dashboard/src/components/TablePaginationSection.tsx b/dashboard/src/components/TablePaginationSection.tsx new file mode 100644 index 0000000..2db686b --- /dev/null +++ b/dashboard/src/components/TablePaginationSection.tsx @@ -0,0 +1,68 @@ +import KeyboardArrowLeftIcon from "@mui/icons-material/KeyboardArrowLeft"; +import KeyboardArrowRightIcon from "@mui/icons-material/KeyboardArrowRight"; +import { Box, Button, IconButton, iconButtonClasses } from "@mui/joy"; +import { FC } from "react"; +import { generatePageNumbers } from "../utilities/tablePagination"; + +type TablePaginationSectionProps = { + currentPage: number; + pageCount: number; + onPageChange: (page: number) => void; +}; + +export const TablePaginationSection: FC = ({ + currentPage, + pageCount, + onPageChange, +}) => { + return ( + + + + + {generatePageNumbers(pageCount, currentPage).map((page, index) => ( + typeof page === "number" && onPageChange(page)} + disabled={page === currentPage || typeof page !== "number"} + > + {page} + + ))} + + + + + ); +}; diff --git a/dashboard/src/index.scss b/dashboard/src/index.scss new file mode 100644 index 0000000..cdc5d5a --- /dev/null +++ b/dashboard/src/index.scss @@ -0,0 +1,77 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +html, +body, +#root { + height: 100%; + width: 100%; +} + +#root { + overflow: auto; + padding-top: 2rem; + padding-bottom: 2rem; +} + +a { + font-weight: 500; + text-decoration: inherit; + + &:hover { + opacity: 0.8; + } +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; + + &:hover { + border-color: #646cff; + } + + &:focus, + &:focus-visible { + outline: 4px auto -webkit-focus-ring-color; + } +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} diff --git a/dashboard/src/interfaces/projects.ts b/dashboard/src/interfaces/projects.ts new file mode 100644 index 0000000..4877b3a --- /dev/null +++ b/dashboard/src/interfaces/projects.ts @@ -0,0 +1,14 @@ +export type GetProjectsResponse = { + projects: Project[]; +}; + +export type postProjectRequest = { + name: string; +}; + +export type Project = { + id: number; + name: string; + numberOfVersions: number; + createdAt: string; // ISO 8601 +}; diff --git a/dashboard/src/interfaces/versions.ts b/dashboard/src/interfaces/versions.ts new file mode 100644 index 0000000..4835196 --- /dev/null +++ b/dashboard/src/interfaces/versions.ts @@ -0,0 +1,9 @@ +export type GetVersionsResponse = { + versions: Version[]; +}; + +export type Version = { + id: number; + name: string; + createdAt: string; // ISO 8601 +}; diff --git a/dashboard/src/main.tsx b/dashboard/src/main.tsx new file mode 100644 index 0000000..31b2f5b --- /dev/null +++ b/dashboard/src/main.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; + +import { Container, CssBaseline, CssVarsProvider } from "@mui/joy"; +import { BrowserRouter } from "react-router-dom"; +import { ReactQueryClientProvider } from "./api/client"; +import "./index.scss"; +import Routes from "./routes"; + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + + + + + + + + + + , +); diff --git a/dashboard/src/routes/index.tsx b/dashboard/src/routes/index.tsx new file mode 100644 index 0000000..f36bad2 --- /dev/null +++ b/dashboard/src/routes/index.tsx @@ -0,0 +1,21 @@ +import { FC } from "react"; +import { Navigate, Route, Routes as RouterRoutes } from "react-router-dom"; +import { ProjectsOverview } from "../views/Projects"; +import { VersionsOverview } from "../views/Projects/Versions"; + +const Routes: FC = () => { + return ( + + + + } /> + } /> + + + } /> + + + ); +}; + +export default Routes; diff --git a/dashboard/src/utilities/tablePagination.ts b/dashboard/src/utilities/tablePagination.ts new file mode 100644 index 0000000..288b7c2 --- /dev/null +++ b/dashboard/src/utilities/tablePagination.ts @@ -0,0 +1,28 @@ +export const generatePageNumbers = (pageCount: number, currentPage: number) => { + const pageNumbers = []; + + // If there are less than 7 pages, show all pages buttons + // otherwise show only 5 pages buttons + if (pageCount <= 7) { + pageNumbers.push(...[...Array(pageCount).keys()].map(i => i++)); + } else { + if (currentPage <= 4) { + pageNumbers.push(...[...Array(5).keys()].map(i => i++)); + pageNumbers.push("...", pageCount); + } else if (currentPage >= pageCount - 3) { + pageNumbers.push(1, "..."); + pageNumbers.push(...[...Array(5).keys()].map(i => pageCount - 4 + i)); + } else { + pageNumbers.push( + 1, + "...", + currentPage - 1, + currentPage, + currentPage + 1, + "...", + pageCount, + ); + } + } + return pageNumbers; +}; diff --git a/dashboard/src/views/Projects/Versions/components/VersionTableRow/index.tsx b/dashboard/src/views/Projects/Versions/components/VersionTableRow/index.tsx new file mode 100644 index 0000000..a923a58 --- /dev/null +++ b/dashboard/src/views/Projects/Versions/components/VersionTableRow/index.tsx @@ -0,0 +1,57 @@ +import { Typography } from "@mui/joy"; +import { FC } from "react"; +import { ManageVersionModal } from "../../../../../components/ManageVersionModal"; + +type VersionTableRowProps = { + projectId: number; + versionId: number; + versionName: string; + createdAt: string; +}; + +export const VersionTableRow: FC = ({ + projectId, + versionId, + versionName, + createdAt, +}) => { + const formatIsoDateToLocaleString = (isoDate: string) => { + return new Date(isoDate).toLocaleString(); + }; + + const createdAtDate = formatIsoDateToLocaleString(createdAt); + + return ( + + + + {versionName} + + + + + + {createdAtDate} + + + + + + + + ); +}; diff --git a/dashboard/src/views/Projects/Versions/index.tsx b/dashboard/src/views/Projects/Versions/index.tsx new file mode 100644 index 0000000..4cebd81 --- /dev/null +++ b/dashboard/src/views/Projects/Versions/index.tsx @@ -0,0 +1,156 @@ +import SearchIcon from "@mui/icons-material/Search"; +import { FormControl, Input, Sheet, Stack, Table, Typography } from "@mui/joy"; +import { useMemo, useState } from "react"; +import { useGetPageParams } from "../../../api/hooks/useGetPageParams"; +import { useGetProject } from "../../../api/hooks/useGetProject"; +import { useGetVersions } from "../../../api/hooks/useGetVersions"; +import { Header } from "../../../components/Header"; +import { ManageVersionModal } from "../../../components/ManageVersionModal"; +import { Placeholder } from "../../../components/Placeholder"; +import { TablePaginationSection } from "../../../components/TablePaginationSection"; +import { Version } from "../../../interfaces/versions"; +import { VersionTableRow } from "./components/VersionTableRow"; + +const ITEMS_PER_PAGE = 25; + +export const VersionsOverview = () => { + const [searchTerm, setSearchTerm] = useState(""); + const [currentPage, setCurrentPage] = useState(1); + const { projectId } = useGetPageParams(); + const { data: project, isLoading: isProjectLoading } = + useGetProject(projectId); + const { data: versionsData, isLoading: isVersionsDataLoading } = + useGetVersions(projectId); + + const filteredVersions: Version[] = useMemo(() => { + if ( + !versionsData || + !versionsData.versions || + versionsData.versions.length === 0 + ) + return []; + + return versionsData.versions.filter((version: Version) => + version.name.toLowerCase().includes(searchTerm.toLowerCase()), + ); + }, [versionsData, searchTerm]); + + const pageCount = + filteredVersions.length > 0 + ? Math.ceil(filteredVersions.length / ITEMS_PER_PAGE) + : 0; + + const paginatedVersions = useMemo(() => { + if (filteredVersions.length === 0) return []; + + const startIndex = (currentPage - 1) * ITEMS_PER_PAGE; + return filteredVersions.slice(startIndex, startIndex + ITEMS_PER_PAGE); + }, [filteredVersions, currentPage]); + + if (isProjectLoading || isVersionsDataLoading) { + return ; + } + + return ( + <> +
+ + + + } + onChange={(e) => setSearchTerm(e.target.value)} + value={searchTerm} + /> + + + {projectId && } + + + + {filteredVersions.length === 0 && ( + + This project has no versions yet. + + )} + + {filteredVersions.length > 0 && ( + + + + + + + + + + {projectId && ( + + {paginatedVersions.map((version: Version) => ( + + ))} + + )} +
Name + Created At +
+ )} +
+ + {filteredVersions.length > ITEMS_PER_PAGE && ( + + )} + + ); +}; diff --git a/dashboard/src/views/Projects/components/TableRow/index.tsx b/dashboard/src/views/Projects/components/TableRow/index.tsx new file mode 100644 index 0000000..1f25025 --- /dev/null +++ b/dashboard/src/views/Projects/components/TableRow/index.tsx @@ -0,0 +1,68 @@ +import { KeyboardArrowRight } from "@mui/icons-material"; +import { Button, Typography } from "@mui/joy"; +import { FC } from "react"; + +type ProjectTableRowProps = { + projectId: number; + projectName: string; + createdAt: string; + numberOfVersions: number; +}; + +export const ProjectTableRow: FC = ({ + projectId, + projectName, + numberOfVersions, + createdAt, +}) => { + const formatIsoDateToLocaleString = (isoDate: string) => { + return new Date(isoDate).toLocaleString(); + }; + + const createdAtDate = formatIsoDateToLocaleString(createdAt); + + return ( + (window.location.href = `/projects/${projectId}/versions`)} + style={{ cursor: "pointer" }} + > + + + {projectName} + + + + + + {numberOfVersions} + + + + + + {createdAtDate} + + + + + + + + ); +}; diff --git a/dashboard/src/views/Projects/index.tsx b/dashboard/src/views/Projects/index.tsx new file mode 100644 index 0000000..ae04575 --- /dev/null +++ b/dashboard/src/views/Projects/index.tsx @@ -0,0 +1,119 @@ +import SearchIcon from "@mui/icons-material/Search"; +import { FormControl, Input, Sheet, Table } from "@mui/joy"; +import { useMemo, useState } from "react"; +import { useGetProjects } from "../../api/hooks/useGetProjects"; +import { Header } from "../../components/Header"; +import { Placeholder } from "../../components/Placeholder"; +import { TablePaginationSection } from "../../components/TablePaginationSection"; +import { Project } from "../../interfaces/projects"; +import { ProjectTableRow } from "./components/TableRow"; + +const ITEMS_PER_PAGE = 25; + +export const ProjectsOverview = () => { + const [searchTerm, setSearchTerm] = useState(""); + const { data: projects, isLoading } = useGetProjects(); + const [currentPage, setCurrentPage] = useState(1); + + const filteredProjects: Project[] = useMemo(() => { + if (!projects || !projects.projects || projects.projects.length === 0) + return []; + + return projects.projects.filter((project: Project) => + project.name.toLowerCase().includes(searchTerm.toLowerCase()), + ); + }, [projects, searchTerm]); + + const pageCount = filteredProjects + ? Math.ceil(filteredProjects.length / ITEMS_PER_PAGE) + : 0; + + const paginatedProjects = useMemo(() => { + if (filteredProjects.length === 0) return []; + + const startIndex = (currentPage - 1) * ITEMS_PER_PAGE; + return filteredProjects.slice(startIndex, startIndex + ITEMS_PER_PAGE); + }, [filteredProjects, currentPage]); + + if (isLoading) return ; + + return ( + <> +
+ + + } + onChange={(e) => setSearchTerm(e.target.value)} + value={searchTerm} + /> + + + + + + + + + + + + + + {paginatedProjects.map((project) => ( + + ))} + +
Name + Number of versions + + Created At +
+
+ + {filteredProjects.length > ITEMS_PER_PAGE && ( + + )} + + ); +}; diff --git a/dashboard/src/vite-env.d.ts b/dashboard/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/dashboard/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/dashboard/tsconfig.json b/dashboard/tsconfig.json new file mode 100644 index 0000000..a7fc6fb --- /dev/null +++ b/dashboard/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/dashboard/tsconfig.node.json b/dashboard/tsconfig.node.json new file mode 100644 index 0000000..97ede7e --- /dev/null +++ b/dashboard/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/dashboard/vite.config.ts b/dashboard/vite.config.ts new file mode 100644 index 0000000..6091b79 --- /dev/null +++ b/dashboard/vite.config.ts @@ -0,0 +1,12 @@ +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + proxy: { + "/api": "http://localhost:8001", + }, + }, +}); diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000..c10c38f --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,4 @@ +# /bin/sh + +exec nginx -g "daemon off;" & +exec parrot serve --config=/config/config.yaml diff --git a/docker/nginx.conf b/docker/nginx.conf new file mode 100644 index 0000000..926c6f5 --- /dev/null +++ b/docker/nginx.conf @@ -0,0 +1,46 @@ +worker_processes 1; + +events { + worker_connections 1024; +} + +http { + server { + listen 80; + server_name localhost; + + root /usr/share/nginx/html; + index index.html index.htm; + include /etc/nginx/mime.types; + + gzip on; + gzip_min_length 1000; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + + location / { + try_files $uri /index.html; + + add_header X-Content-Type-Options nosniff; + add_header X-Frame-Options SAMEORIGIN; + add_header X-XSS-Protection "1; mode=block"; + add_header Content-Security-Policy "frame-ancestors 'self'"; + + add_header Cache-Control "no-store, no-cache, must-revalidate"; + } + + location ~ ^/(static|assets)/ { + expires 30d; + add_header X-Content-Type-Options nosniff; + add_header X-Frame-Options SAMEORIGIN; + add_header X-XSS-Protection "1; mode=block"; + add_header Content-Security-Policy "frame-ancestors 'self'"; + + add_header Cache-Control "public"; + } + + location /api { + proxy_pass http://localhost:8001; + } + } +} diff --git a/docs/v1.yaml b/docs/v1.yaml index 97f09d9..7c13534 100644 --- a/docs/v1.yaml +++ b/docs/v1.yaml @@ -69,6 +69,12 @@ paths: name: format in: query required: false + - schema: + type: string + description: The version of the return data + name: version + in: query + required: false get: tags: - project @@ -79,6 +85,142 @@ paths: description: "Invalid project id" "404": description: "No translation found for language code" + /v1/projects: + get: + description: Get all projects + tags: + - projects + responses: + "200": + description: "Successful" + content: + application/json: + schema: + type: object + properties: + projects: + type: array + items: + type: object + properties: + id: + type: integer + description: Project id in POEditor + example: 123456 + name: + type: string + description: The name of the project + example: "My project" + numberOfVersions: + type: integer + description: The number of versions in the project + example: 1 + createdAt: + type: string + format: date-time + description: The creation date of the project in ISO 8601 format + example: "2023-09-05T12:34:56Z" + /v1/projects/{project}: + parameters: + - schema: + type: integer + format: int32 + description: Project id in POEditor + name: project + in: path + required: true + get: + description: Get project by project id + tags: + - projects + responses: + "200": + description: "Successful" + content: + application/json: + schema: + type: object + properties: + id: + type: integer + example: 123456 + name: + type: string + example: "My project" + numberOfVersions: + type: integer + example: 1 + createdAt: + type: string + format: date-time + example: "2021-01-01T00:00:00Z" + "400": + description: "Invalid project id" + "404": + description: "Project not found" + /v1/projects/{project}/versions: + parameters: + - schema: + type: integer + format: int32 + description: Project id in POEditor + name: project + in: path + required: true + get: + description: Get all project versions + tags: + - projects + responses: + "200": + description: "Successful" + content: + application/json: + schema: + type: object + properties: + versions: + type: array + items: + type: object + properties: + id: + type: integer + description: Version id in S3 + example: 123456 + name: + type: string + description: Version name + example: "v1" + createdAt: + type: string + format: date-time + description: The creation date of the version in ISO 8601 format + example: "2023-09-05T12:34:56Z" + "400": + description: "Invalid project id" + "404": + description: "Project versions not found" + post: + description: Create a new project version + tags: + - projects + requestBody: + description: Optional description in *Markdown* + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + example: "My project" + responses: + "201": + description: "Successful" + "400": + description: "Invalid project id" /v1/project/{project}/purge: parameters: - schema: @@ -96,6 +238,29 @@ paths: description: "Successful" "400": description: "Invalid project id" + /v1/project/{project}/versions/{version}: + parameters: + - schema: + type: integer + format: int32 + description: Project id in POEditor + name: project + in: path + required: true + - schema: + type: string + description: Version id in S3 + name: version + in: path + required: true + delete: + tags: + - projects + responses: + "200": + description: "Successful" + "400": + description: "Invalid project id or version id" /v1/project/{project}/language/{language}/purge: parameters: - schema: diff --git a/go.mod b/go.mod index 3bcd599..01f2bd0 100644 --- a/go.mod +++ b/go.mod @@ -1,28 +1,96 @@ module github.com/uniwise/parrot -go 1.16 +go 1.23 require ( github.com/AppsFlyer/go-sundheit v0.5.0 - github.com/go-playground/universal-translator v0.17.0 // indirect - github.com/go-playground/validator v9.31.0+incompatible + github.com/alitto/pond v1.9.2 + github.com/aws/aws-sdk-go-v2 v1.32.2 + github.com/aws/aws-sdk-go-v2/config v1.27.43 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.32 + github.com/aws/aws-sdk-go-v2/service/s3 v1.65.3 + github.com/go-playground/validator/v10 v10.22.1 github.com/go-redis/cache/v8 v8.4.4 github.com/go-redis/redis/v8 v8.11.5 github.com/golang/mock v1.6.0 + github.com/google/uuid v1.6.0 + github.com/jarcoal/httpmock v1.3.1 github.com/johngb/langreg v0.0.0-20150123211413-5c6abc6d19d2 github.com/joho/godotenv v1.4.0 github.com/labstack/echo/v4 v4.10.0 - github.com/leodido/go-urn v1.2.1 // indirect github.com/mitchellh/go-homedir v1.1.0 github.com/paulfarver/echo-pack v0.5.1 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.14.0 github.com/sirupsen/logrus v1.9.0 - github.com/smartystreets/goconvey v1.6.4 // indirect github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.14.0 + github.com/stretchr/testify v1.8.4 + go.uber.org/mock v0.4.0 golang.org/x/sync v0.1.0 - golang.org/x/sys v0.3.0 - gopkg.in/go-playground/assert.v1 v1.2.1 // indirect + golang.org/x/sys v0.17.0 gopkg.in/resty.v1 v1.12.0 ) + +require ( + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.41 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 // indirect + github.com/aws/smithy-go v1.22.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/klauspost/compress v1.13.6 // indirect + github.com/labstack/gommon v0.4.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/magiconair/properties v1.8.6 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect + github.com/smartystreets/goconvey v1.6.4 // 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/subosito/gotenv v1.4.1 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/vmihailenco/go-tinylfu v0.2.2 // indirect + github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.2.0 // 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 +) diff --git a/go.sum b/go.sum index 3bad6d0..aebc266 100644 --- a/go.sum +++ b/go.sum @@ -17,217 +17,89 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb 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 v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= -cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= -cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= -cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= -cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= -cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= -cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= -cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= -cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= -cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= -cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= -cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= -cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= -cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= -cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= -cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= -cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= -cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= 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/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= -cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= -cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= -cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= -cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= -cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= -cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= -cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= -cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= -cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= -cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= -cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= -cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= -cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= -cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= -cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= -cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= -cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= -cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= -cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= -cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= -cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= 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/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= -cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= -cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= -cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= -cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= -cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= -cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= -cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= -cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= -cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= -cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= -cloud.google.com/go/firestore v1.8.0/go.mod h1:r3KB8cAdRIe8znzoPWLw8S6gpDVd9treohhn8b09424= -cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= -cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= -cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= -cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= -cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= -cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= -cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= -cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= -cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= -cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= -cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= -cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= -cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= -cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= -cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= -cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= -cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= -cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= -cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= -cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= -cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= -cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= -cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= -cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= -cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= -cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= -cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= -cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= -cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= -cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= -cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= -cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= -cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= -cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= 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/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= -cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= -cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= -cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= -cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= -cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= -cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= -cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= -cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= -cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= -cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= -cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= -cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= -cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= -cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= -cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= -cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= -cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= -cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= -cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= -cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= -cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= -cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= -cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= 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= -cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= -cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= -cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= -cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= -cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= -cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= -cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= -cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= -cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= -cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= -cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= -cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= -cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AppsFlyer/go-sundheit v0.5.0 h1:/VxpyigCfJrq1r97mn9HPiAB2qrhcTFHwNIIDr15CZM= github.com/AppsFlyer/go-sundheit v0.5.0/go.mod h1:2ZM0BnfqT/mljBQO224VbL5XH06TgWuQ6Cn+cTtCpTY= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 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/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -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-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= -github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/alitto/pond v1.9.2 h1:9Qb75z/scEZVCoSU+osVmQ0I0JOeLfdTDafrbcJ8CLs= +github.com/alitto/pond v1.9.2/go.mod h1:xQn3P/sHTYcU/1BR3i86IGIrilcrGC2LiS+E2+CJWsI= +github.com/aws/aws-sdk-go-v2 v1.32.2 h1:AkNLZEyYMLnx/Q/mSKkcMqwNFXMAvFto9bNsHqcTduI= +github.com/aws/aws-sdk-go-v2 v1.32.2/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 h1:pT3hpW0cOHRJx8Y0DfJUEQuqPild8jRGmSFmBgvydr0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6/go.mod h1:j/I2++U0xX+cr44QjHay4Cvxj6FUbnxrgmqN3H1jTZA= +github.com/aws/aws-sdk-go-v2/config v1.27.43 h1:p33fDDihFC390dhhuv8nOmX419wjOSDQRb+USt20RrU= +github.com/aws/aws-sdk-go-v2/config v1.27.43/go.mod h1:pYhbtvg1siOOg8h5an77rXle9tVG8T+BWLWAo7cOukc= +github.com/aws/aws-sdk-go-v2/credentials v1.17.41 h1:7gXo+Axmp+R4Z+AK8YFQO0ZV3L0gizGINCOWxSLY9W8= +github.com/aws/aws-sdk-go-v2/credentials v1.17.41/go.mod h1:u4Eb8d3394YLubphT4jLEwN1rLNq2wFOlT6OuxFwPzU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 h1:TMH3f/SCAWdNtXXVPPu5D6wrr4G5hI1rAxbcocKfC7Q= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17/go.mod h1:1ZRXLdTpzdJb9fwTMXiLipENRxkGMTn1sfKexGllQCw= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.32 h1:C2hE+gJ40Cb4vzhFJ+tTzjvBpPloUq7XP6PD3A2Fk7g= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.32/go.mod h1:0OmMtVNp+10JFBTfmA2AIeqBDm0YthDXmE+N7poaptk= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 h1:UAsR3xA31QGf79WzpG/ixT9FZvQlh5HY1NRqSHBNOCk= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21/go.mod h1:JNr43NFf5L9YaG3eKTm7HQzls9J+A9YYcGI5Quh1r2Y= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 h1:6jZVETqmYCadGFvrYEQfC5fAQmlo80CeL5psbno6r0s= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21/go.mod h1:1SR0GbLlnN3QUmYaflZNiH1ql+1qrSiB2vwcJ+4UM60= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21 h1:7edmS3VOBDhK00b/MwGtGglCm7hhwNYnjJs/PgFdMQE= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21/go.mod h1:Q9o5h4HoIWG8XfzxqiuK/CGUbepCJ8uTlaE3bAbxytQ= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2 h1:4FMHqLfk0efmTqhXVRL5xYRqlEBNBiRI7N6w4jsEdd4= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2/go.mod h1:LWoqeWlK9OZeJxsROW2RqrSPvQHKTpp69r/iDjwsSaw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 h1:s7NA1SOw8q/5c0wr8477yOPp0z+uBaXBnLE0XYb0POA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2/go.mod h1:fnjjWyAW/Pj5HYOxl9LJqWtEwS7W2qgcRLWP+uWbss0= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2 h1:t7iUP9+4wdc5lt3E41huP+GvQZJD38WLsgVp4iOtAjg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2/go.mod h1:/niFCtmuQNxqx9v8WAPq5qh7EH25U4BF6tjoyq9bObM= +github.com/aws/aws-sdk-go-v2/service/s3 v1.65.3 h1:xxHGZ+wUgZNACQmxtdvP5tgzfsxGS3vPpTP5Hy3iToE= +github.com/aws/aws-sdk-go-v2/service/s3 v1.65.3/go.mod h1:cB6oAuus7YXRZhWCc1wIwPywwZ1XwweNp2TVAEGYeB8= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 h1:bSYXVyUzoTHoKalBmwaZxs97HU9DWWI3ehHSAMa7xOk= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.2/go.mod h1:skMqY7JElusiOUjMJMOv1jJsP7YUg7DrhgqZZWuzu1U= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 h1:AhmO1fHINP9vFYUE0LHzCWg/LfUWUF+zFPEcY9QXb7o= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2/go.mod h1:o8aQygT2+MVP0NaV6kbdE1YnnIM8RRVQzoeUH45GOdI= +github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 h1:CiS7i0+FUe+/YY1GvIBLLrR/XNGZ4CtM1Ll0XavNuVo= +github.com/aws/aws-sdk-go-v2/service/sts v1.32.2/go.mod h1:HtaiBI8CjYoNVde8arShXb94UbQQi9L4EMr6D+xGBwo= +github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= +github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= 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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/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/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= -github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= 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/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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= @@ -235,20 +107,12 @@ 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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 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/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= @@ -257,7 +121,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= 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= @@ -269,12 +134,14 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 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-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA= -github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= +github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-redis/cache/v8 v8.4.4 h1:Rm0wZ55X22BA2JMqVtRQNHYyzDd0I5f+Ec/C9Xx3mXY= github.com/go-redis/cache/v8 v8.4.4/go.mod h1:JM6CkupsPvAu/LYEVGQy6UB4WDAzQSXkR0lUCbeIcKc= github.com/go-redis/redis/v8 v8.11.3/go.mod h1:xNJ9xDG09FsIPwh3bWdk+0oDWHbtF9rPN0F/oD9XeKc= @@ -282,16 +149,13 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 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/groupcache v0.0.0-20210331224755-41bb18bfe9da/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= @@ -299,7 +163,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt 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/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -317,10 +180,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/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= @@ -331,19 +192,15 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ 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.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= 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= @@ -354,81 +211,32 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= -github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= -github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= 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 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul/api v1.15.3/go.mod h1:/g/qgcoBcEXALCNZgRRisyTW0nY86++L0KbeAMXYCeY= -github.com/hashicorp/consul/sdk v0.11.0/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= -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-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 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/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 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.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= -github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= -github.com/hashicorp/serf v0.9.8/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 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.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= +github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/johngb/langreg v0.0.0-20150123211413-5c6abc6d19d2 h1:R0Yc1jK2pjDwZeIXmcbELtKLedE+PjuI0S5cguGxTxw= github.com/johngb/langreg v0.0.0-20150123211413-5c6abc6d19d2/go.mod h1:m/usUv5KgruWsRUejHsR568dyOh5pJ1wVoKZKMuEPhI= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -438,7 +246,6 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 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/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= @@ -447,51 +254,32 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv 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.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -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/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/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= github.com/labstack/echo/v4 v4.10.0 h1:5CiyngihEO4HXsz3vVsJn7f8xAlWwRr3aY6Ih280ZKA= github.com/labstack/echo/v4 v4.10.0/go.mod h1:S/T/5fy/GigaXnHTkh0ZGe4LpkkQysvRjFMSUTkDRNQ= -github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= -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/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -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/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= +github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 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= @@ -509,15 +297,11 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/paulfarver/echo-pack v0.5.1 h1:ENNWJfoNwb9CG8nZWD1soDyo1zYMoZEqIONSWxZPuf0= github.com/paulfarver/echo-pack v0.5.1/go.mod h1:7QKZ/daaY1e7zdsH6gSIkhnSoS+2hJiGws25tA+cYDQ= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= @@ -531,16 +315,11 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -550,7 +329,6 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= @@ -558,21 +336,15 @@ github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8 github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/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/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sagikazarmark/crypt v0.8.0/go.mod h1:TmKwZAo97S4Fy4sfMH/HX/cQP5D+ijra2NyLpNNmttY= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -582,7 +354,6 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 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= @@ -608,13 +379,11 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 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.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= @@ -631,40 +400,24 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de 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.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.etcd.io/etcd/api/v3 v3.5.5/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= -go.etcd.io/etcd/client/pkg/v3 v3.5.5/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ= -go.etcd.io/etcd/client/v2 v2.305.5/go.mod h1:zQjKllfqfBVyVStbt4FaosoX2iYd8fV/GRy/PbowgP4= -go.etcd.io/etcd/client/v3 v3.5.5/go.mod h1:aApjR4WGlSumpnJ2kloS75h6aHUmAyaPLjHMxpc7E7c= 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.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-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-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.2.0 h1:BRXPfhNivWL5Yq0BGQ39a2sW6t44aODpfxkWjYdzewE= -golang.org/x/crypto v0.2.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 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= @@ -688,7 +441,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu 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/lint v0.0.0-20210508222113-6edffad5e616/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= @@ -700,7 +452,6 @@ 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/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 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= @@ -717,7 +468,6 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL 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-20190923162816-aa69164e4478/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= @@ -735,34 +485,16 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R 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-20201110031124-69a78807bb2b/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-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 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= @@ -772,21 +504,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ 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/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= 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= @@ -798,18 +517,13 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ 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/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/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-20181116152217-5ac8a444bdc5/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -819,18 +533,13 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w 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-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/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= @@ -853,72 +562,38 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/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-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/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-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/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-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/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-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= 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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE= golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -935,7 +610,6 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw 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-20190907020128-2ca718005c18/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-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -960,7 +634,6 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY 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-20200619180055-7c47624df98f/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= @@ -970,23 +643,13 @@ golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/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.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 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= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= 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= @@ -1006,36 +669,6 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 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/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= -google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= -google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= -google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= -google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 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= @@ -1066,7 +699,6 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= @@ -1079,73 +711,7 @@ google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6D 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-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= -google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= -google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= -google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= -google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= -google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= -google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1159,30 +725,9 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= 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= @@ -1195,8 +740,6 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 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= @@ -1206,8 +749,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= -gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= 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 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= @@ -1216,10 +757,8 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 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.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/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= @@ -1237,4 +776,3 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 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.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/internal/cache/cache.go b/internal/cache/cache.go index 3e30d0c..6849447 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -15,8 +15,8 @@ type CacheItem struct { } type Cache interface { - GetTranslation(ctx context.Context, projectID int, languageCode, format string) (item *CacheItem, err error) - SetTranslation(ctx context.Context, projectID int, languageCode, format string, data []byte) (checksum string, err error) + GetTranslation(ctx context.Context, projectID int, languageCode, format, version string) (item *CacheItem, err error) + SetTranslation(ctx context.Context, projectID int, languageCode, format, version string, data []byte) (checksum string, err error) PurgeTranslation(ctx context.Context, projectID int, languageCode string) (err error) PurgeProject(ctx context.Context, projectID int) (err error) GetTTL() time.Duration diff --git a/internal/cache/cache_mock.go b/internal/cache/cache_mock.go new file mode 100644 index 0000000..78ff893 --- /dev/null +++ b/internal/cache/cache_mock.go @@ -0,0 +1,122 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: cache.go + +// Package cache is a generated GoMock package. +package cache + +import ( + context "context" + reflect "reflect" + time "time" + + gomock "go.uber.org/mock/gomock" +) + +// MockCache is a mock of Cache interface. +type MockCache struct { + ctrl *gomock.Controller + recorder *MockCacheMockRecorder +} + +// MockCacheMockRecorder is the mock recorder for MockCache. +type MockCacheMockRecorder struct { + mock *MockCache +} + +// NewMockCache creates a new mock instance. +func NewMockCache(ctrl *gomock.Controller) *MockCache { + mock := &MockCache{ctrl: ctrl} + mock.recorder = &MockCacheMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCache) EXPECT() *MockCacheMockRecorder { + return m.recorder +} + +// GetTTL mocks base method. +func (m *MockCache) GetTTL() time.Duration { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTTL") + ret0, _ := ret[0].(time.Duration) + return ret0 +} + +// GetTTL indicates an expected call of GetTTL. +func (mr *MockCacheMockRecorder) GetTTL() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTTL", reflect.TypeOf((*MockCache)(nil).GetTTL)) +} + +// GetTranslation mocks base method. +func (m *MockCache) GetTranslation(ctx context.Context, projectID int, languageCode, format, version string) (*CacheItem, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTranslation", ctx, projectID, languageCode, format, version) + ret0, _ := ret[0].(*CacheItem) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTranslation indicates an expected call of GetTranslation. +func (mr *MockCacheMockRecorder) GetTranslation(ctx, projectID, languageCode, format, version interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTranslation", reflect.TypeOf((*MockCache)(nil).GetTranslation), ctx, projectID, languageCode, format, version) +} + +// PingContext mocks base method. +func (m *MockCache) PingContext(ctx context.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PingContext", ctx) + ret0, _ := ret[0].(error) + return ret0 +} + +// PingContext indicates an expected call of PingContext. +func (mr *MockCacheMockRecorder) PingContext(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PingContext", reflect.TypeOf((*MockCache)(nil).PingContext), ctx) +} + +// PurgeProject mocks base method. +func (m *MockCache) PurgeProject(ctx context.Context, projectID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PurgeProject", ctx, projectID) + ret0, _ := ret[0].(error) + return ret0 +} + +// PurgeProject indicates an expected call of PurgeProject. +func (mr *MockCacheMockRecorder) PurgeProject(ctx, projectID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PurgeProject", reflect.TypeOf((*MockCache)(nil).PurgeProject), ctx, projectID) +} + +// PurgeTranslation mocks base method. +func (m *MockCache) PurgeTranslation(ctx context.Context, projectID int, languageCode string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PurgeTranslation", ctx, projectID, languageCode) + ret0, _ := ret[0].(error) + return ret0 +} + +// PurgeTranslation indicates an expected call of PurgeTranslation. +func (mr *MockCacheMockRecorder) PurgeTranslation(ctx, projectID, languageCode interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PurgeTranslation", reflect.TypeOf((*MockCache)(nil).PurgeTranslation), ctx, projectID, languageCode) +} + +// SetTranslation mocks base method. +func (m *MockCache) SetTranslation(ctx context.Context, projectID int, languageCode, format, version string, data []byte) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetTranslation", ctx, projectID, languageCode, format, version, data) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SetTranslation indicates an expected call of SetTranslation. +func (mr *MockCacheMockRecorder) SetTranslation(ctx, projectID, languageCode, format, version, data interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTranslation", reflect.TypeOf((*MockCache)(nil).SetTranslation), ctx, projectID, languageCode, format, version, data) +} diff --git a/internal/cache/filesystem.go b/internal/cache/filesystem.go index 33a25e7..4d441bd 100644 --- a/internal/cache/filesystem.go +++ b/internal/cache/filesystem.go @@ -32,8 +32,8 @@ func NewFilesystemCache(cacheDir string, ttl time.Duration) (*FilesystemCache, e }, nil } -func (f *FilesystemCache) GetTranslation(ctx context.Context, projectID int, languageCode, format string) (*CacheItem, error) { - filePath := f.filePath(projectID, languageCode, format) +func (f *FilesystemCache) GetTranslation(ctx context.Context, projectID int, languageCode, format, version string) (*CacheItem, error) { + filePath := f.filePath(projectID, languageCode, format, version) info, err := os.Stat(filePath) if os.IsNotExist(err) { @@ -67,9 +67,9 @@ func (f *FilesystemCache) GetTranslation(ctx context.Context, projectID int, lan }, nil } -func (f *FilesystemCache) SetTranslation(ctx context.Context, projectID int, languageCode, format string, data []byte) (string, error) { +func (f *FilesystemCache) SetTranslation(ctx context.Context, projectID int, languageCode, format, version string, data []byte) (string, error) { if err := ioutil.WriteFile( - f.filePath(projectID, languageCode, format), + f.filePath(projectID, languageCode, format, version), data, os.ModePerm, ); err != nil { @@ -80,7 +80,7 @@ func (f *FilesystemCache) SetTranslation(ctx context.Context, projectID int, lan hash := hex.EncodeToString(hashBytes[:]) if err := ioutil.WriteFile( - f.md5Path(projectID, languageCode, format), + f.md5Path(projectID, languageCode, format, version), []byte(hash), os.ModePerm, ); err != nil { @@ -112,25 +112,25 @@ func (f *FilesystemCache) PurgeProject(ctx context.Context, projectID int) error return nil } -func (f *FilesystemCache) filePath(projectID int, languageCode, format string) string { +func (f *FilesystemCache) filePath(projectID int, languageCode, format, version string) string { return path.Join( f.dir, - f.filename(projectID, languageCode, format), + f.filename(projectID, languageCode, format, version), ) } -func (f *FilesystemCache) md5Path(projectID int, languageCode, format string) string { +func (f *FilesystemCache) md5Path(projectID int, languageCode, format, version string) string { return path.Join( f.dir, fmt.Sprintf( "%s.md5", - f.filename(projectID, languageCode, format), + f.filename(projectID, languageCode, format, version), ), ) } -func (f *FilesystemCache) filename(projectID int, languageCode, format string) string { - return fmt.Sprintf("%d_%s_%s", projectID, languageCode, format) +func (f *FilesystemCache) filename(projectID int, languageCode, format, version string) string { + return fmt.Sprintf("%d_%s_%s_%s", projectID, languageCode, format, version) } func (f *FilesystemCache) removeFilesWithPrefix(prefix string) error { diff --git a/internal/cache/redis.go b/internal/cache/redis.go index 77a48b1..42ec41e 100644 --- a/internal/cache/redis.go +++ b/internal/cache/redis.go @@ -48,8 +48,8 @@ func NewRedisCache(c *redis.Client, ttl time.Duration) *RedisCache { } } -func (r *RedisCache) GetTranslation(ctx context.Context, projectID int, languageCode, format string) (*CacheItem, error) { - key := r.key(projectID, languageCode, format) +func (r *RedisCache) GetTranslation(ctx context.Context, projectID int, languageCode, format, version string) (*CacheItem, error) { + key := r.key(projectID, languageCode, format, version) var item RedisCacheItem err := r.rc.Get(ctx, key, &item) @@ -68,8 +68,8 @@ func (r *RedisCache) GetTranslation(ctx context.Context, projectID int, language }, nil } -func (r *RedisCache) SetTranslation(ctx context.Context, projectID int, languageCode, format string, data []byte) (string, error) { - key := r.key(projectID, languageCode, format) +func (r *RedisCache) SetTranslation(ctx context.Context, projectID int, languageCode, format, version string, data []byte) (string, error) { + key := r.key(projectID, languageCode, format, version) hashBytes := md5.Sum(data) checksum := hex.EncodeToString(hashBytes[:]) @@ -111,8 +111,8 @@ func (r *RedisCache) PurgeProject(ctx context.Context, projectID int) error { return nil } -func (r *RedisCache) key(projectID int, languageCode, format string) string { - return fmt.Sprintf("%d:%s:%s", projectID, languageCode, format) +func (r *RedisCache) key(projectID int, languageCode, format, version string) string { + return fmt.Sprintf("%d:%s:%s:%s", projectID, languageCode, format, version) } func (r *RedisCache) deleteKeysMatching(ctx context.Context, pattern string) error { diff --git a/internal/project/entity.go b/internal/project/entity.go new file mode 100644 index 0000000..dac7e13 --- /dev/null +++ b/internal/project/entity.go @@ -0,0 +1,16 @@ +package project + +import "time" + +type Version struct { + ID string + Name string + CreatedAt time.Time +} + +type Project struct { + ID int64 + Name string + NumberOfVersions int + CreatedAt string +} diff --git a/internal/project/errors.go b/internal/project/errors.go new file mode 100644 index 0000000..fb7de47 --- /dev/null +++ b/internal/project/errors.go @@ -0,0 +1,20 @@ +package project + +import ( + "fmt" +) + +type ErrLanguageNotFoundInStorage struct { + ProjectID int + LanguageCode string + Version string +} + +func (e *ErrLanguageNotFoundInStorage) Error() string { + return fmt.Sprintf( + "Project %d does not contain specified language %s with version %s in storage", + e.ProjectID, + e.LanguageCode, + e.Version, + ) +} diff --git a/internal/project/service.go b/internal/project/service.go index 41ae7fd..6643a87 100644 --- a/internal/project/service.go +++ b/internal/project/service.go @@ -1,20 +1,30 @@ +//go:generate mockgen --source=service.go -destination=service_mock.go -package=project + package project import ( "context" - "io/ioutil" + "fmt" + "io" "net/http" + "strconv" + "strings" "time" gosundheit "github.com/AppsFlyer/go-sundheit" "github.com/AppsFlyer/go-sundheit/checks" + "github.com/alitto/pond" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/uniwise/parrot/internal/cache" + "github.com/uniwise/parrot/internal/storage" "github.com/uniwise/parrot/pkg/poedit" "golang.org/x/sync/semaphore" ) +var ErrVersionAlreadyExist = errors.New("Project version already exists") + type Translation struct { TTL time.Duration Checksum string @@ -22,10 +32,15 @@ type Translation struct { } type Service interface { - GetTranslation(ctx context.Context, projectID int, languageCode, format string) (trans *Translation, err error) + GetTranslation(ctx context.Context, projectID int, languageCode, format, version string) (trans *Translation, err error) PurgeTranslation(ctx context.Context, projectID int, languageCode string) (err error) PurgeProject(ctx context.Context, projectID int) (err error) RegisterChecks(h gosundheit.Health) (err error) + GetAllProjects(ctx context.Context) ([]Project, error) + GetProjectByID(ctx context.Context, id int) (*Project, error) + GetProjectVersions(ctx context.Context, projectID int) ([]Version, error) + DeleteProjectVersionByIDAndProjectID(ctx context.Context, ID string, projectID uint) error + CreateLanguagesVersion(ctx context.Context, projectID int, name string) error } type ServiceImpl struct { @@ -34,20 +49,28 @@ type ServiceImpl struct { Cache cache.Cache RenewalThreshold time.Duration PreFetchSemaphore *semaphore.Weighted + storage storage.Storage + generateUUID func() (string, error) + generateTimestamp func() int64 + getContentMetaMap func() map[string]poedit.ContentMeta } -func NewService(cli poedit.Client, cache cache.Cache, renewalThreshold time.Duration, entry *logrus.Entry) *ServiceImpl { +func NewService(cli poedit.Client, storage storage.Storage, cache cache.Cache, renewalThreshold time.Duration, entry *logrus.Entry) *ServiceImpl { return &ServiceImpl{ Logger: entry, Client: cli, Cache: cache, RenewalThreshold: renewalThreshold, PreFetchSemaphore: semaphore.NewWeighted(1), + storage: storage, + generateUUID: GenerateUUID, + generateTimestamp: GenerateTimestamp, + getContentMetaMap: GetContentMetaMap, } } -func (s *ServiceImpl) GetTranslation(ctx context.Context, projectID int, languageCode, format string) (*Translation, error) { - item, err := s.Cache.GetTranslation(ctx, projectID, languageCode, format) +func (s *ServiceImpl) GetTranslation(ctx context.Context, projectID int, languageCode, format, version string) (*Translation, error) { + item, err := s.Cache.GetTranslation(ctx, projectID, languageCode, format, version) if err != nil && !errors.Is(err, cache.ErrCacheMiss) { return nil, err } @@ -64,7 +87,7 @@ func (s *ServiceImpl) GetTranslation(ctx context.Context, projectID int, languag s.Logger.Debugf("Pre-fetching language %s format %s for project %d", languageCode, format, projectID) - _, _, err := s.fetchAndCacheTranslation(context.Background(), projectID, languageCode, format) + _, _, err := s.fetchAndCacheTranslation(context.Background(), projectID, languageCode, format, version) if err != nil { s.Logger.Errorf("Failed to pre-fetch language %s format %s for project %d", languageCode, format, projectID) } @@ -78,7 +101,7 @@ func (s *ServiceImpl) GetTranslation(ctx context.Context, projectID int, languag }, nil } - data, checksum, err := s.fetchAndCacheTranslation(ctx, projectID, languageCode, format) + data, checksum, err := s.fetchAndCacheTranslation(ctx, projectID, languageCode, format, version) if err != nil { return nil, err } @@ -98,7 +121,72 @@ func (s *ServiceImpl) PurgeProject(ctx context.Context, projectID int) error { return errors.New("Not implemented") } -func (s *ServiceImpl) fetchAndCacheTranslation(ctx context.Context, projectID int, languageCode, format string) ([]byte, string, error) { +func (s *ServiceImpl) fetchAndCacheTranslation(ctx context.Context, projectID int, languageCode, format, version string) ([]byte, string, error) { + data, err := s.fetchTranslation(ctx, projectID, languageCode, format, version) + if err != nil { + return nil, "", err + } + + checksum, err := s.Cache.SetTranslation(ctx, projectID, languageCode, format, version, data) + if err != nil { + return nil, "", err + } + + return data, checksum, nil +} + +func (s *ServiceImpl) fetchTranslation(ctx context.Context, projectID int, languageCode, format, version string) ([]byte, error) { + if version == "latest" { + return s.getProjectTranslationFromPOedit(ctx, projectID, languageCode, format) + } + return s.getProjectTranslationFromS3(ctx, projectID, version, languageCode, format) +} + +func (s *ServiceImpl) getProjectTranslationFromS3(ctx context.Context, projectID int, versionName, languageCode, format string) ([]byte, error) { + s3Output, err := s.storage.ListObjects(ctx, fmt.Sprintf("%d/", projectID)) + if err != nil { + return nil, errors.Wrap(err, "failed to get project versions") + } + + s3Key := "" + for _, object := range s3Output.CommonPrefixes { + // Example Prefix : {projectID}/{versionID_versionName_timestamp}/ + // 720964/61ded6dc-c8b7-4d4e-aa70-cd37dd1216b3_v2_123456789/ + prefixData := strings.Split(aws.ToString(object.Prefix), "/") + + versionData := strings.Split(prefixData[1], "_") + version := versionData[1] + + if version == versionName { + contentMeta, _ := poedit.GetContentMeta(format) + s3Key = fmt.Sprintf("%s%s.%s", aws.ToString(object.Prefix), languageCode, contentMeta.Extension) + break + } + } + + if s3Key != "" { + + reader, err := s.storage.GetObject(ctx, s3Key) + if err != nil { + return nil, errors.Wrap(err, "Failed to get object from S3") + } + + data, err := io.ReadAll(reader.Body) + if err != nil { + return nil, errors.Wrap(err, "Failed to read object from S3") + } + + return data, nil + } + + return nil, &ErrLanguageNotFoundInStorage{ + ProjectID: projectID, + LanguageCode: languageCode, + Version: versionName, + } +} + +func (s *ServiceImpl) getProjectTranslationFromPOedit(ctx context.Context, projectID int, languageCode, format string) ([]byte, error) { resp, err := s.Client.ExportProject(ctx, poedit.ExportProjectRequest{ ID: projectID, Language: languageCode, @@ -106,31 +194,26 @@ func (s *ServiceImpl) fetchAndCacheTranslation(ctx context.Context, projectID in Filters: []string{"translated"}, }) if err != nil { - return nil, "", err + return nil, err } // TODO: Make use of injected http client, to support timeouts d, err := http.Get(resp.Result.URL) if err != nil { - return nil, "", err + return nil, err } defer d.Body.Close() if d.StatusCode != http.StatusOK { - return nil, "", errors.Errorf("Response code '%d' from download GET", d.StatusCode) + return nil, errors.Errorf("Response code '%d' from download GET", d.StatusCode) } - data, err := ioutil.ReadAll(d.Body) + data, err := io.ReadAll(d.Body) if err != nil { - return nil, "", err - } - - checksum, err := s.Cache.SetTranslation(ctx, projectID, languageCode, format, data) - if err != nil { - return nil, "", err + return nil, err } - return data, checksum, nil + return data, nil } func (s *ServiceImpl) RegisterChecks(h gosundheit.Health) error { @@ -145,3 +228,218 @@ func (s *ServiceImpl) RegisterChecks(h gosundheit.Health) error { return nil } + +func (s *ServiceImpl) GetAllProjects(ctx context.Context) ([]Project, error) { + var projects []Project + + projectsResponse, err := s.Client.ListProjects(ctx) + if err != nil { + return nil, errors.Wrap(err, "failed to get projects from poeditor") + } + + for _, project := range projectsResponse.Result.Projects { + s3Output, err := s.storage.ListObjects(ctx, fmt.Sprintf("%d/", project.ID)) + if err != nil { + return nil, errors.Wrap(err, "failed to get project versions from S3") + } + + numberOfVersions := len(s3Output.CommonPrefixes) + + projects = append(projects, Project{ + ID: project.ID, + Name: project.Name, + NumberOfVersions: numberOfVersions, + CreatedAt: project.Created, + }) + } + + return projects, nil +} + +func (s *ServiceImpl) GetProjectByID(ctx context.Context, id int) (*Project, error) { + projectResponse, err := s.Client.ViewProject(ctx, poedit.ViewProjectRequest{ + ID: id, + }) + if err != nil { + return nil, errors.Wrap(err, "failed to view project") + } + + projectResult := projectResponse.Result.Project + s3Output, err := s.storage.ListObjects(ctx, fmt.Sprintf("%d/", id)) + if err != nil { + return nil, errors.Wrap(err, "failed to get project versions") + } + + numberOfVersions := len(s3Output.CommonPrefixes) + + project := &Project{ + ID: projectResult.ID, + Name: projectResult.Name, + NumberOfVersions: numberOfVersions, + CreatedAt: projectResult.Created, + } + + return project, nil +} + +func (s *ServiceImpl) GetProjectVersions(ctx context.Context, projectID int) ([]Version, error) { + var versions []Version + + s3Output, err := s.storage.ListObjects(ctx, fmt.Sprintf("%d/", projectID)) + if err != nil { + return nil, errors.Wrap(err, "failed to get project versions") + } + + for _, object := range s3Output.CommonPrefixes { + // Example Prefix : {projectID}/{versionID_versionName_timestamp}/ + // 720964/61ded6dc-c8b7-4d4e-aa70-cd37dd1216b3_v2_123456789/ + prefixData := strings.Split(aws.ToString(object.Prefix), "/") + + versionData := strings.Split(prefixData[1], "_") + versionID := versionData[0] + versionName := versionData[1] + versionTimestamp, err := strconv.ParseInt(versionData[2], 10, 64) + if err != nil { + return nil, errors.Wrap(err, "failed to parse timestamp") + } + + versions = append(versions, Version{ + ID: versionID, + Name: versionName, + CreatedAt: time.Unix(versionTimestamp, 0), + }) + } + + return versions, nil +} + +func (s *ServiceImpl) DeleteProjectVersionByIDAndProjectID(ctx context.Context, ID string, projectID uint) error { + storageKey := fmt.Sprintf("%d/%s", projectID, ID) + if err := s.storage.DeleteObjects(ctx, storageKey); err != nil { + return errors.Wrap(err, "Failed to delete project version in S3") + } + + return nil +} + +func (s *ServiceImpl) checkProjectVersionExistInS3(ctx context.Context, projectID int, name string) (bool, error) { + s3Output, err := s.storage.ListObjects(ctx, fmt.Sprintf("%d/", projectID)) + if err != nil { + return false, errors.Wrap(err, "failed to get project versions") + } + + for _, object := range s3Output.CommonPrefixes { + // Example Prefix : {projectID}/{versionID_versionName_timestamp}/ + // 720964/61ded6dc-c8b7-4d4e-aa70-cd37dd1216b3_1.0.0_123456789/ + prefixData := strings.Split(aws.ToString(object.Prefix), "/") + + versionData := strings.Split(prefixData[1], "_") + versionName := versionData[1] + + if versionName == name { + return true, nil + } + } + + return false, nil +} + +func (s *ServiceImpl) CreateLanguagesVersion(ctx context.Context, projectID int, name string) error { + exists, err := s.checkProjectVersionExistInS3(ctx, projectID, name) + if err != nil { + return errors.Wrap(err, "Failed to check project version existence in S3") + } + if exists { + return ErrVersionAlreadyExist + } + languagesResponse, err := s.Client.ListProjectLanguages(ctx, poedit.ListProjectLanguagesRequest{ + ID: projectID, + }) + if err != nil { + return errors.Wrap(err, "Failed to list project languages") + } + + uuid, err := s.generateUUID() + if err != nil { + return errors.Wrap(err, "Failed to generate UUID") + } + + timeStamp := s.generateTimestamp() + contentMetaMap := s.getContentMetaMap() + + jobErrors := []error{} + pool := pond.New(10, 1000) // 10 workers, 1000 buffered jobs + + versionKey := fmt.Sprintf("%d/%s_%s_%d", projectID, uuid, name, timeStamp) + + for _, language := range languagesResponse.Result.Languages { + for contentMetaKey, contentMeta := range contentMetaMap { + pool.Submit(func() { + resp, err := s.Client.ExportProject(ctx, poedit.ExportProjectRequest{ + ID: projectID, + Language: language.Code, + Type: contentMetaKey, + Filters: []string{"translated"}, + }) + if err != nil { + jobErrors = append(jobErrors, errors.Wrap(err, "Failed to export project")) + return + } + + d, err := http.Get(resp.Result.URL) + if err != nil { + jobErrors = append(jobErrors, errors.Wrap(err, "Failed to download project language file")) + return + } + defer d.Body.Close() + + if d.StatusCode != http.StatusOK { + jobErrors = append(jobErrors, errors.Errorf("Response code '%d' from download GET", d.StatusCode)) + return + } + + s3Key := fmt.Sprintf("%s/%s.%s", versionKey, language.Code, contentMeta.Extension) + + meta := map[string]string{ + "project": fmt.Sprintf("%d", projectID), + "lang": language.Code, + "versionName": name, + "format": contentMeta.Extension, + } + + l := s.Logger.WithFields(logrus.Fields{ + "project": projectID, + "lang": language.Code, + "versionName": name, + "format": contentMeta.Extension, + "key": s3Key, + }) + + l.Debug("Uploading") + + if err := s.storage.PutObject(ctx, s3Key, d.Body, meta, contentMeta.Type); err != nil { + jobErrors = append(jobErrors, errors.Wrap(err, "Failed to upload project language file to S3")) + return + } + + l.Debug("Done uploading") + }) + } + } + + pool.StopAndWait() + + if len(jobErrors) > 0 { + if err := s.storage.DeleteObjects(ctx, versionKey); err != nil { + return errors.Wrap(err, "Failed to delete project version in S3") + } + + var err = errors.New("Failed to create language versions") + for _, e := range jobErrors { + err = errors.Wrap(err, e.Error()) + } + return err + } + + return nil +} diff --git a/internal/project/service_mock.go b/internal/project/service_mock.go new file mode 100644 index 0000000..a4eacd5 --- /dev/null +++ b/internal/project/service_mock.go @@ -0,0 +1,171 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: service.go + +// Generated by this command: +// +// mockgen --source=service.go -destination=service_mock.go -package=project +// + +// Package project is a generated GoMock package. +package project + +import ( + context "context" + reflect "reflect" + + go_sundheit "github.com/AppsFlyer/go-sundheit" + gomock "go.uber.org/mock/gomock" +) + +// MockService is a mock of Service interface. +type MockService struct { + ctrl *gomock.Controller + recorder *MockServiceMockRecorder +} + +// MockServiceMockRecorder is the mock recorder for MockService. +type MockServiceMockRecorder struct { + mock *MockService +} + +// NewMockService creates a new mock instance. +func NewMockService(ctrl *gomock.Controller) *MockService { + mock := &MockService{ctrl: ctrl} + mock.recorder = &MockServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockService) EXPECT() *MockServiceMockRecorder { + return m.recorder +} + +// CreateLanguagesVersion mocks base method. +func (m *MockService) CreateLanguagesVersion(ctx context.Context, projectID int, name string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateLanguagesVersion", ctx, projectID, name) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateLanguagesVersion indicates an expected call of CreateLanguagesVersion. +func (mr *MockServiceMockRecorder) CreateLanguagesVersion(ctx, projectID, name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLanguagesVersion", reflect.TypeOf((*MockService)(nil).CreateLanguagesVersion), ctx, projectID, name) +} + +// DeleteProjectVersionByIDAndProjectID mocks base method. +func (m *MockService) DeleteProjectVersionByIDAndProjectID(ctx context.Context, ID string, projectID uint) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteProjectVersionByIDAndProjectID", ctx, ID, projectID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteProjectVersionByIDAndProjectID indicates an expected call of DeleteProjectVersionByIDAndProjectID. +func (mr *MockServiceMockRecorder) DeleteProjectVersionByIDAndProjectID(ctx, ID, projectID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteProjectVersionByIDAndProjectID", reflect.TypeOf((*MockService)(nil).DeleteProjectVersionByIDAndProjectID), ctx, ID, projectID) +} + +// GetAllProjects mocks base method. +func (m *MockService) GetAllProjects(ctx context.Context) ([]Project, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAllProjects", ctx) + ret0, _ := ret[0].([]Project) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAllProjects indicates an expected call of GetAllProjects. +func (mr *MockServiceMockRecorder) GetAllProjects(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllProjects", reflect.TypeOf((*MockService)(nil).GetAllProjects), ctx) +} + +// GetProjectByID mocks base method. +func (m *MockService) GetProjectByID(ctx context.Context, id int) (*Project, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetProjectByID", ctx, id) + ret0, _ := ret[0].(*Project) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetProjectByID indicates an expected call of GetProjectByID. +func (mr *MockServiceMockRecorder) GetProjectByID(ctx, id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProjectByID", reflect.TypeOf((*MockService)(nil).GetProjectByID), ctx, id) +} + +// GetProjectVersions mocks base method. +func (m *MockService) GetProjectVersions(ctx context.Context, projectID int) ([]Version, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetProjectVersions", ctx, projectID) + ret0, _ := ret[0].([]Version) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetProjectVersions indicates an expected call of GetProjectVersions. +func (mr *MockServiceMockRecorder) GetProjectVersions(ctx, projectID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProjectVersions", reflect.TypeOf((*MockService)(nil).GetProjectVersions), ctx, projectID) +} + +// GetTranslation mocks base method. +func (m *MockService) GetTranslation(ctx context.Context, projectID int, languageCode, format, version string) (*Translation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTranslation", ctx, projectID, languageCode, format, version) + ret0, _ := ret[0].(*Translation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTranslation indicates an expected call of GetTranslation. +func (mr *MockServiceMockRecorder) GetTranslation(ctx, projectID, languageCode, format, version interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTranslation", reflect.TypeOf((*MockService)(nil).GetTranslation), ctx, projectID, languageCode, format, version) +} + +// PurgeProject mocks base method. +func (m *MockService) PurgeProject(ctx context.Context, projectID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PurgeProject", ctx, projectID) + ret0, _ := ret[0].(error) + return ret0 +} + +// PurgeProject indicates an expected call of PurgeProject. +func (mr *MockServiceMockRecorder) PurgeProject(ctx, projectID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PurgeProject", reflect.TypeOf((*MockService)(nil).PurgeProject), ctx, projectID) +} + +// PurgeTranslation mocks base method. +func (m *MockService) PurgeTranslation(ctx context.Context, projectID int, languageCode string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PurgeTranslation", ctx, projectID, languageCode) + ret0, _ := ret[0].(error) + return ret0 +} + +// PurgeTranslation indicates an expected call of PurgeTranslation. +func (mr *MockServiceMockRecorder) PurgeTranslation(ctx, projectID, languageCode interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PurgeTranslation", reflect.TypeOf((*MockService)(nil).PurgeTranslation), ctx, projectID, languageCode) +} + +// RegisterChecks mocks base method. +func (m *MockService) RegisterChecks(h go_sundheit.Health) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RegisterChecks", h) + ret0, _ := ret[0].(error) + return ret0 +} + +// RegisterChecks indicates an expected call of RegisterChecks. +func (mr *MockServiceMockRecorder) RegisterChecks(h interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterChecks", reflect.TypeOf((*MockService)(nil).RegisterChecks), h) +} diff --git a/internal/project/service_test.go b/internal/project/service_test.go new file mode 100644 index 0000000..3dcde99 --- /dev/null +++ b/internal/project/service_test.go @@ -0,0 +1,575 @@ +package project + +import ( + "bytes" + "context" + "fmt" + "io" + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/jarcoal/httpmock" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/uniwise/parrot/internal/cache" + "github.com/uniwise/parrot/internal/storage" + "github.com/uniwise/parrot/pkg/poedit" + gomock "go.uber.org/mock/gomock" +) + +var ( + errTest = errors.New("test error") + testCtx = context.Background() + testID uint = 1 + testName string = "testname" + testProjectID uint = 1 + testStorageKeyForDeletion string = "1/test-uuid" + testStorageKeyForListing string = "1/" + testCreatedAt time.Time = time.Now() + testRenewalThreshold = time.Hour + testUUID = "test-uuid" + testGenerateUUID = func() (string, error) { + return testUUID, nil + } + testGenerateTimestamp = func() int64 { + return testCreatedAt.Unix() + } + testContentMetaMap = map[string]poedit.ContentMeta{ + "key_value_json": {Extension: "json", Type: "application/json"}, + } + testGetContentMetaMap = func() map[string]poedit.ContentMeta { + return testContentMetaMap + } +) + +func TestServiceGetAllProjects(t *testing.T) { + t.Parallel() + + controller := gomock.NewController(t) + storage := storage.NewMockStorage(controller) + client := poedit.NewMockClient(controller) + + service := NewService(client, storage, nil, testRenewalThreshold, nil) + + listProjectsResponse := &poedit.ListProjectsResponse{ + Result: struct { + Projects []struct { + ID int64 `json:"id"` + Name string `json:"name"` + Public int64 `json:"public"` + Open int64 `json:"open"` + Created string `json:"created"` + } `json:"projects"` + }{ + Projects: []struct { + ID int64 `json:"id"` + Name string `json:"name"` + Public int64 `json:"public"` + Open int64 `json:"open"` + Created string `json:"created"` + }{ + { + ID: int64(testID), + Name: testName, + Created: testCreatedAt.String(), + }, + }, + }, + } + + prefix := fmt.Sprintf("%d/%s_%s_%d", testID, testUUID, testName, testGenerateTimestamp()) + + listObjectsV2Output := &s3.ListObjectsV2Output{ + CommonPrefixes: []types.CommonPrefix{ + { + Prefix: &prefix, + }, + }, + } + + t.Run("Success", func(t *testing.T) { + client.EXPECT().ListProjects(testCtx).Return(listProjectsResponse, nil) + + storage.EXPECT().ListObjects(testCtx, testStorageKeyForListing).Times(1).Return(listObjectsV2Output, nil) + + projects, err := service.GetAllProjects(testCtx) + + assert.NoError(t, err) + assert.Equal(t, int64(testID), projects[0].ID) + assert.Equal(t, testName, projects[0].Name) + assert.Equal(t, 1, projects[0].NumberOfVersions) + assert.Equal(t, testCreatedAt.String(), projects[0].CreatedAt) + }) + + t.Run("Failure, client fail", func(t *testing.T) { + client.EXPECT().ListProjects(testCtx).Return(nil, errTest) + + projects, err := service.GetAllProjects(testCtx) + + assert.Error(t, err) + assert.ErrorIs(t, err, errTest) + assert.Nil(t, projects) + }) + + t.Run("Failure, storage fail", func(t *testing.T) { + client.EXPECT().ListProjects(testCtx).Return(listProjectsResponse, nil) + + storage.EXPECT().ListObjects(testCtx, testStorageKeyForListing).Times(1).Return(nil, errTest) + + projects, err := service.GetAllProjects(testCtx) + + assert.Error(t, err) + assert.ErrorIs(t, err, errTest) + assert.Nil(t, projects) + }) +} + +func TestServiceGetProjectById(t *testing.T) { + t.Parallel() + + controller := gomock.NewController(t) + storage := storage.NewMockStorage(controller) + client := poedit.NewMockClient(controller) + service := NewService(client, storage, nil, testRenewalThreshold, nil) + + projectResponse := &poedit.ViewProjectResponse{ + Result: struct { + Project struct { + ID int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Public int64 `json:"public"` + Open int64 `json:"open"` + ReferenceLanguage string `json:"reference_language"` // nolint:tagliatelle + Terms int64 `json:"terms"` + Created string `json:"created"` + } `json:"project"` + }{ + Project: struct { + ID int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Public int64 `json:"public"` + Open int64 `json:"open"` + ReferenceLanguage string `json:"reference_language"` // nolint:tagliatelle + Terms int64 `json:"terms"` + Created string `json:"created"` + }{ + ID: int64(testID), + Name: testName, + Created: testCreatedAt.String(), + }, + }, + } + + prefix := fmt.Sprintf("%d/%s_%s_%d", testID, testUUID, testName, testGenerateTimestamp()) + + listObjectsV2Output := &s3.ListObjectsV2Output{ + CommonPrefixes: []types.CommonPrefix{ + { + Prefix: &prefix, + }, + }, + } + + t.Run("Success", func(t *testing.T) { + client.EXPECT().ViewProject(testCtx, poedit.ViewProjectRequest{ + ID: int(testID), + }).Return(projectResponse, nil) + + storage.EXPECT().ListObjects(testCtx, testStorageKeyForListing).Times(1).Return(listObjectsV2Output, nil) + + project, err := service.GetProjectByID(testCtx, int(testID)) + + assert.NoError(t, err) + assert.Equal(t, int64(testID), (*project).ID) + assert.Equal(t, testName, (*project).Name) + assert.Equal(t, 1, (*project).NumberOfVersions) + assert.Equal(t, testCreatedAt.String(), (*project).CreatedAt) + }) + + t.Run("Failure, client fail", func(t *testing.T) { + client.EXPECT().ViewProject(testCtx, poedit.ViewProjectRequest{ + ID: int(testID), + }).Return(nil, errTest) + + project, err := service.GetProjectByID(testCtx, int(testID)) + + assert.Error(t, err) + assert.Nil(t, project) + }) + + t.Run("Failure, storage fail", func(t *testing.T) { + client.EXPECT().ViewProject(testCtx, poedit.ViewProjectRequest{ + ID: int(testID), + }).Return(projectResponse, nil) + + storage.EXPECT().ListObjects(testCtx, testStorageKeyForListing).Times(1).Return(nil, errTest) + + project, err := service.GetProjectByID(testCtx, int(testID)) + + assert.Error(t, err) + assert.ErrorIs(t, err, errTest) + assert.Nil(t, project) + }) +} + +func TestServiceGetProjectVersions(t *testing.T) { + t.Parallel() + + storage := storage.NewMockStorage(gomock.NewController(t)) + service := NewService(nil, storage, nil, testRenewalThreshold, nil) + + prefix := fmt.Sprintf("%d/%s_%s_%d", testID, testUUID, testName, testGenerateTimestamp()) + + listObjectsV2Output := &s3.ListObjectsV2Output{ + CommonPrefixes: []types.CommonPrefix{ + { + Prefix: &prefix, + }, + }, + } + + t.Run("Success", func(t *testing.T) { + storage.EXPECT().ListObjects(testCtx, testStorageKeyForListing).Times(1).Return(listObjectsV2Output, nil) + + versions, err := service.GetProjectVersions(testCtx, int(testProjectID)) + + assert.NoError(t, err) + assert.Equal(t, testUUID, versions[0].ID) + assert.Equal(t, testName, versions[0].Name) + assert.Equal(t, testCreatedAt.Unix(), versions[0].CreatedAt.Unix()) + }) + + t.Run("Failure", func(t *testing.T) { + storage.EXPECT().ListObjects(testCtx, testStorageKeyForListing).Times(1).Return(nil, errTest) + + versions, err := service.GetProjectVersions(testCtx, int(testProjectID)) + + assert.Error(t, err) + assert.ErrorIs(t, err, errTest) + assert.Nil(t, versions) + }) +} + +func TestServiceDeleteProjectVersionByIDAndVersionID(t *testing.T) { + t.Parallel() + + storage := storage.NewMockStorage(gomock.NewController(t)) + service := NewService(nil, storage, nil, testRenewalThreshold, nil) + + t.Run("fail, fails to delete objects in S3", func(t *testing.T) { + storage.EXPECT().DeleteObjects(testCtx, testStorageKeyForDeletion).Times(1).Return(errTest) + + err := service.DeleteProjectVersionByIDAndProjectID(testCtx, testUUID, testProjectID) + + assert.ErrorIs(t, err, errTest) + assert.ErrorContains(t, err, "Failed to delete project version in S3") + }) + + t.Run("success", func(t *testing.T) { + storage.EXPECT().DeleteObjects(testCtx, testStorageKeyForDeletion).Times(1).Return(nil) + + err := service.DeleteProjectVersionByIDAndProjectID(testCtx, testUUID, testProjectID) + + assert.NoError(t, err) + }) +} + +func TestServiceCreateLanguagesVersion(t *testing.T) { + t.Parallel() + + controller := gomock.NewController(t) + storage := storage.NewMockStorage(controller) + poeditClient := poedit.NewMockClient(controller) + + service := NewService(poeditClient, storage, nil, testRenewalThreshold, logrus.NewEntry(logrus.New())) + service.generateUUID = testGenerateUUID + service.generateTimestamp = testGenerateTimestamp + service.getContentMetaMap = testGetContentMetaMap + + listProjectLanguagesRequest := poedit.ListProjectLanguagesRequest{ + ID: int(testProjectID), + } + + listAvailableLanguagesResponse := &poedit.ListProjectLanguagesResponse{ + Result: struct { + Languages []struct { + Name string `json:"name"` + Code string `json:"code"` + Translations int64 `json:"translations"` + Percentage float64 `json:"percentage"` + Updated string `json:"updated"` + } `json:"languages"` + }{ + Languages: []struct { + Name string `json:"name"` + Code string `json:"code"` + Translations int64 `json:"translations"` + Percentage float64 `json:"percentage"` + Updated string `json:"updated"` + }{ + { + Name: "English", + Code: "en", + }, + }, + }, + } + + prefix := fmt.Sprintf("%d/%s_%s_%d", testID, testUUID, "1.0.0", testGenerateTimestamp()) + + listObjectsV2Output := &s3.ListObjectsV2Output{ + CommonPrefixes: []types.CommonPrefix{ + { + Prefix: &prefix, + }, + }, + } + + exportProjectRequest := poedit.ExportProjectRequest{ + ID: int(testProjectID), + Language: "en", + Type: "key_value_json", + Filters: []string{"translated"}, + } + + exportProjectResponse := &poedit.ExportProjectResponse{ + Result: struct { + URL string `json:"url"` + }{ + URL: "http://example.com/file.json", + }, + } + + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + fileName := fmt.Sprintf("%d/%s_%s_%d/%s.json", testID, testUUID, testName, testGenerateTimestamp(), "en") + + t.Run("Success", func(t *testing.T) { + storage.EXPECT().ListObjects(testCtx, testStorageKeyForListing).Times(1).Return(listObjectsV2Output, nil) + poeditClient.EXPECT().ListProjectLanguages(testCtx, listProjectLanguagesRequest).Return(listAvailableLanguagesResponse, nil) + poeditClient.EXPECT().ExportProject(testCtx, exportProjectRequest).Return(exportProjectResponse, nil) + + httpmock.RegisterResponder("GET", "http://example.com/file.json", + httpmock.NewStringResponder(200, `{"key":"value"}`)) + + storage.EXPECT().PutObject(testCtx, fileName, gomock.Any(), gomock.Any(), "application/json").Return(nil) + + err := service.CreateLanguagesVersion(testCtx, int(testProjectID), testName) + + assert.NoError(t, err) + }) + + t.Run("Fail, version already exist in S3", func(t *testing.T) { + prefixExist := fmt.Sprintf("%d/%s_%s_%d", testID, testUUID, testName, testGenerateTimestamp()) + + listObjectsV2Output := &s3.ListObjectsV2Output{ + CommonPrefixes: []types.CommonPrefix{ + { + Prefix: &prefixExist, + }, + }, + } + + storage.EXPECT().ListObjects(testCtx, testStorageKeyForListing).Times(1).Return(listObjectsV2Output, nil) + + err := service.CreateLanguagesVersion(testCtx, int(testProjectID), testName) + + assert.ErrorIs(t, err, ErrVersionAlreadyExist) + assert.ErrorContains(t, err, "Project version already exists") + }) + + t.Run("Fail, fails to list project languages", func(t *testing.T) { + storage.EXPECT().ListObjects(testCtx, testStorageKeyForListing).Times(1).Return(listObjectsV2Output, nil) + poeditClient.EXPECT().ListProjectLanguages(testCtx, listProjectLanguagesRequest).Return(nil, errTest) + + err := service.CreateLanguagesVersion(testCtx, int(testProjectID), testName) + + assert.ErrorIs(t, err, errTest) + }) + + t.Run("Fail, fails to export project", func(t *testing.T) { + fileName := fmt.Sprintf("%d/%s_%s_%d", testID, testUUID, testName, testGenerateTimestamp()) + storage.EXPECT().ListObjects(testCtx, testStorageKeyForListing).Times(1).Return(listObjectsV2Output, nil) + poeditClient.EXPECT().ListProjectLanguages(testCtx, listProjectLanguagesRequest).Return(listAvailableLanguagesResponse, nil) + poeditClient.EXPECT().ExportProject(testCtx, exportProjectRequest).Return(nil, errTest) + storage.EXPECT().DeleteObjects(testCtx, fileName).Times(1).Return(nil) + err := service.CreateLanguagesVersion(testCtx, int(testProjectID), testName) + + assert.ErrorContains(t, err, "Failed to create language versions") + }) + + t.Run("Fail, fails to download file", func(t *testing.T) { + fileName := fmt.Sprintf("%d/%s_%s_%d", testID, testUUID, testName, testGenerateTimestamp()) + storage.EXPECT().ListObjects(testCtx, testStorageKeyForListing).Times(1).Return(listObjectsV2Output, nil) + poeditClient.EXPECT().ListProjectLanguages(testCtx, listProjectLanguagesRequest).Return(listAvailableLanguagesResponse, nil) + poeditClient.EXPECT().ExportProject(testCtx, exportProjectRequest).Return(exportProjectResponse, nil) + storage.EXPECT().DeleteObjects(testCtx, fileName).Times(1).Return(nil) + + httpmock.RegisterResponder("GET", "http://example.com/file.json", + httpmock.NewErrorResponder(errTest)) + + err := service.CreateLanguagesVersion(testCtx, int(testProjectID), testName) + + assert.ErrorContains(t, err, "Failed to create language versions") + }) + + t.Run("Fail, fails to upload file to S3", func(t *testing.T) { + fileNameForDeletion := fmt.Sprintf("%d/%s_%s_%d", testID, testUUID, testName, testGenerateTimestamp()) + storage.EXPECT().ListObjects(testCtx, testStorageKeyForListing).Times(1).Return(listObjectsV2Output, nil) + poeditClient.EXPECT().ListProjectLanguages(testCtx, listProjectLanguagesRequest).Return(listAvailableLanguagesResponse, nil) + poeditClient.EXPECT().ExportProject(testCtx, exportProjectRequest).Return(exportProjectResponse, nil) + + httpmock.RegisterResponder("GET", "http://example.com/file.json", + httpmock.NewStringResponder(200, `{"key":"value"}`)) + + storage.EXPECT().PutObject(testCtx, fileName, gomock.Any(), gomock.Any(), "application/json").Return(errTest) + storage.EXPECT().DeleteObjects(testCtx, fileNameForDeletion).Times(1).Return(nil) + + err := service.CreateLanguagesVersion(testCtx, int(testProjectID), testName) + + assert.ErrorContains(t, err, "Failed to create language versions") + }) +} + +func TestServiceGetTranslation(t *testing.T) { + t.Parallel() + + controller := gomock.NewController(t) + storage := storage.NewMockStorage(controller) + poeditClient := poedit.NewMockClient(controller) + cacheClient := cache.NewMockCache(controller) + + service := NewService(poeditClient, storage, cacheClient, testRenewalThreshold, logrus.NewEntry(logrus.New())) + + prefix := fmt.Sprintf("%d/%s_%s_%d/", testID, testUUID, testName, testGenerateTimestamp()) + + listObjectsV2Output := &s3.ListObjectsV2Output{ + CommonPrefixes: []types.CommonPrefix{ + { + Prefix: &prefix, + }, + }, + } + + s3Key := fmt.Sprintf("%d/%s_%s_%d/%s.%s", testID, testUUID, testName, testGenerateTimestamp(), "en", "json") + + exportProjectRequest := poedit.ExportProjectRequest{ + ID: int(testProjectID), + Language: "en", + Type: "key_value_json", + Filters: []string{"translated"}, + } + exportProjectResponse := &poedit.ExportProjectResponse{ + Result: struct { + URL string `json:"url"` + }{ + URL: "http://example.com/file.json", + }, + } + + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + t.Run("Success, cache", func(t *testing.T) { + cacheItem := &cache.CacheItem{ + CreatedAt: testCreatedAt.Add(time.Hour), + Checksum: "checksum", + Data: []byte(`{"key":"value"}`), + } + + cacheClient.EXPECT().GetTranslation(testCtx, int(testProjectID), "en", "key_value_json", "v1").Return(cacheItem, nil) + cacheClient.EXPECT().GetTTL().Times(2).Return(time.Hour) + + translation, err := service.GetTranslation(testCtx, int(testProjectID), "en", "key_value_json", "v1") + + assert.NoError(t, err) + assert.Equal(t, cacheItem.Data, translation.Data) + }) + + t.Run("Success, poedit", func(t *testing.T) { + cacheClient.EXPECT().GetTranslation(testCtx, int(testProjectID), "en", "key_value_json", "latest").Return(nil, cache.ErrCacheMiss) + poeditClient.EXPECT().ExportProject(testCtx, exportProjectRequest).Return(exportProjectResponse, nil) + cacheClient.EXPECT().GetTTL().Times(1).Return(time.Hour) + + httpmock.RegisterResponder("GET", "http://example.com/file.json", + httpmock.NewStringResponder(200, `{"key":"value"}`)) + + cacheClient.EXPECT().SetTranslation(testCtx, int(testProjectID), "en", "key_value_json", "latest", gomock.Any()).Return("checksum", nil) + + _, err := service.GetTranslation(testCtx, int(testProjectID), "en", "key_value_json", "latest") + + assert.NoError(t, err) + }) + + t.Run("Success, S3", func(t *testing.T) { + getObjectOutput := &s3.GetObjectOutput{ + Body: io.NopCloser(bytes.NewReader([]byte(`{"key":"value"}`))), + } + + cacheClient.EXPECT().GetTranslation(testCtx, int(testProjectID), "en", "key_value_json", testName).Return(nil, cache.ErrCacheMiss) + storage.EXPECT().ListObjects(testCtx, testStorageKeyForListing).Times(1).Return(listObjectsV2Output, nil) + storage.EXPECT().GetObject(testCtx, s3Key).Return(getObjectOutput, nil) + cacheClient.EXPECT().GetTTL().Times(1).Return(time.Hour) + + cacheClient.EXPECT().SetTranslation(testCtx, int(testProjectID), "en", "key_value_json", testName, gomock.Any()).Return("checksum", nil) + + _, err := service.GetTranslation(testCtx, int(testProjectID), "en", "key_value_json", testName) + + assert.NoError(t, err) + }) + + t.Run("Fail, cache", func(t *testing.T) { + cacheClient.EXPECT().GetTranslation(testCtx, int(testProjectID), "en", "key_value_json", "latest").Return(nil, errTest) + + _, err := service.GetTranslation(testCtx, int(testProjectID), "en", "key_value_json", "latest") + + assert.ErrorIs(t, err, errTest) + }) + + t.Run("Fail, S3 list Objects", func(t *testing.T) { + cacheClient.EXPECT().GetTranslation(testCtx, int(testProjectID), "en", "key_value_json", testName).Return(nil, cache.ErrCacheMiss) + storage.EXPECT().ListObjects(testCtx, testStorageKeyForListing).Times(1).Return(nil, errTest) + + _, err := service.GetTranslation(testCtx, int(testProjectID), "en", "key_value_json", testName) + + assert.ErrorIs(t, err, errTest) + }) + + t.Run("Fail, S3 get object", func(t *testing.T) { + s3Key := fmt.Sprintf("%d/%s_%s_%d/%s.%s", testID, testUUID, testName, testGenerateTimestamp(), "en", "json") + cacheClient.EXPECT().GetTranslation(testCtx, int(testProjectID), "en", "key_value_json", testName).Return(nil, cache.ErrCacheMiss) + storage.EXPECT().ListObjects(testCtx, testStorageKeyForListing).Times(1).Return(listObjectsV2Output, nil) + storage.EXPECT().GetObject(testCtx, s3Key).Return(nil, errTest) + + _, err := service.GetTranslation(testCtx, int(testProjectID), "en", "key_value_json", testName) + + assert.ErrorIs(t, err, errTest) + }) + + t.Run("Fail, poedit export project", func(t *testing.T) { + cacheClient.EXPECT().GetTranslation(testCtx, int(testProjectID), "en", "key_value_json", "latest").Return(nil, cache.ErrCacheMiss) + poeditClient.EXPECT().ExportProject(testCtx, exportProjectRequest).Return(nil, errTest) + + _, err := service.GetTranslation(testCtx, int(testProjectID), "en", "key_value_json", "latest") + + assert.ErrorIs(t, err, errTest) + }) + + t.Run("Fail, poedit fail to download file", func(t *testing.T) { + cacheClient.EXPECT().GetTranslation(testCtx, int(testProjectID), "en", "key_value_json", "latest").Return(nil, cache.ErrCacheMiss) + poeditClient.EXPECT().ExportProject(testCtx, exportProjectRequest).Return(exportProjectResponse, nil) + + httpmock.RegisterResponder("GET", "http://example.com/file.json", + httpmock.NewErrorResponder(errTest)) + + _, err := service.GetTranslation(testCtx, int(testProjectID), "en", "key_value_json", "latest") + + assert.ErrorIs(t, err, errTest) + }) +} diff --git a/internal/project/utilities.go b/internal/project/utilities.go new file mode 100644 index 0000000..1e3d833 --- /dev/null +++ b/internal/project/utilities.go @@ -0,0 +1,25 @@ +package project + +import ( + "time" + + "github.com/google/uuid" + "github.com/uniwise/parrot/pkg/poedit" +) + +func GenerateUUID() (string, error) { + uuid, err := uuid.NewRandom() + if err != nil { + return "", err + } + + return uuid.String(), nil +} + +func GenerateTimestamp() int64 { + return time.Now().Unix() +} + +func GetContentMetaMap() map[string]poedit.ContentMeta { + return poedit.ContentMetaMap +} diff --git a/internal/rest/v1/helpers/validator.go b/internal/rest/v1/helpers/validator.go new file mode 100644 index 0000000..39b8d4f --- /dev/null +++ b/internal/rest/v1/helpers/validator.go @@ -0,0 +1,42 @@ +package helpers + +import ( + "regexp" + + "github.com/go-playground/validator/v10" + "github.com/johngb/langreg" +) + +type Validator struct { + validator *validator.Validate +} + +func NewValidator() (*Validator, error) { + v := validator.New() + + if err := v.RegisterValidation("languageCode", validateLanguageCode); err != nil { + return nil, err + } + + if err := v.RegisterValidation("version", validateVersion); err != nil { + return nil, err + } + + return &Validator{ + validator: v, + }, nil +} + +func (cv *Validator) Validate(i interface{}) error { + return cv.validator.Struct(i) +} + +func validateLanguageCode(fl validator.FieldLevel) bool { + return langreg.IsValidLanguageCode(fl.Field().String()) +} + +func validateVersion(fl validator.FieldLevel) bool { + r := regexp.MustCompile(`^[a-zA-Z0-9.-_]*$`) + + return r.MatchString(fl.Field().String()) +} diff --git a/internal/rest/v1/private/controllers/project.go b/internal/rest/v1/private/controllers/project.go new file mode 100644 index 0000000..756e5f0 --- /dev/null +++ b/internal/rest/v1/private/controllers/project.go @@ -0,0 +1,259 @@ +package controllers + +import ( + "errors" + "net/http" + "time" + + "github.com/labstack/echo/v4" + "github.com/sirupsen/logrus" + "github.com/uniwise/parrot/internal/project" +) + +type getProjectItemResponse struct { + ID int64 `json:"id"` + Name string `json:"name"` + NumberOfVersions int `json:"numberOfVersions"` + CreatedAt string `json:"createdAt"` +} + +type getAllProjectsResponse struct { + Projects []getProjectItemResponse `json:"projects"` +} + +func (h *Handlers) getAllProjects(ctx echo.Context, l *logrus.Entry) error { + l.Debug("Retrieving projects") + + projects, err := h.ProjectService.GetAllProjects(ctx.Request().Context()) + if err != nil { + l.WithError(err).Error("Error retrieving projects") + + return echo.ErrInternalServerError + } + + response := h.newGetAllProjectsResponse(projects) + + return ctx.JSON(http.StatusOK, response) +} + +func (h *Handlers) newGetAllProjectsResponse( + projects []project.Project, +) *getAllProjectsResponse { + response := &getAllProjectsResponse{ + Projects: make([]getProjectItemResponse, len(projects)), + } + + for i, project := range projects { + response.Projects[i] = getProjectItemResponse{ + ID: project.ID, + Name: project.Name, + NumberOfVersions: project.NumberOfVersions, + CreatedAt: project.CreatedAt, + } + } + + return response +} + +type getProjectRequest struct { + ID int `param:"id" validate:"required"` +} + +type getProjectResponse struct { + ID int64 `json:"id"` + Name string `json:"name"` + NumberOfVersions int `json:"numberOfVersions"` + CreatedAt string `json:"createdAt"` +} + +func (h *Handlers) getProject(ctx echo.Context, l *logrus.Entry) error { + req := new(getProjectRequest) + if err := ctx.Bind(req); err != nil { + l.WithError(err).Error("Error binding request") + + return echo.ErrBadRequest + } + + l = l.WithField("project", req.ID) + + l.Debug("Retrieving project") + + project, err := h.ProjectService.GetProjectByID( + ctx.Request().Context(), + req.ID, + ) + if err != nil { + if err.Error() == "failed to get project: not found" { + return echo.ErrNotFound + } + + l.WithError(err).Error("Error retrieving project") + + return echo.ErrInternalServerError + } + + response := h.newGetProjectResponse(*project) + + return ctx.JSON(http.StatusOK, response) +} + +func (h *Handlers) newGetProjectResponse( + project project.Project, +) *getProjectResponse { + return &getProjectResponse{ + ID: project.ID, + Name: project.Name, + NumberOfVersions: project.NumberOfVersions, + CreatedAt: project.CreatedAt, + } +} + +type getProjectVersionsRequest struct { + ProjectID int `param:"id" validate:"required"` +} + +type getProjectVersionsItemResponse struct { + ID string `json:"id"` + Name string `json:"name"` + CreatedAt time.Time `json:"createdAt"` +} + +type getProjectVersionsResponse struct { + Versions []getProjectVersionsItemResponse `json:"versions"` +} + +func (h *Handlers) getProjectVersions(c echo.Context, l *logrus.Entry) error { + req := new(getProjectVersionsRequest) + if err := c.Bind(req); err != nil { + l.WithError(err).Error("Error binding request") + + return echo.ErrBadRequest + } + + l = l.WithField("project", req.ProjectID) + + l.Debug("Retrieving project versions") + + versions, err := h.ProjectService.GetProjectVersions( + c.Request().Context(), + req.ProjectID, + ) + if err != nil { + if err.Error() == "failed to get project versions: not found" { + return echo.ErrNotFound + } + + l.WithError(err).Error("Error retrieving project versions") + + return echo.ErrInternalServerError + } + + response := h.newGetProjectVersionsResponse(versions) + + return c.JSON(http.StatusOK, response) +} + +func (h *Handlers) newGetProjectVersionsResponse( + versions []project.Version, +) *getProjectVersionsResponse { + response := &getProjectVersionsResponse{ + Versions: make([]getProjectVersionsItemResponse, len(versions)), + } + + for i, version := range versions { + response.Versions[i] = getProjectVersionsItemResponse{ + ID: version.ID, + Name: version.Name, + CreatedAt: version.CreatedAt, + } + } + + return response +} + +type deleteProjectVersionRequest struct { + ProjectID uint `param:"project_id" validate:"required,numeric"` + VersionID string `param:"version_id" validate:"required,version,max=20"` +} + +func (h *Handlers) deleteProjectVersion(c echo.Context, l *logrus.Entry) error { + req := new(deleteProjectVersionRequest) + if err := c.Bind(req); err != nil { + l.WithError(err).Error("Error binding request") + + return echo.ErrBadRequest + } + + l = l.WithFields(logrus.Fields{ + "project": req.ProjectID, + "version": req.VersionID, + }) + + l.Debug("Deleting project version") + + err := h.ProjectService.DeleteProjectVersionByIDAndProjectID( + c.Request().Context(), + req.VersionID, + req.ProjectID, + ) + if err != nil { + l.WithError(err).Error("Error deleting project version") + + return echo.ErrInternalServerError + } + + l.Info("Project version deleted") + + return c.NoContent(http.StatusOK) +} + +type postProjectVersionRequest struct { + ID int `param:"id" validate:"required,numeric"` + Name string `json:"name" validate:"required,version,max=20"` +} + +func (h *Handlers) postProjectVersion(c echo.Context, l *logrus.Entry) error { + req := new(postProjectVersionRequest) + if err := c.Bind(req); err != nil { + l.WithError(err).Error("Error binding request") + + return echo.ErrBadRequest + } + + l = l.WithFields(logrus.Fields{ + "project": req.ID, + "name": req.Name, + }) + + l.Debug("Creating project version") + + if err := c.Validate(req); err != nil { + l.WithError(err).Error("Error binding request") + + return echo.ErrBadRequest + } + + l = l.WithFields(logrus.Fields{ + "project": req.ID, + "name": req.Name, + }) + + err := h.ProjectService.CreateLanguagesVersion( + c.Request().Context(), + req.ID, + req.Name, + ) + if err != nil { + if errors.Is(err, project.ErrVersionAlreadyExist) { + return echo.ErrBadRequest + } + + l.WithError(err).Error("Error creating project version") + + return echo.ErrInternalServerError + } + + l.Info("Creating project version") + + return c.NoContent(http.StatusCreated) +} diff --git a/internal/rest/v1/private/controllers/project_test.go b/internal/rest/v1/private/controllers/project_test.go new file mode 100644 index 0000000..d611555 --- /dev/null +++ b/internal/rest/v1/private/controllers/project_test.go @@ -0,0 +1,377 @@ +package controllers + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/http/httptest" + "testing" + "time" + "regexp" + + "github.com/go-playground/validator/v10" + "github.com/labstack/echo/v4" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/uniwise/parrot/internal/project" + + gomock "go.uber.org/mock/gomock" +) + +var ( + errTest = errors.New("test error") + testID int64 = 1 + testVersionID string = "test-id" + testName string = "testname" + testNumberOfVersions int = 3 + testCreatedAt = time.Now() + testCreatedAtString string = testCreatedAt.String() +) + +func TestGetAllProjects(t *testing.T) { + t.Parallel() + + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + resp := httptest.NewRecorder() + + testCtx := e.NewContext(req, resp) + + testCtx.SetPath("/projects") + + projectService := project.NewMockService(gomock.NewController(t)) + + h := &Handlers{ + ProjectService: projectService, + } + t.Run("GetAllProjects, success", func(t *testing.T) { + projectService.EXPECT().GetAllProjects(context.Background()).Times(1).Return([]project.Project{{ + ID: testID, + Name: testName, + NumberOfVersions: testNumberOfVersions, + CreatedAt: testCreatedAtString, + }}, nil) + + err := h.getAllProjects(testCtx, logrus.NewEntry(logrus.New())) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.Code) + + var response getAllProjectsResponse + err = json.Unmarshal(resp.Body.Bytes(), &response) + assert.NoError(t, err) + + assert.Equal(t, response, getAllProjectsResponse{ + Projects: []getProjectItemResponse{ + { + ID: testID, + Name: testName, + NumberOfVersions: testNumberOfVersions, + CreatedAt: testCreatedAtString, + }, + }, + }) + }) + + t.Run("GetAllProjects, error", func(t *testing.T) { + projectService.EXPECT().GetAllProjects(context.Background()).Times(1).Return(nil, errTest) + + err := h.getAllProjects(testCtx, logrus.NewEntry(logrus.New())) + assert.Error(t, err) + }) +} + +func TestHandlers_newGetAllProjectsResponse(t *testing.T) { + t.Parallel() + + projects := []project.Project{{ + ID: testID, + Name: testName, + NumberOfVersions: testNumberOfVersions, + CreatedAt: testCreatedAtString, + }} + + h := &Handlers{} + + response := h.newGetAllProjectsResponse(projects) + + assert.NotNil(t, response) + assert.Len(t, response.Projects, 1) + assert.Equal(t, response.Projects[0].ID, testID) + assert.Equal(t, response.Projects[0].Name, testName) + assert.Equal(t, response.Projects[0].NumberOfVersions, testNumberOfVersions) + assert.Equal(t, response.Projects[0].CreatedAt, testCreatedAtString) +} + +func TestGetProject(t *testing.T) { + t.Parallel() + + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + resp := httptest.NewRecorder() + + testCtx := e.NewContext(req, resp) + + testCtx.SetPath("/projects/:id") + testCtx.SetParamNames("id") + testCtx.SetParamValues(fmt.Sprintf("%d", testID)) + + projectService := project.NewMockService(gomock.NewController(t)) + + h := &Handlers{ + ProjectService: projectService, + } + t.Run("GetProject, success", func(t *testing.T) { + projectService.EXPECT().GetProjectByID(context.Background(), int(testID)).Times(1).Return(&project.Project{ + ID: testID, + Name: testName, + NumberOfVersions: testNumberOfVersions, + CreatedAt: testCreatedAtString, + }, nil) + + err := h.getProject(testCtx, logrus.NewEntry(logrus.New())) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.Code) + + var response getProjectResponse + err = json.Unmarshal(resp.Body.Bytes(), &response) + assert.NoError(t, err) + + assert.Equal(t, response, getProjectResponse{ + ID: testID, + Name: testName, + NumberOfVersions: testNumberOfVersions, + CreatedAt: testCreatedAtString, + }) + }) + + t.Run("GetProject, error", func(t *testing.T) { + projectService.EXPECT().GetProjectByID(context.Background(), int(testID)).Times(1).Return(nil, errTest) + + err := h.getProject(testCtx, logrus.NewEntry(logrus.New())) + assert.Error(t, err) + }) +} + +func TestHandlers_newGetProjectResponse(t *testing.T) { + t.Parallel() + + project := project.Project{ + ID: testID, + Name: testName, + NumberOfVersions: testNumberOfVersions, + CreatedAt: testCreatedAtString, + } + + h := &Handlers{} + + response := h.newGetProjectResponse(project) + + assert.NotNil(t, response) + assert.Equal(t, response.ID, testID) + assert.Equal(t, response.Name, testName) + assert.Equal(t, response.NumberOfVersions, testNumberOfVersions) + assert.Equal(t, response.CreatedAt, testCreatedAtString) +} + +func TestGetProjectVersions(t *testing.T) { + t.Parallel() + + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + resp := httptest.NewRecorder() + + testCtx := e.NewContext(req, resp) + + testCtx.SetPath("/projects/:id/versions") + testCtx.SetParamNames("id") + testCtx.SetParamValues(fmt.Sprintf("%d", testID)) + + projectService := project.NewMockService(gomock.NewController(t)) + + h := &Handlers{ + ProjectService: projectService, + } + t.Run("GetProjectVersions, success", func(t *testing.T) { + projectService.EXPECT().GetProjectVersions(context.Background(), int(testID)).Times(1).Return([]project.Version{{ + ID: testVersionID, + Name: testName, + CreatedAt: testCreatedAt.UTC(), + }}, nil) + + err := h.getProjectVersions(testCtx, logrus.NewEntry(logrus.New())) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.Code) + + var response getProjectVersionsResponse + err = json.Unmarshal(resp.Body.Bytes(), &response) + assert.NoError(t, err) + + assert.Equal(t, response, getProjectVersionsResponse{ + Versions: []getProjectVersionsItemResponse{ + { + ID: testVersionID, + Name: testName, + CreatedAt: testCreatedAt.UTC(), + }, + }, + }) + }) + + t.Run("GetProjectVersions, error", func(t *testing.T) { + projectService.EXPECT().GetProjectVersions(context.Background(), int(testID)).Times(1).Return(nil, errTest) + + err := h.getProjectVersions(testCtx, logrus.NewEntry(logrus.New())) + assert.Error(t, err) + }) +} + +func TestHandlers_newGetProjectVersionsResponse(t *testing.T) { + t.Parallel() + + versions := []project.Version{{ + ID: testVersionID, + Name: testName, + CreatedAt: testCreatedAt.UTC(), + }} + + h := &Handlers{} + + response := h.newGetProjectVersionsResponse(versions) + + assert.NotNil(t, response) + assert.Len(t, response.Versions, 1) + assert.Equal(t, response.Versions[0].ID, testVersionID) + assert.Equal(t, response.Versions[0].Name, testName) + assert.Equal(t, response.Versions[0].CreatedAt, testCreatedAt.UTC()) +} + +func TestDeleteProjectVersion(t *testing.T) { + t.Parallel() + + e := echo.New() + req := httptest.NewRequest(http.MethodDelete, "/", nil) + resp := httptest.NewRecorder() + + testCtx := e.NewContext(req, resp) + + testCtx.SetPath("/projects/:project_id/versions/:version_id") + testCtx.SetParamNames("project_id", "version_id") + testCtx.SetParamValues(fmt.Sprintf("%d", testID), testVersionID) + + projectService := project.NewMockService(gomock.NewController(t)) + + h := &Handlers{ + ProjectService: projectService, + } + t.Run("deleteProjectVersion, success", func(t *testing.T) { + projectService.EXPECT().DeleteProjectVersionByIDAndProjectID(context.Background(), testVersionID, uint(testID)).Times(1).Return(nil) + + err := h.deleteProjectVersion(testCtx, logrus.NewEntry(logrus.New())) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.Code) + }) + + t.Run("deleteProjectVersion, error", func(t *testing.T) { + projectService.EXPECT().DeleteProjectVersionByIDAndProjectID(context.Background(), testVersionID, uint(testID)).Times(1).Return(errTest) + + err := h.deleteProjectVersion(testCtx, logrus.NewEntry(logrus.New())) + assert.Error(t, err) + }) +} + +type CustomValidator struct { + validator *validator.Validate +} + +// Validate implements the echo.Validator interface +func (cv *CustomValidator) Validate(i interface{}) error { + return cv.validator.Struct(i) +} + +func versionValidator(fl validator.FieldLevel) bool { + versionPattern := `^[a-zA-Z0-9.-_]*$` + matched, _ := regexp.MatchString(versionPattern, fl.Field().String()) + return matched +} + +func TestPostProjectVersion(t *testing.T) { + t.Parallel() + + e := echo.New() + validator := validator.New() + validator.RegisterValidation("version", versionValidator) + e.Validator = &CustomValidator{validator: validator} + + projectService := project.NewMockService(gomock.NewController(t)) + + h := &Handlers{ + ProjectService: projectService, + } + + t.Run("postProjectVersion, success", func(t *testing.T) { + reqBody := map[string]interface{}{ + "name": testName, + } + reqJSON, _ := json.Marshal(reqBody) + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(reqJSON)) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + resp := httptest.NewRecorder() + + testCtx := e.NewContext(req, resp) + + testCtx.SetPath("/projects/:id/versions") + testCtx.SetParamNames("id") + testCtx.SetParamValues(fmt.Sprintf("%d", testID)) + + projectService.EXPECT().CreateLanguagesVersion(context.Background(), int(testID), testName).Times(1).Return(nil) + + err := h.postProjectVersion(testCtx, logrus.NewEntry(logrus.New())) + assert.NoError(t, err) + assert.Equal(t, http.StatusCreated, resp.Code) + }) + + t.Run("postProjectVersion, error", func(t *testing.T) { + reqBody := map[string]interface{}{ + "id": testID, + "name": testName, + } + reqJSON, _ := json.Marshal(reqBody) + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(reqJSON)) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + resp := httptest.NewRecorder() + + testCtx := e.NewContext(req, resp) + + testCtx.SetPath("/projects/:id/versions") + testCtx.SetParamNames("id") + testCtx.SetParamValues(fmt.Sprintf("%d", testID)) + + projectService.EXPECT().CreateLanguagesVersion(context.Background(), int(testID), testName).Times(1).Return(errTest) + + err := h.postProjectVersion(testCtx, logrus.NewEntry(logrus.New())) + assert.Error(t, err) + }) + + t.Run("postProjectVersion, validation error", func(t *testing.T) { + reqBody := map[string]interface{}{ + "id": testID, + "name": "", + } + reqJSON, _ := json.Marshal(reqBody) + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(reqJSON)) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + resp := httptest.NewRecorder() + + testCtx := e.NewContext(req, resp) + + testCtx.SetPath("/projects/:id/versions") + testCtx.SetParamNames("id") + testCtx.SetParamValues(fmt.Sprintf("%d", testID)) + + err := h.postProjectVersion(testCtx, logrus.NewEntry(logrus.New())) + assert.Error(t, err) + }) +} diff --git a/internal/rest/v1/private/controllers/routes.go b/internal/rest/v1/private/controllers/routes.go new file mode 100644 index 0000000..5dee8ee --- /dev/null +++ b/internal/rest/v1/private/controllers/routes.go @@ -0,0 +1,49 @@ +package controllers + +import ( + "github.com/labstack/echo/v4" + eprom "github.com/paulfarver/echo-pack/middleware" + "github.com/sirupsen/logrus" + "github.com/uniwise/parrot/internal/project" +) + +type Handlers struct { + ProjectService project.Service +} + +type HandlerFunction func(ctx echo.Context, l *logrus.Entry) error + +func Register(e *echo.Echo, l *logrus.Entry, projectService project.Service, enablePrometheus bool) { + h := &Handlers{ + ProjectService: projectService, + } + + g := e.Group("/api/v1") + + if enablePrometheus { + prom, err := eprom.Prometheus() + if err != nil { + l.WithError(err).Fatal("failed to create prometheus middleware") + } + + g.Use(prom) + } + + g.GET("/projects", wrap(h.getAllProjects, l)) + g.GET("/projects/:id", wrap(h.getProject, l)) + g.GET("/projects/:id/versions", wrap(h.getProjectVersions, l)) + g.POST("/projects/:id/versions", wrap(h.postProjectVersion, l)) + g.DELETE("/projects/:project_id/versions/:version_id", wrap(h.deleteProjectVersion, l)) +} + +func wrap(fn HandlerFunction, logger *logrus.Entry) echo.HandlerFunc { + return func(ctx echo.Context) error { + l := logger.WithFields(logrus.Fields{ + "method": ctx.Request().Method, + "path": ctx.Request().URL.Path, + "requestId": ctx.Response().Header().Get(echo.HeaderXRequestID), + }) + + return fn(ctx, l) + } +} diff --git a/internal/rest/server.go b/internal/rest/v1/private/server.go similarity index 76% rename from internal/rest/server.go rename to internal/rest/v1/private/server.go index 166e303..991991a 100644 --- a/internal/rest/server.go +++ b/internal/rest/v1/private/server.go @@ -1,4 +1,4 @@ -package rest +package private import ( "context" @@ -12,7 +12,8 @@ import ( "github.com/labstack/echo/v4/middleware" "github.com/sirupsen/logrus" "github.com/uniwise/parrot/internal/project" - v1 "github.com/uniwise/parrot/internal/rest/v1" + "github.com/uniwise/parrot/internal/rest/v1/helpers" + controllers "github.com/uniwise/parrot/internal/rest/v1/private/controllers" ) const ( @@ -28,7 +29,12 @@ func NewServer(l *logrus.Entry, projectService project.Service, enablePrometheus e.HideBanner = true e.HidePort = true - e.Validator = NewValidator() + + v, err := helpers.NewValidator() + if err != nil { + return nil, errors.Wrap(err, "Failed to create validator") + } + e.Validator = v e.Use(middleware.Recover()) e.Use(middleware.RequestID()) @@ -36,7 +42,7 @@ func NewServer(l *logrus.Entry, projectService project.Service, enablePrometheus Level: gzipCompressionLevel, })) - v1.Register(e, l, projectService, enablePrometheus) + controllers.Register(e, l, projectService, enablePrometheus) h := gosundheit.New() diff --git a/internal/rest/v1/project.go b/internal/rest/v1/public/controllers/project.go similarity index 88% rename from internal/rest/v1/project.go rename to internal/rest/v1/public/controllers/project.go index 25d1204..9dee939 100644 --- a/internal/rest/v1/project.go +++ b/internal/rest/v1/public/controllers/project.go @@ -1,4 +1,4 @@ -package v1 +package controllers import ( "bytes" @@ -9,6 +9,7 @@ import ( "github.com/labstack/echo/v4" "github.com/sirupsen/logrus" + "github.com/uniwise/parrot/internal/project" "github.com/uniwise/parrot/pkg/poedit" ) @@ -16,6 +17,7 @@ type getProjectLanguageRequest struct { Project int `param:"project" validate:"required"` Language string `param:"language" validate:"required,languageCode"` Format string `query:"format" validate:"omitempty,oneof=po pot mo xls xlsx csv ini resw resx android_strings apple_strings xliff properties key_value_json json yml xlf xmb xtb arb rise_360_xliff"` + Version string `query:"version" validate:"omitempty,max=20,semver"` } func (h *Handlers) getProjectLanguage(ctx echo.Context, l *logrus.Entry) error { @@ -30,6 +32,7 @@ func (h *Handlers) getProjectLanguage(ctx echo.Context, l *logrus.Entry) error { "project": req.Project, "language": req.Language, "format": req.Format, + "version": req.Version, }) if err := ctx.Validate(req); err != nil { @@ -43,6 +46,11 @@ func (h *Handlers) getProjectLanguage(ctx echo.Context, l *logrus.Entry) error { format = req.Format } + version := "latest" + if req.Version != "" { + version = req.Version + } + contentMeta, err := poedit.GetContentMeta(format) if err != nil { l.WithError(err).Errorf("No content meta found for format %s", format) @@ -55,7 +63,9 @@ func (h *Handlers) getProjectLanguage(ctx echo.Context, l *logrus.Entry) error { req.Project, req.Language, format, + version, ) + if errors.Is(err, context.Canceled) { return echo.NewHTTPError(499, "client closed request") } @@ -64,7 +74,8 @@ func (h *Handlers) getProjectLanguage(ctx echo.Context, l *logrus.Entry) error { switch err.(type) { case *poedit.ErrProjectPermissionDenied: return echo.ErrBadRequest - case *poedit.ErrLanguageNotFound: + case *poedit.ErrLanguageNotFound, + *project.ErrLanguageNotFoundInStorage: return echo.ErrNotFound default: l.WithError(err).Error("Error retrieving translation") diff --git a/internal/rest/v1/public/controllers/project_test.go b/internal/rest/v1/public/controllers/project_test.go new file mode 100644 index 0000000..25fcf28 --- /dev/null +++ b/internal/rest/v1/public/controllers/project_test.go @@ -0,0 +1,88 @@ +package controllers + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/http/httptest" + "regexp" + "testing" + "time" + + "github.com/go-playground/validator/v10" + "github.com/labstack/echo/v4" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/uniwise/parrot/internal/project" + + gomock "go.uber.org/mock/gomock" +) + +var ( + errTest = errors.New("test error") + testID int64 = 1 + testLanguage string = "en" + testFormat string = "key_value_json" + testVersion string = "latest" +) + +type CustomValidator struct { + validator *validator.Validate +} + +// Validate implements the echo.Validator interface +func (cv *CustomValidator) Validate(i interface{}) error { + return cv.validator.Struct(i) +} + +// Custom validation function to check if the language code is valid +func languageCodeValidator(fl validator.FieldLevel) bool { + // Define a regex for language code validation (e.g., 'en', 'fr', 'es') + languageCodePattern := `^[a-z]{2}$` + matched, _ := regexp.MatchString(languageCodePattern, fl.Field().String()) + return matched +} + +func TestGetProjectLanguage(t *testing.T) { + t.Parallel() + + e := echo.New() + validator := validator.New() + validator.RegisterValidation("languageCode", languageCodeValidator) + e.Validator = &CustomValidator{validator: validator} + + req := httptest.NewRequest(http.MethodGet, "/", nil) + resp := httptest.NewRecorder() + + testCtx := e.NewContext(req, resp) + + testCtx.SetPath("/projects/:project/language/:language?format=:format&version=:version") + testCtx.SetParamNames("project", "language", "format", "version") + testCtx.SetParamValues(fmt.Sprintf("%d", testID), testLanguage, testFormat, testVersion) + + projectService := project.NewMockService(gomock.NewController(t)) + + h := &Handlers{ + ProjectService: projectService, + } + + t.Run("getProjectLanguage, success", func(t *testing.T) { + projectService.EXPECT().GetTranslation(context.Background(), int(testID), testLanguage, testFormat, testVersion).Times(1).Return(&project.Translation{ + Data: []byte("test"), + Checksum: "test", + TTL: time.Second, + }, nil) + + err := h.getProjectLanguage(testCtx, logrus.NewEntry(logrus.New())) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.Code) + }) + + t.Run("getProjectLanguage, fail", func(t *testing.T) { + projectService.EXPECT().GetTranslation(context.Background(), int(testID), testLanguage, testFormat, testVersion).Times(1).Return(nil, errTest) + + err := h.getProjectLanguage(testCtx, logrus.NewEntry(logrus.New())) + assert.Error(t, err) + }) +} diff --git a/internal/rest/v1/routes.go b/internal/rest/v1/public/controllers/routes.go similarity index 74% rename from internal/rest/v1/routes.go rename to internal/rest/v1/public/controllers/routes.go index a909869..f66ec01 100644 --- a/internal/rest/v1/routes.go +++ b/internal/rest/v1/public/controllers/routes.go @@ -1,8 +1,7 @@ -package v1 +package controllers import ( "github.com/labstack/echo/v4" - eprom "github.com/paulfarver/echo-pack/middleware" "github.com/sirupsen/logrus" "github.com/uniwise/parrot/internal/project" ) @@ -13,22 +12,13 @@ type Handlers struct { type HandlerFunction func(ctx echo.Context, l *logrus.Entry) error -func Register(e *echo.Echo, l *logrus.Entry, projectService project.Service, enablePrometheus bool) { +func Register(e *echo.Echo, l *logrus.Entry, projectService project.Service) { h := &Handlers{ ProjectService: projectService, } g := e.Group("/v1") - if enablePrometheus { - prom, err := eprom.Prometheus() - if err != nil { - l.WithError(err).Fatal("failed to create prometheus middleware") - } - - g.Use(prom) - } - g.GET("/project/:project/language/:language", wrap(h.getProjectLanguage, l)) } diff --git a/internal/rest/v1/public/server.go b/internal/rest/v1/public/server.go new file mode 100644 index 0000000..04beb63 --- /dev/null +++ b/internal/rest/v1/public/server.go @@ -0,0 +1,66 @@ +package public + +import ( + "context" + "fmt" + + gosundheit "github.com/AppsFlyer/go-sundheit" + healthhttp "github.com/AppsFlyer/go-sundheit/http" + "github.com/pkg/errors" + + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + "github.com/sirupsen/logrus" + "github.com/uniwise/parrot/internal/project" + "github.com/uniwise/parrot/internal/rest/v1/helpers" + controllers "github.com/uniwise/parrot/internal/rest/v1/public/controllers" +) + +const ( + gzipCompressionLevel = 5 +) + +type Server struct { + Echo *echo.Echo +} + +func NewServer(l *logrus.Entry, projectService project.Service) (*Server, error) { + e := echo.New() + + e.HideBanner = true + e.HidePort = true + + v, err := helpers.NewValidator() + if err != nil { + return nil, errors.Wrap(err, "Failed to create validator") + } + e.Validator = v + + e.Use(middleware.Recover()) + e.Use(middleware.RequestID()) + e.Use(middleware.GzipWithConfig(middleware.GzipConfig{ + Level: gzipCompressionLevel, + })) + + controllers.Register(e, l, projectService) + + h := gosundheit.New() + + if err := projectService.RegisterChecks(h); err != nil { + return nil, errors.Wrap(err, "Failed to register healthchecks") + } + + e.GET("/health", echo.WrapHandler(healthhttp.HandleHealthJSON(h))) + + return &Server{ + Echo: e, + }, nil +} + +func (s *Server) Start(port int) error { + return s.Echo.Start(fmt.Sprintf(":%d", port)) +} + +func (s *Server) Shutdown(ctx context.Context) error { + return s.Echo.Shutdown(ctx) +} diff --git a/internal/rest/validator.go b/internal/rest/validator.go deleted file mode 100644 index f4d771f..0000000 --- a/internal/rest/validator.go +++ /dev/null @@ -1,28 +0,0 @@ -package rest - -import ( - "github.com/go-playground/validator" - "github.com/johngb/langreg" -) - -type Validator struct { - validator *validator.Validate -} - -func NewValidator() *Validator { - v := validator.New() - - v.RegisterValidation("languageCode", validateLanguageCode) - - return &Validator{ - validator: v, - } -} - -func (cv *Validator) Validate(i interface{}) error { - return cv.validator.Struct(i) -} - -func validateLanguageCode(fl validator.FieldLevel) bool { - return langreg.IsValidLanguageCode(fl.Field().String()) -} diff --git a/internal/storage/repository.go b/internal/storage/repository.go new file mode 100644 index 0000000..4c3d0f6 --- /dev/null +++ b/internal/storage/repository.go @@ -0,0 +1,73 @@ +//go:generate mockgen --source=repository.go -destination=repository_mock.go -package=storage +package storage + +import ( + "context" + "io" + + "github.com/aws/aws-sdk-go-v2/service/s3" +) + +type S3API interface { + DeleteObject( + ctx context.Context, + params *s3.DeleteObjectInput, + optFns ...func(*s3.Options), + ) (*s3.DeleteObjectOutput, error) + + PutObject( + ctx context.Context, + params *s3.PutObjectInput, + optFns ...func(*s3.Options), + ) (*s3.PutObjectOutput, error) + + GetObject( + ctx context.Context, + params *s3.GetObjectInput, + optFns ...func(*s3.Options), + ) (*s3.GetObjectOutput, error) + + ListObjectsV2( + ctx context.Context, + params *s3.ListObjectsV2Input, + optFns ...func(*s3.Options), + ) (*s3.ListObjectsV2Output, error) + + DeleteObjects( + ctx context.Context, + params *s3.DeleteObjectsInput, + optFns ...func(*s3.Options), + ) (*s3.DeleteObjectsOutput, error) + + AbortMultipartUpload( + ctx context.Context, + params *s3.AbortMultipartUploadInput, + optFns ...func(*s3.Options), + ) (*s3.AbortMultipartUploadOutput, error) + + CompleteMultipartUpload( + ctx context.Context, + params *s3.CompleteMultipartUploadInput, + optFns ...func(*s3.Options), + ) (*s3.CompleteMultipartUploadOutput, error) + + CreateMultipartUpload( + ctx context.Context, + params *s3.CreateMultipartUploadInput, + optFns ...func(*s3.Options), + ) (*s3.CreateMultipartUploadOutput, error) + + UploadPart( + ctx context.Context, + params *s3.UploadPartInput, + optFns ...func(*s3.Options), + ) (*s3.UploadPartOutput, error) +} + +type Storage interface { + PutObject(ctx context.Context, key string, payloadReader io.Reader, metadata map[string]string, mimeType string) error + DeleteObject(ctx context.Context, key string) error + GetObject(ctx context.Context, key string) (*s3.GetObjectOutput, error) + ListObjects(ctx context.Context, storageKey string) (*s3.ListObjectsV2Output, error) + DeleteObjects(ctx context.Context, key string) error +} diff --git a/internal/storage/repository_mock.go b/internal/storage/repository_mock.go new file mode 100644 index 0000000..010be93 --- /dev/null +++ b/internal/storage/repository_mock.go @@ -0,0 +1,304 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: repository.go + +// Package storage is a generated GoMock package. +package storage + +import ( + context "context" + io "io" + reflect "reflect" + + s3 "github.com/aws/aws-sdk-go-v2/service/s3" + gomock "go.uber.org/mock/gomock" +) + +// MockS3API is a mock of S3API interface. +type MockS3API struct { + ctrl *gomock.Controller + recorder *MockS3APIMockRecorder +} + +// MockS3APIMockRecorder is the mock recorder for MockS3API. +type MockS3APIMockRecorder struct { + mock *MockS3API +} + +// NewMockS3API creates a new mock instance. +func NewMockS3API(ctrl *gomock.Controller) *MockS3API { + mock := &MockS3API{ctrl: ctrl} + mock.recorder = &MockS3APIMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockS3API) EXPECT() *MockS3APIMockRecorder { + return m.recorder +} + +// DeleteObject mocks base method. +func (m *MockS3API) DeleteObject(ctx context.Context, params *s3.DeleteObjectInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, params} + for _, a := range optFns { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DeleteObject", varargs...) + ret0, _ := ret[0].(*s3.DeleteObjectOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteObject indicates an expected call of DeleteObject. +func (mr *MockS3APIMockRecorder) DeleteObject(ctx, params interface{}, optFns ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, params}, optFns...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteObject", reflect.TypeOf((*MockS3API)(nil).DeleteObject), varargs...) +} + +// DeleteObjects mocks base method. +func (m *MockS3API) DeleteObjects(ctx context.Context, params *s3.DeleteObjectsInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectsOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, params} + for _, a := range optFns { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DeleteObjects", varargs...) + ret0, _ := ret[0].(*s3.DeleteObjectsOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteObjects indicates an expected call of DeleteObjects. +func (mr *MockS3APIMockRecorder) DeleteObjects(ctx, params interface{}, optFns ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, params}, optFns...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteObjects", reflect.TypeOf((*MockS3API)(nil).DeleteObjects), varargs...) +} + +// GetObject mocks base method. +func (m *MockS3API) GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, params} + for _, a := range optFns { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetObject", varargs...) + ret0, _ := ret[0].(*s3.GetObjectOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetObject indicates an expected call of GetObject. +func (mr *MockS3APIMockRecorder) GetObject(ctx, params interface{}, optFns ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, params}, optFns...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetObject", reflect.TypeOf((*MockS3API)(nil).GetObject), varargs...) +} + +// ListObjectsV2 mocks base method. +func (m *MockS3API) ListObjectsV2(ctx context.Context, params *s3.ListObjectsV2Input, optFns ...func(*s3.Options)) (*s3.ListObjectsV2Output, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, params} + for _, a := range optFns { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ListObjectsV2", varargs...) + ret0, _ := ret[0].(*s3.ListObjectsV2Output) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListObjectsV2 indicates an expected call of ListObjectsV2. +func (mr *MockS3APIMockRecorder) ListObjectsV2(ctx, params interface{}, optFns ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, params}, optFns...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListObjectsV2", reflect.TypeOf((*MockS3API)(nil).ListObjectsV2), varargs...) +} + +// PutObject mocks base method. +func (m *MockS3API) PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, params} + for _, a := range optFns { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "PutObject", varargs...) + ret0, _ := ret[0].(*s3.PutObjectOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (m *MockS3API) AbortMultipartUpload(ctx context.Context, params *s3.AbortMultipartUploadInput, optFns ...func(*s3.Options)) (*s3.AbortMultipartUploadOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, params} + for _, a := range optFns { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AbortMultipartUpload", varargs...) + ret0, _ := ret[0].(*s3.AbortMultipartUploadOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (m *MockS3API) CompleteMultipartUpload(ctx context.Context, params *s3.CompleteMultipartUploadInput, optFns ...func(*s3.Options)) (*s3.CompleteMultipartUploadOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, params} + for _, a := range optFns { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CompleteMultipartUpload", varargs...) + ret0, _ := ret[0].(*s3.CompleteMultipartUploadOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (m *MockS3API) CreateMultipartUpload(ctx context.Context, params *s3.CreateMultipartUploadInput, optFns ...func(*s3.Options)) (*s3.CreateMultipartUploadOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, params} + for _, a := range optFns { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CreateMultipartUpload", varargs...) + ret0, _ := ret[0].(*s3.CreateMultipartUploadOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (m *MockS3API) UploadPart(ctx context.Context, params *s3.UploadPartInput, optFns ...func(*s3.Options)) (*s3.UploadPartOutput, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, params} + for _, a := range optFns { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "UploadPart", varargs...) + ret0, _ := ret[0].(*s3.UploadPartOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PutObject indicates an expected call of PutObject. +func (mr *MockS3APIMockRecorder) PutObject(ctx, params interface{}, optFns ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, params}, optFns...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutObject", reflect.TypeOf((*MockS3API)(nil).PutObject), varargs...) +} + +// MockStorage is a mock of Storage interface. +type MockStorage struct { + ctrl *gomock.Controller + recorder *MockStorageMockRecorder +} + +// MockStorageMockRecorder is the mock recorder for MockStorage. +type MockStorageMockRecorder struct { + mock *MockStorage +} + +// NewMockStorage creates a new mock instance. +func NewMockStorage(ctrl *gomock.Controller) *MockStorage { + mock := &MockStorage{ctrl: ctrl} + mock.recorder = &MockStorageMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStorage) EXPECT() *MockStorageMockRecorder { + return m.recorder +} + +// DeleteObject mocks base method. +func (m *MockStorage) DeleteObject(ctx context.Context, key string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteObject", ctx, key) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteObject indicates an expected call of DeleteObject. +func (mr *MockStorageMockRecorder) DeleteObject(ctx, key interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteObject", reflect.TypeOf((*MockStorage)(nil).DeleteObject), ctx, key) +} + +// DeleteObjects mocks base method. +func (m *MockStorage) DeleteObjects(ctx context.Context, key string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteObjects", ctx, key) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteObjects indicates an expected call of DeleteObjects. +func (mr *MockStorageMockRecorder) DeleteObjects(ctx, key interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteObjects", reflect.TypeOf((*MockStorage)(nil).DeleteObjects), ctx, key) +} + +// GetObject mocks base method. +func (m *MockStorage) GetObject(ctx context.Context, key string) (*s3.GetObjectOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetObject", ctx, key) + ret0, _ := ret[0].(*s3.GetObjectOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetObject indicates an expected call of GetObject. +func (mr *MockStorageMockRecorder) GetObject(ctx, key interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetObject", reflect.TypeOf((*MockStorage)(nil).GetObject), ctx, key) +} + +// ListObjects mocks base method. +func (m *MockStorage) ListObjects(ctx context.Context, storageKey string) (*s3.ListObjectsV2Output, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListObjects", ctx, storageKey) + ret0, _ := ret[0].(*s3.ListObjectsV2Output) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListObjects indicates an expected call of ListObjects. +func (mr *MockStorageMockRecorder) ListObjects(ctx, storageKey interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListObjects", reflect.TypeOf((*MockStorage)(nil).ListObjects), ctx, storageKey) +} + +// PutObject mocks base method. +func (m *MockStorage) PutObject(ctx context.Context, key string, payloadReader io.Reader, metadata map[string]string, mimeType string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PutObject", ctx, key, payloadReader, metadata, mimeType) + ret0, _ := ret[0].(error) + return ret0 +} + +// PutObject indicates an expected call of PutObject. +func (mr *MockStorageMockRecorder) PutObject(ctx, key, payloadReader, metadata, mimeType interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutObject", reflect.TypeOf((*MockStorage)(nil).PutObject), ctx, key, payloadReader, metadata, mimeType) +} + +func (mr *MockS3APIMockRecorder) AbortMultipartUpload(ctx, params interface{}, optFns ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, params}, optFns...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AbortMultipartUpload", reflect.TypeOf((*MockS3API)(nil).AbortMultipartUpload), varargs...) +} + +func (mr *MockS3APIMockRecorder) CompleteMultipartUpload(ctx, params interface{}, optFns ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, params}, optFns...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CompleteMultipartUpload", reflect.TypeOf((*MockS3API)(nil).CompleteMultipartUpload), varargs...) +} + +func (mr *MockS3APIMockRecorder) CreateMultipartUpload(ctx, params interface{}, optFns ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, params}, optFns...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMultipartUpload", reflect.TypeOf((*MockS3API)(nil).CreateMultipartUpload), varargs...) +} + +func (mr *MockS3APIMockRecorder) UploadPart(ctx, params interface{}, optFns ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, params}, optFns...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UploadPart", reflect.TypeOf((*MockS3API)(nil).UploadPart), varargs...) +} diff --git a/internal/storage/repository_s3.go b/internal/storage/repository_s3.go new file mode 100644 index 0000000..3a1affe --- /dev/null +++ b/internal/storage/repository_s3.go @@ -0,0 +1,176 @@ +package storage + +import ( + "context" + "io" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/feature/s3/manager" + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/pkg/errors" +) + +const batchSize = 1000 // Max objects to delete at once, restricted by AWS + +type S3StorageConfig struct { + Region string `mapstructure:"region" validate:"required"` + Bucket string `mapstructure:"bucket" validate:"required"` +} + +type S3ClientImpl struct { + config S3StorageConfig + client S3API +} + +func NewS3Client(ctx context.Context, conf S3StorageConfig) (*S3ClientImpl, error) { + cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(conf.Region)) + if err != nil { + return nil, errors.Wrap(err, "failed to load configuration") + } + + client := s3.NewFromConfig(cfg) + + return &S3ClientImpl{ + config: conf, + client: client, + }, nil +} + +// PutObject creates an S3 object +func (c *S3ClientImpl) PutObject(ctx context.Context, key string, payloadReader io.Reader, metadata map[string]string, mimeType string) error { + uploader := manager.NewUploader(c.client) + + if _, err := uploader.Upload(ctx, &s3.PutObjectInput{ + Body: payloadReader, + Bucket: aws.String(c.config.Bucket), + Key: aws.String(key), + Metadata: metadata, + ContentType: &mimeType, + }); err != nil { + return errors.Wrap(err, "Failed to put object to S3") + } + + return nil +} + +// DeleteObject deletes a single S3 object +func (c *S3ClientImpl) DeleteObject(ctx context.Context, key string) error { + if _, err := c.client.DeleteObject(ctx, &s3.DeleteObjectInput{ + Bucket: aws.String(c.config.Bucket), + Key: aws.String(key), + }); err != nil { + return errors.Wrap(err, "Failed to delete object into S3") + } + + return nil +} + +func (s *S3ClientImpl) DeleteObjects(ctx context.Context, key string) error { + output, err := s.client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ + Bucket: aws.String(s.config.Bucket), + Prefix: &key, + }) + if err != nil { + return errors.Wrap(err, "Failed to list objects") + } + + keys := make([]string, len(output.Contents)) + for i, object := range output.Contents { + keys[i] = aws.ToString(object.Key) + } + + objects := make([]types.ObjectIdentifier, len(keys)) + for i, key := range keys { + objects[i] = types.ObjectIdentifier{ + Key: aws.String(key), + } + } + + for i := 0; i < len(objects); i += batchSize { + j := i + batchSize + if j > len(objects) { + j = len(objects) + } + + if _, err := s.client.DeleteObjects(ctx, &s3.DeleteObjectsInput{ + Bucket: aws.String(s.config.Bucket), + Delete: &types.Delete{ + Objects: objects[i:j], + Quiet: aws.Bool(true), + }, + }); err != nil { + return errors.Wrap(err, "Failed to delete objects") + } + } + + return nil +} + +// GetObject gets a single S3 object +func (s *S3ClientImpl) GetObject(ctx context.Context, key string) (*s3.GetObjectOutput, error) { + object, err := s.client.GetObject(ctx, &s3.GetObjectInput{ + Bucket: aws.String(s.config.Bucket), + Key: aws.String(key), + }) + if err != nil { + return nil, errors.Wrap(err, "Failed to get object from S3") + } + + return object, err +} + +// ListObjects lists all objects in an S3 bucket +func (s *S3ClientImpl) ListObjects(ctx context.Context, storageKey string) (*s3.ListObjectsV2Output, error) { + output, err := s.client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ + Bucket: aws.String(s.config.Bucket), + Prefix: &storageKey, + Delimiter: aws.String("/"), + }) + if err != nil { + return nil, errors.Wrap(err, "Failed to list objects in S3") + } + + return output, nil +} + +func (s *S3ClientImpl) AbortMultipartUpload(ctx context.Context, uploadID string) error { + _, err := s.client.AbortMultipartUpload(ctx, &s3.AbortMultipartUploadInput{ + Bucket: aws.String(s.config.Bucket), + Key: aws.String(uploadID), + UploadId: aws.String(uploadID), + }) + if err != nil { + return errors.Wrap(err, "Failed to abort multipart upload") + } + + return nil +} + +func (s *S3ClientImpl) CompleteMultipartUpload(ctx context.Context, input *s3.CompleteMultipartUploadInput) (*s3.CompleteMultipartUploadOutput, error) { + output, err := s.client.CompleteMultipartUpload(ctx, input) + if err != nil { + return nil, errors.Wrap(err, "Failed to complete multipart upload") + } + + return output, nil +} + +func (s *S3ClientImpl) CreateMultipartUpload(ctx context.Context, input *s3.CreateMultipartUploadInput) (*s3.CreateMultipartUploadOutput, error) { + output, err := s.client.CreateMultipartUpload(ctx, input) + if err != nil { + return nil, errors.Wrap(err, "Failed to create multipart upload") + } + + return output, nil +} + +func (s *S3ClientImpl) UploadPart(ctx context.Context, input *s3.UploadPartInput) (*s3.UploadPartOutput, error) { + output, err := s.client.UploadPart(ctx, input) + if err != nil { + return nil, errors.Wrap(err, "Failed to upload part") + } + + return output, nil +} diff --git a/internal/storage/repository_s3_test.go b/internal/storage/repository_s3_test.go new file mode 100644 index 0000000..9d487ca --- /dev/null +++ b/internal/storage/repository_s3_test.go @@ -0,0 +1,370 @@ +package storage + +import ( + "context" + "errors" + "io" + "strings" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/stretchr/testify/assert" +) + +var errForTesting = errors.New("this is an error for testing") + +type MockS3APIMethods struct { + DeleteObjectFunction func(ctx context.Context, params *s3.DeleteObjectInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectOutput, error) + PutObjectFunction func(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error) + GetObjectFunction func(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) + ListObjectsV2Function func(ctx context.Context, params *s3.ListObjectsV2Input, optFns ...func(*s3.Options)) (*s3.ListObjectsV2Output, error) + DeleteObjectsFunction func(ctx context.Context, params *s3.DeleteObjectsInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectsOutput, error) + AbortMultipartUploadFunction func(ctx context.Context, params *s3.AbortMultipartUploadInput, optFns ...func(*s3.Options)) (*s3.AbortMultipartUploadOutput, error) + CompleteMultipartUploadFunction func(ctx context.Context, params *s3.CompleteMultipartUploadInput, optFns ...func(*s3.Options)) (*s3.CompleteMultipartUploadOutput, error) + CreateMultipartUploadFunction func(ctx context.Context, params *s3.CreateMultipartUploadInput, optFns ...func(*s3.Options)) (*s3.CreateMultipartUploadOutput, error) + UploadPartFunction func(ctx context.Context, params *s3.UploadPartInput, optFns ...func(*s3.Options)) (*s3.UploadPartOutput, error) +} + +func (m *MockS3APIMethods) DeleteObject(ctx context.Context, params *s3.DeleteObjectInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectOutput, error) { + return m.DeleteObjectFunction(ctx, params, optFns...) +} + +func (m *MockS3APIMethods) PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error) { + return m.PutObjectFunction(ctx, params, optFns...) +} + +func (m *MockS3APIMethods) GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) { + return m.GetObjectFunction(ctx, params, optFns...) +} + +func (m *MockS3APIMethods) ListObjectsV2(ctx context.Context, params *s3.ListObjectsV2Input, optFns ...func(*s3.Options)) (*s3.ListObjectsV2Output, error) { + return m.ListObjectsV2Function(ctx, params, optFns...) +} + +func (m *MockS3APIMethods) DeleteObjects(ctx context.Context, params *s3.DeleteObjectsInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectsOutput, error) { + return m.DeleteObjectsFunction(ctx, params, optFns...) +} + +func (m *MockS3APIMethods) AbortMultipartUpload(ctx context.Context, params *s3.AbortMultipartUploadInput, optFns ...func(*s3.Options)) (*s3.AbortMultipartUploadOutput, error) { + return m.AbortMultipartUploadFunction(ctx, params, optFns...) +} + +func (m *MockS3APIMethods) CompleteMultipartUpload(ctx context.Context, params *s3.CompleteMultipartUploadInput, optFns ...func(*s3.Options)) (*s3.CompleteMultipartUploadOutput, error) { + return m.CompleteMultipartUploadFunction(ctx, params, optFns...) +} + +func (m *MockS3APIMethods) CreateMultipartUpload(ctx context.Context, params *s3.CreateMultipartUploadInput, optFns ...func(*s3.Options)) (*s3.CreateMultipartUploadOutput, error) { + return m.CreateMultipartUploadFunction(ctx, params, optFns...) +} + +func (m *MockS3APIMethods) UploadPart(ctx context.Context, params *s3.UploadPartInput, optFns ...func(*s3.Options)) (*s3.UploadPartOutput, error) { + return m.UploadPartFunction(ctx, params, optFns...) +} + +func TestNewS3Client(t *testing.T) { + t.Parallel() + + client, err := NewS3Client(context.Background(), S3StorageConfig{}) + + assert.NoError(t, err) + assert.NotNil(t, client.client) + assert.NotNil(t, client.config) +} + +func TestDeleteObject(t *testing.T) { + t.Parallel() + + var ( + ctx context.Context + bucket string = "test-bucket" + region string = "test-region" + key string = "test/keyOne" + ) + + t.Run("Delete object, fail", func(t *testing.T) { + mockClient := &MockS3APIMethods{ + DeleteObjectFunction: func( + _ctx context.Context, + _params *s3.DeleteObjectInput, + _optFns ...func(*s3.Options), + ) (*s3.DeleteObjectOutput, error) { + assert.Equal(t, ctx, _ctx) + assert.Equal(t, bucket, *_params.Bucket) + return nil, errForTesting + }, + } + + storage := &S3ClientImpl{ + config: S3StorageConfig{Bucket: bucket, Region: region}, + client: mockClient, + } + + actualError := storage.DeleteObject(ctx, key) + + assert.Error(t, actualError) + }) + t.Run("Delete object, success", func(t *testing.T) { + mockClient := &MockS3APIMethods{ + DeleteObjectFunction: func( + _ctx context.Context, + _params *s3.DeleteObjectInput, + _optFns ...func(*s3.Options), + ) (*s3.DeleteObjectOutput, error) { + assert.Equal(t, ctx, _ctx) + assert.Equal(t, bucket, *_params.Bucket) + return &s3.DeleteObjectOutput{}, nil + }, + } + + storage := &S3ClientImpl{ + config: S3StorageConfig{Bucket: bucket, Region: region}, + client: mockClient, + } + + actualError := storage.DeleteObject(ctx, key) + + assert.NoError(t, actualError) + }) +} + +func TestPutObject(t *testing.T) { + t.Parallel() + + var ( + ctx context.Context + bucket string = "test-bucket" + region string = "test-region" + key string = "test/keyOne" + payloadReader io.Reader = strings.NewReader("Hello, Reader!") + ) + + t.Run("Put object, fail", func(t *testing.T) { + mockClient := &MockS3APIMethods{ + PutObjectFunction: func( + _ctx context.Context, + _params *s3.PutObjectInput, + _optFns ...func(*s3.Options), + ) (*s3.PutObjectOutput, error) { + assert.Equal(t, ctx, _ctx) + assert.Equal(t, bucket, *_params.Bucket) + assert.Equal(t, payloadReader, _params.Body) + return nil, errForTesting + }, + } + + storage := &S3ClientImpl{ + config: S3StorageConfig{Bucket: bucket, Region: region}, + client: mockClient, + } + + actualError := storage.PutObject(ctx, key, payloadReader, nil, "txt") + + assert.Error(t, actualError) + }) + t.Run("Put object, success", func(t *testing.T) { + mockClient := &MockS3APIMethods{ + PutObjectFunction: func( + _ctx context.Context, + _params *s3.PutObjectInput, + _optFns ...func(*s3.Options), + ) (*s3.PutObjectOutput, error) { + assert.Equal(t, ctx, _ctx) + assert.Equal(t, bucket, *_params.Bucket) + assert.Equal(t, payloadReader, _params.Body) + return &s3.PutObjectOutput{}, nil + }, + } + + storage := &S3ClientImpl{ + config: S3StorageConfig{Bucket: bucket, Region: region}, + client: mockClient, + } + + actualError := storage.PutObject(ctx, key, payloadReader, nil, "txt") + + assert.NoError(t, actualError) + }) +} + +func TestGetObject(t *testing.T) { + t.Parallel() + + var ( + ctx context.Context + bucket string = "test-bucket" + region string = "test-region" + key string = "test/keyOne" + ) + + t.Run("Get object, fail", func(t *testing.T) { + mockClient := &MockS3APIMethods{ + GetObjectFunction: func( + _ctx context.Context, + _params *s3.GetObjectInput, + _optFns ...func(*s3.Options), + ) (*s3.GetObjectOutput, error) { + assert.Equal(t, ctx, _ctx) + assert.Equal(t, bucket, *_params.Bucket) + assert.Equal(t, key, *_params.Key) + return nil, errForTesting + }, + } + + storage := &S3ClientImpl{ + config: S3StorageConfig{Bucket: bucket, Region: region}, + client: mockClient, + } + + actualOutput, actualError := storage.GetObject(ctx, key) + + assert.Error(t, actualError) + assert.Nil(t, actualOutput) + }) + t.Run("Get object, success", func(t *testing.T) { + mockClient := &MockS3APIMethods{ + GetObjectFunction: func( + _ctx context.Context, + _params *s3.GetObjectInput, + _optFns ...func(*s3.Options), + ) (*s3.GetObjectOutput, error) { + assert.Equal(t, ctx, _ctx) + assert.Equal(t, bucket, *_params.Bucket) + assert.Equal(t, key, *_params.Key) + return &s3.GetObjectOutput{}, nil + }, + } + + storage := &S3ClientImpl{ + config: S3StorageConfig{Bucket: bucket, Region: region}, + client: mockClient, + } + + actualOutput, actualError := storage.GetObject(ctx, key) + + assert.NoError(t, actualError) + assert.NotNil(t, actualOutput) + }) +} + +func TestListObjects(t *testing.T) { + t.Parallel() + + var ( + ctx context.Context + bucket string = "test-bucket" + region string = "test-region" + storageKey string = "test/key" + ) + + t.Run("List objects, fail", func(t *testing.T) { + mockClient := &MockS3APIMethods{ + ListObjectsV2Function: func( + _ctx context.Context, + _params *s3.ListObjectsV2Input, + _optFns ...func(*s3.Options), + ) (*s3.ListObjectsV2Output, error) { + assert.Equal(t, ctx, _ctx) + assert.Equal(t, bucket, *_params.Bucket) + return nil, errForTesting + }, + } + + storage := &S3ClientImpl{ + config: S3StorageConfig{Bucket: bucket, Region: region}, + client: mockClient, + } + + actualOutput, actualError := storage.ListObjects(ctx, storageKey) + + assert.Error(t, actualError) + assert.Nil(t, actualOutput) + }) + t.Run("List objects, success", func(t *testing.T) { + mockClient := &MockS3APIMethods{ + ListObjectsV2Function: func( + _ctx context.Context, + _params *s3.ListObjectsV2Input, + _optFns ...func(*s3.Options), + ) (*s3.ListObjectsV2Output, error) { + assert.Equal(t, ctx, _ctx) + assert.Equal(t, bucket, *_params.Bucket) + return &s3.ListObjectsV2Output{}, nil + }, + } + + storage := &S3ClientImpl{ + config: S3StorageConfig{Bucket: bucket, Region: region}, + client: mockClient, + } + + actualOutput, actualError := storage.ListObjects(ctx, storageKey) + + assert.NoError(t, actualError) + assert.NotNil(t, actualOutput) + }) +} + +func TestDeleteObjects(t *testing.T) { + t.Parallel() + + var ( + ctx context.Context + bucket string = "test-bucket" + region string = "test-region" + key string = "test/key" + ) + + t.Run("Delete objects, fail", func(t *testing.T) { + mockClient := &MockS3APIMethods{ + ListObjectsV2Function: func( + _ctx context.Context, + _params *s3.ListObjectsV2Input, + _optFns ...func(*s3.Options), + ) (*s3.ListObjectsV2Output, error) { + assert.Equal(t, ctx, _ctx) + assert.Equal(t, bucket, *_params.Bucket) + return nil, errForTesting + }, + } + + storage := &S3ClientImpl{ + config: S3StorageConfig{Bucket: bucket, Region: region}, + client: mockClient, + } + + actualError := storage.DeleteObjects(ctx, key) + + assert.Error(t, actualError) + }) + t.Run("Delete objects, success", func(t *testing.T) { + mockClient := &MockS3APIMethods{ + ListObjectsV2Function: func( + _ctx context.Context, + _params *s3.ListObjectsV2Input, + _optFns ...func(*s3.Options), + ) (*s3.ListObjectsV2Output, error) { + assert.Equal(t, ctx, _ctx) + assert.Equal(t, bucket, *_params.Bucket) + return &s3.ListObjectsV2Output{}, nil + }, + DeleteObjectsFunction: func( + _ctx context.Context, + _params *s3.DeleteObjectsInput, + _optFns ...func(*s3.Options), + ) (*s3.DeleteObjectsOutput, error) { + assert.Equal(t, ctx, _ctx) + assert.Equal(t, bucket, *_params.Bucket) + return &s3.DeleteObjectsOutput{}, nil + }, + } + + storage := &S3ClientImpl{ + config: S3StorageConfig{Bucket: bucket, Region: region}, + client: mockClient, + } + + actualError := storage.DeleteObjects(ctx, key) + + assert.NoError(t, actualError) + }) +} diff --git a/internal/storage/service.go b/internal/storage/service.go new file mode 100644 index 0000000..66340fa --- /dev/null +++ b/internal/storage/service.go @@ -0,0 +1,45 @@ +package storage + +import ( + "context" + "io" + + "github.com/aws/aws-sdk-go-v2/service/s3" +) + +type ServiceImpl struct { + storage Storage +} + +type Service interface { + PutObject(ctx context.Context, key string, payloadReader io.Reader, metadata map[string]string, mimeType string) error + DeleteObject(ctx context.Context, key string) error + GetObject(ctx context.Context, key string) (*s3.GetObjectOutput, error) + ListObjects(ctx context.Context, storageKey string) (*s3.ListObjectsV2Output, error) +} + +func NewService(ctx context.Context, storage Storage) *ServiceImpl { + return &ServiceImpl{ + storage: storage, + } +} + +func (s *ServiceImpl) PutObject(ctx context.Context, key string, payloadReader io.Reader, metadata map[string]string, mimeType string) error { + return s.storage.PutObject(ctx, key, payloadReader, metadata, mimeType) +} + +func (s *ServiceImpl) DeleteObject(ctx context.Context, key string) error { + return s.storage.DeleteObject(ctx, key) +} + +func (s *ServiceImpl) GetObject(ctx context.Context, key string) (*s3.GetObjectOutput, error) { + return s.storage.GetObject(ctx, key) +} + +func (s *ServiceImpl) ListObjects(ctx context.Context, storageKey string) (*s3.ListObjectsV2Output, error) { + return s.storage.ListObjects(ctx, storageKey) +} + +func (s *ServiceImpl) DeleteObjects(ctx context.Context, key string) error { + return s.storage.DeleteObjects(ctx, key) +} diff --git a/internal/storage/service_mock.go b/internal/storage/service_mock.go new file mode 100644 index 0000000..1a12613 --- /dev/null +++ b/internal/storage/service_mock.go @@ -0,0 +1,95 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: service.go + +// Package storage is a generated GoMock package. +package storage + +import ( + context "context" + io "io" + reflect "reflect" + + s3 "github.com/aws/aws-sdk-go-v2/service/s3" + gomock "go.uber.org/mock/gomock" +) + +// MockService is a mock of Service interface. +type MockService struct { + ctrl *gomock.Controller + recorder *MockServiceMockRecorder +} + +// MockServiceMockRecorder is the mock recorder for MockService. +type MockServiceMockRecorder struct { + mock *MockService +} + +// NewMockService creates a new mock instance. +func NewMockService(ctrl *gomock.Controller) *MockService { + mock := &MockService{ctrl: ctrl} + mock.recorder = &MockServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockService) EXPECT() *MockServiceMockRecorder { + return m.recorder +} + +// DeleteObject mocks base method. +func (m *MockService) DeleteObject(ctx context.Context, key string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteObject", ctx, key) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteObject indicates an expected call of DeleteObject. +func (mr *MockServiceMockRecorder) DeleteObject(ctx, key interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteObject", reflect.TypeOf((*MockService)(nil).DeleteObject), ctx, key) +} + +// GetObject mocks base method. +func (m *MockService) GetObject(ctx context.Context, key string) (*s3.GetObjectOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetObject", ctx, key) + ret0, _ := ret[0].(*s3.GetObjectOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetObject indicates an expected call of GetObject. +func (mr *MockServiceMockRecorder) GetObject(ctx, key interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetObject", reflect.TypeOf((*MockService)(nil).GetObject), ctx, key) +} + +// ListObjects mocks base method. +func (m *MockService) ListObjects(ctx context.Context, storageKey string) (*s3.ListObjectsV2Output, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListObjects", ctx, storageKey) + ret0, _ := ret[0].(*s3.ListObjectsV2Output) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListObjects indicates an expected call of ListObjects. +func (mr *MockServiceMockRecorder) ListObjects(ctx, storageKey interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListObjects", reflect.TypeOf((*MockService)(nil).ListObjects), ctx, storageKey) +} + +// PutObject mocks base method. +func (m *MockService) PutObject(ctx context.Context, key string, payloadReader io.Reader, metadata map[string]string, mimeType string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PutObject", ctx, key, payloadReader, metadata, mimeType) + ret0, _ := ret[0].(error) + return ret0 +} + +// PutObject indicates an expected call of PutObject. +func (mr *MockServiceMockRecorder) PutObject(ctx, key, payloadReader, metadata, mimeType interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutObject", reflect.TypeOf((*MockService)(nil).PutObject), ctx, key, payloadReader, metadata, mimeType) +} diff --git a/internal/storage/service_test.go b/internal/storage/service_test.go new file mode 100644 index 0000000..34502c1 --- /dev/null +++ b/internal/storage/service_test.go @@ -0,0 +1,134 @@ +package storage + +import ( + "context" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/stretchr/testify/assert" + gomock "go.uber.org/mock/gomock" +) + +var ( + testCtx = context.Background() + testKey = "test/key" +) + +func TestService_PutObject(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + + storage := NewMockStorage(ctrl) + service := NewService(testCtx, storage) + + t.Run("PutObject should succeed", func(t *testing.T) { + storage.EXPECT().PutObject(testCtx, testKey, nil, nil, "").Times(1).Return(nil) + + err := service.PutObject(testCtx, testKey, nil, nil, "") + assert.NoError(t, err) + }) + + t.Run("PutObject should fail", func(t *testing.T) { + storage.EXPECT().PutObject(testCtx, testKey, nil, nil, "").Times(1).Return(errForTesting) + + err := service.PutObject(testCtx, testKey, nil, nil, "") + assert.ErrorIs(t, err, errForTesting) + }) +} + +func TestService_DeleteObject(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + + storage := NewMockStorage(ctrl) + service := NewService(testCtx, storage) + + t.Run("DeleteObject should succeed", func(t *testing.T) { + storage.EXPECT().DeleteObject(testCtx, testKey).Times(1).Return(nil) + + err := service.DeleteObject(testCtx, testKey) + assert.NoError(t, err) + }) + + t.Run("DeleteObject should fail", func(t *testing.T) { + storage.EXPECT().DeleteObject(testCtx, testKey).Times(1).Return(errForTesting) + + err := service.DeleteObject(testCtx, testKey) + assert.ErrorIs(t, err, errForTesting) + }) +} + +func TestService_GetObject(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + + storage := NewMockStorage(ctrl) + service := NewService(testCtx, storage) + + t.Run("GetObject should succeed", func(t *testing.T) { + getObjectOutput := &s3.GetObjectOutput{} + storage.EXPECT().GetObject(testCtx, testKey).Times(1).Return(getObjectOutput, nil) + + output, err := service.GetObject(testCtx, testKey) + assert.NoError(t, err) + assert.Equal(t, getObjectOutput, output) + }) + + t.Run("GetObject should fail", func(t *testing.T) { + storage.EXPECT().GetObject(testCtx, testKey).Times(1).Return(nil, errForTesting) + + _, err := service.GetObject(testCtx, testKey) + assert.ErrorIs(t, err, errForTesting) + }) +} + +func TestService_ListObjects(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + + storage := NewMockStorage(ctrl) + service := NewService(testCtx, storage) + + t.Run("ListObjects should succeed", func(t *testing.T) { + listObjectsOutput := &s3.ListObjectsV2Output{} + storage.EXPECT().ListObjects(testCtx, testKey).Times(1).Return(listObjectsOutput, nil) + + output, err := service.ListObjects(testCtx, testKey) + assert.NoError(t, err) + assert.Equal(t, listObjectsOutput, output) + }) + + t.Run("ListObjects should fail", func(t *testing.T) { + storage.EXPECT().ListObjects(testCtx, testKey).Times(1).Return(nil, errForTesting) + + _, err := service.ListObjects(testCtx, testKey) + assert.ErrorIs(t, err, errForTesting) + }) +} + +func TestService_DeleteObjects(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + + storage := NewMockStorage(ctrl) + service := NewService(testCtx, storage) + + t.Run("DeleteObjects should succeed", func(t *testing.T) { + storage.EXPECT().DeleteObjects(testCtx, testKey).Times(1).Return(nil) + + err := service.DeleteObjects(testCtx, testKey) + assert.NoError(t, err) + }) + + t.Run("DeleteObjects should fail", func(t *testing.T) { + storage.EXPECT().DeleteObjects(testCtx, testKey).Times(1).Return(errForTesting) + + err := service.DeleteObjects(testCtx, testKey) + assert.ErrorIs(t, err, errForTesting) + }) +} diff --git a/jenkinsfile-helm b/jenkinsfile-helm deleted file mode 100644 index 6226c06..0000000 --- a/jenkinsfile-helm +++ /dev/null @@ -1,9 +0,0 @@ -#!groovy -@Library('utils') -import jenkinslib.PodBuilder -import jenkinslib.Utilities - -def podbuilder = new PodBuilder(this, false) -def utils = new Utilities(this, false) - -utils.packagePipeline(podbuilder, "chart", "parrot") diff --git a/jenkinsfile-helm-push b/jenkinsfile-helm-push deleted file mode 100644 index 165a7f2..0000000 --- a/jenkinsfile-helm-push +++ /dev/null @@ -1,9 +0,0 @@ -#!groovy -@Library('utils') -import jenkinslib.PodBuilder -import jenkinslib.Utilities - -def podbuilder = new PodBuilder(this, false) -def utils = new Utilities(this, false) - -utils.pushPipeline(podbuilder, "chart", "parrot") diff --git a/pkg/poedit/client.go b/pkg/poedit/client.go index 860e336..6d6a1c9 100644 --- a/pkg/poedit/client.go +++ b/pkg/poedit/client.go @@ -10,6 +10,9 @@ import ( // Client is an interface to poeditors api type Client interface { ExportProject(ctx context.Context, req ExportProjectRequest) (result *ExportProjectResponse, err error) + ListProjectLanguages(ctx context.Context, r ListProjectLanguagesRequest) (*ListProjectLanguagesResponse, error) + ViewProject(ctx context.Context, r ViewProjectRequest) (*ViewProjectResponse, error) + ListProjects(ctx context.Context) (*ListProjectsResponse, error) } // ClientImpl is an implementation of the poeditor client interface diff --git a/pkg/poedit/client_mock.go b/pkg/poedit/client_mock.go new file mode 100644 index 0000000..4338b1e --- /dev/null +++ b/pkg/poedit/client_mock.go @@ -0,0 +1,95 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: client.go + +// Package poedit is a generated GoMock package. +package poedit + +import ( + context "context" + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockClient is a mock of Client interface. +type MockClient struct { + ctrl *gomock.Controller + recorder *MockClientMockRecorder +} + +// MockClientMockRecorder is the mock recorder for MockClient. +type MockClientMockRecorder struct { + mock *MockClient +} + +// NewMockClient creates a new mock instance. +func NewMockClient(ctrl *gomock.Controller) *MockClient { + mock := &MockClient{ctrl: ctrl} + mock.recorder = &MockClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockClient) EXPECT() *MockClientMockRecorder { + return m.recorder +} + +// ExportProject mocks base method. +func (m *MockClient) ExportProject(ctx context.Context, req ExportProjectRequest) (*ExportProjectResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExportProject", ctx, req) + ret0, _ := ret[0].(*ExportProjectResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExportProject indicates an expected call of ExportProject. +func (mr *MockClientMockRecorder) ExportProject(ctx, req interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExportProject", reflect.TypeOf((*MockClient)(nil).ExportProject), ctx, req) +} + +// ListProjectLanguages mocks base method. +func (m *MockClient) ListProjectLanguages(ctx context.Context, r ListProjectLanguagesRequest) (*ListProjectLanguagesResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListProjectLanguages", ctx, r) + ret0, _ := ret[0].(*ListProjectLanguagesResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListProjectLanguages indicates an expected call of ListProjectLanguages. +func (mr *MockClientMockRecorder) ListProjectLanguages(ctx, r interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListProjectLanguages", reflect.TypeOf((*MockClient)(nil).ListProjectLanguages), ctx, r) +} + +// ListProjects mocks base method. +func (m *MockClient) ListProjects(ctx context.Context) (*ListProjectsResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListProjects", ctx) + ret0, _ := ret[0].(*ListProjectsResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListProjects indicates an expected call of ListProjects. +func (mr *MockClientMockRecorder) ListProjects(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListProjects", reflect.TypeOf((*MockClient)(nil).ListProjects), ctx) +} + +// ViewProject mocks base method. +func (m *MockClient) ViewProject(ctx context.Context, r ViewProjectRequest) (*ViewProjectResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ViewProject", ctx, r) + ret0, _ := ret[0].(*ViewProjectResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ViewProject indicates an expected call of ViewProject. +func (mr *MockClientMockRecorder) ViewProject(ctx, r interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ViewProject", reflect.TypeOf((*MockClient)(nil).ViewProject), ctx, r) +} diff --git a/pkg/poedit/content.go b/pkg/poedit/content.go index 8e35268..50a2cd1 100644 --- a/pkg/poedit/content.go +++ b/pkg/poedit/content.go @@ -20,16 +20,13 @@ var ContentMetaMap = map[string]ContentMeta{ "resx": {Extension: "resx", Type: "application/xml"}, "android_strings": {Extension: "xml", Type: "application/xml"}, "apple_strings": {Extension: "strings", Type: "text/plain; charset=utf-8"}, - "xliff": {Extension: "xliff", Type: "application/xml"}, "properties": {Extension: "properties", Type: "text/plain; charset=utf-8"}, "key_value_json": {Extension: "json", Type: "application/json"}, "json": {Extension: "json", Type: "application/json"}, "yml": {Extension: "yml", Type: "text/plain; charset=utf-8"}, - "xlf": {Extension: "xlf", Type: "application/xml"}, "xmb": {Extension: "xmb", Type: "application/xml"}, "xtb": {Extension: "xtb", Type: "application/xml"}, "arb": {Extension: "arb", Type: "application/json"}, - "rise_360_xliff": {Extension: "xliff", Type: "application/xml"}, } func GetContentMeta(format string) (*ContentMeta, error) { @@ -40,3 +37,7 @@ func GetContentMeta(format string) (*ContentMeta, error) { return &m, nil } + +func GetContentMetaMap() map[string]ContentMeta { + return ContentMetaMap +} diff --git a/pkg/poedit/projects.go b/pkg/poedit/projects.go index 8f9770d..9e54015 100644 --- a/pkg/poedit/projects.go +++ b/pkg/poedit/projects.go @@ -437,7 +437,7 @@ func (c *ClientImpl) ExportProject(ctx context.Context, r ExportProjectRequest) } } - if res.Response.Code == "4044" { + if res.Response.Code == "404" { return nil, &ErrLanguageNotFound{ ProjectID: r.ID, LanguageCode: r.Language, @@ -445,6 +445,7 @@ func (c *ClientImpl) ExportProject(ctx context.Context, r ExportProjectRequest) } if res.Response.Code != "200" { + fmt.Println(res.Response) return nil, errors.New(res.Response.Message) }