294 lines
8.1 KiB
Go
294 lines
8.1 KiB
Go
package app
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"net/http"
|
||
"os"
|
||
"os/signal"
|
||
"syscall"
|
||
"time"
|
||
|
||
"bitbucket.org/skeris/bbfoundation/middleware"
|
||
"bitbucket.org/skeris/bbfoundation/role"
|
||
r "bitbucket.org/skeris/bbfoundation/router"
|
||
"bitbucket.org/skeris/treasurer/dal"
|
||
h "bitbucket.org/skeris/treasurer/handlers"
|
||
"bitbucket.org/skeris/treasurer/payway"
|
||
v "bitbucket.org/skeris/treasurer/version"
|
||
"github.com/danilsolovyov/validator"
|
||
"github.com/skeris/appInit"
|
||
"github.com/themakers/hlog"
|
||
"go.uber.org/zap"
|
||
tb "gopkg.in/tucnak/telebot.v2"
|
||
)
|
||
|
||
//#region ======== Application Structs & Functions ========
|
||
|
||
type Options struct {
|
||
IsProduction bool `env:"IS_PRODUCTION" default:"false"`
|
||
Development bool `env:"DEVELOPMENT" default:"true"`
|
||
AppName string `env:"APP_NAME" default:"treasurer"`
|
||
AppAddr string `env:"APP_ADDR" default:"localhost:3001"` //localhost:3000
|
||
AllowedOrigins string `env:"ALLOWED_ORIGINS" default:"*"`
|
||
AllowedHeaders string `env:"ALLOWED_HEADERS" default:"*"`
|
||
ExposeHeaders string `env:"EXPOSE_HEADERS" default:"*"`
|
||
MongoDbUri string `env:"DATABASE_URI" default:"mongodb+srv://user_1:cEDxC8ptLMMeoA5m@cluster0.aomle.mongodb.net/test"`
|
||
PayWayDbUri string `env:"PAYWAY_DB_URI" default:"mongodb+srv://user_2:vW3WNWViJaXYSraT@cluster0.aomle.mongodb.net/test"`
|
||
MongoDbTable string `env:"DATABASE_TABLE" default:"treasurer"`
|
||
MongoCollections string `env:"COLLECTION_NAME" default:"payment"`
|
||
}
|
||
|
||
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
|
||
}
|
||
|
||
//#endregion
|
||
|
||
//#region ======== Hl Info Structs ========
|
||
|
||
type InfoSvcStarted struct{}
|
||
type InfoInterruptSignal struct{}
|
||
type InfoTerminateSignal struct{}
|
||
type InfoSvcShuttingDown struct{}
|
||
type InfoSvcDone struct{}
|
||
|
||
//#endregion
|
||
|
||
//#region ======== Hl ErrorTypeApp Structs ========
|
||
|
||
type ErrorTypeApp struct {
|
||
Error error
|
||
}
|
||
type ErrorCanNotServe ErrorTypeApp
|
||
type ErrorSvcShuttingDown ErrorTypeApp
|
||
type ErrorZlogSync ErrorTypeApp
|
||
type ErrorConnectToMongo ErrorTypeApp
|
||
type ErrorDisconnectFromMongo ErrorTypeApp
|
||
type ErrorConnectToToRoles ErrorTypeApp
|
||
|
||
//#endregion
|
||
|
||
func New(ctx context.Context, options interface{}) (appInit.CommonApp, error) {
|
||
opts := options.(Options)
|
||
var errCh chan error
|
||
|
||
// Create logger
|
||
zlog, err := zap.NewDevelopment()
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
|
||
zlog = zlog.WithOptions(zap.AddCallerSkip(2))
|
||
hlogger := hlog.New(zlog)
|
||
hlogger = hlogger.With(v.GetVersion())
|
||
|
||
defer func() {
|
||
err = zlog.Sync()
|
||
if err != nil {
|
||
hlogger.Emit(ErrorZlogSync{err})
|
||
}
|
||
}()
|
||
|
||
// Connect to MongoDb "Treasurer"
|
||
connMongo, err := dal.CreateMongo(ctx, hlogger, dal.MongoDbOptions{
|
||
DalName: "MongoDB",
|
||
URI: opts.MongoDbUri,
|
||
DbTable: opts.MongoDbTable,
|
||
Collections: opts.MongoCollections,
|
||
})
|
||
|
||
if err != nil {
|
||
hlogger.Emit(ErrorConnectToMongo{err})
|
||
return App{zlog, errCh}, err
|
||
}
|
||
ctxL, cancL := context.WithCancel(ctx)
|
||
defer cancL()
|
||
|
||
go connMongo.ListenPayment(ctxL)
|
||
|
||
// Connect to MongoDb "PayWay"
|
||
connPayWay, err := dal.CreateMongo(ctx, hlogger, dal.MongoDbOptions{
|
||
DalName: "MongoPayWay",
|
||
URI: opts.PayWayDbUri,
|
||
DbTable: "payway",
|
||
Collections: "payway,paywayPayment",
|
||
})
|
||
|
||
if err != nil {
|
||
hlogger.Emit(ErrorConnectToMongo{err})
|
||
return App{zlog, errCh}, err
|
||
}
|
||
|
||
payWay, err := payway.NewPayWay(ctx, hlogger, connPayWay, connMongo)
|
||
if err != nil {
|
||
fmt.Println("PAYWAYERR", err)
|
||
//return nil, err
|
||
}
|
||
|
||
go connPayWay.ListenPayWay(ctx, func(data *dal.PayWay) error {
|
||
_, ok := payWay.Cache[data.ShortName]
|
||
if !ok {
|
||
errText := fmt.Sprintf("%v (%v) not found in cache", data.ID, data.ShortName)
|
||
return errors.New(errText)
|
||
}
|
||
|
||
payWay.Cache[data.ShortName].UpdateData(data)
|
||
|
||
return nil
|
||
})
|
||
|
||
// TODO: remove roles logic from project
|
||
// Start Roles
|
||
roles, err := role.NewRoles(ctx, hlogger, role.MongoDalOptions{
|
||
DalName: "MongoRoles",
|
||
URI: opts.MongoDbUri,
|
||
})
|
||
|
||
if err != nil {
|
||
hlogger.Emit(ErrorConnectToToRoles{err})
|
||
return App{zlog, errCh}, err
|
||
}
|
||
|
||
// Set routers
|
||
// TODO: remove
|
||
validators := h.CreateValidators(
|
||
&h.RequestCreatePayment{},
|
||
&h.RequestCreatePayout{},
|
||
)
|
||
|
||
chCommon := h.CommonOpts{
|
||
AllowedOrigins: opts.AllowedOrigins,
|
||
Mongo: connMongo,
|
||
Hl: hlogger,
|
||
}
|
||
// TODO: remove unnecessary with all usages
|
||
crucsNotifier, err := tb.NewBot(tb.Settings{
|
||
Token: "",
|
||
Verbose: false,
|
||
ParseMode: tb.ModeHTML,
|
||
Poller: &tb.LongPoller{
|
||
Timeout: time.Second,
|
||
},
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// TODO: use echo or gofiber. please remove this f*cking strange shit
|
||
// TODO: actualise api for invoice and available methods
|
||
// invoice используется для получения ссылки на оплату. по сути он выполняет 2 действия: складывает запись запроса в базу, чтобы хранились actions которые надо выполнить в ответ на события, и запросить у соответствующего платёжного сервиса правильную платёжную ссылку
|
||
// TODO: для каждого платёжного аггрегатора надо реализовать хендлер типа /listener/{paywayId}, где paywayId - айди аггрегатора
|
||
|
||
router, svcRouter, allowedRoles := r.NewCombineRouter("", r.Handlers{
|
||
"/listener/create/refill": {
|
||
Handle: func(w http.ResponseWriter, r *http.Request) {
|
||
ch := h.NewHandler(w, r, chCommon, validators["RequestCreatePayment"])
|
||
h.CreatePaymentHandler(ch, payWay, crucsNotifier)
|
||
},
|
||
},
|
||
"/listener/create/payout": {
|
||
Handle: func(w http.ResponseWriter, r *http.Request) {
|
||
ch := h.NewHandler(w, r, chCommon, validators["RequestCreatePayout"])
|
||
h.CreatePayoutHandler(ch, payWay)
|
||
},
|
||
}, // pub 0b922668de7e440f263d36bf91c506d3 sek 82afd8dad1db3785d4a94e539e142a39
|
||
"/listener/{payWay}": {
|
||
Handle: func(w http.ResponseWriter, r *http.Request) {
|
||
ch := h.NewHandler(w, r, chCommon, validator.Validator{})
|
||
h.PaymentListenerHandler(ch, payWay)
|
||
},
|
||
},
|
||
"/payoutListener/{payWay}": { //TODO: remove unnecessary
|
||
Handle: func(w http.ResponseWriter, r *http.Request) {
|
||
ch := h.NewHandler(w, r, chCommon, validator.Validator{})
|
||
h.PayoutListenerHandler(ch, payWay)
|
||
},
|
||
},
|
||
"/listener/getPayWays": {
|
||
Handle: func(w http.ResponseWriter, r *http.Request) {
|
||
ch := h.NewHandler(w, r, chCommon, validator.Validator{})
|
||
h.GetPayWays(ch, payWay)
|
||
},
|
||
},
|
||
})
|
||
|
||
|
||
// TODO: remove unnecessary
|
||
// Set Middlewares
|
||
mw := middleware.NewMiddleware(
|
||
opts.IsProduction,
|
||
hlogger,
|
||
roles,
|
||
opts.AllowedOrigins,
|
||
opts.AllowedHeaders,
|
||
allowedRoles,
|
||
opts.ExposeHeaders,
|
||
)
|
||
|
||
|
||
//TODO: remove unnecessary
|
||
router.Use(
|
||
mw.MiddlewareLogger,
|
||
mw.MiddlewareOriginAccess,
|
||
mw.MiddlewareRecovery,
|
||
)
|
||
|
||
// TODO: remove unnecessary
|
||
svcRouter.Use(
|
||
//mw.MiddlewareJwtCookie,
|
||
//mw.MiddlewareJwt,
|
||
//mw.MiddlewareGetJwt,
|
||
//mw.MiddlewareRoleAccess,
|
||
)
|
||
|
||
// Startup server
|
||
srv := &http.Server{Addr: opts.AppAddr, Handler: router}
|
||
|
||
go func() {
|
||
tmplKey := "%s.key"
|
||
tmplCrt := "%s.crt"
|
||
//if !options.LoggerDevMode {
|
||
tmplCrt = fmt.Sprintf(tmplCrt, "prod")
|
||
tmplKey = fmt.Sprintf(tmplKey, "prod")
|
||
|
||
if err := srv.ListenAndServeTLS(tmplCrt, tmplKey); err != http.ErrServerClosed && err != nil {
|
||
hlogger.Emit(ErrorCanNotServe{Error: err})
|
||
}
|
||
}()
|
||
hlogger.Emit(InfoSvcStarted{})
|
||
|
||
// Graceful Shutdown
|
||
interrupt := make(chan os.Signal, 1)
|
||
signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)
|
||
killSignal := <-interrupt
|
||
switch killSignal {
|
||
case os.Interrupt:
|
||
err = errors.New("interrupted")
|
||
hlogger.Emit(InfoInterruptSignal{})
|
||
case syscall.SIGTERM:
|
||
err = errors.New("terminated")
|
||
hlogger.Emit(InfoTerminateSignal{})
|
||
}
|
||
|
||
hlogger.Emit(InfoSvcShuttingDown{})
|
||
err = srv.Shutdown(ctx)
|
||
if err != nil {
|
||
hlogger.Emit(ErrorSvcShuttingDown{err})
|
||
}
|
||
hlogger.Emit(InfoSvcDone{})
|
||
|
||
err = errors.New("finished") // Костыль чтобы приложение нормально закрывалось...
|
||
return App{zlog, errCh}, err
|
||
}
|