first adding
This commit is contained in:
parent
7e0ebc99f6
commit
d78fd38511
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@ -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
|
158
app/app.go
Normal file
158
app/app.go
Normal file
@ -0,0 +1,158 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/skeris/appInit"
|
||||
"github.com/themakers/hlog"
|
||||
"go.uber.org/zap"
|
||||
"penahub.gitlab.yandexcloud.net/backend/penahub_common/privilege"
|
||||
"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/core.git/clients/auth"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/core.git/middleware"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/core.git/service"
|
||||
)
|
||||
|
||||
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"`
|
||||
NumberPort string `env:"PORT" default:"1488"`
|
||||
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"`
|
||||
HubAdminUrl string `env:"HUB_ADMIN_URL"`
|
||||
ServiceName string `env:"SERVICE_NAME" default:"squiz"`
|
||||
AuthServiceURL string `env:"AUTH_URL"`
|
||||
}
|
||||
|
||||
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{})
|
||||
|
||||
authClient := auth.NewAuthClient(options.AuthServiceURL)
|
||||
|
||||
pgdal, err := dal.New(ctx, options.PostgresCredentials, authClient)
|
||||
if err != nil {
|
||||
fmt.Println("NEW", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := pgdal.Init(); err != nil {
|
||||
fmt.Println("INIT", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientData := privilege.Client{
|
||||
URL: options.HubAdminUrl,
|
||||
ServiceName: options.ServiceName,
|
||||
Privileges: model.Privileges,
|
||||
}
|
||||
fiberClient := &fiber.Client{}
|
||||
privilegeController := privilege.NewPrivilege(clientData, fiberClient)
|
||||
err = privilegeController.PublishPrivileges()
|
||||
if err != nil {
|
||||
logger.Module("Failed to publish privileges")
|
||||
}
|
||||
|
||||
app := fiber.New()
|
||||
app.Use(middleware.JWTAuth())
|
||||
app.Get("/liveness", healthchecks.Liveness)
|
||||
app.Get("/readiness", healthchecks.Readiness(&workerErr)) //todo parametrized readiness. should discuss ready reason
|
||||
|
||||
svc := service.New(pgdal)
|
||||
svc.Register(app)
|
||||
|
||||
logger.Emit(InfoSvcReady{})
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if pgdal != nil {
|
||||
pgdal.Close()
|
||||
}
|
||||
err := app.Shutdown()
|
||||
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
|
||||
}
|
10
app/logrecords.go
Normal file
10
app/logrecords.go
Normal file
@ -0,0 +1,10 @@
|
||||
package app
|
||||
|
||||
type InfoSvcStarted struct{}
|
||||
type InfoSvcReady struct{}
|
||||
type InfoSvcShutdown struct {
|
||||
Signal string
|
||||
}
|
||||
type ErrorCanNotServe struct {
|
||||
Err error
|
||||
}
|
44
clients/auth/auth.go
Normal file
44
clients/auth/auth.go
Normal file
@ -0,0 +1,44 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"log"
|
||||
)
|
||||
|
||||
type AuthClient struct {
|
||||
URL string
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Email string `json:"login"`
|
||||
}
|
||||
|
||||
func NewAuthClient(url string) *AuthClient {
|
||||
if url == "" {
|
||||
log.Panicln("url is nil on <NewAuthClient>")
|
||||
}
|
||||
|
||||
return &AuthClient{
|
||||
URL: url,
|
||||
}
|
||||
}
|
||||
|
||||
func (client *AuthClient) GetUserEmail(userID string) (string, error) {
|
||||
fiberClient := fiber.Client{}
|
||||
|
||||
userURL := fmt.Sprintf("%s/%s", client.URL, userID)
|
||||
|
||||
var user User
|
||||
status, resp, errs := fiberClient.Get(userURL).Struct(&user)
|
||||
|
||||
if len(errs) > 0 {
|
||||
return "", errs[0]
|
||||
}
|
||||
|
||||
if status != fiber.StatusOK {
|
||||
return "", fmt.Errorf("unexpected status code: %d, user: %s, reesponse: %s", status, userID, string(resp))
|
||||
}
|
||||
|
||||
return user.Email, nil
|
||||
}
|
12
deployments/local/docker-compose.yaml
Normal file
12
deployments/local/docker-compose.yaml
Normal file
@ -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
|
77
deployments/main/docker-compose.yaml
Normal file
77
deployments/main/docker-compose.yaml
Normal file
@ -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
|
77
deployments/main/staging/docker-compose.yaml
Normal file
77
deployments/main/staging/docker-compose.yaml
Normal file
@ -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
|
77
deployments/staging/docker-compose.yaml
Normal file
77
deployments/staging/docker-compose.yaml
Normal file
@ -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
|
102
deployments/test/docker-compose.yaml
Normal file
102
deployments/test/docker-compose.yaml
Normal file
@ -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:
|
23
deployments/testmigrate/docker-compose.yaml
Normal file
23
deployments/testmigrate/docker-compose.yaml
Normal file
@ -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")
|
30
go.mod
Normal file
30
go.mod
Normal file
@ -0,0 +1,30 @@
|
||||
module penahub.gitlab.yandexcloud.net/backend/quiz/core.git
|
||||
|
||||
go 1.21.4
|
||||
|
||||
require (
|
||||
github.com/gofiber/fiber/v2 v2.52.0
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/skeris/appInit v1.0.2
|
||||
github.com/tealeg/xlsx v1.0.5
|
||||
github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf
|
||||
go.uber.org/zap v1.26.0
|
||||
penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240202120244-c4ef330cfe5d
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/klauspost/compress v1.17.0 // 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/rivo/uniseg v0.2.0 // 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/multierr v1.10.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240219174347-aa1b1a89378e // indirect
|
||||
)
|
100
go.sum
Normal file
100
go.sum
Normal file
@ -0,0 +1,100 @@
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
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/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/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
|
||||
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
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/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
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/pkg/errors v0.8.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/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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
|
||||
github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
|
||||
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/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/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/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/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-20220811171246-fbc7d0a398ab/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/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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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-20240219173525-b14fbf58477f h1:4i2MBe+UZeboeWTOW37x0cAMml+ZIzXO0DIQPhm5rLo=
|
||||
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240219173525-b14fbf58477f/go.mod h1:rcY5DQK14XW+/kYNOujQXPf79oZE5eI74sJntAKY7ek=
|
||||
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240219174347-aa1b1a89378e h1:/LG1jEu8gPL6K4vLBuHpm6/0kIua+J+KnbJL1yd8CC8=
|
||||
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240219174347-aa1b1a89378e/go.mod h1:rcY5DQK14XW+/kYNOujQXPf79oZE5eI74sJntAKY7ek=
|
10
main.go
Normal file
10
main.go
Normal file
@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/skeris/appInit"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/core.git/app"
|
||||
)
|
||||
|
||||
func main() {
|
||||
appInit.Initialize(app.New, app.Options{})
|
||||
}
|
80
middleware/middleware.go
Normal file
80
middleware/middleware.go
Normal file
@ -0,0 +1,80 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
const (
|
||||
AccountId = "id"
|
||||
)
|
||||
|
||||
func JWTAuth() fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
authHeader := c.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
c.Status(fiber.StatusUnauthorized).SendString("no JWT found")
|
||||
return nil
|
||||
}
|
||||
|
||||
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
if tokenString == authHeader {
|
||||
c.Status(fiber.StatusUnauthorized).SendString("invalid JWT Header: missing Bearer")
|
||||
return nil
|
||||
}
|
||||
|
||||
publicKey := os.Getenv("PUBLIC_ACCESS_SECRET_KEY")
|
||||
if publicKey == "" {
|
||||
// TODO log
|
||||
c.Status(fiber.StatusInternalServerError).SendString("public key not found")
|
||||
return nil
|
||||
}
|
||||
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
return jwt.ParseRSAPublicKeyFromPEM([]byte(publicKey))
|
||||
})
|
||||
if err != nil {
|
||||
c.Status(fiber.StatusUnauthorized).SendString("invalid JWT")
|
||||
return nil
|
||||
}
|
||||
|
||||
if token.Valid {
|
||||
expirationTime, err := token.Claims.GetExpirationTime()
|
||||
if err != nil {
|
||||
c.Status(fiber.StatusUnauthorized).SendString("no expiration time in JWT")
|
||||
return nil
|
||||
}
|
||||
if time.Now().Unix() >= expirationTime.Unix() {
|
||||
c.Status(fiber.StatusUnauthorized).SendString("expired JWT")
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
c.Status(fiber.StatusUnauthorized).SendString("invalid JWT")
|
||||
return nil
|
||||
}
|
||||
|
||||
m, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
c.Status(fiber.StatusInternalServerError).SendString("broken token claims")
|
||||
return nil
|
||||
}
|
||||
|
||||
id, ok := m["id"].(string)
|
||||
if !ok || id == "" {
|
||||
c.Status(fiber.StatusUnauthorized).SendString("missing id claim in JWT")
|
||||
return nil
|
||||
}
|
||||
|
||||
c.Context().SetUserValue(AccountId, id)
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func GetAccountId(c *fiber.Ctx) (string, bool) {
|
||||
id, ok := c.Context().UserValue(AccountId).(string)
|
||||
return id, ok
|
||||
}
|
0
openapi.yaml
Normal file
0
openapi.yaml
Normal file
99
pkg/excel_export.go
Normal file
99
pkg/excel_export.go
Normal file
@ -0,0 +1,99 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"github.com/tealeg/xlsx"
|
||||
"io"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func WriteDataToExcel(buffer io.Writer, questions []model.Question, answers []model.Answer) error {
|
||||
file := xlsx.NewFile()
|
||||
sheet, err := file.AddSheet("Results")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
headers := []string{"Данные респондента"}
|
||||
mapQueRes := make(map[uint64]string)
|
||||
|
||||
for _, q := range questions {
|
||||
if !q.Deleted {
|
||||
if q.Type == model.TypeResult {
|
||||
mapQueRes[q.Id] = q.Title + "\n" + q.Description
|
||||
} else {
|
||||
headers = append(headers, q.Title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
headers = append(headers, "Результат")
|
||||
|
||||
// добавляем заголовки в первую строку
|
||||
row := sheet.AddRow()
|
||||
for _, header := range headers {
|
||||
cell := row.AddCell()
|
||||
cell.Value = header
|
||||
}
|
||||
|
||||
// мапа для хранения обычных ответов респондентов
|
||||
standart := make(map[string][]model.Answer)
|
||||
|
||||
// мапа для хранения данных респондентов
|
||||
results := make(map[string]model.Answer)
|
||||
|
||||
// заполняем мапу ответами и данными респондентов
|
||||
for _, answer := range answers {
|
||||
if answer.Result {
|
||||
// если это результат то данные респондента берутся из контента ответа по сессии
|
||||
results[answer.Session] = answer
|
||||
} else {
|
||||
// если это обычный ответ то добавляем его в соответствующий список ответов респондента
|
||||
standart[answer.Session] = append(standart[answer.Session], answer)
|
||||
}
|
||||
}
|
||||
// записываем данные в файл
|
||||
for session, _ := range results {
|
||||
response := standart[session]
|
||||
row := sheet.AddRow()
|
||||
row.AddCell().Value = results[session].Content // данные респондента
|
||||
for _, q := range questions {
|
||||
if !q.Deleted && q.Type != model.TypeResult {
|
||||
sort.Slice(response, func(i, j int) bool {
|
||||
return response[i].QuestionId < response[j].QuestionId
|
||||
})
|
||||
index := binarySearch(response, q.Id)
|
||||
if index != -1 {
|
||||
row.AddCell().Value = response[index].Content
|
||||
} else {
|
||||
row.AddCell().Value = "-"
|
||||
}
|
||||
}
|
||||
}
|
||||
row.AddCell().Value = mapQueRes[results[session].QuestionId]
|
||||
}
|
||||
|
||||
// cохраняем данные в буфер
|
||||
err = file.Write(buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func binarySearch(answers []model.Answer, questionID uint64) int {
|
||||
left := 0
|
||||
right := len(answers) - 1
|
||||
for left <= right {
|
||||
mid := left + (right-left)/2
|
||||
if answers[mid].QuestionId == questionID {
|
||||
return mid
|
||||
} else if answers[mid].QuestionId < questionID {
|
||||
left = mid + 1
|
||||
} else {
|
||||
right = mid - 1
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
187
service/account_svc.go
Normal file
187
service/account_svc.go
Normal file
@ -0,0 +1,187 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/core.git/middleware"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CreateAccountReq struct {
|
||||
UserID string `json:"userId"`
|
||||
}
|
||||
|
||||
type CreateAccountResp struct {
|
||||
CreatedAccount model.Account `json:"created_account"`
|
||||
}
|
||||
|
||||
type DeleteAccountResp struct {
|
||||
DeletedAccountID string `json:"account_Id"`
|
||||
}
|
||||
|
||||
type GetPrivilegeByUserIDReq struct {
|
||||
UserID string `json:"userId"`
|
||||
}
|
||||
|
||||
type DeleteAccountByUserIDReq struct {
|
||||
UserID string `json:"userId"`
|
||||
}
|
||||
|
||||
type DeleteAccountByUserIDResp struct {
|
||||
DeletedAccountUserID string `json:"userId"`
|
||||
}
|
||||
|
||||
type GetAccountsReq struct {
|
||||
Limit uint64 `json:"limit"`
|
||||
Page uint64 `json:"page"`
|
||||
}
|
||||
|
||||
type GetAccountsResp struct {
|
||||
Count uint64 `json:"count"`
|
||||
Items []model.Account `json:"items"`
|
||||
}
|
||||
|
||||
// getCurrentAccount обработчик для получения текущего аккаунта
|
||||
func (s *Service) getCurrentAccount(ctx *fiber.Ctx) error {
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
account, err := s.dal.AccountRepo.GetAccountByID(ctx.Context(), accountID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
//TODO: fix this later
|
||||
if account.ID == "" {
|
||||
return ctx.Status(fiber.StatusNotFound).SendString("no account")
|
||||
}
|
||||
|
||||
return ctx.Status(fiber.StatusOK).JSON(account)
|
||||
}
|
||||
|
||||
// createAccount обработчик для создания нового аккаунта
|
||||
func (s *Service) createAccount(ctx *fiber.Ctx) error {
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
existingAccount, err := s.dal.AccountRepo.GetAccountByID(ctx.Context(), accountID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
if existingAccount.ID != "" {
|
||||
return ctx.Status(fiber.StatusConflict).SendString("user with this ID already exists")
|
||||
}
|
||||
|
||||
newAccount := model.Account{
|
||||
UserID: accountID,
|
||||
CreatedAt: time.Now(),
|
||||
Deleted: false,
|
||||
Privileges: map[string]model.ShortPrivilege{
|
||||
"quizUnlimTime": {
|
||||
PrivilegeID: "quizUnlimTime",
|
||||
PrivilegeName: "Безлимит Опросов",
|
||||
Amount: 14,
|
||||
CreatedAt: time.Now(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := s.dal.AccountRepo.CreateAccount(ctx.Context(), &newAccount); err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
return ctx.JSON(CreateAccountResp{
|
||||
CreatedAccount: newAccount,
|
||||
})
|
||||
}
|
||||
|
||||
// deleteAccount обработчик для удаления текущего аккаунта
|
||||
func (s *Service) deleteAccount(ctx *fiber.Ctx) error {
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
account, err := s.dal.AccountRepo.GetAccountByID(ctx.Context(), accountID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
if err := s.dal.AccountRepo.DeleteAccount(ctx.Context(), account.ID); err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
return ctx.JSON(DeleteAccountResp{
|
||||
DeletedAccountID: accountID,
|
||||
})
|
||||
}
|
||||
|
||||
// getPrivilegeByUserID обработчик для получения привилегий аккаунта по ID пользователя
|
||||
func (s *Service) getPrivilegeByUserID(ctx *fiber.Ctx) error {
|
||||
var req GetPrivilegeByUserIDReq
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
|
||||
}
|
||||
|
||||
privilege, err := s.dal.AccountRepo.GetPrivilegesByAccountID(ctx.Context(), req.UserID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
return ctx.Status(fiber.StatusOK).JSON(privilege)
|
||||
}
|
||||
|
||||
// deleteAccountByUserID обработчик для удаления аккаунта по ID пользователя
|
||||
func (s *Service) deleteAccountByUserID(ctx *fiber.Ctx) error {
|
||||
var req DeleteAccountByUserIDReq
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
|
||||
}
|
||||
|
||||
existingAccount, err := s.dal.AccountRepo.GetAccountByID(ctx.Context(), req.UserID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
if existingAccount.ID == "" {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("user with this ID not found")
|
||||
}
|
||||
|
||||
if err := s.dal.AccountRepo.DeleteAccount(ctx.Context(), existingAccount.ID); err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
return ctx.JSON(DeleteAccountByUserIDResp{
|
||||
DeletedAccountUserID: req.UserID,
|
||||
})
|
||||
}
|
||||
|
||||
// getAccounts обработчик для получения списка аккаунтов с пагинацией
|
||||
func (s *Service) getAccounts(ctx *fiber.Ctx) error {
|
||||
var req GetAccountsReq
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
|
||||
}
|
||||
|
||||
_, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
accounts, totalCount, err := s.dal.AccountRepo.GetAccounts(ctx.Context(), req.Limit, req.Page)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
response := GetAccountsResp{
|
||||
Count: totalCount,
|
||||
Items: accounts,
|
||||
}
|
||||
|
||||
return ctx.Status(fiber.StatusOK).JSON(response)
|
||||
}
|
294
service/question_svc.go
Normal file
294
service/question_svc.go
Normal file
@ -0,0 +1,294 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/lib/pq"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// QuestionCreateReq request structure for creating Question
|
||||
type QuestionCreateReq struct {
|
||||
QuizId uint64 `json:"quiz_id"` // relation to quiz
|
||||
|
||||
Title string `json:"title"` // title of question
|
||||
Description string `json:"description"` // additional content in question such as pics, html markup or plain text
|
||||
Type string `json:"type"` // button/select/file/checkbox/text
|
||||
Required bool `json:"required"` // set true if question must be answered for valid quiz passing
|
||||
Page int `json:"page"` // set page of question
|
||||
Content string `json:"content"` // json serialized config of question
|
||||
}
|
||||
|
||||
// CreateQuestion service handler for creating question for quiz
|
||||
func (s *Service) CreateQuestion(ctx *fiber.Ctx) error {
|
||||
var req QuestionCreateReq
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
|
||||
}
|
||||
if utf8.RuneCountInString(req.Title) >= 512 {
|
||||
return ctx.Status(fiber.StatusUnprocessableEntity).SendString("title field should have less then 512 chars")
|
||||
}
|
||||
|
||||
if req.Type != model.TypeText &&
|
||||
req.Type != model.TypeVariant &&
|
||||
req.Type != model.TypeImages &&
|
||||
req.Type != model.TypeSelect &&
|
||||
req.Type != model.TypeVarImages &&
|
||||
req.Type != model.TypeEmoji &&
|
||||
req.Type != model.TypeDate &&
|
||||
req.Type != model.TypeNumber &&
|
||||
req.Type != model.TypePage &&
|
||||
req.Type != model.TypeRating &&
|
||||
req.Type != model.TypeResult &&
|
||||
req.Type != model.TypeFile {
|
||||
return ctx.Status(fiber.StatusNotAcceptable).SendString("type must be only test,button,file,checkbox,select, none")
|
||||
}
|
||||
|
||||
result := model.Question{
|
||||
QuizId: req.QuizId,
|
||||
Title: req.Title,
|
||||
Description: req.Description,
|
||||
Type: req.Type,
|
||||
Required: req.Required,
|
||||
Deleted: false,
|
||||
Page: req.Page,
|
||||
Content: req.Content,
|
||||
}
|
||||
if err := s.dal.QuestionRepo.CreateQuestion(ctx.Context(), &result); err != nil {
|
||||
if e, ok := err.(pq.Error); ok {
|
||||
if e.Constraint == "quiz_relation" {
|
||||
return ctx.Status(fiber.StatusFailedDependency).SendString(e.Error())
|
||||
}
|
||||
}
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
return ctx.Status(fiber.StatusOK).JSON(result)
|
||||
}
|
||||
|
||||
// GetQuestionListReq request structure for get question page
|
||||
type GetQuestionListReq struct {
|
||||
Limit uint64 `json:"limit"` // page size
|
||||
Page uint64 `json:"page"` // page number
|
||||
From int64 `json:"from"` // start of time period
|
||||
To int64 `json:"to"` // end of time period
|
||||
QuizId uint64 `json:"quiz_id"` // relation to quiz
|
||||
|
||||
Search string `json:"search"` // search string to search in files
|
||||
Type string `json:"type"` // type of questions. check types in model
|
||||
Deleted bool `json:"deleted"` // true to get only deleted questions
|
||||
Required bool `json:"required"`
|
||||
}
|
||||
|
||||
// GetQuestionListResp response to get page questions with count of all filtered items
|
||||
type GetQuestionListResp struct {
|
||||
Count uint64 `json:"count"`
|
||||
Items []model.Question `json:"items"`
|
||||
}
|
||||
|
||||
// GetQuestionList handler for paginated list question
|
||||
func (s *Service) GetQuestionList(ctx *fiber.Ctx) error {
|
||||
var req GetQuestionListReq
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
|
||||
}
|
||||
|
||||
if req.Type != "" &&
|
||||
req.Type != model.TypeText &&
|
||||
req.Type != model.TypeVariant &&
|
||||
req.Type != model.TypeImages &&
|
||||
req.Type != model.TypeSelect &&
|
||||
req.Type != model.TypeVarImages &&
|
||||
req.Type != model.TypeEmoji &&
|
||||
req.Type != model.TypeDate &&
|
||||
req.Type != model.TypeNumber &&
|
||||
req.Type != model.TypePage &&
|
||||
req.Type != model.TypeRating &&
|
||||
req.Type != model.TypeResult &&
|
||||
req.Type != model.TypeFile {
|
||||
return ctx.Status(fiber.StatusNotAcceptable).SendString("inappropriate type, allowed only '', " +
|
||||
"'test','none','file', 'button','select','checkbox'")
|
||||
}
|
||||
|
||||
res, cnt, err := s.dal.QuestionRepo.GetQuestionList(ctx.Context(),
|
||||
req.Limit,
|
||||
req.Page*req.Limit,
|
||||
uint64(req.From),
|
||||
uint64(req.To),
|
||||
req.QuizId,
|
||||
req.Deleted,
|
||||
req.Required,
|
||||
req.Search,
|
||||
req.Type,
|
||||
)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
return ctx.JSON(GetQuestionListResp{
|
||||
Items: res,
|
||||
Count: cnt,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateQuestionReq struct for request to update question
|
||||
type UpdateQuestionReq struct {
|
||||
Id uint64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"desc"`
|
||||
Type string `json:"type"`
|
||||
Required bool `json:"required"`
|
||||
Content string `json:"content"`
|
||||
Page int `json:"page"`
|
||||
}
|
||||
|
||||
// UpdateResp id you change question that you need only new question id
|
||||
type UpdateResp struct {
|
||||
Updated uint64 `json:"updated"`
|
||||
}
|
||||
|
||||
// UpdateQuestion handler for update question
|
||||
func (s *Service) UpdateQuestion(ctx *fiber.Ctx) error {
|
||||
var req UpdateQuestionReq
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
|
||||
}
|
||||
|
||||
if req.Id == 0 {
|
||||
return ctx.Status(fiber.StatusFailedDependency).SendString("need id of question for update")
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(req.Title) >= 512 {
|
||||
return ctx.Status(fiber.StatusUnprocessableEntity).SendString("title field should have less then 512 chars")
|
||||
}
|
||||
|
||||
if req.Type != model.TypeText &&
|
||||
req.Type != model.TypeVariant &&
|
||||
req.Type != model.TypeImages &&
|
||||
req.Type != model.TypeSelect &&
|
||||
req.Type != model.TypeVarImages &&
|
||||
req.Type != model.TypeEmoji &&
|
||||
req.Type != model.TypeDate &&
|
||||
req.Type != model.TypeNumber &&
|
||||
req.Type != model.TypePage &&
|
||||
req.Type != model.TypeRating &&
|
||||
req.Type != model.TypeFile &&
|
||||
req.Type != model.TypeResult &&
|
||||
req.Type != "" {
|
||||
return ctx.Status(fiber.StatusNotAcceptable).SendString("type must be only test,button,file,checkbox,select, none or empty string")
|
||||
}
|
||||
|
||||
question, err := s.dal.QuestionRepo.MoveToHistoryQuestion(ctx.Context(), req.Id)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
question.ParentIds = append(question.ParentIds, int32(question.Id))
|
||||
question.Id = req.Id
|
||||
question.Version += 1
|
||||
|
||||
if req.Title != "" {
|
||||
question.Title = req.Title
|
||||
}
|
||||
|
||||
if req.Description != "" {
|
||||
question.Description = req.Description
|
||||
}
|
||||
|
||||
if req.Page != 0 {
|
||||
question.Page = req.Page
|
||||
}
|
||||
|
||||
if req.Type != "" {
|
||||
question.Type = req.Type
|
||||
}
|
||||
|
||||
if req.Required != question.Required {
|
||||
question.Required = req.Required
|
||||
}
|
||||
|
||||
if req.Content != question.Content {
|
||||
question.Content = req.Content
|
||||
}
|
||||
|
||||
if err := s.dal.QuestionRepo.UpdateQuestion(ctx.Context(), question); err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
return ctx.JSON(UpdateResp{
|
||||
Updated: question.Id,
|
||||
})
|
||||
}
|
||||
|
||||
// CopyQuestionReq request struct for copy or duplicate question
|
||||
type CopyQuestionReq struct {
|
||||
Id uint64 `json:"id"`
|
||||
QuizId uint64 `json:"quiz_id"`
|
||||
}
|
||||
|
||||
// CopyQuestion handler for copy question
|
||||
func (s *Service) CopyQuestion(ctx *fiber.Ctx) error {
|
||||
var req CopyQuestionReq
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
|
||||
}
|
||||
|
||||
if req.Id == 0 {
|
||||
return ctx.Status(fiber.StatusFailedDependency).SendString("no id provided")
|
||||
}
|
||||
|
||||
question, err := s.dal.QuestionRepo.CopyQuestion(ctx.Context(), req.Id, req.QuizId)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
return ctx.JSON(UpdateResp{
|
||||
Updated: question.Id,
|
||||
})
|
||||
}
|
||||
|
||||
// GetQuestionHistoryReq struct of get history request
|
||||
type GetQuestionHistoryReq struct {
|
||||
Id uint64 `json:"id"`
|
||||
Limit uint64 `json:"l"`
|
||||
Page uint64 `json:"p"`
|
||||
}
|
||||
|
||||
// GetQuestionHistory handler for history of quiz
|
||||
func (s *Service) GetQuestionHistory(ctx *fiber.Ctx) error {
|
||||
var req GetQuizHistoryReq
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
|
||||
}
|
||||
|
||||
if req.Id == 0 {
|
||||
return ctx.Status(fiber.StatusFailedDependency).SendString("no id provided")
|
||||
}
|
||||
|
||||
history, err := s.dal.QuestionRepo.QuestionHistory(ctx.Context(), req.Id, req.Limit, req.Page*req.Limit)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
return ctx.Status(fiber.StatusOK).JSON(history)
|
||||
}
|
||||
|
||||
// DeleteQuestion handler for fake delete question
|
||||
func (s *Service) DeleteQuestion(ctx *fiber.Ctx) error {
|
||||
var req DeactivateReq
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
|
||||
}
|
||||
|
||||
if req.Id == 0 {
|
||||
return ctx.Status(fiber.StatusFailedDependency).SendString("id for deleting question is required")
|
||||
}
|
||||
|
||||
deleted, err := s.dal.QuestionRepo.DeleteQuestion(ctx.Context(), req.Id)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
return ctx.JSON(DeactivateResp{
|
||||
Deactivated: deleted.Id,
|
||||
})
|
||||
}
|
432
service/quiz_svc.go
Normal file
432
service/quiz_svc.go
Normal file
@ -0,0 +1,432 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/repository/quiz"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/core.git/middleware"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type CreateQuizReq struct {
|
||||
Fingerprinting bool `json:"fingerprinting"` // true if you need to store device id
|
||||
Repeatable bool `json:"repeatable"` // make it true for allow more than one quiz checkouting
|
||||
NotePrevented bool `json:"note_prevented"` // note answers even if the quiz was aborted
|
||||
MailNotifications bool `json:"mail_notifications"` // set true if you want get an email with every quiz passing
|
||||
UniqueAnswers bool `json:"unique_answers"` // set true if we you mention only last quiz passing
|
||||
|
||||
Name string `json:"name"` // max 280 chars
|
||||
Description string `json:"description"`
|
||||
Config string `json:"config"` // serialize json with config for page rules. fill it up only if implement one form scenario
|
||||
Status string `json:"status"` // status of quiz as enum. see Status const. fill it up only if implement one form scenario
|
||||
Limit uint64 `json:"limit"` // max count of quiz passing
|
||||
DueTo uint64 `json:"due_to"` // time when quiz is end
|
||||
|
||||
QuestionCnt uint64 `json:"question_cnt"` // for creating at one request
|
||||
|
||||
TimeOfPassing uint64 `json:"time_of_passing"` // amount of seconds for give all appropriate answers for quiz
|
||||
Pausable bool `json:"pausable"` // true allows to pause the quiz taking
|
||||
|
||||
Super bool `json:"super"` // set true if you want to create group
|
||||
GroupId uint64 `json:"group_id"` // if you create quiz in group provide there the id of super quiz
|
||||
}
|
||||
|
||||
// CreateQuiz handler for quiz creating request
|
||||
func (s *Service) CreateQuiz(ctx *fiber.Ctx) error {
|
||||
var req CreateQuizReq
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
|
||||
}
|
||||
|
||||
accountId, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
// check that we can store name
|
||||
if utf8.RuneCountInString(req.Name) >= 280 {
|
||||
return ctx.Status(fiber.StatusUnprocessableEntity).SendString("name field should have less then 280 chars")
|
||||
}
|
||||
|
||||
// status should be empty or equal one of status enum strings
|
||||
// I mean not draft, template, stop, start statuses
|
||||
if req.Status != "" &&
|
||||
req.Status != model.StatusDraft &&
|
||||
req.Status != model.StatusTemplate &&
|
||||
req.Status != model.StatusStop &&
|
||||
req.Status != model.StatusStart {
|
||||
return ctx.Status(fiber.StatusNotAcceptable).SendString("status on creating must be only draft,template,stop,start")
|
||||
}
|
||||
|
||||
// DueTo should be bigger then now
|
||||
if req.DueTo != 0 && req.DueTo <= uint64(time.Now().Unix()) {
|
||||
return ctx.Status(fiber.StatusNotAcceptable).SendString("due to time must be lesser then now")
|
||||
}
|
||||
|
||||
// you can pause quiz only if it has deadline for passing
|
||||
if req.Pausable && req.TimeOfPassing == 0 {
|
||||
return ctx.Status(fiber.StatusConflict).SendString("you can pause quiz only if it has deadline for passing")
|
||||
}
|
||||
|
||||
record := model.Quiz{
|
||||
AccountId: accountId,
|
||||
Fingerprinting: req.Fingerprinting,
|
||||
Repeatable: req.Repeatable,
|
||||
NotePrevented: req.NotePrevented,
|
||||
MailNotifications: req.MailNotifications,
|
||||
UniqueAnswers: req.UniqueAnswers,
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
Config: req.Config,
|
||||
Status: req.Status,
|
||||
Limit: req.Limit,
|
||||
DueTo: req.DueTo,
|
||||
TimeOfPassing: req.TimeOfPassing,
|
||||
Pausable: req.Pausable,
|
||||
QuestionsCount: req.QuestionCnt,
|
||||
ParentIds: []int32{},
|
||||
Super: req.Super,
|
||||
GroupId: req.GroupId,
|
||||
}
|
||||
|
||||
if err := s.dal.QuizRepo.CreateQuiz(ctx.Context(), &record); err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
return ctx.Status(fiber.StatusCreated).JSON(record)
|
||||
}
|
||||
|
||||
// GetQuizListReq request struct for paginated quiz table
|
||||
type GetQuizListReq struct {
|
||||
Limit uint64 `json:"limit"`
|
||||
Page uint64 `json:"page"`
|
||||
From int64 `json:"from"`
|
||||
To int64 `json:"to"`
|
||||
|
||||
Search string `json:"search"`
|
||||
Status string `json:"status"`
|
||||
Deleted bool `json:"deleted"`
|
||||
Archived bool `json:"archived"`
|
||||
Super bool `json:"super"`
|
||||
GroupId uint64 `json:"group_id"`
|
||||
}
|
||||
|
||||
type GetQuizListResp struct {
|
||||
Count uint64 `json:"count"`
|
||||
Items []model.Quiz `json:"items"`
|
||||
}
|
||||
|
||||
// GetQuizList handler for paginated list quiz
|
||||
func (s *Service) GetQuizList(ctx *fiber.Ctx) error {
|
||||
var req GetQuizListReq
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
|
||||
}
|
||||
|
||||
accountId, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
if req.Status != "" &&
|
||||
req.Status != model.StatusStop &&
|
||||
req.Status != model.StatusStart &&
|
||||
req.Status != model.StatusDraft &&
|
||||
req.Status != model.StatusTemplate &&
|
||||
req.Status != model.StatusTimeout &&
|
||||
req.Status != model.StatusOffLimit {
|
||||
return ctx.Status(fiber.StatusNotAcceptable).SendString("inappropriate status, allowed only '', " +
|
||||
"'stop','start','draft', 'template','timeout','offlimit'")
|
||||
}
|
||||
|
||||
res, cnt, err := s.dal.QuizRepo.GetQuizList(ctx.Context(),
|
||||
quiz.GetQuizListDeps{
|
||||
Limit: req.Limit,
|
||||
Offset: req.Limit * req.Page,
|
||||
From: uint64(req.From),
|
||||
To: uint64(req.To),
|
||||
Group: req.GroupId,
|
||||
Deleted: req.Deleted,
|
||||
Archived: req.Archived,
|
||||
Super: req.Super,
|
||||
Search: req.Search,
|
||||
Status: req.Status,
|
||||
AccountId: accountId,
|
||||
})
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
return ctx.JSON(GetQuizListResp{
|
||||
Items: res,
|
||||
Count: cnt,
|
||||
})
|
||||
}
|
||||
|
||||
type UpdateQuizReq struct {
|
||||
Id uint64 `json:"id"`
|
||||
Fingerprinting bool `json:"fp"`
|
||||
Repeatable bool `json:"rep"`
|
||||
NotePrevented bool `json:"note_prevented"`
|
||||
MailNotifications bool `json:"mailing"`
|
||||
UniqueAnswers bool `json:"uniq"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"desc"`
|
||||
Config string `json:"conf"`
|
||||
Status string `json:"status"`
|
||||
Limit uint64 `json:"limit"`
|
||||
DueTo uint64 `json:"due_to"`
|
||||
TimeOfPassing uint64 `json:"time_of_passing"`
|
||||
Pausable bool `json:"pausable"`
|
||||
QuestionCnt uint64 `json:"question_cnt"` // for creating at one request
|
||||
Super bool `json:"super"`
|
||||
GroupId uint64 `json:"group_id"`
|
||||
}
|
||||
|
||||
func (s *Service) UpdateQuiz(ctx *fiber.Ctx) error {
|
||||
var req UpdateQuizReq
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
|
||||
}
|
||||
|
||||
accountId, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
if req.Id == 0 {
|
||||
return ctx.Status(fiber.StatusFailedDependency).SendString("need id of question for update")
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(req.Name) >= 280 {
|
||||
return ctx.Status(fiber.StatusUnprocessableEntity).SendString("name field should have less then 280 chars")
|
||||
}
|
||||
|
||||
// status should be empty or equal one of status enum strings
|
||||
// I mean not draft, template, stop, start statuses
|
||||
if req.Status != "" &&
|
||||
req.Status != model.StatusDraft &&
|
||||
req.Status != model.StatusTemplate &&
|
||||
req.Status != model.StatusStop &&
|
||||
req.Status != model.StatusStart {
|
||||
return ctx.Status(fiber.StatusNotAcceptable).SendString("status on creating must be only draft,template,stop,start")
|
||||
}
|
||||
|
||||
// DueTo should be bigger then now
|
||||
if req.DueTo != 0 && req.DueTo <= uint64(time.Now().Unix()) {
|
||||
return ctx.Status(fiber.StatusNotAcceptable).SendString("due to time must be lesser then now")
|
||||
}
|
||||
|
||||
// you can pause quiz only if it has deadline for passing
|
||||
if req.Pausable && req.TimeOfPassing == 0 {
|
||||
return ctx.Status(fiber.StatusConflict).SendString("you can pause quiz only if it has deadline for passing")
|
||||
}
|
||||
|
||||
quiz, err := s.dal.QuizRepo.MoveToHistoryQuiz(ctx.Context(), req.Id, accountId)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
quiz.ParentIds = append(quiz.ParentIds, int32(quiz.Id))
|
||||
quiz.Id = req.Id
|
||||
quiz.Version += 1
|
||||
|
||||
if req.Fingerprinting != quiz.Fingerprinting {
|
||||
quiz.Fingerprinting = req.Fingerprinting
|
||||
}
|
||||
|
||||
if req.Repeatable != quiz.Repeatable {
|
||||
quiz.Repeatable = req.Repeatable
|
||||
}
|
||||
|
||||
if req.MailNotifications != quiz.MailNotifications {
|
||||
quiz.MailNotifications = req.MailNotifications
|
||||
}
|
||||
|
||||
if req.NotePrevented != quiz.NotePrevented {
|
||||
quiz.NotePrevented = req.NotePrevented
|
||||
}
|
||||
|
||||
if req.UniqueAnswers != quiz.UniqueAnswers {
|
||||
quiz.UniqueAnswers = req.UniqueAnswers
|
||||
}
|
||||
|
||||
if req.Pausable != quiz.Pausable {
|
||||
quiz.Pausable = req.Pausable
|
||||
}
|
||||
|
||||
if req.Name != "" && req.Name != quiz.Name {
|
||||
quiz.Name = req.Name
|
||||
}
|
||||
|
||||
if req.Description != "" && req.Description != quiz.Description {
|
||||
quiz.Description = req.Description
|
||||
}
|
||||
|
||||
if req.Status != "" && req.Status != quiz.Status {
|
||||
quiz.Status = req.Status
|
||||
}
|
||||
|
||||
if req.TimeOfPassing != quiz.TimeOfPassing {
|
||||
quiz.TimeOfPassing = req.TimeOfPassing
|
||||
}
|
||||
|
||||
if req.DueTo != quiz.DueTo {
|
||||
quiz.DueTo = req.DueTo
|
||||
}
|
||||
|
||||
if req.Limit != quiz.Limit {
|
||||
quiz.Limit = req.Limit
|
||||
}
|
||||
|
||||
if req.Config != "" && req.Config != quiz.Config {
|
||||
quiz.Config = req.Config
|
||||
}
|
||||
|
||||
if req.Super != quiz.Super {
|
||||
quiz.Super = req.Super
|
||||
}
|
||||
|
||||
if req.GroupId != quiz.GroupId {
|
||||
quiz.GroupId = req.GroupId
|
||||
}
|
||||
|
||||
quiz.QuestionsCount = req.QuestionCnt
|
||||
|
||||
quiz.ParentIds = append(quiz.ParentIds, int32(quiz.Id))
|
||||
|
||||
if err := s.dal.QuizRepo.UpdateQuiz(ctx.Context(), accountId, quiz); err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
return ctx.JSON(UpdateResp{
|
||||
Updated: quiz.Id,
|
||||
})
|
||||
}
|
||||
|
||||
// CopyQuizReq request struct for copy quiz
|
||||
type CopyQuizReq struct {
|
||||
Id uint64 `json:"id"`
|
||||
}
|
||||
|
||||
// CopyQuiz request handler for copy quiz
|
||||
func (s *Service) CopyQuiz(ctx *fiber.Ctx) error {
|
||||
var req CopyQuizReq
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
|
||||
}
|
||||
|
||||
accountId, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
if req.Id == 0 {
|
||||
return ctx.Status(fiber.StatusFailedDependency).SendString("no id provided")
|
||||
}
|
||||
|
||||
quiz, err := s.dal.QuizRepo.CopyQuiz(ctx.Context(), accountId, req.Id)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
return ctx.JSON(UpdateResp{
|
||||
Updated: quiz.Id,
|
||||
})
|
||||
}
|
||||
|
||||
// GetQuizHistoryReq struct of get history request
|
||||
type GetQuizHistoryReq struct {
|
||||
Id uint64 `json:"id"`
|
||||
Limit uint64 `json:"l"`
|
||||
Page uint64 `json:"p"`
|
||||
}
|
||||
|
||||
// GetQuizHistory handler for history of quiz
|
||||
func (s *Service) GetQuizHistory(ctx *fiber.Ctx) error {
|
||||
var req GetQuizHistoryReq
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
|
||||
}
|
||||
|
||||
accountId, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
if req.Id == 0 {
|
||||
return ctx.Status(fiber.StatusFailedDependency).SendString("no id provided")
|
||||
}
|
||||
|
||||
history, err := s.dal.QuizRepo.QuizHistory(ctx.Context(), quiz.QuizHistoryDeps{
|
||||
Id: req.Id,
|
||||
Limit: req.Limit,
|
||||
Offset: req.Page * req.Limit,
|
||||
AccountId: accountId,
|
||||
})
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
return ctx.Status(fiber.StatusCreated).JSON(history)
|
||||
}
|
||||
|
||||
// DeactivateReq request structure for archiving and deleting
|
||||
type DeactivateReq struct {
|
||||
Id uint64 `json:"id"`
|
||||
}
|
||||
|
||||
type DeactivateResp struct {
|
||||
Deactivated uint64 `json:"deactivated"`
|
||||
}
|
||||
|
||||
// DeleteQuiz handler for fake delete quiz
|
||||
func (s *Service) DeleteQuiz(ctx *fiber.Ctx) error {
|
||||
var req DeactivateReq
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
|
||||
}
|
||||
|
||||
accountId, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
if req.Id == 0 {
|
||||
return ctx.Status(fiber.StatusFailedDependency).SendString("id for deleting is required")
|
||||
}
|
||||
|
||||
deleted, err := s.dal.QuizRepo.DeleteQuiz(ctx.Context(), accountId, req.Id)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
return ctx.JSON(DeactivateResp{
|
||||
Deactivated: deleted.Id,
|
||||
})
|
||||
}
|
||||
|
||||
// ArchiveQuiz handler for archiving quiz
|
||||
func (s *Service) ArchiveQuiz(ctx *fiber.Ctx) error {
|
||||
var req DeactivateReq
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
|
||||
}
|
||||
|
||||
accountId, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
if req.Id == 0 {
|
||||
return ctx.Status(fiber.StatusFailedDependency).SendString("id for archive quiz is required")
|
||||
}
|
||||
|
||||
archived, err := s.dal.QuizRepo.DeleteQuiz(ctx.Context(), accountId, req.Id)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
return ctx.JSON(DeactivateResp{
|
||||
Deactivated: archived.Id,
|
||||
})
|
||||
}
|
213
service/result_svc.go
Normal file
213
service/result_svc.go
Normal file
@ -0,0 +1,213 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/repository/result"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/core.git/middleware"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/core.git/pkg"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ReqExport struct {
|
||||
To, From time.Time
|
||||
New bool
|
||||
Page uint64
|
||||
Limit uint64
|
||||
}
|
||||
|
||||
type ReqExportResponse struct {
|
||||
TotalCount uint64 `json:"total_count"`
|
||||
Results []model.AnswerExport `json:"results"`
|
||||
}
|
||||
|
||||
func (s *Service) GetResultsByQuizID(ctx *fiber.Ctx) error {
|
||||
payment := true // параметр для определения существования текущих привилегий юзера
|
||||
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
var req ReqExport
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
|
||||
}
|
||||
|
||||
quizIDStr := ctx.Params("quizId")
|
||||
quizID, err := strconv.ParseUint(quizIDStr, 10, 64)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid quiz ID format")
|
||||
}
|
||||
|
||||
account, err := s.dal.AccountRepo.GetAccountByID(ctx.Context(), accountID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
if len(account.Privileges) == 0 {
|
||||
payment = false
|
||||
}
|
||||
|
||||
results, totalCount, err := s.dal.ResultRepo.GetQuizResults(ctx.Context(), quizID, result.GetQuizResDeps{
|
||||
To: req.To,
|
||||
From: req.From,
|
||||
New: req.New,
|
||||
Page: req.Page,
|
||||
Limit: req.Limit,
|
||||
}, payment)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
resp := &ReqExportResponse{
|
||||
TotalCount: totalCount,
|
||||
Results: results,
|
||||
}
|
||||
|
||||
return ctx.Status(fiber.StatusOK).JSON(resp)
|
||||
}
|
||||
|
||||
func (s *Service) DelResultByID(ctx *fiber.Ctx) error {
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("could not get account ID from token")
|
||||
}
|
||||
|
||||
resultIDStr := ctx.Params("resultId")
|
||||
resultID, err := strconv.ParseUint(resultIDStr, 10, 64)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid result ID format")
|
||||
}
|
||||
|
||||
isOwner, err := s.dal.ResultRepo.CheckResultOwner(ctx.Context(), resultID, accountID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
if !isOwner {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("not the owner of the result")
|
||||
}
|
||||
|
||||
if err := s.dal.ResultRepo.SoftDeleteResultByID(ctx.Context(), resultID); err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
ctx.Status(fiber.StatusOK)
|
||||
return nil
|
||||
}
|
||||
|
||||
type ReqSeen struct {
|
||||
Answers []int64
|
||||
}
|
||||
|
||||
func (s *Service) SetStatus(ctx *fiber.Ctx) error {
|
||||
var req ReqSeen
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
|
||||
}
|
||||
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("could not get account ID from token")
|
||||
}
|
||||
|
||||
answers, err := s.dal.ResultRepo.CheckResultsOwner(ctx.Context(), req.Answers, accountID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
if len(answers) != len(req.Answers) {
|
||||
return ctx.Status(fiber.StatusNotAcceptable).SendString("could not update some answers because you don't have rights")
|
||||
}
|
||||
|
||||
if err := s.dal.ResultRepo.UpdateAnswersStatus(ctx.Context(), accountID, answers); err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
return ctx.Status(fiber.StatusOK).JSON(nil)
|
||||
}
|
||||
|
||||
func (s *Service) ExportResultsToCSV(ctx *fiber.Ctx) error {
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
quizIDStr := ctx.Params("quizID")
|
||||
quizID, err := strconv.ParseUint(quizIDStr, 10, 64)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("invalid quiz ID")
|
||||
}
|
||||
|
||||
req := ReqExport{}
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("invalid request body")
|
||||
}
|
||||
|
||||
account, err := s.dal.AccountRepo.GetAccountByID(ctx.Context(), accountID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
if len(account.Privileges) == 0 {
|
||||
return ctx.Status(fiber.StatusPaymentRequired).SendString("payment required")
|
||||
}
|
||||
|
||||
questions, err := s.dal.ResultRepo.GetQuestions(ctx.Context(), quizID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("failed to get questions")
|
||||
}
|
||||
|
||||
answers, err := s.dal.ResultRepo.GetQuizResultsCSV(ctx.Context(), quizID, result.GetQuizResDeps{
|
||||
To: req.To,
|
||||
From: req.From,
|
||||
New: req.New,
|
||||
Page: req.Page,
|
||||
Limit: req.Limit,
|
||||
})
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("failed to get quiz results")
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
|
||||
if err := pkg.WriteDataToExcel(buffer, questions, answers); err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("failed to write data to Excel")
|
||||
}
|
||||
|
||||
ctx.Set(fiber.HeaderContentType, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
ctx.Set(fiber.HeaderContentDisposition, `attachment; filename="results.xlsx"`)
|
||||
|
||||
return ctx.Send(buffer.Bytes())
|
||||
}
|
||||
|
||||
func (s *Service) GetResultAnswers(ctx *fiber.Ctx) error {
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
resultID, err := strconv.ParseUint(ctx.Params("resultID"), 10, 64)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("invalid quiz ID")
|
||||
}
|
||||
|
||||
account, err := s.dal.AccountRepo.GetAccountByID(ctx.Context(), accountID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
if len(account.Privileges) == 0 {
|
||||
return ctx.Status(fiber.StatusPaymentRequired).SendString("payment required")
|
||||
}
|
||||
|
||||
answers, err := s.dal.ResultRepo.GetResultAnswers(ctx.Context(), resultID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("failed to get result answers")
|
||||
}
|
||||
|
||||
return ctx.JSON(answers)
|
||||
}
|
50
service/service.go
Normal file
50
service/service.go
Normal file
@ -0,0 +1,50 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
|
||||
)
|
||||
|
||||
// Service is an entity for http requests handling
|
||||
type Service struct {
|
||||
dal *dal.DAL
|
||||
}
|
||||
|
||||
func New(d *dal.DAL) *Service {
|
||||
return &Service{dal: d}
|
||||
}
|
||||
|
||||
// Register is a function for add handlers of service to external multiplexer
|
||||
func (s *Service) Register(app *fiber.App) {
|
||||
// quiz manipulating handlers
|
||||
app.Post("/quiz/create", s.CreateQuiz)
|
||||
app.Post("/quiz/getList", s.GetQuizList)
|
||||
app.Patch("/quiz/edit", s.UpdateQuiz)
|
||||
app.Post("/quiz/copy", s.CopyQuiz)
|
||||
app.Post("/quiz/history", s.GetQuizHistory)
|
||||
app.Delete("/quiz/delete", s.DeleteQuiz)
|
||||
app.Patch("/quiz/archive", s.ArchiveQuiz)
|
||||
|
||||
// question manipulating handlers
|
||||
app.Post("/question/create", s.CreateQuestion)
|
||||
app.Post("/question/getList", s.GetQuestionList)
|
||||
app.Patch("/question/edit", s.UpdateQuestion)
|
||||
app.Post("/question/copy", s.CopyQuestion)
|
||||
app.Post("/question/history", s.GetQuestionHistory)
|
||||
app.Delete("/question/delete", s.DeleteQuestion)
|
||||
|
||||
// account handlers
|
||||
app.Get("/account/get", s.getCurrentAccount)
|
||||
app.Post("/account/create", s.createAccount)
|
||||
app.Delete("/account/delete", s.deleteAccount)
|
||||
app.Get("/accounts", s.getAccounts)
|
||||
app.Get("/privilege/:userId", s.getPrivilegeByUserID)
|
||||
app.Delete("/account/:userId", s.deleteAccountByUserID)
|
||||
|
||||
// result handlers
|
||||
app.Post("/results/getResults/:quizId", s.GetResultsByQuizID)
|
||||
app.Delete("/results/delete/:resultId", s.DelResultByID)
|
||||
app.Patch("/result/seen", s.SetStatus)
|
||||
app.Post("/results/:quizID/export", s.ExportResultsToCSV)
|
||||
app.Get("/result/:resultID", s.GetResultAnswers)
|
||||
}
|
Loading…
Reference in New Issue
Block a user