201 lines
5.1 KiB
Go
201 lines
5.1 KiB
Go
package app
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"errors"
|
||
"fmt"
|
||
"gitea.pena/PenaSide/trashlog/dal/bbolt"
|
||
_ "github.com/ClickHouse/clickhouse-go"
|
||
"github.com/caarlos0/env/v8"
|
||
bolt "go.etcd.io/bbolt"
|
||
tb "gopkg.in/tucnak/telebot.v2"
|
||
"log"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
func NewOptions() *Options {
|
||
opts := &Options{}
|
||
if err := env.Parse(opts); err != nil {
|
||
log.Fatalf("failed to parse environment variables: %v", err)
|
||
}
|
||
return opts
|
||
}
|
||
|
||
func (o *Options) Validate() error {
|
||
var err error
|
||
|
||
if o.ClickhouseTableMainName == "" {
|
||
err = errors.New("invalid value for DBClickHouseName, some value is empty")
|
||
return err
|
||
}
|
||
|
||
if err = o.validateAddr(); err != nil {
|
||
return err
|
||
}
|
||
|
||
if err = o.validateClickhouseCred(); err != nil {
|
||
return err
|
||
}
|
||
|
||
if err = o.validateDBBolt(); err != nil {
|
||
return err
|
||
}
|
||
|
||
if err = o.validateTgToken(); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (o *Options) validateAddr() error {
|
||
if !strings.HasPrefix(o.GrpcURL, ":") {
|
||
return errors.New("GRPC_URL must start with ':'")
|
||
}
|
||
|
||
if o.AdminHttpURL == "" {
|
||
return errors.New("ADMIN_HTTP_URL not be empty")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (o *Options) validateClickhouseCred() error {
|
||
var err error
|
||
if o.ClickhouseURL == "" {
|
||
err = errors.New("CLICKHOUSE_URL is empty")
|
||
return err
|
||
} else {
|
||
connect, openErr := sql.Open(
|
||
"clickhouse",
|
||
o.ClickhouseURL,
|
||
)
|
||
if openErr != nil {
|
||
err = fmt.Errorf("clickhouse connection error: %s", openErr.Error())
|
||
return err
|
||
}
|
||
|
||
if pingErr := connect.PingContext(context.TODO()); pingErr != nil {
|
||
err = fmt.Errorf("clickhouse ping error: %s", pingErr.Error())
|
||
return err
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (o *Options) validateDBBolt() error {
|
||
dir := filepath.Dir(o.DBPath)
|
||
|
||
tmpFile, err := os.CreateTemp(dir, ".tmp-validate")
|
||
if err != nil {
|
||
return fmt.Errorf("unable to create temporary file in directory '%s': %w", dir, err)
|
||
}
|
||
|
||
err = func(name string) error {
|
||
err := tmpFile.Close()
|
||
if err != nil {
|
||
return fmt.Errorf("unable to close temporary file in directory '%s': %v", dir, err)
|
||
}
|
||
err = os.Remove(name)
|
||
if err != nil {
|
||
return fmt.Errorf("error removing temporary file in directory '%s': %v", name, err)
|
||
}
|
||
return nil
|
||
}(tmpFile.Name())
|
||
|
||
exs, dbFileErr := exists(o.DBPath)
|
||
if dbFileErr != nil {
|
||
err = fmt.Errorf("error checking DBPath: %v", dbFileErr)
|
||
return err
|
||
} else if !exs {
|
||
// если файла базы данных нет то создаем
|
||
db, createFileErr := bolt.Open(o.DBPath, 0664, &bolt.Options{
|
||
Timeout: 5 * time.Second, // устанавливает максимальное время ожидания для доступа к базе данных
|
||
NoGrowSync: false, // не будет увеличивать размер файла синхронно при записи
|
||
FreelistType: bolt.FreelistArrayType, // для повышения производительности бд
|
||
})
|
||
if createFileErr != nil {
|
||
err = fmt.Errorf("err create file at path '%s': %v", o.DBPath, createFileErr)
|
||
return err
|
||
}
|
||
|
||
// проверяем что файл действительно появился
|
||
exs, dbFileErr = exists(o.DBPath)
|
||
if dbFileErr != nil {
|
||
err = fmt.Errorf("error checking DBPath after crreate bbolt db: %v", dbFileErr)
|
||
return err
|
||
}
|
||
|
||
// если нет, то возвращаем ошибку
|
||
if !exs {
|
||
err = errors.New("error checking DBPath after create bbolt db")
|
||
return err
|
||
}
|
||
|
||
// создаем бакет как элемент проверки того что писать и изменять мы можем файл
|
||
createBucketErr := db.Update(func(tx *bolt.Tx) error {
|
||
_, createBucketErr := tx.CreateBucketIfNotExists([]byte(bbolt.BucketName))
|
||
if createBucketErr != nil {
|
||
return createBucketErr
|
||
}
|
||
return nil
|
||
})
|
||
if createBucketErr != nil {
|
||
err = fmt.Errorf("error create bucket in bbolt db: %v", createBucketErr.Error())
|
||
return err
|
||
}
|
||
|
||
closeErr := db.Close()
|
||
if closeErr != nil {
|
||
err = fmt.Errorf("error closing DB connection: %v", closeErr)
|
||
return err
|
||
}
|
||
// удаляем после, чтобы главный процесс сам его создавал
|
||
removeFileErr := os.Remove(o.DBPath)
|
||
if removeFileErr != nil {
|
||
err = fmt.Errorf("err remove file at path '%s': %v", o.DBPath, removeFileErr)
|
||
return err
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (o *Options) validateTgToken() error {
|
||
var err error
|
||
if strings.TrimSpace(o.TelegramToken) == "" || o.TelegramChannelID == 0 {
|
||
err = errors.New("TELEGRAM_TOKEN or TELEGRAM_CHANNEL_ID empty")
|
||
return err
|
||
}
|
||
|
||
bot, newBotErr := tb.NewBot(tb.Settings{
|
||
Token: o.TelegramToken,
|
||
})
|
||
if newBotErr != nil {
|
||
return fmt.Errorf("failed create telegram bot: %w", newBotErr)
|
||
}
|
||
|
||
if _, sendErr := bot.Send(&tb.Chat{ID: int64(o.TelegramChannelID) * -1}, "PING", tb.ModeMarkdown); sendErr != nil {
|
||
err = errors.New(fmt.Sprintf("failed send telegram message: %v", sendErr.Error()))
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func exists(path string) (bool, error) {
|
||
_, err := os.Stat(path)
|
||
if err == nil {
|
||
return true, nil
|
||
}
|
||
if os.IsNotExist(err) {
|
||
return false, nil
|
||
}
|
||
return false, err
|
||
}
|