formatting to our pj layout

This commit is contained in:
Pavel 2024-10-25 18:26:03 +03:00
parent 8f56724bac
commit da9c292adf
60 changed files with 886 additions and 763 deletions

@ -1,259 +0,0 @@
package app
import (
"context"
"errors"
"fmt"
"github.com/go-redis/redis/v8"
"github.com/gofiber/fiber/v2"
"github.com/skeris/appInit"
"github.com/themakers/hlog"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"penahub.gitlab.yandexcloud.net/backend/penahub_common/log_mw"
"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/middleware"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/brokers"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/clients/auth"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/clients/telegram"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/initialize"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/models"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/server"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/service"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/tools"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/workers"
"penahub.gitlab.yandexcloud.net/external/trashlog/wrappers/zaptrashlog"
"time"
)
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=35432 user=squiz password=Redalert2 dbname=squiz sslmode=disable"`
HubAdminUrl string `env:"HUB_ADMIN_URL" default:"http://localhost:8001/"`
ServiceName string `env:"SERVICE_NAME" default:"squiz"`
AuthServiceURL string `env:"AUTH_URL" default:"http://localhost:8000/"`
GrpcHost string `env:"GRPC_HOST" default:"localhost"`
GrpcPort string `env:"GRPC_PORT" default:"9000"`
KafkaBrokers string `env:"KAFKA_BROKERS" default:"localhost:9092"`
KafkaTopic string `env:"KAFKA_TOPIC" default:"test-topic"`
KafkaGroup string `env:"KAFKA_GROUP" default:"mailnotifier"`
TrashLogHost string `env:"TRASH_LOG_HOST" default:"localhost:7113"`
ModuleLogger string `env:"MODULE_LOGGER" default:"core-local"`
ClickHouseCred string `env:"CLICK_HOUSE_CRED" default:"tcp://10.8.0.15:9000/default?sslmode=disable"`
RedisHost string `env:"REDIS_HOST" default:"localhost:6379"`
RedisPassword string `env:"REDIS_PASSWORD" default:"admin"`
RedisDB uint64 `env:"REDIS_DB" default:"2"`
S3Prefix string `env:"S3_PREFIX"`
}
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),
)
clickHouseLogger, err := zaptrashlog.NewCore(ctx, zap.InfoLevel, options.TrashLogHost, ver.Release, ver.Commit, time.Now().Unix())
if err != nil {
panic(err)
}
loggerForHlog := zapLogger.WithOptions(zap.WrapCore(func(core zapcore.Core) zapcore.Core {
return zapcore.NewTee(core, clickHouseLogger)
}))
loggerHlog := hlog.New(loggerForHlog).Module(options.ModuleLogger)
loggerHlog.With(models.AllFields{})
loggerHlog.Emit(InfoSvcStarted{})
authClient := auth.NewAuthClient(options.AuthServiceURL)
pgdal, err := dal.New(ctx, options.PostgresCredentials, nil)
if err != nil {
fmt.Println("NEW", err)
return nil, err
}
chDal, err := dal.NewClickHouseDAL(ctx, options.ClickHouseCred)
if err != nil {
fmt.Println("failed init clickhouse", err)
return nil, err
}
kafkaClient, err := initialize.KafkaInit(ctx, initialize.KafkaDeps{
KafkaGroup: options.KafkaGroup,
KafkaBrokers: options.KafkaBrokers,
KafkaTopic: options.KafkaTopic,
})
if err != nil {
return nil, err
}
producer := brokers.NewProducer(brokers.ProducerDeps{
KafkaClient: kafkaClient,
Logger: zapLogger,
})
redisClient := redis.NewClient(&redis.Options{
Addr: options.RedisHost,
Password: options.RedisPassword,
DB: int(options.RedisDB),
})
err = redisClient.Ping(ctx).Err()
if err != nil {
panic(fmt.Sprintf("error ping to redis db %v", err))
}
clientData := privilege.Client{
URL: options.HubAdminUrl,
ServiceName: options.ServiceName,
Privileges: model.Privileges,
}
fiberClient := &fiber.Client{}
privilegeController := privilege.NewPrivilege(clientData, fiberClient)
go tools.PublishPrivilege(privilegeController, 10, 5*time.Minute)
tgClient, err := telegram.NewTelegramClient(ctx, pgdal)
if err != nil {
panic(fmt.Sprintf("failed init tg clietns: %v", err))
}
tgWC := workers.NewTgListenerWC(workers.Deps{
BotID: int64(6712573453), // todo убрать
Redis: redisClient,
Dal: pgdal,
TgClient: tgClient,
})
go tgWC.Start(ctx)
// todo подумать над реализацией всего а то пока мне кажется что немного каша получается такой предикт что через некоторое время
// сложно будет разобраться что есть где
grpcControllers := initialize.InitRpcControllers(pgdal)
grpc, err := server.NewGRPC(zapLogger)
if err != nil {
fmt.Println("error:", err)
panic("err init grpc server")
}
grpc.Register(grpcControllers)
go grpc.Run(server.DepsGrpcRun{
Host: options.GrpcHost,
Port: options.GrpcPort,
})
app := fiber.New()
app.Use(middleware.JWTAuth())
app.Use(log_mw.ContextLogger(loggerHlog))
app.Get("/liveness", healthchecks.Liveness)
app.Get("/readiness", healthchecks.Readiness(&workerErr)) //todo parametrized readiness. should discuss ready reason
svc := service.New(service.Deps{
Dal: pgdal,
AuthClient: authClient,
Producer: producer,
ServiceName: options.ServiceName,
ChDAL: chDal,
TelegramClient: tgClient,
RedisClient: redisClient,
S3Prefix: options.S3Prefix,
})
svc.Register(app)
loggerHlog.Emit(InfoSvcReady{})
go func() {
defer func() {
if pgdal != nil {
pgdal.Close()
}
if chDal != nil {
if derr := chDal.Close(ctx); derr != nil {
fmt.Printf("error closing clickhouse: %v", derr)
}
}
err := grpc.Stop(ctx)
err = app.Shutdown()
loggerHlog.Emit(InfoSvcShutdown{Signal: err.Error()})
}()
if options.IsProd {
if err := app.ListenTLS(fmt.Sprintf(":%s", options.NumberPort), options.CrtFile, options.KeyFile); err != nil {
loggerHlog.Emit(ErrorCanNotServe{
Err: err,
})
errChan <- err
}
} else {
if err := app.Listen(fmt.Sprintf(":%s", options.NumberPort)); err != nil {
loggerHlog.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
}

41
cmd/main.go Normal file

@ -0,0 +1,41 @@
package main
import (
"context"
"fmt"
"go.uber.org/zap"
"os"
"os/signal"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/app"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/initialize"
"syscall"
)
var (
commit string = os.Getenv("COMMIT")
buildTime string = os.Getenv("BUILD_TIME")
version string = os.Getenv("VERSION")
)
func main() {
logger, err := zap.NewProduction()
if err != nil {
fmt.Printf("Failed to initialize logger: %v\n", err)
os.Exit(1)
}
config, err := initialize.LoadConfig()
if err != nil {
logger.Fatal("Failed to load config", zap.Error(err))
}
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
if err = app.Run(ctx, *config, logger, app.Build{
Commit: commit,
Version: version,
}); err != nil {
logger.Fatal("App exited with error", zap.Error(err))
}
}

3
go.mod

@ -20,7 +20,7 @@ require (
google.golang.org/grpc v1.64.0 google.golang.org/grpc v1.64.0
google.golang.org/protobuf v1.34.2 google.golang.org/protobuf v1.34.2
penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240607202348-efe5f2bf3e8c penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240607202348-efe5f2bf3e8c
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240711133242-0b8534fae5b2 penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20241025130405-8ab347d96f1f
penahub.gitlab.yandexcloud.net/backend/quiz/worker.git v0.0.0-20240421230341-0e086fcbb990 penahub.gitlab.yandexcloud.net/backend/quiz/worker.git v0.0.0-20240421230341-0e086fcbb990
penahub.gitlab.yandexcloud.net/backend/tdlib v0.0.0-20240701075856-1731684c936f penahub.gitlab.yandexcloud.net/backend/tdlib v0.0.0-20240701075856-1731684c936f
penahub.gitlab.yandexcloud.net/external/trashlog v0.1.6-0.20240827173635-78ce9878c387 penahub.gitlab.yandexcloud.net/external/trashlog v0.1.6-0.20240827173635-78ce9878c387
@ -29,6 +29,7 @@ require (
require ( require (
github.com/ClickHouse/clickhouse-go v1.5.4 // indirect github.com/ClickHouse/clickhouse-go v1.5.4 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect github.com/andybalholm/brotli v1.1.0 // indirect
github.com/caarlos0/env/v8 v8.0.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 // indirect github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect

6
go.sum

@ -7,6 +7,8 @@ github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer5
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk= github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk=
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0=
github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@ -140,6 +142,7 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
github.com/themakers/bdd v0.0.0-20210316111417-6b1dfe326f33 h1:N9f/Q+2Ssa+yDcbfaoLTYvXmdeyUUxsJKdPUVsjSmiA= github.com/themakers/bdd v0.0.0-20210316111417-6b1dfe326f33 h1:N9f/Q+2Ssa+yDcbfaoLTYvXmdeyUUxsJKdPUVsjSmiA=
github.com/themakers/bdd v0.0.0-20210316111417-6b1dfe326f33/go.mod h1:rpcH99JknBh8seZmlOlUg51gasZH6QH34oXNsIwYT6E= github.com/themakers/bdd v0.0.0-20210316111417-6b1dfe326f33/go.mod h1:rpcH99JknBh8seZmlOlUg51gasZH6QH34oXNsIwYT6E=
github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf h1:TJJm6KcBssmbWzplF5lzixXl1RBAi/ViPs1GaSOkhwo= github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf h1:TJJm6KcBssmbWzplF5lzixXl1RBAi/ViPs1GaSOkhwo=
@ -260,6 +263,7 @@ google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6h
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
@ -284,6 +288,8 @@ penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240607202348-efe5
penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240607202348-efe5f2bf3e8c/go.mod h1:+bPxq2wfW5S1gd+83vZYmHm33AE7nEBfznWS8AM1TKE= penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240607202348-efe5f2bf3e8c/go.mod h1:+bPxq2wfW5S1gd+83vZYmHm33AE7nEBfznWS8AM1TKE=
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240711133242-0b8534fae5b2 h1:0t6pQHJvA3jMeBB3FPUmA+hw8rWlksTah8KJEtf2KD8= penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240711133242-0b8534fae5b2 h1:0t6pQHJvA3jMeBB3FPUmA+hw8rWlksTah8KJEtf2KD8=
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240711133242-0b8534fae5b2/go.mod h1:uOuosXduBzd2WbLH6TDZO7ME7ZextulA662oZ6OsoB0= penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240711133242-0b8534fae5b2/go.mod h1:uOuosXduBzd2WbLH6TDZO7ME7ZextulA662oZ6OsoB0=
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20241025130405-8ab347d96f1f h1:HMXxbIkNmFeQTFI/MlsDBIRUTu8MTbn3i2PVcpQbXEI=
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20241025130405-8ab347d96f1f/go.mod h1:uOuosXduBzd2WbLH6TDZO7ME7ZextulA662oZ6OsoB0=
penahub.gitlab.yandexcloud.net/backend/quiz/worker.git v0.0.0-20240421230341-0e086fcbb990 h1:jiO8GWO+3sCnDAV8/NAV8tQIUwae/I6/xiDilW7zf0o= penahub.gitlab.yandexcloud.net/backend/quiz/worker.git v0.0.0-20240421230341-0e086fcbb990 h1:jiO8GWO+3sCnDAV8/NAV8tQIUwae/I6/xiDilW7zf0o=
penahub.gitlab.yandexcloud.net/backend/quiz/worker.git v0.0.0-20240421230341-0e086fcbb990/go.mod h1:zswBuTwmEsFHBVRu1nkG3/Fwylk5Vcm8OUm9iWxccSE= penahub.gitlab.yandexcloud.net/backend/quiz/worker.git v0.0.0-20240421230341-0e086fcbb990/go.mod h1:zswBuTwmEsFHBVRu1nkG3/Fwylk5Vcm8OUm9iWxccSE=
penahub.gitlab.yandexcloud.net/backend/tdlib v0.0.0-20240701075856-1731684c936f h1:Qli89wgu0T7nG4VECXZOZ40fjE/hVVfxF3hTaSYS008= penahub.gitlab.yandexcloud.net/backend/tdlib v0.0.0-20240701075856-1731684c936f h1:Qli89wgu0T7nG4VECXZOZ40fjE/hVVfxF3hTaSYS008=

@ -1,16 +0,0 @@
package initialize
import (
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/rpc_service"
)
type RpcRegister struct {
MailNotify *rpc_service.MailNotify
}
func InitRpcControllers(dal *dal.DAL) *RpcRegister {
return &RpcRegister{
MailNotify: rpc_service.NewMailNotify(dal),
}
}

187
internal/app/app.go Normal file

@ -0,0 +1,187 @@
package app
import (
"context"
"errors"
"github.com/gofiber/fiber/v2"
"github.com/themakers/hlog"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"penahub.gitlab.yandexcloud.net/backend/penahub_common/privilege"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/brokers"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/initialize"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/models"
server "penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/server/grpc"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/server/http"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/tools"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/workers"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/pkg/closer"
"penahub.gitlab.yandexcloud.net/external/trashlog/wrappers/zaptrashlog"
"time"
)
type Build struct {
Commit string
Version string
}
var zapOptions = []zap.Option{
zap.AddCaller(),
zap.AddCallerSkip(2),
zap.AddStacktrace(zap.ErrorLevel),
}
func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger, build Build) error {
var (
err error
zapLogger *zap.Logger
)
defer func() {
if r := recover(); r != nil {
logger.Error("Recovered from a panic", zap.Any("error", r))
}
}()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
if cfg.LoggerProdMode {
zapLogger, err = zap.NewProduction(zapOptions...)
if err != nil {
return err
}
} else {
zapLogger, err = zap.NewDevelopment(zapOptions...)
if err != nil {
return err
}
}
zapLogger = zapLogger.With(
zap.String("SvcCommit", build.Commit),
zap.String("SvcVersion", build.Version),
zap.String("SvcBuildTime", time.Now().String()),
)
clickHouseLogger, err := zaptrashlog.NewCore(ctx, zap.InfoLevel, cfg.TrashLogHost, build.Version, build.Commit, time.Now().Unix())
if err != nil {
panic(err)
}
loggerForHlog := zapLogger.WithOptions(zap.WrapCore(func(core zapcore.Core) zapcore.Core {
return zapcore.NewTee(core, clickHouseLogger)
}))
loggerHlog := hlog.New(loggerForHlog).Module(cfg.ModuleLogger)
loggerHlog.With(models.AllFields{})
loggerHlog.Emit(InfoSvcStarted{})
shutdownGroup := closer.NewCloserGroup()
dalS, err := initialize.NewDALs(ctx, cfg)
if err != nil {
logger.Error("Error initializing dals", zap.Error(err))
return err
}
kafkaClient, err := initialize.KafkaInit(ctx, initialize.KafkaDeps{
KafkaGroup: cfg.KafkaGroup,
KafkaBrokers: cfg.KafkaBrokers,
KafkaTopic: cfg.KafkaTopic,
})
if err != nil {
logger.Error("Error initializing kafka", zap.Error(err))
return err
}
producer := brokers.NewProducer(brokers.ProducerDeps{
KafkaClient: kafkaClient,
Logger: zapLogger,
})
redisClient, err := initialize.Redis(ctx, cfg)
if err != nil {
logger.Error("Error initializing redis", zap.Error(err))
return err
}
go tools.PublishPrivilege(privilege.NewPrivilege(privilege.Client{
URL: cfg.HubAdminUrl,
ServiceName: cfg.ServiceName,
Privileges: model.Privileges,
}, &fiber.Client{}), 10, 5*time.Minute)
clients, err := initialize.NewClients(ctx, cfg, dalS.PgDAL)
if err != nil {
logger.Error("Error initializing clients", zap.Error(err))
return err
}
tgWC := workers.NewTgListenerWC(workers.Deps{
BotID: int64(6712573453), // todo убрать
Redis: redisClient,
Dal: dalS.PgDAL,
TgClient: clients.TgClient,
})
go tgWC.Start(ctx)
controllers := initialize.NewControllers(initialize.ControllerDeps{
Clients: clients,
DALs: dalS,
Config: cfg,
Producer: producer,
RedisClient: redisClient,
})
grpc, err := server.NewGRPC(zapLogger)
if err != nil {
logger.Error("Error initializing grpc", zap.Error(err))
return err
}
grpc.Register(controllers.GRpcControllers)
srv := http.NewServer(http.ServerConfig{
Logger: logger,
Controllers: []http.Controller{controllers.HttpControllers.Account, controllers.HttpControllers.Telegram, controllers.HttpControllers.Result,
controllers.HttpControllers.Question, controllers.HttpControllers.Quiz, controllers.HttpControllers.Statistic},
Hlogger: loggerHlog,
})
go func() {
if err := srv.Start(cfg.HttpHost + ":" + cfg.NumberPort); err != nil {
logger.Error("HTTP server startup error", zap.Error(err))
cancel()
}
}()
go grpc.Run(server.DepsGrpcRun{
Host: cfg.GrpcHost,
Port: cfg.GrpcPort,
})
srv.ListRoutes()
shutdownGroup.Add(closer.CloserFunc(srv.Shutdown))
shutdownGroup.Add(closer.CloserFunc(grpc.Stop))
shutdownGroup.Add(closer.CloserFunc(dalS.PgDAL.Close))
shutdownGroup.Add(closer.CloserFunc(dalS.ChDAL.Close))
<-ctx.Done()
timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer timeoutCancel()
if err := shutdownGroup.Call(timeoutCtx); err != nil {
if errors.Is(err, context.DeadlineExceeded) {
logger.Error("Shutdown timed out", zap.Error(err))
} else {
logger.Error("Failed to shutdown services gracefully", zap.Error(err))
}
return err
}
logger.Info("Application has stopped")
return nil
}

@ -1,21 +1,50 @@
package service package account
import ( import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/go-redis/redis/v8"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"penahub.gitlab.yandexcloud.net/backend/penahub_common/log_mw" "penahub.gitlab.yandexcloud.net/backend/penahub_common/log_mw"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/middleware" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/middleware"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/brokers" "penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/brokers"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/models" "penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/clients/auth"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/models"
"strconv" "strconv"
"time" "time"
) )
type Deps struct {
Dal *dal.DAL
AuthClient *auth.AuthClient
Producer *brokers.Producer
ServiceName string
RedisClient *redis.Client
}
type Account struct {
dal *dal.DAL
authClient *auth.AuthClient
producer *brokers.Producer
serviceName string
redisClient *redis.Client
}
func NewAccountController(deps Deps) *Account {
return &Account{
dal: deps.Dal,
authClient: deps.AuthClient,
producer: deps.Producer,
serviceName: deps.ServiceName,
redisClient: deps.RedisClient,
}
}
type CreateAccountReq struct { type CreateAccountReq struct {
UserID string `json:"userId"` UserID string `json:"userId"`
} }
@ -51,13 +80,13 @@ type GetAccountsResp struct {
} }
// getCurrentAccount обработчик для получения текущего аккаунта // getCurrentAccount обработчик для получения текущего аккаунта
func (s *Service) getCurrentAccount(ctx *fiber.Ctx) error { func (r *Account) GetCurrentAccount(ctx *fiber.Ctx) error {
accountID, ok := middleware.GetAccountId(ctx) accountID, ok := middleware.GetAccountId(ctx)
if !ok { if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required") return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
} }
account, err := s.dal.AccountRepo.GetAccountByID(ctx.Context(), accountID) account, err := r.dal.AccountRepo.GetAccountByID(ctx.Context(), accountID)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -71,14 +100,14 @@ func (s *Service) getCurrentAccount(ctx *fiber.Ctx) error {
} }
// createAccount обработчик для создания нового аккаунта // createAccount обработчик для создания нового аккаунта
func (s *Service) createAccount(ctx *fiber.Ctx) error { func (r *Account) CreateAccount(ctx *fiber.Ctx) error {
accountID, ok := middleware.GetAccountId(ctx) accountID, ok := middleware.GetAccountId(ctx)
if !ok { if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required") return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
} }
hlogger := log_mw.ExtractLogger(ctx) hlogger := log_mw.ExtractLogger(ctx)
existingAccount, err := s.dal.AccountRepo.GetAccountByID(ctx.Context(), accountID) existingAccount, err := r.dal.AccountRepo.GetAccountByID(ctx.Context(), accountID)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -86,7 +115,7 @@ func (s *Service) createAccount(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusConflict).SendString("user with this ID already exists") return ctx.Status(fiber.StatusConflict).SendString("user with this ID already exists")
} }
email, err := s.authClient.GetUserEmail(accountID) email, err := r.authClient.GetUserEmail(accountID)
if err != nil { if err != nil {
return err return err
} }
@ -105,11 +134,11 @@ func (s *Service) createAccount(ctx *fiber.Ctx) error {
}, },
} }
createdAcc, err := s.dal.AccountRepo.CreateAccount(ctx.Context(), &newAccount) createdAcc, err := r.dal.AccountRepo.CreateAccount(ctx.Context(), &newAccount)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
_, err = s.dal.AccountRepo.PostLeadTarget(ctx.Context(), model.LeadTarget{ _, err = r.dal.AccountRepo.PostLeadTarget(ctx.Context(), model.LeadTarget{
AccountID: accountID, AccountID: accountID,
Target: email, Target: email,
Type: model.LeadTargetEmail, Type: model.LeadTargetEmail,
@ -124,10 +153,10 @@ func (s *Service) createAccount(ctx *fiber.Ctx) error {
CtxAccountID: createdAcc.ID, CtxAccountID: createdAcc.ID,
}) })
err = s.producer.ToMailNotify(ctx.Context(), brokers.Message{ err = r.producer.ToMailNotify(ctx.Context(), brokers.Message{
AccountID: accountID, AccountID: accountID,
Email: email, Email: email,
ServiceKey: s.serviceName, ServiceKey: r.serviceName,
SendAt: time.Now(), SendAt: time.Now(),
}) })
if err != nil { if err != nil {
@ -140,18 +169,18 @@ func (s *Service) createAccount(ctx *fiber.Ctx) error {
} }
// deleteAccount обработчик для удаления текущего аккаунта // deleteAccount обработчик для удаления текущего аккаунта
func (s *Service) deleteAccount(ctx *fiber.Ctx) error { func (r *Account) DeleteAccount(ctx *fiber.Ctx) error {
accountID, ok := middleware.GetAccountId(ctx) accountID, ok := middleware.GetAccountId(ctx)
if !ok { if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required") return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
} }
account, err := s.dal.AccountRepo.GetAccountByID(ctx.Context(), accountID) account, err := r.dal.AccountRepo.GetAccountByID(ctx.Context(), accountID)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
if err := s.dal.AccountRepo.DeleteAccount(ctx.Context(), account.ID); err != nil { if err := r.dal.AccountRepo.DeleteAccount(ctx.Context(), account.ID); err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -161,13 +190,13 @@ func (s *Service) deleteAccount(ctx *fiber.Ctx) error {
} }
// getPrivilegeByUserID обработчик для получения привилегий аккаунта по ID пользователя // getPrivilegeByUserID обработчик для получения привилегий аккаунта по ID пользователя
func (s *Service) getPrivilegeByUserID(ctx *fiber.Ctx) error { func (r *Account) GetPrivilegeByUserID(ctx *fiber.Ctx) error {
var req GetPrivilegeByUserIDReq var req GetPrivilegeByUserIDReq
if err := ctx.BodyParser(&req); err != nil { if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
} }
privilege, err := s.dal.AccountRepo.GetPrivilegesByAccountID(ctx.Context(), req.UserID) privilege, err := r.dal.AccountRepo.GetPrivilegesByAccountID(ctx.Context(), req.UserID)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -176,13 +205,13 @@ func (s *Service) getPrivilegeByUserID(ctx *fiber.Ctx) error {
} }
// deleteAccountByUserID обработчик для удаления аккаунта по ID пользователя // deleteAccountByUserID обработчик для удаления аккаунта по ID пользователя
func (s *Service) deleteAccountByUserID(ctx *fiber.Ctx) error { func (r *Account) DeleteAccountByUserID(ctx *fiber.Ctx) error {
var req DeleteAccountByUserIDReq var req DeleteAccountByUserIDReq
if err := ctx.BodyParser(&req); err != nil { if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
} }
existingAccount, err := s.dal.AccountRepo.GetAccountByID(ctx.Context(), req.UserID) existingAccount, err := r.dal.AccountRepo.GetAccountByID(ctx.Context(), req.UserID)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -191,7 +220,7 @@ func (s *Service) deleteAccountByUserID(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusInternalServerError).SendString("user with this ID not found") return ctx.Status(fiber.StatusInternalServerError).SendString("user with this ID not found")
} }
if err := s.dal.AccountRepo.DeleteAccount(ctx.Context(), existingAccount.ID); err != nil { if err := r.dal.AccountRepo.DeleteAccount(ctx.Context(), existingAccount.ID); err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -201,7 +230,7 @@ func (s *Service) deleteAccountByUserID(ctx *fiber.Ctx) error {
} }
// getAccounts обработчик для получения списка аккаунтов с пагинацией // getAccounts обработчик для получения списка аккаунтов с пагинацией
func (s *Service) getAccounts(ctx *fiber.Ctx) error { func (r *Account) GetAccounts(ctx *fiber.Ctx) error {
var req GetAccountsReq var req GetAccountsReq
if err := ctx.BodyParser(&req); err != nil { if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
@ -212,7 +241,7 @@ func (s *Service) getAccounts(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required") return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
} }
accounts, totalCount, err := s.dal.AccountRepo.GetAccounts(ctx.Context(), req.Limit, req.Page) accounts, totalCount, err := r.dal.AccountRepo.GetAccounts(ctx.Context(), req.Limit, req.Page)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -225,7 +254,7 @@ func (s *Service) getAccounts(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusOK).JSON(response) return ctx.Status(fiber.StatusOK).JSON(response)
} }
func (s *Service) ManualDone(ctx *fiber.Ctx) error { func (r *Account) ManualDone(ctx *fiber.Ctx) error {
var req struct { var req struct {
Id string `json:"id"` Id string `json:"id"`
} }
@ -237,7 +266,7 @@ func (s *Service) ManualDone(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusBadRequest).SendString("User id is required") return ctx.Status(fiber.StatusBadRequest).SendString("User id is required")
} }
err := s.dal.AccountRepo.ManualDone(ctx.Context(), req.Id) err := r.dal.AccountRepo.ManualDone(ctx.Context(), req.Id)
if err != nil { if err != nil {
if errors.Is(err, pj_errors.ErrNotFound) { if errors.Is(err, pj_errors.ErrNotFound) {
return ctx.Status(fiber.StatusNotFound).SendString("user don't have this privilege") return ctx.Status(fiber.StatusNotFound).SendString("user don't have this privilege")
@ -248,7 +277,7 @@ func (s *Service) ManualDone(ctx *fiber.Ctx) error {
return ctx.SendStatus(fiber.StatusOK) return ctx.SendStatus(fiber.StatusOK)
} }
func (s *Service) PostLeadTarget(ctx *fiber.Ctx) error { func (r *Account) PostLeadTarget(ctx *fiber.Ctx) error {
var req struct { var req struct {
Type string `json:"type"` Type string `json:"type"`
QuizID int32 `json:"quizID"` QuizID int32 `json:"quizID"`
@ -276,7 +305,7 @@ func (s *Service) PostLeadTarget(ctx *fiber.Ctx) error {
switch req.Type { switch req.Type {
case "mail": case "mail":
_, err := s.dal.AccountRepo.PostLeadTarget(ctx.Context(), model.LeadTarget{ _, err := r.dal.AccountRepo.PostLeadTarget(ctx.Context(), model.LeadTarget{
AccountID: accountID, AccountID: accountID,
Target: req.Target, Target: req.Target,
Type: model.LeadTargetType(req.Type), Type: model.LeadTargetType(req.Type),
@ -287,7 +316,7 @@ func (s *Service) PostLeadTarget(ctx *fiber.Ctx) error {
} }
return ctx.SendStatus(fiber.StatusOK) return ctx.SendStatus(fiber.StatusOK)
case "telegram": case "telegram":
targets, err := s.dal.AccountRepo.GetLeadTarget(ctx.Context(), accountID, req.QuizID) targets, err := r.dal.AccountRepo.GetLeadTarget(ctx.Context(), accountID, req.QuizID)
if err != nil && !errors.Is(err, pj_errors.ErrNotFound) { if err != nil && !errors.Is(err, pj_errors.ErrNotFound) {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -311,7 +340,7 @@ func (s *Service) PostLeadTarget(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
if err := s.redisClient.Set(ctx.Context(), taskKey, taskData, 0).Err(); err != nil { if err := r.redisClient.Set(ctx.Context(), taskKey, taskData, 0).Err(); err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
case "whatsapp": case "whatsapp":
@ -321,21 +350,21 @@ func (s *Service) PostLeadTarget(ctx *fiber.Ctx) error {
return nil return nil
} }
func (s *Service) DeleteLeadTarget(ctx *fiber.Ctx) error { func (r *Account) DeleteLeadTarget(ctx *fiber.Ctx) error {
leadIDStr := ctx.Params("id") leadIDStr := ctx.Params("id")
leadID, err := strconv.ParseInt(leadIDStr, 10, 64) leadID, err := strconv.ParseInt(leadIDStr, 10, 64)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid lead ID format") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid lead ID format")
} }
err = s.dal.AccountRepo.DeleteLeadTarget(ctx.Context(), leadID) err = r.dal.AccountRepo.DeleteLeadTarget(ctx.Context(), leadID)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
return ctx.SendStatus(fiber.StatusOK) return ctx.SendStatus(fiber.StatusOK)
} }
func (s *Service) GetLeadTarget(ctx *fiber.Ctx) error { func (r *Account) GetLeadTarget(ctx *fiber.Ctx) error {
accountID, ok := middleware.GetAccountId(ctx) accountID, ok := middleware.GetAccountId(ctx)
if !ok { if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required") return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
@ -347,7 +376,7 @@ func (s *Service) GetLeadTarget(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid quiz ID format") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid quiz ID format")
} }
result, err := s.dal.AccountRepo.GetLeadTarget(ctx.Context(), accountID, int32(quizID)) result, err := r.dal.AccountRepo.GetLeadTarget(ctx.Context(), accountID, int32(quizID))
if err != nil { if err != nil {
switch { switch {
case errors.Is(err, pj_errors.ErrNotFound): case errors.Is(err, pj_errors.ErrNotFound):
@ -360,7 +389,7 @@ func (s *Service) GetLeadTarget(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusOK).JSON(result) return ctx.Status(fiber.StatusOK).JSON(result)
} }
func (s *Service) UpdateLeadTarget(ctx *fiber.Ctx) error { func (r *Account) UpdateLeadTarget(ctx *fiber.Ctx) error {
var req struct { var req struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Target string `json:"target"` Target string `json:"target"`
@ -374,7 +403,7 @@ func (s *Service) UpdateLeadTarget(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusBadRequest).SendString("ID and Target don't be nil") return ctx.Status(fiber.StatusBadRequest).SendString("ID and Target don't be nil")
} }
result, err := s.dal.AccountRepo.UpdateLeadTarget(ctx.Context(), model.LeadTarget{ result, err := r.dal.AccountRepo.UpdateLeadTarget(ctx.Context(), model.LeadTarget{
ID: req.ID, ID: req.ID,
Target: req.Target, Target: req.Target,
}) })

@ -0,0 +1,21 @@
package account
import "github.com/gofiber/fiber/v2"
func (r *Account) Register(router fiber.Router) {
router.Get("/account/get", r.GetCurrentAccount)
router.Post("/account/create", r.CreateAccount)
router.Delete("/account/delete", r.DeleteAccount)
router.Get("/accounts", r.GetAccounts)
router.Get("/privilege/:userId", r.GetPrivilegeByUserID)
router.Delete("/account/:userId", r.DeleteAccountByUserID)
router.Post("/account/manualdone", r.ManualDone)
router.Post("/account/leadtarget", r.PostLeadTarget)
router.Delete("/account/leadtarget/:id", r.DeleteLeadTarget)
router.Get("/account/leadtarget/:quizID", r.GetLeadTarget)
router.Put("/account/leadtarget", r.UpdateLeadTarget)
}
func (r *Account) Name() string {
return ""
}

@ -1,15 +1,28 @@
package service package question
import ( import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/lib/pq" "github.com/lib/pq"
"penahub.gitlab.yandexcloud.net/backend/penahub_common/log_mw" "penahub.gitlab.yandexcloud.net/backend/penahub_common/log_mw"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/middleware" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/middleware"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/models" "penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/models"
"unicode/utf8" "unicode/utf8"
) )
type Deps struct {
DAL *dal.DAL
}
type Question struct {
dal *dal.DAL
}
func NewQuestionController(deps Deps) *Question {
return &Question{dal: deps.DAL}
}
// QuestionCreateReq request structure for creating Question // QuestionCreateReq request structure for creating Question
type QuestionCreateReq struct { type QuestionCreateReq struct {
QuizId uint64 `json:"quiz_id"` // relation to quiz QuizId uint64 `json:"quiz_id"` // relation to quiz
@ -23,7 +36,7 @@ type QuestionCreateReq struct {
} }
// CreateQuestion service handler for creating question for quiz // CreateQuestion service handler for creating question for quiz
func (s *Service) CreateQuestion(ctx *fiber.Ctx) error { func (r *Question) CreateQuestion(ctx *fiber.Ctx) error {
accountID, ok := middleware.GetAccountId(ctx) accountID, ok := middleware.GetAccountId(ctx)
if !ok { if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required") return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
@ -64,7 +77,7 @@ func (s *Service) CreateQuestion(ctx *fiber.Ctx) error {
Content: req.Content, Content: req.Content,
} }
questionID, err := s.dal.QuestionRepo.CreateQuestion(ctx.Context(), &result) questionID, err := r.dal.QuestionRepo.CreateQuestion(ctx.Context(), &result)
if err != nil { if err != nil {
if e, ok := err.(*pq.Error); ok { if e, ok := err.(*pq.Error); ok {
if e.Constraint == "quiz_relation" { if e.Constraint == "quiz_relation" {
@ -104,7 +117,7 @@ type GetQuestionListResp struct {
} }
// GetQuestionList handler for paginated list question // GetQuestionList handler for paginated list question
func (s *Service) GetQuestionList(ctx *fiber.Ctx) error { func (r *Question) GetQuestionList(ctx *fiber.Ctx) error {
var req GetQuestionListReq var req GetQuestionListReq
if err := ctx.BodyParser(&req); err != nil { if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
@ -127,7 +140,7 @@ func (s *Service) GetQuestionList(ctx *fiber.Ctx) error {
"'test','none','file', 'button','select','checkbox'") "'test','none','file', 'button','select','checkbox'")
} }
res, cnt, err := s.dal.QuestionRepo.GetQuestionList(ctx.Context(), res, cnt, err := r.dal.QuestionRepo.GetQuestionList(ctx.Context(),
req.Limit, req.Limit,
req.Page*req.Limit, req.Page*req.Limit,
uint64(req.From), uint64(req.From),
@ -165,7 +178,7 @@ type UpdateResp struct {
} }
// UpdateQuestion handler for update question // UpdateQuestion handler for update question
func (s *Service) UpdateQuestion(ctx *fiber.Ctx) error { func (r *Question) UpdateQuestion(ctx *fiber.Ctx) error {
var req UpdateQuestionReq var req UpdateQuestionReq
if err := ctx.BodyParser(&req); err != nil { if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
@ -195,7 +208,7 @@ func (s *Service) UpdateQuestion(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusNotAcceptable).SendString("type must be only test,button,file,checkbox,select, none or empty string") 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) question, err := r.dal.QuestionRepo.MoveToHistoryQuestion(ctx.Context(), req.Id)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -227,7 +240,7 @@ func (s *Service) UpdateQuestion(ctx *fiber.Ctx) error {
question.Content = req.Content question.Content = req.Content
} }
if err := s.dal.QuestionRepo.UpdateQuestion(ctx.Context(), question); err != nil { if err := r.dal.QuestionRepo.UpdateQuestion(ctx.Context(), question); err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -243,7 +256,7 @@ type CopyQuestionReq struct {
} }
// CopyQuestion handler for copy question // CopyQuestion handler for copy question
func (s *Service) CopyQuestion(ctx *fiber.Ctx) error { func (r *Question) CopyQuestion(ctx *fiber.Ctx) error {
var req CopyQuestionReq var req CopyQuestionReq
if err := ctx.BodyParser(&req); err != nil { if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
@ -253,7 +266,7 @@ func (s *Service) CopyQuestion(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusFailedDependency).SendString("no id provided") return ctx.Status(fiber.StatusFailedDependency).SendString("no id provided")
} }
question, err := s.dal.QuestionRepo.CopyQuestion(ctx.Context(), req.Id, req.QuizId) question, err := r.dal.QuestionRepo.CopyQuestion(ctx.Context(), req.Id, req.QuizId)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -271,8 +284,8 @@ type GetQuestionHistoryReq struct {
} }
// GetQuestionHistory handler for history of quiz // GetQuestionHistory handler for history of quiz
func (s *Service) GetQuestionHistory(ctx *fiber.Ctx) error { func (r *Question) GetQuestionHistory(ctx *fiber.Ctx) error {
var req GetQuizHistoryReq var req GetQuestionHistoryReq
if err := ctx.BodyParser(&req); err != nil { if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
} }
@ -281,7 +294,7 @@ func (s *Service) GetQuestionHistory(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusFailedDependency).SendString("no id provided") 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) history, err := r.dal.QuestionRepo.QuestionHistory(ctx.Context(), req.Id, req.Limit, req.Page*req.Limit)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -289,15 +302,22 @@ func (s *Service) GetQuestionHistory(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusOK).JSON(history) return ctx.Status(fiber.StatusOK).JSON(history)
} }
type DeactivateResp struct {
Deactivated uint64 `json:"deactivated"`
}
// DeleteQuestion handler for fake delete question // DeleteQuestion handler for fake delete question
func (s *Service) DeleteQuestion(ctx *fiber.Ctx) error { func (r *Question) DeleteQuestion(ctx *fiber.Ctx) error {
accountID, ok := middleware.GetAccountId(ctx) accountID, ok := middleware.GetAccountId(ctx)
if !ok { if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required") return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
} }
hlogger := log_mw.ExtractLogger(ctx) hlogger := log_mw.ExtractLogger(ctx)
var req DeactivateReq var req struct {
Id uint64 `json:"id"`
}
if err := ctx.BodyParser(&req); err != nil { if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
} }
@ -306,7 +326,7 @@ func (s *Service) DeleteQuestion(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusFailedDependency).SendString("id for deleting question is required") return ctx.Status(fiber.StatusFailedDependency).SendString("id for deleting question is required")
} }
deleted, err := s.dal.QuestionRepo.DeleteQuestion(ctx.Context(), req.Id) deleted, err := r.dal.QuestionRepo.DeleteQuestion(ctx.Context(), req.Id)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }

@ -0,0 +1,16 @@
package question
import "github.com/gofiber/fiber/v2"
func (r *Question) Register(router fiber.Router) {
router.Post("/create", r.CreateQuestion)
router.Post("/getList", r.GetQuestionList)
router.Patch("/edit", r.UpdateQuestion)
router.Post("/copy", r.CopyQuestion)
router.Post("/history", r.GetQuestionHistory)
router.Delete("/delete", r.DeleteQuestion)
}
func (r *Question) Name() string {
return "question"
}

@ -1,17 +1,30 @@
package service package quiz
import ( import (
"fmt"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"penahub.gitlab.yandexcloud.net/backend/penahub_common/log_mw" "penahub.gitlab.yandexcloud.net/backend/penahub_common/log_mw"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/middleware" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/middleware"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model" "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/common.git/repository/quiz"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/models" "penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/models"
"time" "time"
"unicode/utf8" "unicode/utf8"
"fmt"
) )
type Deps struct {
DAL *dal.DAL
}
type Quiz struct {
dal *dal.DAL
}
func NewQuizController(deps Deps) *Quiz {
return &Quiz{dal: deps.DAL}
}
type CreateQuizReq struct { type CreateQuizReq struct {
Fingerprinting bool `json:"fingerprinting"` // true if you need to store device id 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 Repeatable bool `json:"repeatable"` // make it true for allow more than one quiz checkouting
@ -36,7 +49,7 @@ type CreateQuizReq struct {
} }
// CreateQuiz handler for quiz creating request // CreateQuiz handler for quiz creating request
func (s *Service) CreateQuiz(ctx *fiber.Ctx) error { func (r *Quiz) CreateQuiz(ctx *fiber.Ctx) error {
var req CreateQuizReq var req CreateQuizReq
if err := ctx.BodyParser(&req); err != nil { if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
@ -95,7 +108,7 @@ func (s *Service) CreateQuiz(ctx *fiber.Ctx) error {
GroupId: req.GroupId, GroupId: req.GroupId,
} }
quizID, err := s.dal.QuizRepo.CreateQuiz(ctx.Context(), &record) quizID, err := r.dal.QuizRepo.CreateQuiz(ctx.Context(), &record)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -129,7 +142,7 @@ type GetQuizListResp struct {
} }
// GetQuizList handler for paginated list quiz // GetQuizList handler for paginated list quiz
func (s *Service) GetQuizList(ctx *fiber.Ctx) error { func (r *Quiz) GetQuizList(ctx *fiber.Ctx) error {
var req GetQuizListReq var req GetQuizListReq
if err := ctx.BodyParser(&req); err != nil { if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
@ -151,7 +164,7 @@ func (s *Service) GetQuizList(ctx *fiber.Ctx) error {
"'stop','start','draft', 'template','timeout','offlimit'") "'stop','start','draft', 'template','timeout','offlimit'")
} }
res, cnt, err := s.dal.QuizRepo.GetQuizList(ctx.Context(), res, cnt, err := r.dal.QuizRepo.GetQuizList(ctx.Context(),
quiz.GetQuizListDeps{ quiz.GetQuizListDeps{
Limit: req.Limit, Limit: req.Limit,
Offset: req.Limit * req.Page, Offset: req.Limit * req.Page,
@ -195,7 +208,11 @@ type UpdateQuizReq struct {
GroupId uint64 `json:"group_id"` GroupId uint64 `json:"group_id"`
} }
func (s *Service) UpdateQuiz(ctx *fiber.Ctx) error { type UpdateResp struct {
Updated uint64 `json:"updated"`
}
func (r *Quiz) UpdateQuiz(ctx *fiber.Ctx) error {
var req UpdateQuizReq var req UpdateQuizReq
if err := ctx.BodyParser(&req); err != nil { if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
@ -236,7 +253,7 @@ func (s *Service) UpdateQuiz(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusConflict).SendString("you can pause quiz only if it has deadline for passing") 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) quiz, err := r.dal.QuizRepo.MoveToHistoryQuiz(ctx.Context(), req.Id, accountId)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -308,7 +325,7 @@ func (s *Service) UpdateQuiz(ctx *fiber.Ctx) error {
quiz.ParentIds = append(quiz.ParentIds, int32(quiz.Id)) quiz.ParentIds = append(quiz.ParentIds, int32(quiz.Id))
if err := s.dal.QuizRepo.UpdateQuiz(ctx.Context(), accountId, quiz); err != nil { if err := r.dal.QuizRepo.UpdateQuiz(ctx.Context(), accountId, quiz); err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -336,7 +353,7 @@ type CopyQuizReq struct {
} }
// CopyQuiz request handler for copy quiz // CopyQuiz request handler for copy quiz
func (s *Service) CopyQuiz(ctx *fiber.Ctx) error { func (r *Quiz) CopyQuiz(ctx *fiber.Ctx) error {
var req CopyQuizReq var req CopyQuizReq
if err := ctx.BodyParser(&req); err != nil { if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
@ -351,7 +368,7 @@ func (s *Service) CopyQuiz(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusFailedDependency).SendString("no id provided") return ctx.Status(fiber.StatusFailedDependency).SendString("no id provided")
} }
quiz, err := s.dal.QuizRepo.CopyQuiz(ctx.Context(), accountId, req.Id) quiz, err := r.dal.QuizRepo.CopyQuiz(ctx.Context(), accountId, req.Id)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -369,7 +386,7 @@ type GetQuizHistoryReq struct {
} }
// GetQuizHistory handler for history of quiz // GetQuizHistory handler for history of quiz
func (s *Service) GetQuizHistory(ctx *fiber.Ctx) error { func (r *Quiz) GetQuizHistory(ctx *fiber.Ctx) error {
var req GetQuizHistoryReq var req GetQuizHistoryReq
if err := ctx.BodyParser(&req); err != nil { if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
@ -383,7 +400,7 @@ func (s *Service) GetQuizHistory(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusFailedDependency).SendString("no id provided") return ctx.Status(fiber.StatusFailedDependency).SendString("no id provided")
} }
history, err := s.dal.QuizRepo.QuizHistory(ctx.Context(), quiz.QuizHistoryDeps{ history, err := r.dal.QuizRepo.QuizHistory(ctx.Context(), quiz.QuizHistoryDeps{
Id: req.Id, Id: req.Id,
Limit: req.Limit, Limit: req.Limit,
Offset: req.Page * req.Limit, Offset: req.Page * req.Limit,
@ -406,7 +423,7 @@ type DeactivateResp struct {
} }
// DeleteQuiz handler for fake delete quiz // DeleteQuiz handler for fake delete quiz
func (s *Service) DeleteQuiz(ctx *fiber.Ctx) error { func (r *Quiz) DeleteQuiz(ctx *fiber.Ctx) error {
var req DeactivateReq var req DeactivateReq
if err := ctx.BodyParser(&req); err != nil { if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
@ -422,7 +439,7 @@ func (s *Service) DeleteQuiz(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusFailedDependency).SendString("id for deleting is required") return ctx.Status(fiber.StatusFailedDependency).SendString("id for deleting is required")
} }
deleted, err := s.dal.QuizRepo.DeleteQuiz(ctx.Context(), accountId, req.Id) deleted, err := r.dal.QuizRepo.DeleteQuiz(ctx.Context(), accountId, req.Id)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -438,7 +455,7 @@ func (s *Service) DeleteQuiz(ctx *fiber.Ctx) error {
} }
// ArchiveQuiz handler for archiving quiz // ArchiveQuiz handler for archiving quiz
func (s *Service) ArchiveQuiz(ctx *fiber.Ctx) error { func (r *Quiz) ArchiveQuiz(ctx *fiber.Ctx) error {
var req DeactivateReq var req DeactivateReq
if err := ctx.BodyParser(&req); err != nil { if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
@ -453,7 +470,7 @@ func (s *Service) ArchiveQuiz(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusFailedDependency).SendString("id for archive quiz is required") return ctx.Status(fiber.StatusFailedDependency).SendString("id for archive quiz is required")
} }
archived, err := s.dal.QuizRepo.DeleteQuiz(ctx.Context(), accountId, req.Id) archived, err := r.dal.QuizRepo.DeleteQuiz(ctx.Context(), accountId, req.Id)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -467,7 +484,7 @@ type QuizMoveReq struct {
Qid, AccountID string Qid, AccountID string
} }
func (s *Service) QuizMove(ctx *fiber.Ctx) error { func (r *Quiz) QuizMove(ctx *fiber.Ctx) error {
var req QuizMoveReq var req QuizMoveReq
if err := ctx.BodyParser(&req); err != nil { if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
@ -477,7 +494,7 @@ func (s *Service) QuizMove(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request qid and accountID is required") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request qid and accountID is required")
} }
resp, err := s.dal.QuizRepo.QuizMove(ctx.Context(), req.Qid, req.AccountID) resp, err := r.dal.QuizRepo.QuizMove(ctx.Context(), req.Qid, req.AccountID)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -485,7 +502,7 @@ func (s *Service) QuizMove(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusOK).JSON(resp) return ctx.Status(fiber.StatusOK).JSON(resp)
} }
func (s *Service) TemplateCopy(ctx *fiber.Ctx) error { func (r *Quiz) TemplateCopy(ctx *fiber.Ctx) error {
accountID, ok := middleware.GetAccountId(ctx) accountID, ok := middleware.GetAccountId(ctx)
if !ok { if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required") return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
@ -504,7 +521,7 @@ func (s *Service) TemplateCopy(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request qid is required") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request qid is required")
} }
qizID, err := s.dal.QuizRepo.TemplateCopy(ctx.Context(), accountID, req.Qid) qizID, err := r.dal.QuizRepo.TemplateCopy(ctx.Context(), accountID, req.Qid)
if err != nil { if err != nil {
fmt.Println("TEMPLERR", err) fmt.Println("TEMPLERR", err)
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())

@ -0,0 +1,19 @@
package quiz
import "github.com/gofiber/fiber/v2"
func (r *Quiz) Register(router fiber.Router) {
router.Post("/create", r.CreateQuiz)
router.Post("/getList", r.GetQuizList)
router.Patch("/edit", r.UpdateQuiz)
router.Post("/copy", r.CopyQuiz)
router.Post("/history", r.GetQuizHistory)
router.Delete("/delete", r.DeleteQuiz)
router.Patch("/archive", r.ArchiveQuiz)
router.Post("/move", r.QuizMove)
router.Post("/template", r.TemplateCopy)
}
func (r *Quiz) Name() string {
return "quiz"
}

@ -1,16 +1,34 @@
package service package result
import ( import (
"bytes" "bytes"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/middleware" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/middleware"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model" "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/common.git/repository/result"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/tools" "penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/tools"
"strconv" "strconv"
"time" "time"
) )
type Deps struct {
DAL *dal.DAL
S3Prefix string
}
type Result struct {
dal *dal.DAL
s3Prefix string
}
func NewResultController(deps Deps) *Result {
return &Result{
dal: deps.DAL,
s3Prefix: deps.S3Prefix,
}
}
type ReqExport struct { type ReqExport struct {
To, From time.Time To, From time.Time
New bool New bool
@ -23,7 +41,7 @@ type ReqExportResponse struct {
Results []model.AnswerExport `json:"results"` Results []model.AnswerExport `json:"results"`
} }
func (s *Service) GetResultsByQuizID(ctx *fiber.Ctx) error { func (r *Result) GetResultsByQuizID(ctx *fiber.Ctx) error {
payment := true // параметр для определения существования текущих привилегий юзера payment := true // параметр для определения существования текущих привилегий юзера
accountID, ok := middleware.GetAccountId(ctx) accountID, ok := middleware.GetAccountId(ctx)
@ -42,7 +60,7 @@ func (s *Service) GetResultsByQuizID(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid quiz ID format") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid quiz ID format")
} }
account, err := s.dal.AccountRepo.GetAccountByID(ctx.Context(), accountID) account, err := r.dal.AccountRepo.GetAccountByID(ctx.Context(), accountID)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -52,7 +70,7 @@ func (s *Service) GetResultsByQuizID(ctx *fiber.Ctx) error {
} }
} }
results, totalCount, err := s.dal.ResultRepo.GetQuizResults(ctx.Context(), quizID, result.GetQuizResDeps{ results, totalCount, err := r.dal.ResultRepo.GetQuizResults(ctx.Context(), quizID, result.GetQuizResDeps{
To: req.To, To: req.To,
From: req.From, From: req.From,
New: req.New, New: req.New,
@ -71,7 +89,7 @@ func (s *Service) GetResultsByQuizID(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusOK).JSON(resp) return ctx.Status(fiber.StatusOK).JSON(resp)
} }
func (s *Service) DelResultByID(ctx *fiber.Ctx) error { func (r *Result) DelResultByID(ctx *fiber.Ctx) error {
accountID, ok := middleware.GetAccountId(ctx) accountID, ok := middleware.GetAccountId(ctx)
if !ok { if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("could not get account ID from token") return ctx.Status(fiber.StatusUnauthorized).SendString("could not get account ID from token")
@ -83,7 +101,7 @@ func (s *Service) DelResultByID(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid result ID format") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid result ID format")
} }
isOwner, err := s.dal.ResultRepo.CheckResultOwner(ctx.Context(), resultID, accountID) isOwner, err := r.dal.ResultRepo.CheckResultOwner(ctx.Context(), resultID, accountID)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -92,7 +110,7 @@ func (s *Service) DelResultByID(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusUnauthorized).SendString("not the owner of the result") return ctx.Status(fiber.StatusUnauthorized).SendString("not the owner of the result")
} }
if err := s.dal.ResultRepo.SoftDeleteResultByID(ctx.Context(), resultID); err != nil { if err := r.dal.ResultRepo.SoftDeleteResultByID(ctx.Context(), resultID); err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -104,7 +122,7 @@ type ReqSeen struct {
Answers []int64 Answers []int64
} }
func (s *Service) SetStatus(ctx *fiber.Ctx) error { func (r *Result) SetStatus(ctx *fiber.Ctx) error {
var req ReqSeen var req ReqSeen
if err := ctx.BodyParser(&req); err != nil { if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
@ -115,7 +133,7 @@ func (s *Service) SetStatus(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusUnauthorized).SendString("could not get account ID from token") return ctx.Status(fiber.StatusUnauthorized).SendString("could not get account ID from token")
} }
answers, err := s.dal.ResultRepo.CheckResultsOwner(ctx.Context(), req.Answers, accountID) answers, err := r.dal.ResultRepo.CheckResultsOwner(ctx.Context(), req.Answers, accountID)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -124,14 +142,14 @@ func (s *Service) SetStatus(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusNotAcceptable).SendString("could not update some answers because you don't have rights") 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 { if err := r.dal.ResultRepo.UpdateAnswersStatus(ctx.Context(), accountID, answers); err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
return ctx.Status(fiber.StatusOK).JSON(nil) return ctx.Status(fiber.StatusOK).JSON(nil)
} }
func (s *Service) ExportResultsToCSV(ctx *fiber.Ctx) error { func (r *Result) ExportResultsToCSV(ctx *fiber.Ctx) error {
accountID, ok := middleware.GetAccountId(ctx) accountID, ok := middleware.GetAccountId(ctx)
if !ok { if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required") return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
@ -148,7 +166,7 @@ func (s *Service) ExportResultsToCSV(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusBadRequest).SendString("invalid request body") return ctx.Status(fiber.StatusBadRequest).SendString("invalid request body")
} }
account, err := s.dal.AccountRepo.GetAccountByID(ctx.Context(), accountID) account, err := r.dal.AccountRepo.GetAccountByID(ctx.Context(), accountID)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -159,17 +177,17 @@ func (s *Service) ExportResultsToCSV(ctx *fiber.Ctx) error {
} }
} }
quiz, err := s.dal.QuizRepo.GetQuizById(ctx.Context(), accountID, quizID) quiz, err := r.dal.QuizRepo.GetQuizById(ctx.Context(), accountID, quizID)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString("failed to get quiz") return ctx.Status(fiber.StatusInternalServerError).SendString("failed to get quiz")
} }
questions, err := s.dal.ResultRepo.GetQuestions(ctx.Context(), quizID) questions, err := r.dal.ResultRepo.GetQuestions(ctx.Context(), quizID)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString("failed to get questions") return ctx.Status(fiber.StatusInternalServerError).SendString("failed to get questions")
} }
answers, err := s.dal.ResultRepo.GetQuizResultsCSV(ctx.Context(), quizID, result.GetQuizResDeps{ answers, err := r.dal.ResultRepo.GetQuizResultsCSV(ctx.Context(), quizID, result.GetQuizResDeps{
To: req.To, To: req.To,
From: req.From, From: req.From,
New: req.New, New: req.New,
@ -182,7 +200,7 @@ func (s *Service) ExportResultsToCSV(ctx *fiber.Ctx) error {
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
if err := tools.WriteDataToExcel(buffer, questions, answers, s.s3Prefix + quiz.Qid + "/"); err != nil { if err := tools.WriteDataToExcel(buffer, questions, answers, r.s3Prefix+quiz.Qid+"/"); err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString("failed to write data to Excel") return ctx.Status(fiber.StatusInternalServerError).SendString("failed to write data to Excel")
} }
@ -192,7 +210,7 @@ func (s *Service) ExportResultsToCSV(ctx *fiber.Ctx) error {
return ctx.Send(buffer.Bytes()) return ctx.Send(buffer.Bytes())
} }
func (s *Service) GetResultAnswers(ctx *fiber.Ctx) error { func (r *Result) GetResultAnswers(ctx *fiber.Ctx) error {
accountID, ok := middleware.GetAccountId(ctx) accountID, ok := middleware.GetAccountId(ctx)
if !ok { if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required") return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
@ -203,7 +221,7 @@ func (s *Service) GetResultAnswers(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusBadRequest).SendString("invalid quiz ID") return ctx.Status(fiber.StatusBadRequest).SendString("invalid quiz ID")
} }
account, err := s.dal.AccountRepo.GetAccountByID(ctx.Context(), accountID) account, err := r.dal.AccountRepo.GetAccountByID(ctx.Context(), accountID)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -214,11 +232,11 @@ func (s *Service) GetResultAnswers(ctx *fiber.Ctx) error {
} }
} }
answers, err := s.dal.ResultRepo.GetResultAnswers(ctx.Context(), resultID) answers, err := r.dal.ResultRepo.GetResultAnswers(ctx.Context(), resultID)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString("failed to get result answers") return ctx.Status(fiber.StatusInternalServerError).SendString("failed to get result answers")
} }
sortedAnswers, err := s.dal.QuestionRepo.ForSortingResults(ctx.Context(), answers) sortedAnswers, err := r.dal.QuestionRepo.ForSortingResults(ctx.Context(), answers)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString("failed sort result answers") return ctx.Status(fiber.StatusInternalServerError).SendString("failed sort result answers")
} }

@ -0,0 +1,15 @@
package result
import "github.com/gofiber/fiber/v2"
func (r *Result) Register(router fiber.Router) {
router.Post("/results/getResults/:quizId", r.GetResultsByQuizID)
router.Delete("/results/delete/:resultId", r.DelResultByID)
router.Patch("/result/seen", r.SetStatus)
router.Post("/results/:quizID/export", r.ExportResultsToCSV)
router.Get("/result/:resultID", r.GetResultAnswers)
}
func (r *Result) Name() string {
return ""
}

@ -0,0 +1,15 @@
package statistic
import "github.com/gofiber/fiber/v2"
func (r *Statistic) Register(router fiber.Router) {
router.Post("/statistic/:quizID/devices", r.GetDeviceStatistics)
router.Post("/statistic/:quizID/general", r.GetGeneralStatistics)
router.Post("/statistic/:quizID/questions", r.GetQuestionsStatistics)
router.Post("/statistic", r.AllServiceStatistics)
router.Get("/statistics/:quizID/pipelines", r.GetPipelinesStatistics)
}
func (r *Statistic) Name() string {
return ""
}

@ -1,17 +1,35 @@
package service package statistic
import ( import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/repository/statistics" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/repository/statistics"
"strconv" "strconv"
) )
type Deps struct {
DAL *dal.DAL
ChDAL *dal.ClickHouseDAL
}
type Statistic struct {
dal *dal.DAL
chDAL *dal.ClickHouseDAL
}
func NewStatisticController(deps Deps) *Statistic {
return &Statistic{
dal: deps.DAL,
chDAL: deps.ChDAL,
}
}
type DeviceStatReq struct { type DeviceStatReq struct {
From uint64 // временные границы выбора статистики From uint64 // временные границы выбора статистики
To uint64 To uint64
} }
func (s *Service) GetDeviceStatistics(ctx *fiber.Ctx) error { func (r *Statistic) GetDeviceStatistics(ctx *fiber.Ctx) error {
quizIDStr := ctx.Params("quizID") quizIDStr := ctx.Params("quizID")
quizID, err := strconv.ParseInt(quizIDStr, 10, 64) quizID, err := strconv.ParseInt(quizIDStr, 10, 64)
@ -24,7 +42,7 @@ func (s *Service) GetDeviceStatistics(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
} }
deviceStats, err := s.dal.StatisticsRepo.GetDeviceStatistics(ctx.Context(), statistics.DeviceStatReq{ deviceStats, err := r.dal.StatisticsRepo.GetDeviceStatistics(ctx.Context(), statistics.DeviceStatReq{
QuizId: quizID, QuizId: quizID,
From: req.From, From: req.From,
To: req.To, To: req.To,
@ -40,7 +58,7 @@ type GeneralStatsResp struct {
Open, Result, AvTime, Conversion map[uint64]uint64 Open, Result, AvTime, Conversion map[uint64]uint64
} }
func (s *Service) GetGeneralStatistics(ctx *fiber.Ctx) error { func (r *Statistic) GetGeneralStatistics(ctx *fiber.Ctx) error {
quizIDStr := ctx.Params("quizID") quizIDStr := ctx.Params("quizID")
quizID, err := strconv.ParseInt(quizIDStr, 10, 64) quizID, err := strconv.ParseInt(quizIDStr, 10, 64)
if err != nil { if err != nil {
@ -52,7 +70,7 @@ func (s *Service) GetGeneralStatistics(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
} }
generalStats, err := s.dal.StatisticsRepo.GetGeneralStatistics(ctx.Context(), statistics.DeviceStatReq{ generalStats, err := r.dal.StatisticsRepo.GetGeneralStatistics(ctx.Context(), statistics.DeviceStatReq{
QuizId: quizID, QuizId: quizID,
From: req.From, From: req.From,
To: req.To, To: req.To,
@ -64,7 +82,7 @@ func (s *Service) GetGeneralStatistics(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusOK).JSON(generalStats) return ctx.Status(fiber.StatusOK).JSON(generalStats)
} }
func (s *Service) GetQuestionsStatistics(ctx *fiber.Ctx) error { func (r *Statistic) GetQuestionsStatistics(ctx *fiber.Ctx) error {
quizIDStr := ctx.Params("quizID") quizIDStr := ctx.Params("quizID")
quizID, err := strconv.ParseInt(quizIDStr, 0, 64) quizID, err := strconv.ParseInt(quizIDStr, 0, 64)
if err != nil { if err != nil {
@ -76,7 +94,7 @@ func (s *Service) GetQuestionsStatistics(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
} }
questionsStats, err := s.dal.StatisticsRepo.GetQuestionsStatistics(ctx.Context(), statistics.DeviceStatReq{ questionsStats, err := r.dal.StatisticsRepo.GetQuestionsStatistics(ctx.Context(), statistics.DeviceStatReq{
QuizId: quizID, QuizId: quizID,
From: req.From, From: req.From,
To: req.To, To: req.To,
@ -92,13 +110,13 @@ type StatisticReq struct {
From, To uint64 // временные границы выбора статистики From, To uint64 // временные границы выбора статистики
} }
func (s *Service) AllServiceStatistics(ctx *fiber.Ctx) error { func (r *Statistic) AllServiceStatistics(ctx *fiber.Ctx) error {
var req StatisticReq var req StatisticReq
if err := ctx.BodyParser(&req); err != nil { if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
} }
allSvcStats, err := s.dal.StatisticsRepo.AllServiceStatistics(ctx.Context(), req.From, req.To) allSvcStats, err := r.dal.StatisticsRepo.AllServiceStatistics(ctx.Context(), req.From, req.To)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -106,7 +124,7 @@ func (s *Service) AllServiceStatistics(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusOK).JSON(allSvcStats) return ctx.Status(fiber.StatusOK).JSON(allSvcStats)
} }
func (s *Service) GetPipelinesStatistics(ctx *fiber.Ctx) error { func (r *Statistic) GetPipelinesStatistics(ctx *fiber.Ctx) error {
var req StatisticReq var req StatisticReq
if err := ctx.BodyParser(&req); err != nil { if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
@ -118,7 +136,7 @@ func (s *Service) GetPipelinesStatistics(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid quiz ID format") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid quiz ID format")
} }
result, err := s.chDAL.StatisticClickRepo.GetPipelinesStatistics(ctx.Context(), quizID, req.From, req.To) result, err := r.chDAL.StatisticClickRepo.GetPipelinesStatistics(ctx.Context(), quizID, req.From, req.To)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }

@ -0,0 +1,14 @@
package telegram
import "github.com/gofiber/fiber/v2"
func (r *Telegram) Register(router fiber.Router) {
router.Get("/pool", r.GetPoolTgAccounts)
router.Post("/create", r.AddingTgAccount)
router.Delete("/:id", r.DeleteTgAccountByID)
router.Post("/setCode", r.SettingTgCode)
}
func (r *Telegram) Name() string {
return "telegram"
}

@ -1,4 +1,4 @@
package service package telegram
import ( import (
"errors" "errors"
@ -6,20 +6,38 @@ import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/rs/xid" "github.com/rs/xid"
"path/filepath" "path/filepath"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/clients/telegram" "penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/clients/telegram"
"penahub.gitlab.yandexcloud.net/backend/tdlib/client" "penahub.gitlab.yandexcloud.net/backend/tdlib/client"
"strconv" "strconv"
) )
type Deps struct {
DAL *dal.DAL
TelegramClient *telegram.TelegramClient
}
type Telegram struct {
dal *dal.DAL
telegramClient *telegram.TelegramClient
}
func NewTelegramController(deps Deps) *Telegram {
return &Telegram{
dal: deps.DAL,
telegramClient: deps.TelegramClient,
}
}
type Message struct { type Message struct {
Type string `json:"type"` Type string `json:"type"`
Data string `json:"data"` Data string `json:"data"`
} }
func (s *Service) GetPoolTgAccounts(ctx *fiber.Ctx) error { func (r *Telegram) GetPoolTgAccounts(ctx *fiber.Ctx) error {
allAccounts, err := s.dal.TgRepo.GetAllTgAccounts(ctx.Context()) allAccounts, err := r.dal.TgRepo.GetAllTgAccounts(ctx.Context())
if err != nil { if err != nil {
switch { switch {
case errors.Is(err, pj_errors.ErrNotFound): case errors.Is(err, pj_errors.ErrNotFound):
@ -31,7 +49,7 @@ func (s *Service) GetPoolTgAccounts(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusOK).JSON(allAccounts) return ctx.Status(fiber.StatusOK).JSON(allAccounts)
} }
func (s *Service) AddingTgAccount(ctx *fiber.Ctx) error { func (r *Telegram) AddingTgAccount(ctx *fiber.Ctx) error {
var req telegram.AuthTgUserReq var req telegram.AuthTgUserReq
if err := ctx.BodyParser(&req); err != nil { if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
@ -39,7 +57,7 @@ func (s *Service) AddingTgAccount(ctx *fiber.Ctx) error {
if req.ApiID == 0 || req.ApiHash == "" || req.Password == "" || req.PhoneNumber == "" { if req.ApiID == 0 || req.ApiHash == "" || req.Password == "" || req.PhoneNumber == "" {
return ctx.Status(fiber.StatusBadRequest).SendString("empty required fields") return ctx.Status(fiber.StatusBadRequest).SendString("empty required fields")
} }
allAccounts, err := s.dal.TgRepo.GetAllTgAccounts(ctx.Context()) allAccounts, err := r.dal.TgRepo.GetAllTgAccounts(ctx.Context())
if err != nil && !errors.Is(err, pj_errors.ErrNotFound) { if err != nil && !errors.Is(err, pj_errors.ErrNotFound) {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }
@ -84,7 +102,7 @@ func (s *Service) AddingTgAccount(ctx *fiber.Ctx) error {
fmt.Println("new client failed", err) fmt.Println("new client failed", err)
return return
} }
s.telegramClient.SaveTgAccount(req.ApiID, req.ApiHash, tdlibClient) r.telegramClient.SaveTgAccount(req.ApiID, req.ApiHash, tdlibClient)
fmt.Println("i am down") fmt.Println("i am down")
}() }()
if goErr != nil { if goErr != nil {
@ -102,7 +120,7 @@ func (s *Service) AddingTgAccount(ctx *fiber.Ctx) error {
authorizer.PhoneNumber <- req.PhoneNumber authorizer.PhoneNumber <- req.PhoneNumber
case client.TypeAuthorizationStateWaitCode: case client.TypeAuthorizationStateWaitCode:
signature := xid.New() signature := xid.New()
s.telegramClient.AddedToMap(telegram.WaitingClient{ r.telegramClient.AddedToMap(telegram.WaitingClient{
PreviousReq: req, PreviousReq: req,
Authorizer: authorizer, Authorizer: authorizer,
}, signature.String()) }, signature.String())
@ -114,7 +132,7 @@ func (s *Service) AddingTgAccount(ctx *fiber.Ctx) error {
} }
} }
func (s *Service) SettingTgCode(ctx *fiber.Ctx) error { func (r *Telegram) SettingTgCode(ctx *fiber.Ctx) error {
var req struct { var req struct {
Code string `json:"code"` Code string `json:"code"`
Signature string `json:"signature"` Signature string `json:"signature"`
@ -127,7 +145,7 @@ func (s *Service) SettingTgCode(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusBadRequest).SendString("empty required fields") return ctx.Status(fiber.StatusBadRequest).SendString("empty required fields")
} }
data, ok := s.telegramClient.GetFromMap(req.Signature) data, ok := r.telegramClient.GetFromMap(req.Signature)
if !ok { if !ok {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid id, don't have data") return ctx.Status(fiber.StatusBadRequest).SendString("Invalid id, don't have data")
} }
@ -140,7 +158,7 @@ func (s *Service) SettingTgCode(ctx *fiber.Ctx) error {
fmt.Println("currnet state:", state) fmt.Println("currnet state:", state)
switch state.AuthorizationStateType() { switch state.AuthorizationStateType() {
case client.TypeAuthorizationStateReady: case client.TypeAuthorizationStateReady:
id, err := s.dal.TgRepo.CreateTgAccount(ctx.Context(), model.TgAccount{ id, err := r.dal.TgRepo.CreateTgAccount(ctx.Context(), model.TgAccount{
ApiID: data.PreviousReq.ApiID, ApiID: data.PreviousReq.ApiID,
ApiHash: data.PreviousReq.ApiHash, ApiHash: data.PreviousReq.ApiHash,
PhoneNumber: data.PreviousReq.PhoneNumber, PhoneNumber: data.PreviousReq.PhoneNumber,
@ -159,12 +177,12 @@ func (s *Service) SettingTgCode(ctx *fiber.Ctx) error {
} }
} }
func (s *Service) DeleteTgAccountByID(ctx *fiber.Ctx) error { func (r *Telegram) DeleteTgAccountByID(ctx *fiber.Ctx) error {
id, err := strconv.ParseInt(ctx.Params("id"), 10, 64) id, err := strconv.ParseInt(ctx.Params("id"), 10, 64)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("invalid id format") return ctx.Status(fiber.StatusBadRequest).SendString("invalid id format")
} }
err = s.dal.TgRepo.SoftDeleteTgAccount(ctx.Context(), id) err = r.dal.TgRepo.SoftDeleteTgAccount(ctx.Context(), id)
if err != nil { if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
} }

@ -1,13 +1,13 @@
package rpc_service package rpc_controllers
import ( import (
"context" "context"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/proto/notifyer" notifyer2 "penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/proto/notifyer"
) )
type MailNotify struct { type MailNotify struct {
notifyer.UnimplementedQuizServiceServer notifyer2.UnimplementedQuizServiceServer
dal *dal.DAL dal *dal.DAL
} }
@ -17,23 +17,23 @@ func NewMailNotify(dal *dal.DAL) *MailNotify {
} }
} }
func (m *MailNotify) GetQuizzes(ctx context.Context, in *notifyer.GetQuizzesRequest) (*notifyer.GetQuizzesResponse, error) { func (m *MailNotify) GetQuizzes(ctx context.Context, in *notifyer2.GetQuizzesRequest) (*notifyer2.GetQuizzesResponse, error) {
ids, err := m.dal.QuizRepo.GetAllQuizzesID(ctx, in.AccountId) ids, err := m.dal.QuizRepo.GetAllQuizzesID(ctx, in.AccountId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp := &notifyer.GetQuizzesResponse{ resp := &notifyer2.GetQuizzesResponse{
QuizIds: ids, QuizIds: ids,
} }
return resp, nil return resp, nil
} }
func (m *MailNotify) GetStartedQuizzes(ctx context.Context, in *notifyer.GetStartedQuizzesRequest) (*notifyer.GetStartedQuizzesResponse, error) { func (m *MailNotify) GetStartedQuizzes(ctx context.Context, in *notifyer2.GetStartedQuizzesRequest) (*notifyer2.GetStartedQuizzesResponse, error) {
ids, err := m.dal.QuizRepo.GetStartedQuizzesID(ctx, in.AccountId) ids, err := m.dal.QuizRepo.GetStartedQuizzesID(ctx, in.AccountId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp := &notifyer.GetStartedQuizzesResponse{ resp := &notifyer2.GetStartedQuizzesResponse{
QuizIds: ids, QuizIds: ids,
} }
return resp, nil return resp, nil

@ -0,0 +1,25 @@
package initialize
import (
"context"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/clients/auth"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/clients/telegram"
)
type Clients struct {
AuthClient *auth.AuthClient
TgClient *telegram.TelegramClient
}
func NewClients(ctx context.Context, cfg Config, pgDAL *dal.DAL) (*Clients, error) {
tgClient, err := telegram.NewTelegramClient(ctx, pgDAL)
if err != nil {
return nil, err
}
return &Clients{
TgClient: tgClient,
AuthClient: auth.NewAuthClient(cfg.AuthServiceURL),
}, nil
}

@ -0,0 +1,43 @@
package initialize
import (
"github.com/caarlos0/env/v8"
"github.com/joho/godotenv"
"log"
)
type Config struct {
LoggerProdMode bool `env:"IS_PROD_LOG" envDefault:"false"`
IsProd bool `env:"IS_PROD" envDefault:"false"`
NumberPort string `env:"PORT" envDefault:"1488"`
HttpHost string `env:"HTTP_HOST" envDefault:"0.0.0.0"`
CrtFile string `env:"CRT" envDefault:"server.crt"`
KeyFile string `env:"KEY" envDefault:"server.key"`
PostgresCredentials string `env:"PG_CRED" envDefault:"host=localhost port=35432 user=squiz password=Redalert2 dbname=squiz sslmode=disable"`
HubAdminUrl string `env:"HUB_ADMIN_URL" envDefault:"http://localhost:8001/"`
ServiceName string `env:"SERVICE_NAME" envDefault:"squiz"`
AuthServiceURL string `env:"AUTH_URL" envDefault:"http://localhost:8000/"`
GrpcHost string `env:"GRPC_HOST" envDefault:"localhost"`
GrpcPort string `env:"GRPC_PORT" envDefault:"9000"`
KafkaBrokers string `env:"KAFKA_BROKERS" envDefault:"localhost:9092"`
KafkaTopic string `env:"KAFKA_TOPIC" envDefault:"test-topic"`
KafkaGroup string `env:"KAFKA_GROUP" envDefault:"mailnotifier"`
TrashLogHost string `env:"TRASH_LOG_HOST" envDefault:"localhost:7113"`
ModuleLogger string `env:"MODULE_LOGGER" envDefault:"core-local"`
ClickHouseCred string `env:"CLICK_HOUSE_CRED" envDefault:"tcp://10.8.0.15:9000/default?sslmode=disable"`
RedisHost string `env:"REDIS_HOST" envDefault:"localhost:6379"`
RedisPassword string `env:"REDIS_PASSWORD" envDefault:"admin"`
RedisDB uint64 `env:"REDIS_DB" envDefault:"2"`
S3Prefix string `env:"S3_PREFIX"`
}
func LoadConfig() (*Config, error) {
if err := godotenv.Load(); err != nil {
log.Print("No .env file found")
}
var config Config
if err := env.Parse(&config); err != nil {
return nil, err
}
return &config, nil
}

@ -0,0 +1,73 @@
package initialize
import (
"github.com/go-redis/redis/v8"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/brokers"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/controllers/http_controllers/account"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/controllers/http_controllers/question"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/controllers/http_controllers/quiz"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/controllers/http_controllers/result"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/controllers/http_controllers/statistic"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/controllers/http_controllers/telegram"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/controllers/rpc_controllers"
)
type ControllerDeps struct {
Clients *Clients
DALs *DALs
Config Config
Producer *brokers.Producer
RedisClient *redis.Client
}
type Controller struct {
GRpcControllers GRpcControllers
HttpControllers HttpControllers
}
type GRpcControllers struct {
MailNotify *rpc_controllers.MailNotify
}
type HttpControllers struct {
Account *account.Account
Question *question.Question
Quiz *quiz.Quiz
Result *result.Result
Statistic *statistic.Statistic
Telegram *telegram.Telegram
}
func NewControllers(deps ControllerDeps) *Controller {
return &Controller{
GRpcControllers: GRpcControllers{
MailNotify: rpc_controllers.NewMailNotify(deps.DALs.PgDAL),
},
HttpControllers: HttpControllers{
Account: account.NewAccountController(account.Deps{
Dal: deps.DALs.PgDAL,
AuthClient: deps.Clients.AuthClient,
Producer: deps.Producer,
ServiceName: deps.Config.ServiceName,
RedisClient: deps.RedisClient,
}),
Question: question.NewQuestionController(question.Deps{
DAL: deps.DALs.PgDAL,
}),
Quiz: quiz.NewQuizController(quiz.Deps{
DAL: deps.DALs.PgDAL,
}),
Result: result.NewResultController(result.Deps{
DAL: deps.DALs.PgDAL,
S3Prefix: deps.Config.S3Prefix,
}),
Statistic: statistic.NewStatisticController(statistic.Deps{
DAL: deps.DALs.PgDAL,
ChDAL: deps.DALs.ChDAL,
}),
Telegram: telegram.NewTelegramController(telegram.Deps{
DAL: deps.DALs.PgDAL,
TelegramClient: deps.Clients.TgClient,
}),
},
}
}

@ -0,0 +1,28 @@
package initialize
import (
"context"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
)
type DALs struct {
PgDAL *dal.DAL
ChDAL *dal.ClickHouseDAL
}
func NewDALs(ctx context.Context, cfg Config) (*DALs, error) {
pgDal, err := dal.New(ctx, cfg.PostgresCredentials, nil)
if err != nil {
return nil, err
}
chDal, err := dal.NewClickHouseDAL(ctx, cfg.ClickHouseCred)
if err != nil {
return nil, err
}
return &DALs{
PgDAL: pgDal,
ChDAL: chDal,
}, nil
}

@ -0,0 +1,21 @@
package initialize
import (
"context"
"github.com/go-redis/redis/v8"
)
func Redis(ctx context.Context, cfg Config) (*redis.Client, error) {
rdb := redis.NewClient(&redis.Options{
Addr: cfg.RedisHost,
Password: cfg.RedisPassword,
DB: int(cfg.RedisDB),
})
status := rdb.Ping(ctx)
if err := status.Err(); err != nil {
return nil, err
}
return rdb, nil
}

@ -9,8 +9,8 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
"google.golang.org/grpc" "google.golang.org/grpc"
"net" "net"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/initialize" "penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/initialize"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/proto/notifyer" "penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/proto/notifyer"
"time" "time"
) )
@ -58,7 +58,7 @@ func (g *GRPC) Stop(_ context.Context) error {
return nil return nil
} }
func (g *GRPC) Register(reg *initialize.RpcRegister) *GRPC { func (g *GRPC) Register(reg initialize.GRpcControllers) *GRPC {
notifyer.RegisterQuizServiceServer(g.grpc, reg.MailNotify) notifyer.RegisterQuizServiceServer(g.grpc, reg.MailNotify)
// another // another
return g return g

@ -0,0 +1,73 @@
package http
import (
"context"
"fmt"
"github.com/gofiber/fiber/v2"
"github.com/themakers/hlog"
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/backend/penahub_common/log_mw"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/middleware"
)
type ServerConfig struct {
Logger *zap.Logger
Controllers []Controller
Hlogger hlog.Logger
}
type Server struct {
Logger *zap.Logger
Controllers []Controller
app *fiber.App
}
func NewServer(config ServerConfig) *Server {
app := fiber.New()
app.Use(middleware.JWTAuth())
app.Use(log_mw.ContextLogger(config.Hlogger))
//app.Get("/liveness", healthchecks.Liveness)
//app.Get("/readiness", healthchecks.Readiness(&workerErr)) //todo parametrized readiness. should discuss ready reason
s := &Server{
Logger: config.Logger,
Controllers: config.Controllers,
app: app,
}
s.registerRoutes()
return s
}
func (s *Server) Start(addr string) error {
if err := s.app.Listen(addr); err != nil {
s.Logger.Error("Failed to start server", zap.Error(err))
return err
}
return nil
}
func (s *Server) Shutdown(ctx context.Context) error {
return s.app.Shutdown()
}
func (s *Server) registerRoutes() {
for _, c := range s.Controllers {
router := s.app.Group(c.Name())
c.Register(router)
}
}
type Controller interface {
Register(router fiber.Router)
Name() string
}
func (s *Server) ListRoutes() {
fmt.Println("Registered routes:")
for _, stack := range s.app.Stack() {
for _, route := range stack {
fmt.Printf("%s %s\n", route.Method, route.Path)
}
}
}

@ -187,66 +187,6 @@ func handleImage(file *excelize.File, sheet, cell, content string, count, row in
fmt.Println(err.Error()) fmt.Println(err.Error())
} }
} }
count := 2
for _, q := range questions {
if !q.Deleted && q.Type != model.TypeResult {
index := binarySearch(response, q.Id)
if index != -1 {
cell := ToAlphaString(count) + strconv.Itoa(row)
tipe := FileSearch(response[index].Content)
noAccept := make(map[string]struct{})
todoMap := make(map[string]string)
if tipe != "Text" && q.Type == model.TypeImages || q.Type == model.TypeVarImages {
urle := ExtractImageURL(response[index].Content)
urlData := strings.Split(urle, " ")
if len(urlData) == 1 {
u, err := url.Parse(urle)
if err == nil && u.Scheme != "" && u.Host != "" {
picture, err := downloadImage(urle)
if err != nil {
fmt.Println(err.Error())
}
err = file.SetColWidth(sheet, ToAlphaString(count), ToAlphaString(count), 50)
if err != nil {
fmt.Println(err.Error())
}
err = file.SetRowHeight(sheet, row, 150)
if err != nil {
fmt.Println(err.Error())
}
if err := file.AddPictureFromBytes(sheet, cell, picture); err != nil {
fmt.Println(err.Error())
}
noAccept[response[index].Content] = struct{}{}
} else {
todoMap[response[index].Content] = cell
}
} else {
todoMap[response[index].Content] = cell
}
} else if tipe != "Text" && q.Type == model.TypeFile {
urle := ExtractImageURL(response[index].Content)
display, tooltip := urle, urle
if err := file.SetCellValue(sheet, cell, response[index].Content); err != nil {
fmt.Println(err.Error())
}
if err := file.SetCellHyperLink(sheet, cell, urle, "External", excelize.HyperlinkOpts{
Display: &display,
Tooltip: &tooltip,
}); err != nil {
fmt.Println(err.Error())
}
noAccept[response[index].Content] = struct{}{}
} else {
todoMap[response[index].Content] = cell
}
for cnt, cel := range todoMap {
if _, ok := noAccept[cnt]; !ok {
if err := file.SetCellValue(sheet, cel, cnt); err != nil {
fmt.Println(err.Error())
}
}
}
mediaRow := row mediaRow := row
for i, imgContent := range multiImgArr { for i, imgContent := range multiImgArr {
@ -356,7 +296,6 @@ func handleImage(file *excelize.File, sheet, cell, content string, count, row in
} }
} }
} }
func handleFile(file *excelize.File, sheet, cell, content, s3Prefix string, noAccept map[string]struct{}) { func handleFile(file *excelize.File, sheet, cell, content, s3Prefix string, noAccept map[string]struct{}) {
urle := content urle := content
if urle != "" && !strings.HasPrefix(urle, "https") { if urle != "" && !strings.HasPrefix(urle, "https") {

@ -7,7 +7,7 @@ import (
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/clients/telegram" "penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/clients/telegram"
"strconv" "strconv"
"time" "time"
) )

11
main.go

@ -1,11 +0,0 @@
package main
import (
"github.com/skeris/appInit"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/app"
_ "penahub.gitlab.yandexcloud.net/devops/linters/golang.git/pkg/dummy"
)
func main() {
appInit.Initialize(app.New, app.Options{})
}

37
pkg/closer/closer.go Normal file

@ -0,0 +1,37 @@
package closer
import (
"context"
)
type Closer interface {
Close(ctx context.Context) error
}
type CloserFunc func(ctx context.Context) error
func (cf CloserFunc) Close(ctx context.Context) error {
return cf(ctx)
}
type CloserGroup struct {
closers []Closer
}
func NewCloserGroup() *CloserGroup {
return &CloserGroup{}
}
func (cg *CloserGroup) Add(c Closer) {
cg.closers = append(cg.closers, c)
}
func (cg *CloserGroup) Call(ctx context.Context) error {
var closeErr error
for i := len(cg.closers) - 1; i >= 0; i-- {
if err := cg.closers[i].Close(ctx); err != nil && closeErr == nil {
closeErr = err
}
}
return closeErr
}

@ -1,24 +0,0 @@
-- Drop indexes
DROP INDEX IF EXISTS subquizes;
DROP INDEX IF EXISTS birthtime;
DROP INDEX IF EXISTS groups;
DROP INDEX IF EXISTS timeouted;
DROP INDEX IF EXISTS active ON quiz;
DROP INDEX IF EXISTS questiontype;
DROP INDEX IF EXISTS required;
DROP INDEX IF EXISTS relation;
DROP INDEX IF EXISTS active ON question;
-- Drop tables
DROP TABLE IF EXISTS privileges;
DROP TABLE IF EXISTS answer;
DROP TABLE IF EXISTS question;
DROP TABLE IF EXISTS quiz;
DROP TABLE IF EXISTS account;
-- Drop types
DO $$
BEGIN
DROP TYPE IF EXISTS question_type;
DROP TYPE IF EXISTS quiz_status;
END$$;

@ -1,120 +0,0 @@
-- Create types
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'question_type') THEN
CREATE TYPE question_type AS ENUM (
'variant',
'images',
'varimg',
'emoji',
'text',
'select',
'date',
'number',
'file',
'page',
'rating'
);
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'quiz_status') THEN
CREATE TYPE quiz_status AS ENUM (
'draft',
'template',
'stop',
'start',
'timeout',
'offlimit'
);
END IF;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
END$$;
-- Create tables
CREATE TABLE IF NOT EXISTS account (
id UUID PRIMARY KEY,
user_id VARCHAR(24),
email VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted BOOLEAN DEFAULT false
);
CREATE TABLE IF NOT EXISTS quiz (
id bigserial UNIQUE NOT NULL PRIMARY KEY,
qid uuid DEFAULT uuid_generate_v4(),
accountid varchar(30) NOT NULL,
deleted boolean DEFAULT false,
archived boolean DEFAULT false,
fingerprinting boolean DEFAULT false,
repeatable boolean DEFAULT false,
note_prevented boolean DEFAULT false,
mail_notifications boolean DEFAULT false,
unique_answers boolean DEFAULT false,
super boolean DEFAULT false,
group_id bigint DEFAULT 0,
name varchar(280),
description text,
config text,
status quiz_status DEFAULT 'draft',
limit_answers integer DEFAULT 0,
due_to integer DEFAULT 0,
time_of_passing integer DEFAULT 0,
pausable boolean DEFAULT false,
version smallint DEFAULT 0,
version_comment text DEFAULT '',
parent_ids integer[],
created_at timestamp DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp DEFAULT CURRENT_TIMESTAMP,
questions_count integer DEFAULT 0,
answers_count integer DEFAULT 0,
average_time_passing integer DEFAULT 0
);
CREATE TABLE IF NOT EXISTS question (
id bigserial UNIQUE NOT NULL PRIMARY KEY,
quiz_id bigint NOT NULL,
title varchar(512) NOT NULL,
description text,
questiontype question_type DEFAULT 'text',
required boolean DEFAULT false,
deleted boolean DEFAULT false,
page smallint DEFAULT 0,
content text,
version smallint DEFAULT 0,
parent_ids integer[],
created_at timestamp DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT quiz_relation FOREIGN KEY(quiz_id) REFERENCES quiz(id)
);
CREATE TABLE IF NOT EXISTS answer (
id bigserial UNIQUE NOT NULL PRIMARY KEY,
content text,
quiz_id bigint NOT NULL REFERENCES quiz(id),
question_id bigint NOT NULL REFERENCES question(id),
fingerprint varchar(1024),
session varchar(20),
created_at timestamp DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS privileges (
id SERIAL PRIMARY KEY,
privilegeID VARCHAR(50),
account_id UUID,
privilege_name VARCHAR(255),
amount INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (account_id) REFERENCES account (id)
);
-- Create indexes
CREATE INDEX IF NOT EXISTS active ON question(deleted) WHERE deleted=false;
CREATE INDEX IF NOT EXISTS relation ON question(quiz_id DESC);
CREATE INDEX IF NOT EXISTS required ON question(required DESC);
CREATE INDEX IF NOT EXISTS questiontype ON question(questiontype);
CREATE INDEX IF NOT EXISTS active ON quiz(deleted, archived, status) WHERE deleted = false AND archived = false AND status = 'start';
CREATE INDEX IF NOT EXISTS timeouted ON quiz(due_to DESC) WHERE deleted = false AND due_to <> 0 AND status <> 'timeout';
CREATE INDEX IF NOT EXISTS groups ON quiz(super) WHERE super = true;
CREATE INDEX IF NOT EXISTS birthtime ON quiz(created_at DESC);
CREATE INDEX IF NOT EXISTS subquizes ON quiz(group_id DESC) WHERE group_id <> 0;

@ -1 +0,0 @@
ALTER TABLE answer DROP COLUMN IF EXISTS result;

@ -1 +0,0 @@
ALTER TABLE answer ADD COLUMN result BOOLEAN DEFAULT FALSE;

@ -1 +0,0 @@
ALTER TABLE quiz DROP COLUMN IF EXISTS sessions_count;

@ -1 +0,0 @@
ALTER TABLE quiz ADD COLUMN sessions_count integer;

@ -1,2 +0,0 @@
ALTER TABLE quiz DROP COLUMN IF EXISTS new;
ALTER TABLE quiz DROP COLUMN IF EXISTS deleted;

@ -1,2 +0,0 @@
ALTER TABLE answer ADD COLUMN new BOOLEAN DEFAULT TRUE;
ALTER TABLE answer ADD COLUMN deleted BOOLEAN DEFAULT FALSE;

@ -1,2 +0,0 @@
ALTER TABLE answer DROP COLUMN IF EXISTS email;
DROP INDEX IF EXISTS answer_email_unique_idx;

@ -1,2 +0,0 @@
ALTER TABLE answer ADD COLUMN email VARCHAR(50) NOT NULL DEFAULT '';
CREATE UNIQUE INDEX IF NOT EXISTS answer_email_unique_idx ON answer (quiz_id, email) WHERE email <> '';

@ -1,6 +0,0 @@
ALTER TABLE answer
DROP COLUMN device_type,
DROP COLUMN device,
DROP COLUMN os,
DROP COLUMN browser,
DROP COLUMN ip;

@ -1,6 +0,0 @@
ALTER TABLE answer
ADD COLUMN device_type VARCHAR(50) NOT NULL DEFAULT '',
ADD COLUMN device VARCHAR(100) NOT NULL DEFAULT '',
ADD COLUMN os VARCHAR(100) NOT NULL DEFAULT '',
ADD COLUMN browser VARCHAR(100) NOT NULL DEFAULT '',
ADD COLUMN ip VARCHAR(50) NOT NULL DEFAULT '';

@ -1,2 +0,0 @@
ALTER TABLE answer
DROP COLUMN start;

@ -1,2 +0,0 @@
ALTER TABLE answer
ADD COLUMN start BOOLEAN NOT NULL DEFAULT FALSE;

@ -1,4 +0,0 @@
ALTER TABLE answer
ALTER COLUMN device TYPE VARCHAR(100),
ALTER COLUMN os TYPE VARCHAR(100),
ALTER COLUMN browser TYPE VARCHAR(100);

@ -1,4 +0,0 @@
ALTER TABLE answer
ALTER COLUMN device TYPE VARCHAR(1024),
ALTER COLUMN os TYPE VARCHAR(1024),
ALTER COLUMN browser TYPE VARCHAR(1024);

@ -1,2 +0,0 @@
ALTER TABLE quiz
ALTER COLUMN name TYPE VARCHAR(280);

@ -1,2 +0,0 @@
ALTER TABLE quiz
ALTER COLUMN name TYPE VARCHAR(1024);

@ -1,101 +0,0 @@
package service
import (
"github.com/go-redis/redis/v8"
"github.com/gofiber/fiber/v2"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/brokers"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/clients/auth"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/clients/telegram"
)
// Service is an entity for http requests handling
type Service struct {
dal *dal.DAL
authClient *auth.AuthClient
producer *brokers.Producer
serviceName string
chDAL *dal.ClickHouseDAL
telegramClient *telegram.TelegramClient
redisClient *redis.Client
s3Prefix string
}
type Deps struct {
Dal *dal.DAL
AuthClient *auth.AuthClient
Producer *brokers.Producer
ServiceName string
ChDAL *dal.ClickHouseDAL
TelegramClient *telegram.TelegramClient
RedisClient *redis.Client
S3Prefix string
}
func New(deps Deps) *Service {
return &Service{
dal: deps.Dal,
authClient: deps.AuthClient,
producer: deps.Producer,
serviceName: deps.ServiceName,
chDAL: deps.ChDAL,
telegramClient: deps.TelegramClient,
redisClient: deps.RedisClient,
s3Prefix: deps.S3Prefix,
}
}
// 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)
app.Post("/quiz/move", s.QuizMove)
app.Post("/quiz/template", s.TemplateCopy)
// 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)
app.Post("/account/manualdone", s.ManualDone)
app.Post("/account/leadtarget", s.PostLeadTarget)
app.Delete("/account/leadtarget/:id", s.DeleteLeadTarget)
app.Get("/account/leadtarget/:quizID", s.GetLeadTarget)
app.Put("/account/leadtarget", s.UpdateLeadTarget)
// 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)
// statistics handlers
app.Post("/statistic/:quizID/devices", s.GetDeviceStatistics)
app.Post("/statistic/:quizID/general", s.GetGeneralStatistics)
app.Post("/statistic/:quizID/questions", s.GetQuestionsStatistics)
app.Post("/statistic", s.AllServiceStatistics)
app.Get("/statistics/:quizID/pipelines", s.GetPipelinesStatistics)
//telegram handlers
app.Get("/telegram/pool", s.GetPoolTgAccounts)
app.Post("/telegram/create", s.AddingTgAccount)
app.Delete("/telegram/:id", s.DeleteTgAccountByID)
app.Post("/telegram/setCode", s.SettingTgCode)
}

@ -5,8 +5,8 @@ import (
"github.com/pioz/faker" "github.com/pioz/faker"
"go.uber.org/zap" "go.uber.org/zap"
"log" "log"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/brokers"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/initialize" "penahub.gitlab.yandexcloud.net/backend/quiz/core/initialize"
"penahub.gitlab.yandexcloud.net/backend/quiz/core/internal/brokers"
"testing" "testing"
"time" "time"
) )