package app import ( "context" "errors" "fmt" "gitea.pena/PenaSide/common/log_mw" "gitea.pena/PenaSide/hlog" "gitea.pena/PenaSide/trashlog/wrappers/zaptrashlog" "gitea.pena/SQuiz/answerer/clients" dalBS "gitea.pena/SQuiz/answerer/dal" "gitea.pena/SQuiz/answerer/models" "gitea.pena/SQuiz/answerer/savewc" "gitea.pena/SQuiz/answerer/service" "gitea.pena/SQuiz/common/dal" "gitea.pena/SQuiz/common/healthchecks" "gitea.pena/SQuiz/common/middleware" "gitea.pena/SQuiz/common/model" "gitea.pena/SQuiz/common/utils" "github.com/go-redis/redis/v8" "github.com/gofiber/fiber/v2" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" "github.com/skeris/appInit" "go.uber.org/zap" "go.uber.org/zap/zapcore" "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"` MinioEP string `env:"MINIO_EP" default:"localhost:3002"` MinioAK string `env:"MINIO_AK" default:"minio"` MinioSK string `env:"MINIO_SK" default:"miniostorage"` NumberPort string `env:"PORT" default:"1490"` CrtFile string `env:"CRT" default:"server.crt"` KeyFile string `env:"KEY" default:"server.key"` PostgresCredentials string `env:"PG_CRED" default:"host=localhost port=5432 user=squiz password=Redalert2 dbname=squiz sslmode=disable"` RedisHost string `env:"REDIS_HOST"` RedisPassword string `env:"REDIS_PASSWORD"` RedisDB uint64 `env:"REDIS_DB"` RedirectURL string `env:"REDIRECT_URL" default:"https://squiz.pena.digital"` PubKey string `env:"PUBLIC_KEY"` PrivKey string `env:"PRIVATE_KEY"` TrashLogHost string `env:"TRASH_LOG_HOST" default:"localhost:7113"` ModuleLogger string `env:"MODULE_LOGGER" default:"answerer-local"` } 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{}) // Initialize minio client object. minioClient, err := minio.New(options.MinioEP, &minio.Options{ Creds: credentials.NewStaticV4(options.MinioAK, options.MinioSK, ""), Secure: options.IsProd, }) if err != nil { fmt.Println("MINIOERR", options.MinioEP, err) return nil, err } pgdal, err := dal.New(ctx, options.PostgresCredentials, minioClient) if err != nil { return nil, err } zapLogger.Info("config", zap.Any("options", options)) //init redis redisClient := redis.NewClient(&redis.Options{ Addr: options.RedisHost, Password: options.RedisPassword, DB: int(options.RedisDB), }) encrypt := utils.NewEncrypt(options.PubKey, options.PrivKey) workerSendClientCh := make(chan model.Answer, 50) workerRespondentCh := make(chan []model.Answer, 50) blobstore, err := dalBS.New(ctx, minioClient) if err != nil { return nil, err } //svc := service.New(blobstore, pgdal, workerRespondentCh, workerSendClientCh) svc := service.New(service.ServiceDeps{ Store: blobstore, Dal: pgdal, WorkerSendClientCh: workerSendClientCh, WorkerRespondentCh: workerRespondentCh, Encrypt: encrypt, RedirectURl: options.RedirectURL, AiClient: clients.NewAiClient("https://alvatar.com/api/engine/send_answer"), }) saveRespWcData := savewc.DepsForResp{ WorkerRespondentCh: workerRespondentCh, Redis: redisClient, } saveClientWcData := savewc.DepsForClient{ WorkerSendClientCh: workerSendClientCh, Redis: redisClient, } saveRespWorker := savewc.NewSaveRespWorker(saveRespWcData, errChan, loggerHlog) saveClientWorker := savewc.NewSaveClientWorker(saveClientWcData, errChan, loggerHlog) go saveRespWorker.Start(ctx) go saveClientWorker.Start(ctx) app := fiber.New(fiber.Config{BodyLimit: 70 * 1024 * 1024}) app.Use(func(c *fiber.Ctx) error { defer func() { if err := recover(); err != nil { c.Status(fiber.StatusInternalServerError).SendString("internal server error") } }() return c.Next() }) app.Use(middleware.AnswererChain()) app.Use(log_mw.ContextLogger(loggerHlog)) app.Get("/liveness", healthchecks.Liveness) app.Get("/readiness", healthchecks.Readiness(&workerErr)) //todo parametrized readiness. should discuss ready reason app = svc.Register(app) fmt.Println("SERVERSTART", fmt.Sprintf(":%s", options.NumberPort)) loggerHlog.Emit(InfoSvcReady{}) go func() { defer func() { //if pgdal != nil { // pgdal.CloseAnswerer() //} err := app.Shutdown() if err != nil { 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 }