mirror of
https://github.com/tgdrive/teldrive.git
synced 2025-09-03 21:14:28 +08:00
refactor: replace kv usage with boltdb and update related configurations
This commit is contained in:
parent
e966119bd4
commit
518435fcd3
26 changed files with 525 additions and 730 deletions
270
cmd/run.go
270
cmd/run.go
|
@ -5,24 +5,15 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
chimiddleware "github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/go-co-op/gocron"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/tgdrive/teldrive/internal/api"
|
||||
"github.com/tgdrive/teldrive/internal/appcontext"
|
||||
"github.com/tgdrive/teldrive/internal/auth"
|
||||
|
@ -31,12 +22,11 @@ import (
|
|||
"github.com/tgdrive/teldrive/internal/config"
|
||||
"github.com/tgdrive/teldrive/internal/database"
|
||||
"github.com/tgdrive/teldrive/internal/duration"
|
||||
"github.com/tgdrive/teldrive/internal/kv"
|
||||
"github.com/tgdrive/teldrive/internal/logging"
|
||||
"github.com/tgdrive/teldrive/internal/middleware"
|
||||
"github.com/tgdrive/teldrive/internal/tgc"
|
||||
"github.com/tgdrive/teldrive/internal/utils"
|
||||
"github.com/tgdrive/teldrive/ui"
|
||||
"go.etcd.io/bbolt"
|
||||
|
||||
"github.com/tgdrive/teldrive/pkg/cron"
|
||||
"github.com/tgdrive/teldrive/pkg/services"
|
||||
|
@ -46,85 +36,95 @@ import (
|
|||
)
|
||||
|
||||
func NewRun() *cobra.Command {
|
||||
config := config.Config{}
|
||||
runCmd := &cobra.Command{
|
||||
var cfg config.ServerCmdConfig
|
||||
cmd := &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "Start Teldrive Server",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runApplication(&config)
|
||||
runApplication(cmd.Context(), &cfg)
|
||||
|
||||
},
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return initViperConfig(cmd)
|
||||
loader := config.NewConfigLoader()
|
||||
if err := loader.InitializeConfig(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := loader.Load(&cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkRequiredRunFlags(&cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
runCmd.Flags().StringP("config", "c", "", "Config file path (default $HOME/.teldrive/config.toml)")
|
||||
runCmd.Flags().IntVarP(&config.Server.Port, "server-port", "p", 8080, "Server port")
|
||||
duration.DurationVar(runCmd.Flags(), &config.Server.GracefulShutdown, "server-graceful-shutdown", 10*time.Second, "Server graceful shutdown timeout")
|
||||
runCmd.Flags().BoolVar(&config.Server.EnablePprof, "server-enable-pprof", false, "Enable Pprof Profiling")
|
||||
duration.DurationVar(runCmd.Flags(), &config.Server.ReadTimeout, "server-read-timeout", 1*time.Hour, "Server read timeout")
|
||||
duration.DurationVar(runCmd.Flags(), &config.Server.WriteTimeout, "server-write-timeout", 1*time.Hour, "Server write timeout")
|
||||
|
||||
runCmd.Flags().BoolVar(&config.CronJobs.Enable, "cronjobs-enable", true, "Run cron jobs")
|
||||
duration.DurationVar(runCmd.Flags(), &config.CronJobs.CleanFilesInterval, "cronjobs-clean-files-interval", 1*time.Hour, "Clean files interval")
|
||||
duration.DurationVar(runCmd.Flags(), &config.CronJobs.CleanUploadsInterval, "cronjobs-clean-uploads-interval", 12*time.Hour, "Clean uploads interval")
|
||||
duration.DurationVar(runCmd.Flags(), &config.CronJobs.FolderSizeInterval, "cronjobs-folder-size-interval", 2*time.Hour, "Folder size update interval")
|
||||
|
||||
runCmd.Flags().IntVar(&config.Cache.MaxSize, "cache-max-size", 10*1024*1024, "Max Cache max size (memory)")
|
||||
runCmd.Flags().StringVar(&config.Cache.RedisAddr, "cache-redis-addr", "", "Redis address")
|
||||
runCmd.Flags().StringVar(&config.Cache.RedisPass, "cache-redis-pass", "", "Redis password")
|
||||
|
||||
runCmd.Flags().IntVarP(&config.Log.Level, "log-level", "", -1, "Logging level")
|
||||
runCmd.Flags().StringVar(&config.Log.File, "log-file", "", "Logging file path")
|
||||
runCmd.Flags().BoolVar(&config.Log.Development, "log-development", false, "Enable development mode")
|
||||
|
||||
runCmd.Flags().StringVar(&config.JWT.Secret, "jwt-secret", "", "JWT secret key")
|
||||
duration.DurationVar(runCmd.Flags(), &config.JWT.SessionTime, "jwt-session-time", (30*24)*time.Hour, "JWT session duration")
|
||||
runCmd.Flags().StringSliceVar(&config.JWT.AllowedUsers, "jwt-allowed-users", []string{}, "Allowed users")
|
||||
|
||||
runCmd.Flags().StringVar(&config.DB.DataSource, "db-data-source", "", "Database connection string")
|
||||
runCmd.Flags().IntVar(&config.DB.LogLevel, "db-log-level", 1, "Database log level")
|
||||
runCmd.Flags().BoolVar(&config.DB.PrepareStmt, "db-prepare-stmt", true, "Enable prepared statements")
|
||||
runCmd.Flags().BoolVar(&config.DB.Pool.Enable, "db-pool-enable", true, "Enable database pool")
|
||||
runCmd.Flags().IntVar(&config.DB.Pool.MaxIdleConnections, "db-pool-max-open-connections", 25, "Database max open connections")
|
||||
runCmd.Flags().IntVar(&config.DB.Pool.MaxIdleConnections, "db-pool-max-idle-connections", 25, "Database max idle connections")
|
||||
duration.DurationVar(runCmd.Flags(), &config.DB.Pool.MaxLifetime, "db-pool-max-lifetime", 10*time.Minute, "Database max connection lifetime")
|
||||
|
||||
runCmd.Flags().IntVar(&config.TG.AppId, "tg-app-id", 0, "Telegram app ID")
|
||||
runCmd.Flags().StringVar(&config.TG.AppHash, "tg-app-hash", "", "Telegram app hash")
|
||||
runCmd.Flags().StringVar(&config.TG.SessionFile, "tg-session-file", "", "Bot session file path")
|
||||
runCmd.Flags().BoolVar(&config.TG.RateLimit, "tg-rate-limit", true, "Enable rate limiting for telegram client")
|
||||
runCmd.Flags().IntVar(&config.TG.RateBurst, "tg-rate-burst", 5, "Limiting burst for telegram client")
|
||||
runCmd.Flags().IntVar(&config.TG.Rate, "tg-rate", 100, "Limiting rate for telegram client")
|
||||
runCmd.Flags().StringVar(&config.TG.DeviceModel, "tg-device-model",
|
||||
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/116.0", "Device model")
|
||||
runCmd.Flags().StringVar(&config.TG.SystemVersion, "tg-system-version", "Win32", "System version")
|
||||
runCmd.Flags().StringVar(&config.TG.AppVersion, "tg-app-version", "4.6.3 K", "App version")
|
||||
runCmd.Flags().StringVar(&config.TG.LangCode, "tg-lang-code", "en", "Language code")
|
||||
runCmd.Flags().StringVar(&config.TG.SystemLangCode, "tg-system-lang-code", "en-US", "System language code")
|
||||
runCmd.Flags().StringVar(&config.TG.LangPack, "tg-lang-pack", "webk", "Language pack")
|
||||
runCmd.Flags().StringVar(&config.TG.Proxy, "tg-proxy", "", "HTTP OR SOCKS5 proxy URL")
|
||||
runCmd.Flags().BoolVar(&config.TG.DisableStreamBots, "tg-disable-stream-bots", false, "Disable Stream bots")
|
||||
runCmd.Flags().BoolVar(&config.TG.Ntp, "tg-ntp", false, "Use NTP server time")
|
||||
runCmd.Flags().BoolVar(&config.TG.EnableLogging, "tg-enable-logging", false, "Enable telegram client logging")
|
||||
runCmd.Flags().StringVar(&config.TG.Uploads.EncryptionKey, "tg-uploads-encryption-key", "", "Uploads encryption key")
|
||||
runCmd.Flags().IntVar(&config.TG.Uploads.Threads, "tg-uploads-threads", 8, "Uploads threads")
|
||||
runCmd.Flags().IntVar(&config.TG.Uploads.MaxRetries, "tg-uploads-max-retries", 10, "Uploads Retries")
|
||||
runCmd.Flags().Int64Var(&config.TG.PoolSize, "tg-pool-size", 8, "Telegram Session pool size")
|
||||
duration.DurationVar(runCmd.Flags(), &config.TG.ReconnectTimeout, "tg-reconnect-timeout", 5*time.Minute, "Reconnect Timeout")
|
||||
duration.DurationVar(runCmd.Flags(), &config.TG.Uploads.Retention, "tg-uploads-retention", (24*7)*time.Hour, "Uploads retention duration")
|
||||
duration.DurationVar(runCmd.Flags(), &config.TG.BgBotsCheckInterval, "tg-bg-bots-check-interval", 4*time.Hour, "Interval for checking Idle background bots")
|
||||
runCmd.Flags().IntVar(&config.TG.Stream.MultiThreads, "tg-stream-multi-threads", 0, "Stream multi-threads")
|
||||
runCmd.Flags().IntVar(&config.TG.Stream.Buffers, "tg-stream-buffers", 8, "No of Stream buffers")
|
||||
duration.DurationVar(runCmd.Flags(), &config.TG.Stream.ChunkTimeout, "tg-stream-chunk-timeout", 20*time.Second, "Chunk Fetch Timeout")
|
||||
runCmd.MarkFlagRequired("tg-app-id")
|
||||
runCmd.MarkFlagRequired("tg-app-hash")
|
||||
runCmd.MarkFlagRequired("db-data-source")
|
||||
runCmd.MarkFlagRequired("jwt-secret")
|
||||
|
||||
return runCmd
|
||||
addServerFlags(cmd, &cfg)
|
||||
return cmd
|
||||
}
|
||||
func addServerFlags(cmd *cobra.Command, cfg *config.ServerCmdConfig) {
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
config.AddCommonFlags(flags, cfg)
|
||||
|
||||
// Server config
|
||||
flags.IntVarP(&cfg.Server.Port, "server-port", "p", 8080, "Server port")
|
||||
duration.DurationVar(flags, &cfg.Server.GracefulShutdown, "server-graceful-shutdown", 10*time.Second, "Server graceful shutdown timeout")
|
||||
flags.BoolVar(&cfg.Server.EnablePprof, "server-enable-pprof", false, "Enable Pprof Profiling")
|
||||
duration.DurationVar(flags, &cfg.Server.ReadTimeout, "server-read-timeout", 1*time.Hour, "Server read timeout")
|
||||
duration.DurationVar(flags, &cfg.Server.WriteTimeout, "server-write-timeout", 1*time.Hour, "Server write timeout")
|
||||
|
||||
// CronJobs config
|
||||
flags.BoolVar(&cfg.CronJobs.Enable, "cronjobs-enable", true, "Run cron jobs")
|
||||
duration.DurationVar(flags, &cfg.CronJobs.CleanFilesInterval, "cronjobs-clean-files-interval", 1*time.Hour, "Clean files interval")
|
||||
duration.DurationVar(flags, &cfg.CronJobs.CleanUploadsInterval, "cronjobs-clean-uploads-interval", 12*time.Hour, "Clean uploads interval")
|
||||
duration.DurationVar(flags, &cfg.CronJobs.FolderSizeInterval, "cronjobs-folder-size-interval", 2*time.Hour, "Folder size update interval")
|
||||
|
||||
// Cache config
|
||||
flags.IntVar(&cfg.Cache.MaxSize, "cache-max-size", 10*1024*1024, "Max Cache max size (memory)")
|
||||
flags.StringVar(&cfg.Cache.RedisAddr, "cache-redis-addr", "", "Redis address")
|
||||
flags.StringVar(&cfg.Cache.RedisPass, "cache-redis-pass", "", "Redis password")
|
||||
|
||||
// JWT config
|
||||
flags.StringVar(&cfg.JWT.Secret, "jwt-secret", "", "JWT secret key")
|
||||
duration.DurationVar(flags, &cfg.JWT.SessionTime, "jwt-session-time", (30*24)*time.Hour, "JWT session duration")
|
||||
flags.StringSliceVar(&cfg.JWT.AllowedUsers, "jwt-allowed-users", []string{}, "Allowed users")
|
||||
|
||||
// Telegram Uploads config
|
||||
flags.StringVar(&cfg.TG.Uploads.EncryptionKey, "tg-uploads-encryption-key", "", "Uploads encryption key")
|
||||
flags.IntVar(&cfg.TG.Uploads.Threads, "tg-uploads-threads", 8, "Uploads threads")
|
||||
flags.IntVar(&cfg.TG.Uploads.MaxRetries, "tg-uploads-max-retries", 10, "Uploads Retries")
|
||||
duration.DurationVar(flags, &cfg.TG.ReconnectTimeout, "tg-reconnect-timeout", 5*time.Minute, "Reconnect Timeout")
|
||||
duration.DurationVar(flags, &cfg.TG.Uploads.Retention, "tg-uploads-retention", (24*7)*time.Hour, "Uploads retention duration")
|
||||
flags.IntVar(&cfg.TG.Stream.MultiThreads, "tg-stream-multi-threads", 0, "Stream multi-threads")
|
||||
flags.IntVar(&cfg.TG.Stream.Buffers, "tg-stream-buffers", 8, "No of Stream buffers")
|
||||
duration.DurationVar(flags, &cfg.TG.Stream.ChunkTimeout, "tg-stream-chunk-timeout", 20*time.Second, "Chunk Fetch Timeout")
|
||||
|
||||
}
|
||||
|
||||
func checkRequiredRunFlags(cfg *config.ServerCmdConfig) error {
|
||||
var missingFields []string
|
||||
|
||||
if cfg.DB.DataSource == "" {
|
||||
missingFields = append(missingFields, "db-data-source")
|
||||
}
|
||||
if cfg.JWT.Secret == "" {
|
||||
missingFields = append(missingFields, "jwt-secret")
|
||||
}
|
||||
if cfg.TG.AppHash == "" {
|
||||
missingFields = append(missingFields, "tg-app-hash")
|
||||
}
|
||||
if cfg.TG.AppId == 0 {
|
||||
missingFields = append(missingFields, "tg-app-id")
|
||||
}
|
||||
|
||||
if len(missingFields) > 0 {
|
||||
return fmt.Errorf("required configuration values not set: %s", strings.Join(missingFields, ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func findAvailablePort(startPort int) (int, error) {
|
||||
for port := startPort; port < startPort+100; port++ {
|
||||
addr := fmt.Sprintf(":%d", port)
|
||||
|
@ -138,21 +138,16 @@ func findAvailablePort(startPort int) (int, error) {
|
|||
return 0, fmt.Errorf("no available ports found between %d and %d", startPort, startPort+100)
|
||||
}
|
||||
|
||||
func runApplication(conf *config.Config) {
|
||||
func runApplication(ctx context.Context, conf *config.ServerCmdConfig) {
|
||||
logging.SetConfig(&logging.Config{
|
||||
Level: zapcore.Level(conf.Log.Level),
|
||||
Development: conf.Log.Development,
|
||||
FilePath: conf.Log.File,
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
lg := logging.DefaultLogger().Sugar()
|
||||
|
||||
defer func() {
|
||||
logging.DefaultLogger().Sync()
|
||||
cancel()
|
||||
}()
|
||||
defer lg.Sync()
|
||||
|
||||
port, err := findAvailablePort(conf.Server.Port)
|
||||
if err != nil {
|
||||
|
@ -165,23 +160,29 @@ func runApplication(conf *config.Config) {
|
|||
|
||||
scheduler := gocron.NewScheduler(time.UTC)
|
||||
|
||||
cacher := cache.NewCache(ctx, conf)
|
||||
cacher := cache.NewCache(ctx, &conf.Cache)
|
||||
|
||||
db, err := database.NewDatabase(conf, lg)
|
||||
db, err := database.NewDatabase(&conf.DB, lg)
|
||||
|
||||
if err != nil {
|
||||
lg.Fatalw("failed to create database", "err", err)
|
||||
}
|
||||
|
||||
kv := kv.NewBoltKV(conf)
|
||||
err = database.MigrateDB(db)
|
||||
|
||||
if err != nil {
|
||||
lg.Fatalw("failed to migrate database", "err", err)
|
||||
}
|
||||
|
||||
boltDb, err := tgc.NewBoltDB(conf.TG.SessionFile)
|
||||
|
||||
if err != nil {
|
||||
lg.Fatalw("failed to create bolt db", "err", err)
|
||||
}
|
||||
|
||||
worker := tgc.NewBotWorker()
|
||||
|
||||
srv := setupServer(conf, db, cacher, kv, worker)
|
||||
|
||||
stop := make(chan os.Signal, 1)
|
||||
|
||||
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
|
||||
srv := setupServer(conf, db, cacher, boltDb, worker)
|
||||
|
||||
cron.StartCronJobs(scheduler, db, conf)
|
||||
|
||||
|
@ -192,7 +193,7 @@ func runApplication(conf *config.Config) {
|
|||
}
|
||||
}()
|
||||
|
||||
<-stop
|
||||
<-ctx.Done()
|
||||
|
||||
lg.Info("Shutting down server...")
|
||||
|
||||
|
@ -209,13 +210,13 @@ func runApplication(conf *config.Config) {
|
|||
lg.Info("Server stopped")
|
||||
}
|
||||
|
||||
func setupServer(cfg *config.Config, db *gorm.DB, cache cache.Cacher, kv kv.KV, worker *tgc.BotWorker) *http.Server {
|
||||
func setupServer(cfg *config.ServerCmdConfig, db *gorm.DB, cache cache.Cacher, boltdb *bbolt.DB, worker *tgc.BotWorker) *http.Server {
|
||||
|
||||
lg := logging.DefaultLogger()
|
||||
|
||||
apiSrv := services.NewApiService(db, cfg, cache, kv, worker)
|
||||
apiSrv := services.NewApiService(db, cfg, cache, boltdb, worker)
|
||||
|
||||
srv, err := api.NewServer(apiSrv, auth.NewSecurityHandler(db, cache, cfg))
|
||||
srv, err := api.NewServer(apiSrv, auth.NewSecurityHandler(db, cache, &cfg.JWT))
|
||||
|
||||
if err != nil {
|
||||
lg.Fatal("failed to create server", zap.Error(err))
|
||||
|
@ -254,72 +255,3 @@ func setupServer(cfg *config.Config, db *gorm.DB, cache cache.Cacher, kv kv.KV,
|
|||
IdleTimeout: 60 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
func initViperConfig(cmd *cobra.Command) error {
|
||||
|
||||
viper.SetConfigType("toml")
|
||||
|
||||
cfgFile := cmd.Flags().Lookup("config").Value.String()
|
||||
|
||||
if cfgFile != "" {
|
||||
viper.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
home, _ := homedir.Dir()
|
||||
viper.AddConfigPath(filepath.Join(home, ".teldrive"))
|
||||
viper.AddConfigPath(".")
|
||||
viper.AddConfigPath(utils.ExecutableDir())
|
||||
viper.SetConfigName("config")
|
||||
}
|
||||
|
||||
viper.SetEnvPrefix("teldrive")
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
viper.AutomaticEnv()
|
||||
viper.ReadInConfig()
|
||||
bindFlags(cmd.Flags(), "", reflect.ValueOf(config.Config{}))
|
||||
return nil
|
||||
|
||||
}
|
||||
func bindFlags(flags *pflag.FlagSet, prefix string, v reflect.Value) {
|
||||
t := v.Type()
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
for i := range t.NumField() {
|
||||
field := t.Field(i)
|
||||
switch field.Type.Kind() {
|
||||
case reflect.Struct:
|
||||
bindFlags(flags, fmt.Sprintf("%s.%s", prefix, strings.ToLower(field.Name)), v.Field(i))
|
||||
default:
|
||||
newPrefix := prefix[1:]
|
||||
newName := modifyFlag(field.Name)
|
||||
configName := fmt.Sprintf("%s.%s", newPrefix, newName)
|
||||
flag := flags.Lookup(fmt.Sprintf("%s-%s", strings.ReplaceAll(newPrefix, ".", "-"), newName))
|
||||
if !flag.Changed && viper.IsSet(configName) {
|
||||
confVal := viper.Get(configName)
|
||||
if field.Type.Kind() == reflect.Slice {
|
||||
sliceValue, ok := confVal.([]interface{})
|
||||
if ok {
|
||||
for _, v := range sliceValue {
|
||||
flag.Value.Set(fmt.Sprintf("%v", v))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
flags.Set(flag.Name, fmt.Sprintf("%v", confVal))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func modifyFlag(s string) string {
|
||||
var result []rune
|
||||
|
||||
for i, c := range s {
|
||||
if i > 0 && unicode.IsUpper(c) {
|
||||
result = append(result, '-')
|
||||
}
|
||||
result = append(result, unicode.ToLower(c))
|
||||
}
|
||||
|
||||
return string(result)
|
||||
}
|
||||
|
|
25
go.mod
25
go.mod
|
@ -11,9 +11,10 @@ require (
|
|||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gotd/contrib v0.21.0
|
||||
github.com/gotd/td v0.116.0
|
||||
github.com/gotd/td v0.117.0
|
||||
github.com/iyear/connectproxy v0.1.1
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/ogen-go/ogen v1.8.1
|
||||
github.com/redis/go-redis/v9 v9.7.0
|
||||
github.com/spf13/cobra v1.8.1
|
||||
|
@ -22,7 +23,7 @@ require (
|
|||
github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||
go.etcd.io/bbolt v1.3.11
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/time v0.8.0
|
||||
golang.org/x/time v0.9.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gorm.io/datatypes v1.2.5
|
||||
gorm.io/driver/postgres v1.5.11
|
||||
|
@ -46,23 +47,23 @@ require (
|
|||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/magiconair/properties v1.8.9 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.6.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sethvargo/go-retry v0.3.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/afero v1.12.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.33.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/tools v0.28.0 // indirect
|
||||
golang.org/x/tools v0.29.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gorm.io/driver/mysql v1.5.7 // indirect
|
||||
|
@ -85,17 +86,17 @@ require (
|
|||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pressly/goose/v3 v3.24.0
|
||||
github.com/pressly/goose/v3 v3.24.1
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/stretchr/testify v1.10.0
|
||||
go.opentelemetry.io/otel v1.33.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.33.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0
|
||||
golang.org/x/crypto v0.31.0
|
||||
golang.org/x/net v0.33.0
|
||||
golang.org/x/crypto v0.32.0
|
||||
golang.org/x/net v0.34.0
|
||||
golang.org/x/sync v0.10.0
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
rsc.io/qr v0.2.0 // indirect
|
||||
|
|
92
go.sum
92
go.sum
|
@ -2,10 +2,14 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
|||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
||||
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/WinterYukky/gorm-extra-clause-plugin v0.3.0 h1:fQfTkxoRso6mlm7eOfBIk94aqamJeqxCEfU+MyLWhgo=
|
||||
github.com/WinterYukky/gorm-extra-clause-plugin v0.3.0/go.mod h1:GFT8TzxeeGKYXNU/65PsiN2+zNHigm9HjybnbL1T7eg=
|
||||
github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho=
|
||||
github.com/beevik/ntp v1.4.3/go.mod h1:Unr8Zg+2dRn7d8bHFuehIMSvvUYssHMxW3Q5Nx4RW5Q=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
|
@ -15,6 +19,18 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
|
|||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
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/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=
|
||||
github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8=
|
||||
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4=
|
||||
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M=
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
|
||||
github.com/cockroachdb/pebble v1.1.2 h1:CUh2IPtR4swHlEj48Rhfzw6l/d0qA31fItcIszQVIsA=
|
||||
github.com/cockroachdb/pebble v1.1.2/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU=
|
||||
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
|
||||
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
|
||||
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
|
||||
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
|
||||
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
|
||||
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||
github.com/coocood/freecache v1.2.4 h1:UdR6Yz/X1HW4fZOuH0Z94KwG851GWOSknua5VUbb/5M=
|
||||
|
@ -37,6 +53,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
|
|||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
|
||||
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
|
||||
|
@ -54,15 +72,23 @@ github.com/go-faster/xor v1.0.0 h1:2o8vTOgErSGHP3/7XwA5ib1FTtUsNtwCoLLBjl31X38=
|
|||
github.com/go-faster/xor v1.0.0/go.mod h1:x5CaDY9UKErKzqfRfFZdfu+OSTfoZny3w5Ak7UxcipQ=
|
||||
github.com/go-faster/yaml v0.4.6 h1:lOK/EhI04gCpPgPhgt0bChS6bvw7G3WwI8xxVe0sw9I=
|
||||
github.com/go-faster/yaml v0.4.6/go.mod h1:390dRIvV4zbnO7qC9FGo6YYutc+wyyUSHBgbXL52eXk=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
|
@ -76,8 +102,8 @@ github.com/gotd/ige v0.2.2 h1:XQ9dJZwBfDnOGSTxKXBGP4gMud3Qku2ekScRjDWWfEk=
|
|||
github.com/gotd/ige v0.2.2/go.mod h1:tuCRb+Y5Y3eNTo3ypIfNpQ4MFjrnONiL2jN2AKZXmb0=
|
||||
github.com/gotd/neo v0.1.5 h1:oj0iQfMbGClP8xI59x7fE/uHoTJD7NZH9oV1WNuPukQ=
|
||||
github.com/gotd/neo v0.1.5/go.mod h1:9A2a4bn9zL6FADufBdt7tZt+WMhvZoc5gWXihOPoiBQ=
|
||||
github.com/gotd/td v0.116.0 h1:MSZZ4lrfn6wDSYyVSKLvFDtBzBOmb9jv0MUBw/PiIgg=
|
||||
github.com/gotd/td v0.116.0/go.mod h1:G65yEk83sFJU9s0xowP3dDtvXsTp0tmSrv5NfgI+ZY0=
|
||||
github.com/gotd/td v0.117.0 h1:Z6vU5thb5DW/I1s0sLSeSfA/QWvwszx6SxHhEEYJiU8=
|
||||
github.com/gotd/td v0.117.0/go.mod h1:jf1Zf1ViTN+H1x8dhDTCBHOYY/2E/40HsyOsohxqXYA=
|
||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
|
@ -112,9 +138,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
|
||||
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||
|
@ -127,6 +152,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
|
|||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/ogen-go/ogen v1.8.1 h1:7TZ+oIeLkcBiyl0qu0fHPrFUrGWDj3Fi/zKSWg2i2Tg=
|
||||
|
@ -134,11 +161,21 @@ github.com/ogen-go/ogen v1.8.1/go.mod h1:2ShRm6u/nXUHuwdVKv2SeaG8enBKPKAE3kSbHww
|
|||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pressly/goose/v3 v3.24.0 h1:sFbNms7Bd++2VMq6HSgDHDLWa7kHz1qXzPb3ZIU72VU=
|
||||
github.com/pressly/goose/v3 v3.24.0/go.mod h1:rEWreU9uVtt0DHCyLzF9gRcWiiTF/V+528DV+4DORug=
|
||||
github.com/pressly/goose/v3 v3.24.1 h1:bZmxRco2uy5uu5Ng1MMVEfYsFlrMJI+e/VMXHQ3C4LY=
|
||||
github.com/pressly/goose/v3 v3.24.1/go.mod h1:rEWreU9uVtt0DHCyLzF9gRcWiiTF/V+528DV+4DORug=
|
||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
|
||||
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
|
@ -150,8 +187,8 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4
|
|||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
|
||||
github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
|
||||
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
|
||||
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
|
@ -160,8 +197,8 @@ github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah
|
|||
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
|
||||
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
|
@ -188,8 +225,12 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh
|
|||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
|
||||
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw=
|
||||
go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I=
|
||||
go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ=
|
||||
go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M=
|
||||
go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=
|
||||
go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
|
@ -201,27 +242,30 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
|||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
|
||||
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
|
||||
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
|
||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
|
|
@ -93,7 +93,7 @@ func GetSessionByHash(db *gorm.DB, cache cache.Cacher, hash string) (*models.Ses
|
|||
type securityHandler struct {
|
||||
db *gorm.DB
|
||||
cache cache.Cacher
|
||||
cfg *config.Config
|
||||
cfg *config.JWTConfig
|
||||
}
|
||||
|
||||
func (s *securityHandler) HandleApiKeyAuth(ctx context.Context, operationName api.OperationName, t api.ApiKeyAuth) (context.Context, error) {
|
||||
|
@ -105,14 +105,14 @@ func (s *securityHandler) HandleBearerAuth(ctx context.Context, operationName ap
|
|||
}
|
||||
|
||||
func (s *securityHandler) handleAuth(ctx context.Context, token string) (context.Context, error) {
|
||||
claims, err := VerifyUser(s.db, s.cache, s.cfg.JWT.Secret, token)
|
||||
claims, err := VerifyUser(s.db, s.cache, s.cfg.Secret, token)
|
||||
if err != nil {
|
||||
return nil, &ogenerrors.SecurityError{Err: err}
|
||||
}
|
||||
return context.WithValue(ctx, authKey, claims), nil
|
||||
}
|
||||
|
||||
func NewSecurityHandler(db *gorm.DB, cache cache.Cacher, cfg *config.Config) api.SecurityHandler {
|
||||
func NewSecurityHandler(db *gorm.DB, cache cache.Cacher, cfg *config.JWTConfig) api.SecurityHandler {
|
||||
return &securityHandler{db: db, cache: cache, cfg: cfg}
|
||||
}
|
||||
|
||||
|
|
10
internal/cache/cache.go
vendored
10
internal/cache/cache.go
vendored
|
@ -23,14 +23,14 @@ type MemoryCache struct {
|
|||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewCache(ctx context.Context, conf *config.Config) Cacher {
|
||||
func NewCache(ctx context.Context, conf *config.CacheConfig) Cacher {
|
||||
var cacher Cacher
|
||||
if conf.Cache.RedisAddr == "" {
|
||||
cacher = NewMemoryCache(conf.Cache.MaxSize)
|
||||
if conf.RedisAddr == "" {
|
||||
cacher = NewMemoryCache(conf.MaxSize)
|
||||
} else {
|
||||
cacher = NewRedisCache(ctx, redis.NewClient(&redis.Options{
|
||||
Addr: conf.Cache.RedisAddr,
|
||||
Password: conf.Cache.RedisPass,
|
||||
Addr: conf.RedisAddr,
|
||||
Password: conf.RedisPass,
|
||||
}))
|
||||
}
|
||||
return cacher
|
||||
|
|
|
@ -1,91 +1,231 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/tgdrive/teldrive/internal/duration"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Server ServerConfig
|
||||
Log LoggingConfig
|
||||
JWT JWTConfig
|
||||
DB DBConfig
|
||||
TG TGConfig
|
||||
CronJobs CronJobConfig
|
||||
Cache struct {
|
||||
MaxSize int
|
||||
RedisAddr string
|
||||
RedisPass string
|
||||
}
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Port int
|
||||
GracefulShutdown time.Duration
|
||||
EnablePprof bool
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
Port int `mapstructure:"port"`
|
||||
GracefulShutdown time.Duration `mapstructure:"graceful-shutdown"`
|
||||
EnablePprof bool `mapstructure:"enable-pprof"`
|
||||
ReadTimeout time.Duration `mapstructure:"read-timeout"`
|
||||
WriteTimeout time.Duration `mapstructure:"write-timeout"`
|
||||
}
|
||||
|
||||
type CronJobConfig struct {
|
||||
Enable bool
|
||||
CleanFilesInterval time.Duration
|
||||
CleanUploadsInterval time.Duration
|
||||
FolderSizeInterval time.Duration
|
||||
}
|
||||
|
||||
type TGConfig struct {
|
||||
AppId int
|
||||
AppHash string
|
||||
RateLimit bool
|
||||
RateBurst int
|
||||
Rate int
|
||||
DeviceModel string
|
||||
SystemVersion string
|
||||
AppVersion string
|
||||
LangCode string
|
||||
SystemLangCode string
|
||||
LangPack string
|
||||
Ntp bool
|
||||
SessionFile string
|
||||
DisableStreamBots bool
|
||||
BgBotsCheckInterval time.Duration
|
||||
Proxy string
|
||||
ReconnectTimeout time.Duration
|
||||
PoolSize int64
|
||||
EnableLogging bool
|
||||
Uploads struct {
|
||||
EncryptionKey string
|
||||
Threads int
|
||||
MaxRetries int
|
||||
Retention time.Duration
|
||||
}
|
||||
Stream struct {
|
||||
MultiThreads int
|
||||
Buffers int
|
||||
ChunkTimeout time.Duration
|
||||
}
|
||||
type CacheConfig struct {
|
||||
MaxSize int `mapstructure:"max-size"`
|
||||
RedisAddr string `mapstructure:"redis-addr"`
|
||||
RedisPass string `mapstructure:"redis-pass"`
|
||||
}
|
||||
|
||||
type LoggingConfig struct {
|
||||
Level int
|
||||
Development bool
|
||||
File string
|
||||
Level int `mapstructure:"level"`
|
||||
Development bool `mapstructure:"development"`
|
||||
File string `mapstructure:"file"`
|
||||
}
|
||||
|
||||
type JWTConfig struct {
|
||||
Secret string
|
||||
SessionTime time.Duration
|
||||
AllowedUsers []string
|
||||
Secret string `mapstructure:"secret"`
|
||||
SessionTime time.Duration `mapstructure:"session-time"`
|
||||
AllowedUsers []string `mapstructure:"allowed-users"`
|
||||
}
|
||||
|
||||
type DBConfig struct {
|
||||
DataSource string
|
||||
PrepareStmt bool
|
||||
LogLevel int
|
||||
DataSource string `mapstructure:"data-source"`
|
||||
PrepareStmt bool `mapstructure:"prepare-stmt"`
|
||||
LogLevel int `mapstructure:"log-level"`
|
||||
Pool struct {
|
||||
Enable bool
|
||||
MaxOpenConnections int
|
||||
MaxIdleConnections int
|
||||
MaxLifetime time.Duration
|
||||
Enable bool `mapstructure:"enable"`
|
||||
MaxOpenConnections int `mapstructure:"max-open-connections"`
|
||||
MaxIdleConnections int `mapstructure:"max-idle-connections"`
|
||||
MaxLifetime time.Duration `mapstructure:"max-lifetime"`
|
||||
} `mapstructure:"pool"`
|
||||
}
|
||||
|
||||
type CronJobConfig struct {
|
||||
Enable bool `mapstructure:"enable"`
|
||||
CleanFilesInterval time.Duration `mapstructure:"clean-files-interval"`
|
||||
CleanUploadsInterval time.Duration `mapstructure:"clean-uploads-interval"`
|
||||
FolderSizeInterval time.Duration `mapstructure:"folder-size-interval"`
|
||||
}
|
||||
|
||||
type TGConfig struct {
|
||||
AppId int `mapstructure:"app-id"`
|
||||
AppHash string `mapstructure:"app-hash"`
|
||||
RateLimit bool `mapstructure:"rate-limit"`
|
||||
RateBurst int `mapstructure:"rate-burst"`
|
||||
Rate int `mapstructure:"rate"`
|
||||
UserName string `mapstructure:"user-name"`
|
||||
DeviceModel string `mapstructure:"device-model"`
|
||||
SystemVersion string `mapstructure:"system-version"`
|
||||
AppVersion string `mapstructure:"app-version"`
|
||||
LangCode string `mapstructure:"lang-code"`
|
||||
SystemLangCode string `mapstructure:"system-lang-code"`
|
||||
LangPack string `mapstructure:"lang-pack"`
|
||||
Ntp bool `mapstructure:"ntp"`
|
||||
SessionFile string `mapstructure:"session-file"`
|
||||
DisableStreamBots bool `mapstructure:"disable-stream-bots"`
|
||||
Proxy string `mapstructure:"proxy"`
|
||||
ReconnectTimeout time.Duration `mapstructure:"reconnect-timeout"`
|
||||
PoolSize int64 `mapstructure:"pool-size"`
|
||||
EnableLogging bool `mapstructure:"enable-logging"`
|
||||
Uploads struct {
|
||||
EncryptionKey string `mapstructure:"encryption-key"`
|
||||
Threads int `mapstructure:"threads"`
|
||||
MaxRetries int `mapstructure:"max-retries"`
|
||||
Retention time.Duration `mapstructure:"retention"`
|
||||
} `mapstructure:"uploads"`
|
||||
Stream struct {
|
||||
MultiThreads int `mapstructure:"multi-threads"`
|
||||
Buffers int `mapstructure:"buffers"`
|
||||
ChunkTimeout time.Duration `mapstructure:"chunk-timeout"`
|
||||
} `mapstructure:"stream"`
|
||||
}
|
||||
|
||||
type ServerCmdConfig struct {
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
Log LoggingConfig `mapstructure:"log"`
|
||||
JWT JWTConfig `mapstructure:"jwt"`
|
||||
DB DBConfig `mapstructure:"db"`
|
||||
TG TGConfig `mapstructure:"tg"`
|
||||
CronJobs CronJobConfig `mapstructure:"cronjobs"`
|
||||
Cache CacheConfig `mapstructure:"cache"`
|
||||
}
|
||||
|
||||
type MigrateCmdConfig struct {
|
||||
DB DBConfig `mapstructure:"db"`
|
||||
Log LoggingConfig `mapstructure:"log"`
|
||||
}
|
||||
|
||||
type ConfigLoader struct {
|
||||
v *viper.Viper
|
||||
}
|
||||
|
||||
func NewConfigLoader() *ConfigLoader {
|
||||
return &ConfigLoader{
|
||||
v: viper.New(),
|
||||
}
|
||||
}
|
||||
|
||||
func StringToDurationHook() mapstructure.DecodeHookFunc {
|
||||
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
|
||||
if f.Kind() != reflect.String {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
if t != reflect.TypeOf(time.Duration(0)) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
str, ok := data.(string)
|
||||
if !ok {
|
||||
return data, nil
|
||||
}
|
||||
return duration.ParseDuration(str)
|
||||
}
|
||||
}
|
||||
|
||||
func (cl *ConfigLoader) InitializeConfig(cmd *cobra.Command) error {
|
||||
cl.v.SetConfigType("toml")
|
||||
|
||||
cfgFile := cmd.Flags().Lookup("config").Value.String()
|
||||
|
||||
if cfgFile != "" {
|
||||
cl.v.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting home directory: %v", err)
|
||||
}
|
||||
cl.v.AddConfigPath(filepath.Join(home, ".teldrive"))
|
||||
cl.v.AddConfigPath(".")
|
||||
cl.v.SetConfigName("config")
|
||||
}
|
||||
|
||||
cl.v.SetEnvPrefix("teldrive")
|
||||
cl.v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
cl.v.AutomaticEnv()
|
||||
|
||||
if err := cl.v.BindPFlags(cmd.Flags()); err != nil {
|
||||
return fmt.Errorf("error binding flags: %v", err)
|
||||
}
|
||||
|
||||
if err := cl.v.ReadInConfig(); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
||||
return fmt.Errorf("error reading config file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cl *ConfigLoader) Load(cfg interface{}) error {
|
||||
config := &mapstructure.DecoderConfig{
|
||||
DecodeHook: mapstructure.ComposeDecodeHookFunc(
|
||||
StringToDurationHook(),
|
||||
),
|
||||
WeaklyTypedInput: true,
|
||||
Result: cfg,
|
||||
}
|
||||
|
||||
decoder, err := mapstructure.NewDecoder(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create decoder: %v", err)
|
||||
}
|
||||
|
||||
if err := decoder.Decode(cl.v.AllSettings()); err != nil {
|
||||
return fmt.Errorf("failed to decode config: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func AddCommonFlags(flags *pflag.FlagSet, config *ServerCmdConfig) {
|
||||
|
||||
flags.StringP("config", "c", "", "Config file path (default $HOME/.teldrive/config.toml)")
|
||||
|
||||
// Log config
|
||||
flags.IntVarP(&config.Log.Level, "log-level", "", -1, "Logging level")
|
||||
flags.StringVar(&config.Log.File, "log-file", "", "Logging file path")
|
||||
flags.BoolVar(&config.Log.Development, "log-development", false, "Enable development mode")
|
||||
|
||||
// DB config
|
||||
flags.StringVar(&config.DB.DataSource, "db-data-source", "", "Database connection string")
|
||||
flags.IntVar(&config.DB.LogLevel, "db-log-level", 1, "Database log level")
|
||||
flags.BoolVar(&config.DB.PrepareStmt, "db-prepare-stmt", true, "Enable prepared statements")
|
||||
flags.BoolVar(&config.DB.Pool.Enable, "db-pool-enable", true, "Enable database pool")
|
||||
flags.IntVar(&config.DB.Pool.MaxIdleConnections, "db-pool-max-open-connections", 25, "Database max open connections")
|
||||
flags.IntVar(&config.DB.Pool.MaxIdleConnections, "db-pool-max-idle-connections", 25, "Database max idle connections")
|
||||
duration.DurationVar(flags, &config.DB.Pool.MaxLifetime, "db-pool-max-lifetime", 10*time.Minute, "Database max connection lifetime")
|
||||
|
||||
// Telegram config
|
||||
flags.IntVar(&config.TG.AppId, "tg-app-id", 0, "Telegram app ID")
|
||||
flags.StringVar(&config.TG.AppHash, "tg-app-hash", "", "Telegram app hash")
|
||||
flags.StringVar(&config.TG.SessionFile, "tg-session-file", "", "Bot session file path")
|
||||
flags.BoolVar(&config.TG.RateLimit, "tg-rate-limit", true, "Enable rate limiting for telegram client")
|
||||
flags.IntVar(&config.TG.RateBurst, "tg-rate-burst", 5, "Limiting burst for telegram client")
|
||||
flags.IntVar(&config.TG.Rate, "tg-rate", 100, "Limiting rate for telegram client")
|
||||
flags.StringVar(&config.TG.DeviceModel, "tg-device-model",
|
||||
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/116.0", "Device model")
|
||||
flags.StringVar(&config.TG.SystemVersion, "tg-system-version", "Win32", "System version")
|
||||
flags.StringVar(&config.TG.AppVersion, "tg-app-version", "4.6.3 K", "App version")
|
||||
flags.StringVar(&config.TG.LangCode, "tg-lang-code", "en", "Language code")
|
||||
flags.StringVar(&config.TG.SystemLangCode, "tg-system-lang-code", "en-US", "System language code")
|
||||
flags.StringVar(&config.TG.LangPack, "tg-lang-pack", "webk", "Language pack")
|
||||
flags.StringVar(&config.TG.Proxy, "tg-proxy", "", "HTTP OR SOCKS5 proxy URL")
|
||||
flags.BoolVar(&config.TG.DisableStreamBots, "tg-disable-stream-bots", false, "Disable Stream bots")
|
||||
flags.BoolVar(&config.TG.Ntp, "tg-ntp", false, "Use NTP server time")
|
||||
flags.BoolVar(&config.TG.EnableLogging, "tg-enable-logging", false, "Enable telegram client logging")
|
||||
flags.Int64Var(&config.TG.PoolSize, "tg-pool-size", 8, "Telegram Session pool size")
|
||||
}
|
||||
|
|
|
@ -13,16 +13,16 @@ import (
|
|||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
func NewDatabase(cfg *config.Config, lg *zap.SugaredLogger) (*gorm.DB, error) {
|
||||
func NewDatabase(cfg *config.DBConfig, lg *zap.SugaredLogger) (*gorm.DB, error) {
|
||||
var (
|
||||
db *gorm.DB
|
||||
err error
|
||||
logger = NewLogger(lg, time.Second, true, zapcore.Level(cfg.DB.LogLevel))
|
||||
logger = NewLogger(lg, time.Second, true, zapcore.Level(cfg.LogLevel))
|
||||
)
|
||||
for i := 0; i <= 5; i++ {
|
||||
db, err = gorm.Open(postgres.New(postgres.Config{
|
||||
DSN: cfg.DB.DataSource,
|
||||
PreferSimpleProtocol: !cfg.DB.PrepareStmt,
|
||||
DSN: cfg.DataSource,
|
||||
PreferSimpleProtocol: !cfg.PrepareStmt,
|
||||
}), &gorm.Config{
|
||||
Logger: logger,
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
|
@ -46,20 +46,14 @@ func NewDatabase(cfg *config.Config, lg *zap.SugaredLogger) (*gorm.DB, error) {
|
|||
|
||||
db.Use(extraClausePlugin.New())
|
||||
|
||||
if cfg.DB.Pool.Enable {
|
||||
if cfg.Pool.Enable {
|
||||
rawDB, err := db.DB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawDB.SetMaxOpenConns(cfg.DB.Pool.MaxOpenConnections)
|
||||
rawDB.SetMaxIdleConns(cfg.DB.Pool.MaxIdleConnections)
|
||||
rawDB.SetConnMaxLifetime(cfg.DB.Pool.MaxLifetime)
|
||||
}
|
||||
|
||||
sqlDb, _ := db.DB()
|
||||
err = migrateDB(sqlDb)
|
||||
if err != nil {
|
||||
lg.Fatalf("database: %v", err)
|
||||
rawDB.SetMaxOpenConns(cfg.Pool.MaxOpenConnections)
|
||||
rawDB.SetMaxIdleConns(cfg.Pool.MaxIdleConnections)
|
||||
rawDB.SetConnMaxLifetime(cfg.Pool.MaxLifetime)
|
||||
}
|
||||
|
||||
return db, nil
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -44,7 +43,7 @@ func NewTestDatabase(tb testing.TB, migration bool) *gorm.DB {
|
|||
sqlDB.SetConnMaxIdleTime(10 * time.Minute)
|
||||
|
||||
if migration {
|
||||
migrateDB(sqlDB)
|
||||
MigrateDB(db)
|
||||
}
|
||||
|
||||
return db
|
||||
|
@ -69,14 +68,15 @@ func DeleteRecordAll(_ testing.TB, db *gorm.DB, tableWhereClauses []string) erro
|
|||
return nil
|
||||
}
|
||||
|
||||
func migrateDB(db *sql.DB) error {
|
||||
func MigrateDB(db *gorm.DB) error {
|
||||
sqlDb, _ := db.DB()
|
||||
goose.SetBaseFS(embedMigrations)
|
||||
|
||||
if err := goose.SetDialect("postgres"); err != nil {
|
||||
return fmt.Errorf("failed run migrate: %w", err)
|
||||
return err
|
||||
}
|
||||
if err := goose.Up(db, "migrations"); err != nil {
|
||||
return fmt.Errorf("failed run migrate: %w", err)
|
||||
if err := goose.Up(sqlDb, "migrations"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ func (d *Duration) String() string {
|
|||
}
|
||||
|
||||
func (d *Duration) Set(s string) error {
|
||||
v, err := parseDuration(s)
|
||||
v, err := ParseDuration(s)
|
||||
*d = Duration(v)
|
||||
return err
|
||||
}
|
||||
|
@ -89,10 +89,17 @@ func DurationVar(f *pflag.FlagSet, p *time.Duration, name string, value time.Dur
|
|||
f.VarP(newDurationValue(value, p), name, "", usage)
|
||||
}
|
||||
|
||||
func parseDuration(age string) (time.Duration, error) {
|
||||
func ParseDuration(age string) (time.Duration, error) {
|
||||
return parseDurationFromNow(age)
|
||||
}
|
||||
|
||||
func (d *Duration) UnmarshalText(text []byte) error {
|
||||
if err := d.Set(string(text)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Duration) Type() string {
|
||||
return "Duration"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@ package duration
|
|||
import "testing"
|
||||
|
||||
func TestDate(t *testing.T) {
|
||||
res, _ := parseDuration("15h2m10s")
|
||||
res, _ := ParseDuration("15h2m10s")
|
||||
_ = res
|
||||
}
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
package kv
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/tgdrive/teldrive/internal/config"
|
||||
"github.com/tgdrive/teldrive/internal/utils"
|
||||
"go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
type Bolt struct {
|
||||
bucket []byte
|
||||
db *bbolt.DB
|
||||
}
|
||||
|
||||
func (b *Bolt) Get(key string) ([]byte, error) {
|
||||
var val []byte
|
||||
|
||||
if err := b.db.View(func(tx *bbolt.Tx) error {
|
||||
val = tx.Bucket(b.bucket).Get([]byte(key))
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if val == nil {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (b *Bolt) Set(key string, val []byte) error {
|
||||
return b.db.Update(func(tx *bbolt.Tx) error {
|
||||
return tx.Bucket(b.bucket).Put([]byte(key), val)
|
||||
})
|
||||
}
|
||||
|
||||
func (b *Bolt) Delete(key string) error {
|
||||
return b.db.Update(func(tx *bbolt.Tx) error {
|
||||
return tx.Bucket(b.bucket).Delete([]byte(key))
|
||||
})
|
||||
}
|
||||
|
||||
func NewBoltKV(cnf *config.Config) KV {
|
||||
|
||||
sessionFile := cnf.TG.SessionFile
|
||||
if sessionFile == "" {
|
||||
dir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
dir = utils.ExecutableDir()
|
||||
} else {
|
||||
dir = filepath.Join(dir, ".teldrive")
|
||||
err := os.Mkdir(dir, 0755)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
dir = utils.ExecutableDir()
|
||||
}
|
||||
}
|
||||
sessionFile = filepath.Join(dir, "session.db")
|
||||
}
|
||||
boltDB, err := bbolt.Open(sessionFile, 0666, &bbolt.Options{
|
||||
Timeout: time.Second,
|
||||
NoGrowSync: false,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
kv, err := New(Options{Bucket: "teldrive", DB: boltDB})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return kv
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package kv
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Key(indexes ...string) string {
|
||||
return strings.Join(indexes, ":")
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package kv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
var ErrNotFound = errors.New("key not found")
|
||||
|
||||
type KV interface {
|
||||
Get(key string) ([]byte, error)
|
||||
Set(key string, value []byte) error
|
||||
Delete(key string) error
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Bucket string
|
||||
DB *bbolt.DB
|
||||
}
|
||||
|
||||
func New(opts Options) (KV, error) {
|
||||
|
||||
if err := opts.DB.Update(func(tx *bbolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists([]byte(opts.Bucket))
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Bolt{db: opts.DB, bucket: []byte(opts.Bucket)}, nil
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package kv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/gotd/td/telegram"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
kv KV
|
||||
key string
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewSession(kv KV, key string) telegram.SessionStorage {
|
||||
return &Session{kv: kv, key: key}
|
||||
}
|
||||
|
||||
func (s *Session) LoadSession(_ context.Context) ([]byte, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
b, err := s.kv.Get(s.key)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
data := make([]byte, len(b))
|
||||
copy(data, b)
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (s *Session) StoreSession(_ context.Context, data []byte) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.kv.Set(s.key, data)
|
||||
}
|
|
@ -87,10 +87,11 @@ func (p *pool) Default(ctx context.Context) *tg.Client {
|
|||
return p.Client(ctx, p.current())
|
||||
}
|
||||
|
||||
func (p *pool) Close() (err error) {
|
||||
func (p *pool) Close() error {
|
||||
|
||||
if p.close != nil {
|
||||
return p.close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,142 +0,0 @@
|
|||
package reader
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/tgdrive/teldrive/internal/config"
|
||||
)
|
||||
|
||||
type testChunkSource struct {
|
||||
buffer []byte
|
||||
}
|
||||
|
||||
func (m *testChunkSource) Chunk(ctx context.Context, offset int64, limit int64) ([]byte, error) {
|
||||
return m.buffer[offset : offset+limit], nil
|
||||
}
|
||||
|
||||
func (m *testChunkSource) ChunkSize(start, end int64) int64 {
|
||||
return 1
|
||||
}
|
||||
|
||||
type testChunkSourceTimeout struct {
|
||||
buffer []byte
|
||||
}
|
||||
|
||||
func (m *testChunkSourceTimeout) Chunk(ctx context.Context, offset int64, limit int64) ([]byte, error) {
|
||||
if offset == 8 {
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
return m.buffer[offset : offset+limit], nil
|
||||
}
|
||||
|
||||
func (m *testChunkSourceTimeout) ChunkSize(start, end int64) int64 {
|
||||
return 1
|
||||
}
|
||||
|
||||
type TestSuite struct {
|
||||
suite.Suite
|
||||
config *config.TGConfig
|
||||
}
|
||||
|
||||
func (suite *TestSuite) SetupTest() {
|
||||
suite.config = &config.TGConfig{Stream: struct {
|
||||
MultiThreads int
|
||||
Buffers int
|
||||
ChunkTimeout time.Duration
|
||||
}{MultiThreads: 8, Buffers: 10, ChunkTimeout: 1 * time.Second}}
|
||||
}
|
||||
|
||||
func (suite *TestSuite) TestFullRead() {
|
||||
ctx := context.Background()
|
||||
start := int64(0)
|
||||
end := int64(99)
|
||||
data := make([]byte, 100)
|
||||
rand.Read(data)
|
||||
chunkSrc := &testChunkSource{buffer: data}
|
||||
reader, err := newTGMultiReader(ctx, start, end, suite.config, chunkSrc)
|
||||
assert.NoError(suite.T(), err)
|
||||
test_data, err := io.ReadAll(reader)
|
||||
assert.Equal(suite.T(), nil, err)
|
||||
assert.Equal(suite.T(), data[start:end+1], test_data)
|
||||
}
|
||||
|
||||
func (suite *TestSuite) TestPartialRead() {
|
||||
ctx := context.Background()
|
||||
start := int64(0)
|
||||
end := int64(65)
|
||||
data := make([]byte, 100)
|
||||
rand.Read(data)
|
||||
chunkSrc := &testChunkSource{buffer: data}
|
||||
reader, err := newTGMultiReader(ctx, start, end, suite.config, chunkSrc)
|
||||
assert.NoError(suite.T(), err)
|
||||
test_data, err := io.ReadAll(reader)
|
||||
assert.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), data[start:end+1], test_data)
|
||||
}
|
||||
|
||||
func (suite *TestSuite) TestTimeout() {
|
||||
ctx := context.Background()
|
||||
start := int64(0)
|
||||
end := int64(65)
|
||||
data := make([]byte, 100)
|
||||
rand.Read(data)
|
||||
chunkSrc := &testChunkSourceTimeout{buffer: data}
|
||||
reader, err := newTGMultiReader(ctx, start, end, suite.config, chunkSrc)
|
||||
assert.NoError(suite.T(), err)
|
||||
test_data, err := io.ReadAll(reader)
|
||||
assert.Greater(suite.T(), len(test_data), 0)
|
||||
assert.Equal(suite.T(), err, ErrStreamAbandoned)
|
||||
}
|
||||
|
||||
func (suite *TestSuite) TestClose() {
|
||||
ctx := context.Background()
|
||||
start := int64(0)
|
||||
end := int64(65)
|
||||
data := make([]byte, 100)
|
||||
rand.Read(data)
|
||||
chunkSrc := &testChunkSource{buffer: data}
|
||||
reader, err := newTGMultiReader(ctx, start, end, suite.config, chunkSrc)
|
||||
assert.NoError(suite.T(), err)
|
||||
_, err = io.ReadAll(reader)
|
||||
assert.NoError(suite.T(), err)
|
||||
assert.NoError(suite.T(), reader.Close())
|
||||
}
|
||||
|
||||
func (suite *TestSuite) TestCancellation() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
start := int64(0)
|
||||
end := int64(65)
|
||||
data := make([]byte, 100)
|
||||
rand.Read(data)
|
||||
chunkSrc := &testChunkSource{buffer: data}
|
||||
reader, err := newTGMultiReader(ctx, start, end, suite.config, chunkSrc)
|
||||
assert.NoError(suite.T(), err)
|
||||
cancel()
|
||||
_, err = io.ReadAll(reader)
|
||||
assert.Equal(suite.T(), err, context.Canceled)
|
||||
assert.Equal(suite.T(), len(reader.bufferChan), 0)
|
||||
}
|
||||
|
||||
func (suite *TestSuite) TestCancellationWithTimeout() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||
_ = cancel
|
||||
start := int64(0)
|
||||
end := int64(65)
|
||||
data := make([]byte, 100)
|
||||
rand.Read(data)
|
||||
chunkSrc := &testChunkSourceTimeout{buffer: data}
|
||||
reader, err := newTGMultiReader(ctx, start, end, suite.config, chunkSrc)
|
||||
assert.NoError(suite.T(), err)
|
||||
_, err = io.ReadAll(reader)
|
||||
assert.Equal(suite.T(), err, context.DeadlineExceeded)
|
||||
assert.Equal(suite.T(), len(reader.bufferChan), 0)
|
||||
}
|
||||
func Test(t *testing.T) {
|
||||
suite.Run(t, new(TestSuite))
|
||||
}
|
36
internal/tgc/bolt.go
Normal file
36
internal/tgc/bolt.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package tgc
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/tgdrive/teldrive/internal/utils"
|
||||
"go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
func NewBoltDB(sessionFile string) (*bbolt.DB, error) {
|
||||
if sessionFile == "" {
|
||||
dir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
dir = utils.ExecutableDir()
|
||||
} else {
|
||||
dir = filepath.Join(dir, ".teldrive")
|
||||
err := os.Mkdir(dir, 0755)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
dir = utils.ExecutableDir()
|
||||
}
|
||||
}
|
||||
sessionFile = filepath.Join(dir, "session.db")
|
||||
}
|
||||
db, err := bbolt.Open(sessionFile, 0666, &bbolt.Options{
|
||||
Timeout: time.Second,
|
||||
NoGrowSync: false,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
package tgc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/gotd/td/telegram"
|
||||
)
|
||||
|
||||
type StopFunc func() error
|
||||
|
||||
type connectOptions struct {
|
||||
ctx context.Context
|
||||
token string
|
||||
}
|
||||
|
||||
type Option interface {
|
||||
apply(o *connectOptions)
|
||||
}
|
||||
|
||||
type fnOption func(o *connectOptions)
|
||||
|
||||
func (f fnOption) apply(o *connectOptions) {
|
||||
f(o)
|
||||
}
|
||||
|
||||
func WithContext(ctx context.Context) Option {
|
||||
return fnOption(func(o *connectOptions) {
|
||||
o.ctx = ctx
|
||||
})
|
||||
}
|
||||
|
||||
func WithBotToken(token string) Option {
|
||||
return fnOption(func(o *connectOptions) {
|
||||
o.token = token
|
||||
})
|
||||
}
|
||||
|
||||
func Connect(client *telegram.Client, options ...Option) (StopFunc, error) {
|
||||
opt := &connectOptions{
|
||||
ctx: context.Background(),
|
||||
}
|
||||
for _, o := range options {
|
||||
o.apply(opt)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(opt.ctx)
|
||||
|
||||
errC := make(chan error, 1)
|
||||
initDone := make(chan struct{})
|
||||
go func() {
|
||||
defer close(errC)
|
||||
errC <- RunWithAuth(ctx, client, opt.token, func(ctx context.Context) error {
|
||||
close(initDone)
|
||||
<-ctx.Done()
|
||||
if errors.Is(ctx.Err(), context.Canceled) {
|
||||
return nil
|
||||
}
|
||||
return ctx.Err()
|
||||
})
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done(): // context canceled
|
||||
cancel()
|
||||
return func() error { return nil }, ctx.Err()
|
||||
case err := <-errC: // startup timeout
|
||||
cancel()
|
||||
return func() error { return nil }, err
|
||||
case <-initDone: // init done
|
||||
}
|
||||
|
||||
stopFn := func() error {
|
||||
cancel()
|
||||
return <-errC
|
||||
}
|
||||
return stopFn, nil
|
||||
}
|
|
@ -12,8 +12,8 @@ import (
|
|||
"github.com/gotd/td/telegram"
|
||||
"github.com/gotd/td/tg"
|
||||
"github.com/tgdrive/teldrive/internal/config"
|
||||
"github.com/tgdrive/teldrive/internal/kv"
|
||||
"github.com/tgdrive/teldrive/pkg/types"
|
||||
"go.etcd.io/bbolt"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
|
@ -100,7 +100,7 @@ func GetMessages(ctx context.Context, client *tg.Client, ids []int, channelId in
|
|||
return nil, err
|
||||
}
|
||||
|
||||
batchSize := 200
|
||||
batchSize := 100
|
||||
|
||||
batchCount := int(math.Ceil(float64(len(ids)) / float64(batchSize)))
|
||||
|
||||
|
@ -184,10 +184,10 @@ func GetMediaContent(ctx context.Context, client *tg.Client, location tg.InputFi
|
|||
return buff, nil
|
||||
}
|
||||
|
||||
func GetBotInfo(ctx context.Context, KV kv.KV, config *config.TGConfig, token string) (*types.BotInfo, error) {
|
||||
func GetBotInfo(ctx context.Context, boltdb *bbolt.DB, config *config.TGConfig, token string) (*types.BotInfo, error) {
|
||||
var user *tg.User
|
||||
middlewares := NewMiddleware(config, WithFloodWait(), WithRateLimit())
|
||||
client, _ := BotClient(ctx, KV, config, token, middlewares...)
|
||||
client, _ := BotClient(ctx, boltdb, config, token, middlewares...)
|
||||
err := RunWithAuth(ctx, client, token, func(ctx context.Context) error {
|
||||
user, _ = client.Self(ctx)
|
||||
return nil
|
||||
|
|
|
@ -2,10 +2,12 @@ package tgc
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/go-faster/errors"
|
||||
tgbbolt "github.com/gotd/contrib/bbolt"
|
||||
"github.com/gotd/contrib/clock"
|
||||
"github.com/gotd/contrib/middleware/floodwait"
|
||||
"github.com/gotd/contrib/middleware/ratelimit"
|
||||
|
@ -13,17 +15,21 @@ import (
|
|||
"github.com/gotd/td/telegram"
|
||||
"github.com/gotd/td/telegram/dcs"
|
||||
"github.com/tgdrive/teldrive/internal/config"
|
||||
"github.com/tgdrive/teldrive/internal/kv"
|
||||
"github.com/tgdrive/teldrive/internal/logging"
|
||||
"github.com/tgdrive/teldrive/internal/recovery"
|
||||
"github.com/tgdrive/teldrive/internal/retry"
|
||||
"github.com/tgdrive/teldrive/internal/utils"
|
||||
"go.etcd.io/bbolt"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/net/proxy"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
func New(ctx context.Context, config *config.TGConfig, handler telegram.UpdateHandler, storage session.Storage, middlewares ...telegram.Middleware) (*telegram.Client, error) {
|
||||
func sessionKey(indexes ...string) string {
|
||||
return strings.Join(indexes, ":")
|
||||
}
|
||||
|
||||
func newClient(ctx context.Context, config *config.TGConfig, handler telegram.UpdateHandler, storage session.Storage, middlewares ...telegram.Middleware) (*telegram.Client, error) {
|
||||
|
||||
var dialer dcs.DialFunc = proxy.Direct.DialContext
|
||||
if config.Proxy != "" {
|
||||
|
@ -80,7 +86,7 @@ func NoAuthClient(ctx context.Context, config *config.TGConfig, handler telegram
|
|||
floodwait.NewSimpleWaiter(),
|
||||
}
|
||||
middlewares = append(middlewares, ratelimit.New(rate.Every(time.Millisecond*100), 5))
|
||||
return New(ctx, config, handler, storage, middlewares...)
|
||||
return newClient(ctx, config, handler, storage, middlewares...)
|
||||
}
|
||||
|
||||
func AuthClient(ctx context.Context, config *config.TGConfig, sessionStr string, middlewares ...telegram.Middleware) (*telegram.Client, error) {
|
||||
|
@ -98,14 +104,14 @@ func AuthClient(ctx context.Context, config *config.TGConfig, sessionStr string,
|
|||
if err := loader.Save(context.TODO(), data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return New(ctx, config, nil, storage, middlewares...)
|
||||
return newClient(ctx, config, nil, storage, middlewares...)
|
||||
}
|
||||
|
||||
func BotClient(ctx context.Context, KV kv.KV, config *config.TGConfig, token string, middlewares ...telegram.Middleware) (*telegram.Client, error) {
|
||||
func BotClient(ctx context.Context, boltdb *bbolt.DB, config *config.TGConfig, token string, middlewares ...telegram.Middleware) (*telegram.Client, error) {
|
||||
|
||||
storage := kv.NewSession(KV, kv.Key("botsession", token))
|
||||
storage := tgbbolt.NewSessionStorage(boltdb, sessionKey("botsession", token), []byte("teldrive"))
|
||||
|
||||
return New(ctx, config, nil, storage, middlewares...)
|
||||
return newClient(ctx, config, nil, storage, middlewares...)
|
||||
|
||||
}
|
||||
|
||||
|
|
3
main.go
3
main.go
|
@ -4,12 +4,13 @@ import (
|
|||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/tgdrive/teldrive/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
|
||||
defer cancel()
|
||||
|
||||
if err := cmd.New().ExecuteContext(ctx); err != nil {
|
||||
|
|
|
@ -37,11 +37,11 @@ type UploadResult struct {
|
|||
|
||||
type CronService struct {
|
||||
db *gorm.DB
|
||||
cnf *config.Config
|
||||
cnf *config.ServerCmdConfig
|
||||
logger *zap.SugaredLogger
|
||||
}
|
||||
|
||||
func StartCronJobs(scheduler *gocron.Scheduler, db *gorm.DB, cnf *config.Config) {
|
||||
func StartCronJobs(scheduler *gocron.Scheduler, db *gorm.DB, cnf *config.ServerCmdConfig) {
|
||||
if !cnf.CronJobs.Enable {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -7,12 +7,12 @@ import (
|
|||
"github.com/go-faster/errors"
|
||||
"github.com/gotd/td/telegram"
|
||||
"github.com/ogen-go/ogen/ogenerrors"
|
||||
"go.etcd.io/bbolt"
|
||||
|
||||
ht "github.com/ogen-go/ogen/http"
|
||||
"github.com/tgdrive/teldrive/internal/api"
|
||||
"github.com/tgdrive/teldrive/internal/cache"
|
||||
"github.com/tgdrive/teldrive/internal/config"
|
||||
"github.com/tgdrive/teldrive/internal/kv"
|
||||
"github.com/tgdrive/teldrive/internal/tgc"
|
||||
"github.com/tgdrive/teldrive/internal/version"
|
||||
"gorm.io/gorm"
|
||||
|
@ -20,9 +20,9 @@ import (
|
|||
|
||||
type apiService struct {
|
||||
db *gorm.DB
|
||||
cnf *config.Config
|
||||
cnf *config.ServerCmdConfig
|
||||
cache cache.Cacher
|
||||
kv kv.KV
|
||||
boltdb *bbolt.DB
|
||||
worker *tgc.BotWorker
|
||||
middlewares []telegram.Middleware
|
||||
}
|
||||
|
@ -53,11 +53,11 @@ func (a *apiService) NewError(ctx context.Context, err error) *api.ErrorStatusCo
|
|||
}
|
||||
|
||||
func NewApiService(db *gorm.DB,
|
||||
cnf *config.Config,
|
||||
cnf *config.ServerCmdConfig,
|
||||
cache cache.Cacher,
|
||||
kv kv.KV,
|
||||
boltdb *bbolt.DB,
|
||||
worker *tgc.BotWorker) *apiService {
|
||||
return &apiService{db: db, cnf: cnf, cache: cache, kv: kv, worker: worker,
|
||||
return &apiService{db: db, cnf: cnf, cache: cache, boltdb: boltdb, worker: worker,
|
||||
middlewares: tgc.NewMiddleware(&cnf.TG, tgc.WithFloodWait(), tgc.WithRateLimit())}
|
||||
}
|
||||
|
||||
|
|
|
@ -765,7 +765,7 @@ func (e *extendedService) FilesStream(w http.ResponseWriter, r *http.Request, fi
|
|||
|
||||
token, _ = e.api.worker.Next(file.ChannelId.Value)
|
||||
|
||||
client, err = tgc.BotClient(ctx, e.api.kv, &e.api.cnf.TG, token, middlewares...)
|
||||
client, err = tgc.BotClient(ctx, e.api.boltdb, &e.api.cnf.TG, token, middlewares...)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
|
|
@ -120,7 +120,7 @@ func (a *apiService) UploadsUpload(ctx context.Context, req *api.UploadsUploadRe
|
|||
} else {
|
||||
a.worker.Set(tokens, channelId)
|
||||
token, index = a.worker.Next(channelId)
|
||||
client, err = tgc.BotClient(ctx, a.kv, &a.cnf.TG, token)
|
||||
client, err = tgc.BotClient(ctx, a.boltdb, &a.cnf.TG, token)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -21,6 +21,8 @@ import (
|
|||
"github.com/tgdrive/teldrive/pkg/types"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
tgbbolt "github.com/gotd/contrib/bbolt"
|
||||
"github.com/gotd/contrib/storage"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
|
@ -44,36 +46,28 @@ func (a *apiService) UsersAddBots(ctx context.Context, req *api.AddBots) error {
|
|||
}
|
||||
|
||||
func (a *apiService) UsersListChannels(ctx context.Context) ([]api.Channel, error) {
|
||||
_, session := auth.GetUser(ctx)
|
||||
client, err := tgc.AuthClient(ctx, &a.cnf.TG, session, a.middlewares...)
|
||||
if err != nil {
|
||||
return nil, &apiError{err: err}
|
||||
}
|
||||
if client == nil {
|
||||
return nil, &apiError{err: errors.New("failed to initialise tg client")}
|
||||
}
|
||||
|
||||
userID, _ := auth.GetUser(ctx)
|
||||
|
||||
channels := make(map[int64]*api.Channel)
|
||||
|
||||
client.Run(ctx, func(ctx context.Context) error {
|
||||
peerStorage := tgbbolt.NewPeerStorage(a.boltdb, []byte(fmt.Sprintf("peers:%d", userID)))
|
||||
|
||||
dialogs, _ := query.GetDialogs(client.API()).BatchSize(100).Collect(ctx)
|
||||
|
||||
for _, dialog := range dialogs {
|
||||
if !dialog.Deleted() {
|
||||
for _, channel := range dialog.Entities.Channels() {
|
||||
_, exists := channels[channel.ID]
|
||||
if !exists && channel.AdminRights.AddAdmins {
|
||||
channels[channel.ID] = &api.Channel{ChannelId: channel.ID, ChannelName: channel.Title}
|
||||
}
|
||||
}
|
||||
iter, err := peerStorage.Iterate(ctx)
|
||||
if err != nil {
|
||||
return []api.Channel{}, nil
|
||||
}
|
||||
for iter.Next(ctx) {
|
||||
peer := iter.Value()
|
||||
if peer.Channel != nil && peer.Channel.AdminRights.AddAdmins {
|
||||
_, exists := channels[peer.Channel.ID]
|
||||
if !exists {
|
||||
channels[peer.Channel.ID] = &api.Channel{ChannelId: peer.Channel.ID, ChannelName: peer.Channel.Title}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
})
|
||||
}
|
||||
res := []api.Channel{}
|
||||
|
||||
for _, channel := range channels {
|
||||
res = append(res, *channel)
|
||||
|
||||
|
@ -84,6 +78,23 @@ func (a *apiService) UsersListChannels(ctx context.Context) ([]api.Channel, erro
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func (a *apiService) UsersSyncChannels(ctx context.Context) error {
|
||||
userId, session := auth.GetUser(ctx)
|
||||
peerStorage := tgbbolt.NewPeerStorage(a.boltdb, []byte(fmt.Sprintf("peers:%d", userId)))
|
||||
collector := storage.CollectPeers(peerStorage)
|
||||
client, err := tgc.AuthClient(ctx, &a.cnf.TG, session, a.middlewares...)
|
||||
if err != nil {
|
||||
return &apiError{err: err}
|
||||
}
|
||||
err = client.Run(ctx, func(ctx context.Context) error {
|
||||
return collector.Dialogs(ctx, query.GetDialogs(client.API()).Iter())
|
||||
})
|
||||
if err != nil {
|
||||
return &apiError{err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *apiService) UsersListSessions(ctx context.Context) ([]api.UserSession, error) {
|
||||
userId, userSession := auth.GetUser(ctx)
|
||||
|
||||
|
@ -287,7 +298,7 @@ func (a *apiService) addBots(c context.Context, client *telegram.Client, userId
|
|||
|
||||
for _, token := range botsTokens {
|
||||
g.Go(func() error {
|
||||
info, err := tgc.GetBotInfo(c, a.kv, &a.cnf.TG, token)
|
||||
info, err := tgc.GetBotInfo(c, a.boltdb, &a.cnf.TG, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue