From c85d3e66acd0244f52478dfd97962e648c14e633 Mon Sep 17 00:00:00 2001 From: Pavel Date: Mon, 19 Feb 2024 21:27:12 +0300 Subject: [PATCH] first adding --- .gitignore | 21 ++ Dockerfile | 16 + app/app.go | 189 ++++++++++++ app/logrecords.go | 10 + dal/dal.go | 39 +++ deployments/local/docker-compose.yaml | 12 + deployments/main/docker-compose.yaml | 77 +++++ deployments/main/staging/docker-compose.yaml | 77 +++++ deployments/staging/docker-compose.yaml | 77 +++++ deployments/test/docker-compose.yaml | 102 +++++++ deployments/testmigrate/docker-compose.yaml | 23 ++ go.mod | 54 ++++ go.sum | 195 ++++++++++++ main.go | 10 + middleware/middleware.go | 28 ++ openapi.yaml | 0 service/service.go | 295 +++++++++++++++++++ 17 files changed, 1225 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 app/app.go create mode 100644 app/logrecords.go create mode 100644 dal/dal.go create mode 100644 deployments/local/docker-compose.yaml create mode 100644 deployments/main/docker-compose.yaml create mode 100644 deployments/main/staging/docker-compose.yaml create mode 100644 deployments/staging/docker-compose.yaml create mode 100644 deployments/test/docker-compose.yaml create mode 100644 deployments/testmigrate/docker-compose.yaml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 middleware/middleware.go create mode 100644 openapi.yaml create mode 100644 service/service.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ffb511 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ +squiz +.idea/ +gen +worker/worker +storer/storer +answerer/answerer \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f1367af --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM penahub.gitlab.yandexcloud.net:5050/devops/dockerhub-backup/golang as build +WORKDIR /app +COPY . . +ENV GOPRIVATE=penahub.gitlab.yandexcloud.net/backend/penahub_common +RUN git config --global url."https://buildToken:glpat-axA8ttckx3aPf_xd2Dym@penahub.gitlab.yandexcloud.net/".insteadOf "https://penahub.gitlab.yandexcloud.net/" +RUN go mod download +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ans ./answerer/main.go + +FROM penahub.gitlab.yandexcloud.net:5050/devops/dockerhub-backup/alpine as prod +COPY --from=build /app/ans . +EXPOSE 1490 +ENV IS_PROD_LOG=false +ENV IS_PROD=false +ENV PORT=1490 +ENV PG_CRED="host=postgres port=5432 user=squiz password=Redalert2 dbname=squiz sslmode=disable" +CMD ["/ans"] diff --git a/app/app.go b/app/app.go new file mode 100644 index 0000000..58bf232 --- /dev/null +++ b/app/app.go @@ -0,0 +1,189 @@ +package app + +import ( + "context" + "errors" + "fmt" + "github.com/go-redis/redis/v8" + "github.com/gofiber/fiber/v2" + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" + "github.com/skeris/appInit" + "github.com/themakers/hlog" + "go.uber.org/zap" + dalBS "penahub.gitlab.yandexcloud.net/backend/quiz/answerer.git/dal" + "penahub.gitlab.yandexcloud.net/backend/quiz/answerer.git/middleware" + "penahub.gitlab.yandexcloud.net/backend/quiz/answerer.git/service" + "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal" + "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/healthchecks" + "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model" + "penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/savewc" +) + +type App struct { + logger *zap.Logger + err chan error +} + +func (a App) GetLogger() *zap.Logger { + return a.logger +} + +func (a App) GetErr() chan error { + return a.err +} + +var ( + errInvalidOptions = errors.New("invalid options") +) + +var zapOptions = []zap.Option{ + zap.AddCaller(), + zap.AddCallerSkip(2), + zap.AddStacktrace(zap.ErrorLevel), +} + +var _ appInit.CommonApp = (*App)(nil) + +type Options struct { + LoggerProdMode bool `env:"IS_PROD_LOG" default:"false"` + IsProd bool `env:"IS_PROD" default:"false"` + MinioEP string `env:"MINIO_EP" default:"localhost:3002"` + MinioAK string `env:"MINIO_AK" default:"minio"` + MinioSK string `env:"MINIO_SK" default:"miniostorage"` + NumberPort string `env:"PORT" default:"1490"` + CrtFile string `env:"CRT" default:"server.crt"` + KeyFile string `env:"KEY" default:"server.key"` + PostgresCredentials string `env:"PG_CRED" default:"host=localhost port=5432 user=squiz password=Redalert2 dbname=squiz sslmode=disable"` + RedisHost string `env:"REDIS_HOST"` + RedisPassword string `env:"REDIS_PASSWORD"` + RedisDB uint64 `env:"REDIS_DB"` +} + +func New(ctx context.Context, opts interface{}, ver appInit.Version) (appInit.CommonApp, error) { + var ( + err, workerErr error + zapLogger *zap.Logger + errChan = make(chan error) + options Options + ok bool + ) + + if options, ok = opts.(Options); !ok { + return App{}, errInvalidOptions + } + + if options.LoggerProdMode { + zapLogger, err = zap.NewProduction(zapOptions...) + if err != nil { + return nil, err + } + } else { + zapLogger, err = zap.NewDevelopment(zapOptions...) + if err != nil { + return nil, err + } + } + + zapLogger = zapLogger.With( + zap.String("SvcCommit", ver.Commit), + zap.String("SvcVersion", ver.Release), + zap.String("SvcBuildTime", ver.BuildTime), + ) + + logger := hlog.New(zapLogger) + logger.Emit(InfoSvcStarted{}) + + pgdal, err := dal.New(ctx, options.PostgresCredentials, nil) + if err != nil { + return nil, err + } + + // Initialize minio client object. + minioClient, err := minio.New(options.MinioEP, &minio.Options{ + Creds: credentials.NewStaticV4(options.MinioAK, options.MinioSK, ""), + Secure: options.IsProd, + }) + if err != nil { + fmt.Println("MINIOERR", options.MinioEP, err) + return nil, err + } + + zapLogger.Info("config", zap.Any("options", options)) + //init redis + redisClient := redis.NewClient(&redis.Options{ + Addr: options.RedisHost, + Password: options.RedisPassword, + DB: int(options.RedisDB), + }) + + workerSendClientCh := make(chan model.Answer, 50) + workerRespondentCh := make(chan []model.Answer, 50) + blobstore, err := dalBS.New(ctx, minioClient) + if err != nil { + return nil, err + } + svc := service.New(blobstore, pgdal, workerRespondentCh, workerSendClientCh) + + saveRespWcData := savewc.DepsForResp{ + WorkerRespondentCh: workerRespondentCh, + Redis: redisClient, + } + + saveClientWcData := savewc.DepsForClient{ + WorkerSendClientCh: workerSendClientCh, + Redis: redisClient, + } + + saveRespWorker := savewc.NewSaveRespWorker(saveRespWcData, errChan, logger) + + saveClientWorker := savewc.NewSaveClientWorker(saveClientWcData, errChan, logger) + + go saveRespWorker.Start(ctx) + go saveClientWorker.Start(ctx) + + app := fiber.New(fiber.Config{BodyLimit: 70 * 1024 * 1024}) + app.Use(middleware.AnswererChain()) + app.Get("/liveness", healthchecks.Liveness) + app.Get("/readiness", healthchecks.Readiness(&workerErr)) //todo parametrized readiness. should discuss ready reason + app = svc.Register(app) + + fmt.Println("SERVERSTART", fmt.Sprintf(":%s", options.NumberPort)) + + logger.Emit(InfoSvcReady{}) + + go func() { + defer func() { + //if pgdal != nil { + // pgdal.CloseAnswerer() + //} + err := app.Shutdown() + if err != nil { + logger.Emit(InfoSvcShutdown{Signal: err.Error()}) + } + }() + + if options.IsProd { + if err := app.ListenTLS(fmt.Sprintf(":%s", options.NumberPort), options.CrtFile, options.KeyFile); err != nil { + logger.Emit(ErrorCanNotServe{ + Err: err, + }) + errChan <- err + } + } else { + if err := app.Listen(fmt.Sprintf(":%s", options.NumberPort)); err != nil { + logger.Emit(ErrorCanNotServe{ + Err: err, + }) + errChan <- err + } + } + + errChan <- nil + }() + // todo implement helper func for service app type. such as server preparing, logger preparing, healthchecks and etc. + return &App{ + logger: zapLogger, + err: errChan, + }, err +} diff --git a/app/logrecords.go b/app/logrecords.go new file mode 100644 index 0000000..0dbaf96 --- /dev/null +++ b/app/logrecords.go @@ -0,0 +1,10 @@ +package app + +type InfoSvcStarted struct{} +type InfoSvcReady struct{} +type InfoSvcShutdown struct { + Signal string +} +type ErrorCanNotServe struct { + Err error +} diff --git a/dal/dal.go b/dal/dal.go new file mode 100644 index 0000000..d725632 --- /dev/null +++ b/dal/dal.go @@ -0,0 +1,39 @@ +package dal + +import ( + "context" + "fmt" + "github.com/minio/minio-go/v7" + "io" +) + +const ( + bucketAnswers = "squizanswer" +) + +type Storer struct { + client *minio.Client +} + +func New(ctx context.Context, minioClient *minio.Client) (*Storer, error) { + if ok, err := minioClient.BucketExists(ctx, bucketAnswers); !ok { + if err := minioClient.MakeBucket(ctx, bucketAnswers, minio.MakeBucketOptions{}); err != nil { + return nil, err + } + } else if err != nil { + return nil, err + } + + return &Storer{ + client: minioClient, + }, nil +} + +func (s *Storer) PutAnswer(ctx context.Context, file io.Reader, quizId, name string, question uint64, size int64) error { + if _, err := s.client.PutObject(ctx, bucketAnswers, fmt.Sprintf("%s/%d/%s", quizId, question, name), file, size, + minio.PutObjectOptions{}); err != nil { + return err + } + + return nil +} diff --git a/deployments/local/docker-compose.yaml b/deployments/local/docker-compose.yaml new file mode 100644 index 0000000..2f80b77 --- /dev/null +++ b/deployments/local/docker-compose.yaml @@ -0,0 +1,12 @@ +services: + postgres: + image: postgres + restart: always + environment: + POSTGRES_PASSWORD: Redalert2 + POSTGRES_USER: squiz + POSTGRES_DB: squiz + app: + image: penahub.gitlab.yandexcloud.net:5050/backend/squiz:latest + ports: + - 1488:1488 diff --git a/deployments/main/docker-compose.yaml b/deployments/main/docker-compose.yaml new file mode 100644 index 0000000..da5ff20 --- /dev/null +++ b/deployments/main/docker-compose.yaml @@ -0,0 +1,77 @@ +services: + core: + hostname: squiz-core + container_name: squiz-core + image: $CI_REGISTRY_IMAGE/main-core:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID + tty: true + environment: + HUB_ADMIN_URL: 'http://10.8.0.8:59303' + IS_PROD_LOG: 'false' + IS_PROD: 'false' + PORT: 1488 + PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY + PG_CRED: 'host=10.8.0.9 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable' + AUTH_URL: 'http://10.8.0.8:59300/user' + ports: + - 10.8.0.9:1488:1488 + + storer: + hostname: squiz-storer + container_name: squiz-storer + image: $CI_REGISTRY_IMAGE/main-storer:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID + tty: true + environment: + IS_PROD_LOG: 'false' + IS_PROD: 'false' + PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY + PORT: 1489 + MINIO_EP: 'storage.yandexcloud.net' + MINIO_AK: 'YCAJEOcqqTHpiwL4qFwLfHPNA' + MINIO_SK: 'YCNIAIat0XqdDzycWsYKX3OU7mPor6S0WmMoG4Ry' + PG_CRED: 'host=10.8.0.9 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable' + ports: + - 10.8.0.9:1489:1489 + worker: + hostname: squiz-worker + container_name: squiz-worker + image: $CI_REGISTRY_IMAGE/main-worker:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID + tty: true + environment: + IS_PROD_LOG: 'false' + IS_PROD: 'false' + PG_CRED: 'host=10.8.0.9 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable' + KAFKA_BROKER: '10.8.0.8:9092' + KAFKA_TOPIC: 'tariffs' + QUIZ_ID: quizCnt + AMOUNT: 10 + UNLIM_ID: quizUnlimTime + REDIS_HOST: '10.8.0.9:6379' + REDIS_PASSWORD: 'Redalert2' + REDIS_DB: 2 + SMTP_API_URL: 'https://api.smtp.bz/v1/smtp/send' + SMTP_HOST: 'connect.smtp.bz' + SMTP_PORT: '587' + SMTP_UNAME: 'team@pena.digital' + SMTP_PASS: 'AyMfwqA9LkQH' + SMTP_API_KEY: '8tv2xcsfCMBX3TCQxzgeeEwAEYyQrPUp0ggw' + SMTP_SENDER: 'recovery@noreply.pena.digital' + CUSTOMER_SERVICE_ADDRESS: 'http://10.8.0.8:8065/' + answerer: + hostname: squiz-answerer + container_name: squiz-answerer + image: $CI_REGISTRY_IMAGE/main-answerer:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID + tty: true + environment: + IS_PROD_LOG: 'false' + IS_PROD: 'false' + PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY + PORT: 1490 + MINIO_EP: 'storage.yandexcloud.net' + MINIO_AK: 'YCAJEOcqqTHpiwL4qFwLfHPNA' + MINIO_SK: 'YCNIAIat0XqdDzycWsYKX3OU7mPor6S0WmMoG4Ry' + PG_CRED: 'host=10.8.0.9 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable' + REDIS_HOST: '10.8.0.9:6379' + REDIS_PASSWORD: 'Redalert2' + REDIS_DB: 2 + ports: + - 10.8.0.9:1490:1490 diff --git a/deployments/main/staging/docker-compose.yaml b/deployments/main/staging/docker-compose.yaml new file mode 100644 index 0000000..4aaed26 --- /dev/null +++ b/deployments/main/staging/docker-compose.yaml @@ -0,0 +1,77 @@ +services: + core: + hostname: squiz-core + container_name: squiz-core + image: $CI_REGISTRY_IMAGE/core:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID + tty: true + environment: + HUB_ADMIN_URL: 'http://10.6.0.11:59303' + IS_PROD_LOG: 'false' + IS_PROD: 'false' + PORT: 1488 + PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY + PG_CRED: 'host=10.6.0.23 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable' + AUTH_URL: 'http://10.6.0.11:59300/user' + ports: + - 1488:1488 + + storer: + hostname: squiz-storer + container_name: squiz-storer + image: $CI_REGISTRY_IMAGE/storer:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID + tty: true + environment: + IS_PROD_LOG: 'false' + IS_PROD: 'false' + PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY + PORT: 1489 + MINIO_EP: 'storage.yandexcloud.net' + MINIO_AK: 'YCAJEOcqqTHpiwL4qFwLfHPNA' + MINIO_SK: 'YCNIAIat0XqdDzycWsYKX3OU7mPor6S0WmMoG4Ry' + PG_CRED: 'host=10.6.0.23 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable' + ports: + - 1489:1489 + worker: + hostname: squiz-worker + container_name: squiz-worker + image: $CI_REGISTRY_IMAGE/worker:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID + tty: true + environment: + IS_PROD_LOG: 'false' + IS_PROD: 'false' + PG_CRED: 'host=10.6.0.23 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable' + KAFKA_BROKER: '10.6.0.11:9092' + KAFKA_TOPIC: 'tariffs' + QUIZ_ID: quizCnt + AMOUNT: 10 + UNLIM_ID: quizUnlimTime + REDIS_HOST: '10.6.0.23:6379' + REDIS_PASSWORD: 'Redalert2' + REDIS_DB: 2 + SMTP_HOST: 'connect.mailclient.bz' + SMTP_PORT: '587' + SMTP_SENDER: 'noreply@mailing.pena.digital' + SMTP_IDENTITY: '' + SMTP_USERNAME: 'kotilion.95@gmail.com' + SMTP_PASSWORD: 'vWwbCSg4bf0p' + SMTP_API_KEY: 'P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev' + CUSTOMER_SERVICE_ADDRESS: 'http://10.6.0.11:8065/' + answerer: + hostname: squiz-answerer + container_name: squiz-answerer + image: $CI_REGISTRY_IMAGE/answerer:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID + tty: true + environment: + IS_PROD_LOG: 'false' + IS_PROD: 'false' + PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY + PORT: 1490 + MINIO_EP: 'storage.yandexcloud.net' + MINIO_AK: 'YCAJEOcqqTHpiwL4qFwLfHPNA' + MINIO_SK: 'YCNIAIat0XqdDzycWsYKX3OU7mPor6S0WmMoG4Ry' + PG_CRED: 'host=10.6.0.23 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable' + REDIS_HOST: '10.6.0.23:6379' + REDIS_PASSWORD: 'Redalert2' + REDIS_DB: 2 + ports: + - 1490:1490 diff --git a/deployments/staging/docker-compose.yaml b/deployments/staging/docker-compose.yaml new file mode 100644 index 0000000..76c10ae --- /dev/null +++ b/deployments/staging/docker-compose.yaml @@ -0,0 +1,77 @@ +services: + core: + hostname: squiz-core + container_name: squiz-core + image: $CI_REGISTRY_IMAGE/staging-core:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID + tty: true + environment: + HUB_ADMIN_URL: 'http://10.6.0.11:59303' + IS_PROD_LOG: 'false' + IS_PROD: 'false' + PORT: 1488 + PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY + PG_CRED: 'host=10.6.0.23 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable' + AUTH_URL: 'http://10.6.0.11:59300/user' + ports: + - 1488:1488 + + storer: + hostname: squiz-storer + container_name: squiz-storer + image: $CI_REGISTRY_IMAGE/staging-storer:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID + tty: true + environment: + IS_PROD_LOG: 'false' + IS_PROD: 'false' + PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY + PORT: 1489 + MINIO_EP: 'storage.yandexcloud.net' + MINIO_AK: 'YCAJEOcqqTHpiwL4qFwLfHPNA' + MINIO_SK: 'YCNIAIat0XqdDzycWsYKX3OU7mPor6S0WmMoG4Ry' + PG_CRED: 'host=10.6.0.23 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable' + ports: + - 1489:1489 + worker: + hostname: squiz-worker + container_name: squiz-worker + image: $CI_REGISTRY_IMAGE/staging-worker:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID + tty: true + environment: + IS_PROD_LOG: 'false' + IS_PROD: 'false' + PG_CRED: 'host=10.6.0.23 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable' + KAFKA_BROKER: '10.6.0.11:9092' + KAFKA_TOPIC: 'tariffs' + QUIZ_ID: quizCnt + AMOUNT: 10 + UNLIM_ID: quizUnlimTime + REDIS_HOST: '10.6.0.23:6379' + REDIS_PASSWORD: 'Redalert2' + REDIS_DB: 2 + SMTP_HOST: 'connect.mailclient.bz' + SMTP_PORT: '587' + SMTP_SENDER: 'noreply@mailing.pena.digital' + SMTP_IDENTITY: '' + SMTP_USERNAME: 'kotilion.95@gmail.com' + SMTP_PASSWORD: 'vWwbCSg4bf0p' + SMTP_API_KEY: 'P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev' + CUSTOMER_SERVICE_ADDRESS: 'http://10.6.0.11:8065/' + answerer: + hostname: squiz-answerer + container_name: squiz-answerer + image: $CI_REGISTRY_IMAGE/staging-answerer:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID + tty: true + environment: + IS_PROD_LOG: 'false' + IS_PROD: 'false' + PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY + PORT: 1490 + MINIO_EP: 'storage.yandexcloud.net' + MINIO_AK: 'YCAJEOcqqTHpiwL4qFwLfHPNA' + MINIO_SK: 'YCNIAIat0XqdDzycWsYKX3OU7mPor6S0WmMoG4Ry' + PG_CRED: 'host=10.6.0.23 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable' + REDIS_HOST: '10.6.0.23:6379' + REDIS_PASSWORD: 'Redalert2' + REDIS_DB: 2 + ports: + - 1490:1490 diff --git a/deployments/test/docker-compose.yaml b/deployments/test/docker-compose.yaml new file mode 100644 index 0000000..b66713c --- /dev/null +++ b/deployments/test/docker-compose.yaml @@ -0,0 +1,102 @@ +version: '3' +services: + test-postgres: + image: postgres + environment: + POSTGRES_PASSWORD: Redalert2 + POSTGRES_USER: squiz + POSTGRES_DB: squiz + volumes: + - test-postgres:/var/lib/postgresql/data + ports: + - 35432:5432 + networks: + - penatest + healthcheck: + test: pg_isready -U squiz + interval: 2s + timeout: 2s + retries: 10 + +# need update! +# test-pena-auth-service: +# image: penahub.gitlab.yandexcloud.net:5050/pena-services/pena-auth-service:staging.872 +# container_name: test-pena-auth-service +# init: true +# env_file: auth.env.test +# healthcheck: +# test: wget -T1 --spider http://localhost:8000/user +# interval: 2s +# timeout: 2s +# retries: 5 +# environment: +# - DB_HOST=test-pena-auth-db +# - DB_PORT=27017 +# - ENVIRONMENT=staging +# - HTTP_HOST=0.0.0.0 +# - HTTP_PORT=8000 +# - DB_USERNAME=test +# - DB_PASSWORD=test +# - DB_NAME=admin +# - DB_AUTH=admin +# # ports: +# # - 8000:8000 +# depends_on: +# - test-pena-auth-db +# # - pena-auth-migration +# networks: +# - penatest +# +# test-pena-auth-db: +# container_name: test-pena-auth-db +# init: true +# image: "mongo:6.0.3" +# command: mongod --quiet --logpath /dev/null +# volumes: +# - test-mongodb:/data/db +# - test-mongoconfdb:/data/configdb +# environment: +# MONGO_INITDB_ROOT_USERNAME: test +# MONGO_INITDB_ROOT_PASSWORD: test +# # ports: +# # - 27017:27017 +# networks: +# - penatest + + test-minio: + container_name: test-minio + init: true + image: quay.io/minio/minio + volumes: + - test-minio:/data + command: [ "minio", "--quiet", "server", "/data" ] + networks: + - penatest + + test-squiz: + container_name: test-squiz + init: true + build: + context: ../.. + dockerfile: TestsDockerfile + depends_on: + test-postgres: + condition: service_healthy +# test-pena-auth-service: +# condition: service_healthy + # volumes: + # - ./../..:/app:ro + # command: [ "go", "test", "./tests", "-run", "TestFoo" ] + command: [ "go", "test", "-parallel", "1", "./tests" ] + networks: + - penatest + +networks: + penatest: + + +volumes: + test-minio: + test-postgres: + test-mongodb: + test-mongoconfdb: diff --git a/deployments/testmigrate/docker-compose.yaml b/deployments/testmigrate/docker-compose.yaml new file mode 100644 index 0000000..dd98264 --- /dev/null +++ b/deployments/testmigrate/docker-compose.yaml @@ -0,0 +1,23 @@ +version: '3' +services: + test-postgres: + image: postgres + environment: + POSTGRES_PASSWORD: Redalert2 + POSTGRES_USER: squiz + POSTGRES_DB: squiz + ports: + - 35432:5432 + networks: + - penatest + healthcheck: + test: pg_isready -U squiz + interval: 2s + timeout: 2s + retries: 10 + +networks: + penatest: + +# просто чтоб тестануть мигрировала ли бд +# в app/app.go pgdal, err := dal.New(ctx, "host=localhost port=35432 user=squiz password=Redalert2 dbname=squiz sslmode=disable") diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7eab180 --- /dev/null +++ b/go.mod @@ -0,0 +1,54 @@ +module penahub.gitlab.yandexcloud.net/backend/quiz/answerer.git + +go 1.21.4 + +require ( + github.com/go-redis/redis/v8 v8.11.5 + github.com/gofiber/fiber/v2 v2.52.0 + github.com/minio/minio-go/v7 v7.0.67 + github.com/rs/xid v1.5.0 + github.com/skeris/appInit v1.0.2 + github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf + go.uber.org/zap v1.26.0 + penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240219175507-7f8de986a6dc + penahub.gitlab.yandexcloud.net/backend/quiz/worker.git v0.0.0-20240219182009-751c74a08732 +) + +require ( + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/golang-migrate/migrate/v4 v4.17.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.4 // indirect + github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/minio/md5-simd v1.1.2 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.51.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/crypto v0.17.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/protobuf v1.32.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240202120244-c4ef330cfe5d // indirect + penahub.gitlab.yandexcloud.net/backend/quiz/core.git v0.0.0-20240219174804-d78fd38511af // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8098f5c --- /dev/null +++ b/go.sum @@ -0,0 +1,195 @@ +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/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/dhui/dktest v0.4.0 h1:z05UmuXZHO/bgj/ds2bGMBu8FI4WA+Ag/m3ghL+om7M= +github.com/dhui/dktest v0.4.0/go.mod h1:v/Dbz1LgCBOi2Uki2nUqLBGa83hWBGFMu5MrgMDCc78= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= +github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/gofiber/fiber/v2 v2.52.0 h1:S+qXi7y+/Pgvqq4DrSmREGiFwtB7Bu6+QFLuIHYw/UE= +github.com/gofiber/fiber/v2 v2.52.0/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-migrate/migrate/v4 v4.17.0 h1:rd40H3QXU0AA4IoLllFcEAEo9dYKRHYND2gB4p7xcaU= +github.com/golang-migrate/migrate/v4 v4.17.0/go.mod h1:+Cp2mtLP4/aXDTKb9wmXYitdrNx2HGs45rbWAo6OsKM= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +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/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= +github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +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/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +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.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= +github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= +github.com/minio/minio-go/v7 v7.0.67 h1:BeBvZWAS+kRJm1vGTMJYVjKUNoo0FoEt/wUWdUtfmh8= +github.com/minio/minio-go/v7 v7.0.67/go.mod h1:+UXocnUeZ3wHvVh5s95gcrA4YjMIbccT6ubB+1m054A= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +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/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skeris/appInit v1.0.2 h1:Hr4KbXYd6kolTVq4cXGqDpgnpmaauiOiKizA1+Ep4KQ= +github.com/skeris/appInit v1.0.2/go.mod h1:4ElEeXWVGzU3dlYq/eMWJ/U5hd+LKisc1z3+ySh1XmY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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/themakers/hlog v0.0.0-20191205140925-235e0e4baddf h1:TJJm6KcBssmbWzplF5lzixXl1RBAi/ViPs1GaSOkhwo= +github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf/go.mod h1:1FsorU3vnXO9xS9SrhUp8fRb/6H/Zfll0rPt1i4GWaA= +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/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= +github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +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.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= +golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240202120244-c4ef330cfe5d h1:gbaDt35HMDqOK84WYmDIlXMI7rstUcRqNttaT6Kx1do= +penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240202120244-c4ef330cfe5d/go.mod h1:lTmpjry+8evVkXWbEC+WMOELcFkRD1lFMc7J09mOndM= +penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240219175507-7f8de986a6dc h1:jIN9XyfL/FJ/eSsYopE1olHboituwmisC1Sf1d4nhWE= +penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240219175507-7f8de986a6dc/go.mod h1:OXYvMlc+3qfcllPTywUB3QDiPK1kwsMNdZMTlPXFIdo= +penahub.gitlab.yandexcloud.net/backend/quiz/core.git v0.0.0-20240219174804-d78fd38511af h1:jQ7HaXSutDX5iepU7VRImxhikK7lV/lBKkiloOZ4Emo= +penahub.gitlab.yandexcloud.net/backend/quiz/core.git v0.0.0-20240219174804-d78fd38511af/go.mod h1:5S5YwjSXWmnEKjBjG6MtyGtFmljjukDRS8CwHk/CF/I= +penahub.gitlab.yandexcloud.net/backend/quiz/worker.git v0.0.0-20240219182009-751c74a08732 h1:FiUWe2OkFZsTFyoKZIE2uMMEPS0JRetHDG1CWdmayvs= +penahub.gitlab.yandexcloud.net/backend/quiz/worker.git v0.0.0-20240219182009-751c74a08732/go.mod h1:AV230HAt2MMzZVdBNxyBaOFmZ4KY4ieDBpBhokIPlPs= diff --git a/main.go b/main.go new file mode 100644 index 0000000..a2dc792 --- /dev/null +++ b/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/skeris/appInit" + "penahub.gitlab.yandexcloud.net/backend/quiz/answerer.git/app" +) + +func main() { + appInit.Initialize(app.New, app.Options{}) +} diff --git a/middleware/middleware.go b/middleware/middleware.go new file mode 100644 index 0000000..6903804 --- /dev/null +++ b/middleware/middleware.go @@ -0,0 +1,28 @@ +package middleware + +import ( + "github.com/gofiber/fiber/v2" + "github.com/rs/xid" +) + +type ContextKey string + +const ( + SessionKey = "X-SessionKey" +) + +func AnswererChain() fiber.Handler { + return func(c *fiber.Ctx) error { + session := c.Get(SessionKey) + + if session == "" { + session := xid.New().String() + c.Set(SessionKey, session) + c.Locals(ContextKey(SessionKey), session) + } else { + c.Locals(ContextKey(SessionKey), session) + } + + return c.Next() + } +} diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 0000000..e69de29 diff --git a/service/service.go b/service/service.go new file mode 100644 index 0000000..c7a243b --- /dev/null +++ b/service/service.go @@ -0,0 +1,295 @@ +package service + +import ( + "encoding/json" + "fmt" + "github.com/gofiber/fiber/v2" + "penahub.gitlab.yandexcloud.net/backend/quiz/answerer.git/dal" + "penahub.gitlab.yandexcloud.net/backend/quiz/answerer.git/middleware" + quizdal "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal" + "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model" + "strings" + "sync" + "time" + + "github.com/rs/xid" +) + +const ( + quizIdCookie = "qud" + fingerprintCookie = "fp" +) + +type Service struct { + store *dal.Storer + dal *quizdal.DAL + batch []model.Answer + m sync.Mutex + workerRespondentCh chan<- []model.Answer + workerSendClientCh chan<- model.Answer +} + +func New(s *dal.Storer, q *quizdal.DAL, workerRespondentCh chan<- []model.Answer, workerSendClientCh chan<- model.Answer) *Service { + return &Service{ + store: s, + dal: q, + m: sync.Mutex{}, + batch: []model.Answer{}, + workerRespondentCh: workerRespondentCh, + workerSendClientCh: workerSendClientCh, + } +} + +func (s *Service) Register(app *fiber.App) *fiber.App { + app.Post("/answer", s.PutAnswersOnePiece) + app.Post("/settings", s.GetQuizData) + return app +} + +// GetQuizDataReq request data for get data for user +type GetQuizDataReq struct { + QuizId string `json:"quiz_id"` // relation to quiz + Limit uint64 `json:"limit"` + Page uint64 `json:"page"` + NeedConfig bool `json:"need_config"` // true if you need not only question page +} + +// GetQuizDataResp response with prepared data for user +type GetQuizDataResp struct { + Settings ShavedQuiz `json:"settings"` + Items []ShavedQuestion `json:"items"` + Count uint64 `json:"cnt"` +} + +// ShavedQuiz shortened struct for delivery data to customer +type ShavedQuiz struct { + Fingerprinting bool `json:"fp"` + Repeatable bool `json:"rep"` + Name string `json:"name"` + Config string `json:"cfg"` + Limit uint64 `json:"lim"` + DueTo uint64 `json:"due"` + TimeOfPassing uint64 `json:"delay"` + Pausable bool `json:"pausable"` +} + +// ShavedQuestion shortened struct for delivery data to customer +type ShavedQuestion struct { + Id uint64 `json:"id"` + Title string `json:"title"` + Description string `json:"desc"` + Type string `json:"typ"` + Required bool `json:"req"` + Page int `json:"p"` + Content string `json:"c"` +} + +// GetQuizData handler for obtaining data for quiz front rendering +func (s *Service) GetQuizData(c *fiber.Ctx) error { + var req GetQuizDataReq + if err := c.BodyParser(&req); err != nil { + return c.Status(fiber.StatusBadRequest).SendString("Invalid request data") + } + + if req.QuizId == "" { + return c.Status(fiber.StatusBadRequest).SendString("Invalid request data") + } + + if req.Limit == 0 && !req.NeedConfig { + return c.Status(fiber.StatusLengthRequired).SendString("no data requested") + } + + quiz, err := s.dal.QuizRepo.GetQuizByQid(c.Context(), req.QuizId) + if err != nil { + return c.Status(fiber.StatusInternalServerError).SendString(err.Error()) + } + + if req.Limit == 0 && req.NeedConfig { + return c.Status(fiber.StatusOK).JSON(GetQuizDataResp{ + Settings: dao2dtoQuiz(quiz), + }) + } + + if quiz.UniqueAnswers { + //todo implement after creating store answers + } + if quiz.Status != model.StatusStart { + return c.Status(fiber.StatusLocked).SendString("quiz is inactive") + } + + if quiz.Limit > 0 { + // todo implement after creating store answer + } + + if quiz.DueTo < uint64(time.Now().Unix()) && quiz.DueTo > 0 { + return c.Status(fiber.StatusGone).SendString("quiz timeouted") + } + + questions, cnt, err := s.dal.QuestionRepo.GetQuestionList( + c.Context(), + req.Limit, + req.Page*req.Limit, + 0, 0, quiz.Id, false, false, "", "", + ) + if err != nil { + return c.Status(fiber.StatusInternalServerError).SendString(err.Error()) + } + + result := GetQuizDataResp{ + Count: cnt, + Items: []ShavedQuestion{}, + } + + if req.NeedConfig { + result.Settings = dao2dtoQuiz(quiz) + } + + for _, q := range questions { + result.Items = append(result.Items, dao2dtoQuestion(q)) + } + + if cnt <= req.Limit { + return c.Status(fiber.StatusOK).JSON(result) + } else { + return c.Status(fiber.StatusPartialContent).JSON(result) + } +} + +func dao2dtoQuestion(question model.Question) ShavedQuestion { + return ShavedQuestion{ + Id: question.Id, + Title: question.Title, + Description: question.Description, + Type: question.Type, + Required: false, + Page: question.Page, + Content: question.Content, + } +} + +func dao2dtoQuiz(quiz model.Quiz) ShavedQuiz { + + return ShavedQuiz{ + Fingerprinting: quiz.Fingerprinting, + Repeatable: quiz.Repeatable, + Name: quiz.Name, + Config: quiz.Config, + Limit: quiz.Limit, + DueTo: quiz.DueTo, + TimeOfPassing: quiz.TimeOfPassing, + Pausable: quiz.Pausable, + } +} + +// MB Size constants +const ( + MB = 1 << 20 + filePrefix = "file:" +) + +type PutAnswersResponse struct { + FileIDMap map[uint64]string `json:"fileIDMap"` + Stored []uint64 `json:"stored"` +} + +func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error { + cs, ok := c.Context().Value(middleware.ContextKey(middleware.SessionKey)).(string) + if !ok { + return c.Status(fiber.StatusUnauthorized).SendString("no session in cookie") + } + + form, err := c.MultipartForm() + if err != nil || form == nil || form.File == nil { + return c.Status(fiber.StatusBadRequest).SendString("expecting multipart form file") + } + + answersStr := form.Value["answers"] + if len(answersStr) == 0 { + return c.Status(fiber.StatusFailedDependency).SendString("no answers provided") + } + + var ( + answersRaw, answers, trueRes []model.Answer + errs []error + ) + + if err := json.Unmarshal([]byte(answersStr[0]), &answersRaw); err != nil { + return c.Status(fiber.StatusBadRequest).SendString("not valid answers string") + } + + quizID, ok := form.Value["qid"] + if !ok { + return c.Status(fiber.StatusFailedDependency).SendString("no quiz id provided") + } + + fp := "" + if cfp := c.Cookies(fingerprintCookie); cfp != "" { + fp = cfp + } + + quiz, err := s.dal.QuizRepo.GetQuizByQid(c.Context(), quizID[0]) + if err != nil { + return c.Status(fiber.StatusInternalServerError).SendString("can not get quiz") + } + + fileIDMap := make(map[uint64]string) + + for _, ans := range answersRaw { + if strings.HasPrefix(ans.Content, filePrefix) { + filekey := strings.TrimPrefix(ans.Content, filePrefix) + filenameparts := strings.Split(filekey, ".") + filetail := filenameparts[len(filenameparts)-1] + fileparts := form.File[filekey] + + if len(fileparts) == 0 { + errs = append(errs, fmt.Errorf("no parts for file: %s", filekey)) + continue + } + + r, err := fileparts[0].Open() + if err != nil { + errs = append(errs, fmt.Errorf("can not open part for file: %s", filekey)) + continue + } + + fname := fmt.Sprintf("%s.%s", xid.New().String(), filetail) + if err := s.store.PutAnswer(c.Context(), r, quizID[0], fname, ans.QuestionId, fileparts[0].Size); err != nil { + return c.Status(fiber.StatusInternalServerError).SendString("can not upload file answers") + } + ans.Content = fname + + fileIDMap[ans.QuestionId] = fname + } + + ans.Session = cs + ans.QuizId = quiz.Id + ans.CreatedAt = time.Now() + answers = append(answers, ans) + if ans.Result { + s.workerSendClientCh <- ans + trueRes = append(trueRes, ans) + } + } + + quizConfig := model.QuizConfig{} + err = json.Unmarshal([]byte(quiz.Config), &quizConfig) + if err != nil { + return c.Status(fiber.StatusInternalServerError).SendString("can not unmarshal quiz config") + } + + if quizConfig.Mailing.When == "email" && len(trueRes) > 0 { + s.workerRespondentCh <- trueRes + } + + stored, ers := s.dal.AnswerRepo.CreateAnswers(c.Context(), answers, cs, fp, quiz.Id) + if len(ers) != 0 { + return c.Status(fiber.StatusInternalServerError).SendString("some errors are casualted: " + fmt.Sprint(ers)) + } + + response := PutAnswersResponse{ + FileIDMap: fileIDMap, + Stored: stored, + } + + return c.Status(fiber.StatusOK).JSON(response) +}