From d5c6d5719a583abe0cb8be8cccd00c0c7c8456b1 Mon Sep 17 00:00:00 2001 From: eleksir Date: Sun, 12 Oct 2025 16:42:16 +0300 Subject: [PATCH] Join webbapp core modules into single package --- cmd/aleesa-webapp-go/main.go | 36 +++----- cmd/flickr_init/main.go | 6 +- cmd/flickr_populate/main.go | 6 +- cmd/flickr_test/main.go | 6 +- internal/config/types.go | 37 ++++++++ .../MsgParser.go} | 65 +------------ .../config.go => webapp/ReadConfig.go} | 91 ++++--------------- internal/webapp/ReadLines.go | 36 ++++++++ internal/webapp/ValidateRmsg.go | 66 ++++++++++++++ internal/{defaults => webapp}/defaults.go | 10 +- internal/webapp/globals.go | 18 ++++ internal/{types => webapp}/types.go | 2 +- 12 files changed, 203 insertions(+), 176 deletions(-) create mode 100644 internal/config/types.go rename internal/{lib/aleesa-webapp-lib.go => webapp/MsgParser.go} (87%) rename internal/{config/config.go => webapp/ReadConfig.go} (60%) create mode 100644 internal/webapp/ReadLines.go create mode 100644 internal/webapp/ValidateRmsg.go rename internal/{defaults => webapp}/defaults.go (74%) create mode 100644 internal/webapp/globals.go rename internal/{types => webapp}/types.go (99%) diff --git a/cmd/aleesa-webapp-go/main.go b/cmd/aleesa-webapp-go/main.go index cdaf615..8ec40eb 100644 --- a/cmd/aleesa-webapp-go/main.go +++ b/cmd/aleesa-webapp-go/main.go @@ -9,8 +9,8 @@ import ( "time" "aleesa-webapp-go/internal/config" - "aleesa-webapp-go/internal/lib" "aleesa-webapp-go/internal/log" + "aleesa-webapp-go/internal/webapp" "github.com/go-redis/redis/v8" ) @@ -35,7 +35,7 @@ func main() { logfile *os.File ) - cfg, err := config.ReadConfig() + err := webapp.ReadConfig() if err != nil { log.Errorf("Unable to parse config: %s", err) @@ -43,24 +43,24 @@ func main() { } // Откроем лог и скормим его логгеру. - if cfg.Log != "" { - logfile, err = os.OpenFile(cfg.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + if webapp.Config.Log != "" { + logfile, err = os.OpenFile(webapp.Config.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) if err != nil { - log.Errorf("Unable to open log file %s: %s", cfg.Log, err) + log.Errorf("Unable to open log file %s: %s", webapp.Config.Log, err) os.Exit(1) } } - log.Init(cfg.Loglevel, logfile) + log.Init(webapp.Config.Loglevel, logfile) // Иницализируем клиента Редиски. - cfg.RedisClient = redis.NewClient(&redis.Options{ - Addr: fmt.Sprintf("%s:%d", cfg.Server, cfg.Port), - }).WithContext(Ctx).WithTimeout(time.Duration(cfg.Timeout) * time.Second) + webapp.Config.RedisClient = redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:%d", webapp.Config.Server, webapp.Config.Port), + }).WithContext(Ctx).WithTimeout(time.Duration(webapp.Config.Timeout) * time.Second) // Обозначим, что хотим после соединения подписаться на события из канала config.Channel. - Subscriber = cfg.RedisClient.Subscribe(Ctx, cfg.Channel) + Subscriber = webapp.Config.RedisClient.Subscribe(Ctx, webapp.Config.Channel) // Самое время поставить трапы на сигналы. signal.Notify(SigChan, @@ -68,18 +68,8 @@ func main() { syscall.SIGTERM, syscall.SIGQUIT) - // Откроем лог и скормим его логгеру. - if cfg.Log != "" { - logfile, err = os.OpenFile(cfg.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) - - if err != nil { - log.Errorf("Unable to open log file %s: %s", cfg.Log, err) - os.Exit(1) - } - } - // Запустим обработчик сигналов. - go SigHandler(cfg) + go SigHandler(webapp.Config) // Начнём выгребать события из редиски (длина конвеера/буфера канала по-умолчанию - 100 сообщений). ch := Subscriber.Channel() @@ -88,13 +78,15 @@ func main() { for msg := range ch { if !Shutdown { - lib.MsgParser(cfg, Ctx, msg.Payload) + webapp.MsgParser(webapp.Config, Ctx, msg.Payload) } } } // SigHandler хэндлер сигналов закрывает все бд и сваливает из приложения. func SigHandler(cfg *config.MyConfig) { + // TODO: утащить отсюда в модули. + var err error for { diff --git a/cmd/flickr_init/main.go b/cmd/flickr_init/main.go index c6fff9d..effbfe9 100644 --- a/cmd/flickr_init/main.go +++ b/cmd/flickr_init/main.go @@ -1,18 +1,18 @@ package main import ( - "aleesa-webapp-go/internal/config" "aleesa-webapp-go/internal/flickr" + "aleesa-webapp-go/internal/webapp" "fmt" "os" ) func main() { - if c, err := config.ReadConfig(); err != nil { + if err := webapp.ReadConfig(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } else { - if err := flickr.APIClientInit(c); err != nil { + if err := flickr.APIClientInit(webapp.Config); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } diff --git a/cmd/flickr_populate/main.go b/cmd/flickr_populate/main.go index 9eb356b..a1b626b 100644 --- a/cmd/flickr_populate/main.go +++ b/cmd/flickr_populate/main.go @@ -1,18 +1,18 @@ package main import ( - "aleesa-webapp-go/internal/config" "aleesa-webapp-go/internal/flickr" + "aleesa-webapp-go/internal/webapp" "fmt" "os" ) func main() { - if c, err := config.ReadConfig(); err != nil { + if err := webapp.ReadConfig(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } else { - if err := flickr.Populate(c); err != nil { + if err := flickr.Populate(webapp.Config); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } diff --git a/cmd/flickr_test/main.go b/cmd/flickr_test/main.go index 71d90a9..0aee041 100644 --- a/cmd/flickr_test/main.go +++ b/cmd/flickr_test/main.go @@ -1,18 +1,18 @@ package main import ( - "aleesa-webapp-go/internal/config" "aleesa-webapp-go/internal/flickr" + "aleesa-webapp-go/internal/webapp" "fmt" "os" ) func main() { - if c, err := config.ReadConfig(); err != nil { + if err := webapp.ReadConfig(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } else { - result, err := flickr.SearchByTags(c, []string{"snail", "slug"}) + result, err := flickr.SearchByTags(webapp.Config, []string{"snail", "slug"}) if err != nil { fmt.Fprintln(os.Stderr, err) diff --git a/internal/config/types.go b/internal/config/types.go new file mode 100644 index 0000000..ce475b2 --- /dev/null +++ b/internal/config/types.go @@ -0,0 +1,37 @@ +// Этот package нужен, чтобы разбить кольцевую зависимость. +package config + +import ( + "github.com/cockroachdb/pebble" + "github.com/go-redis/redis/v8" +) + +// MyConfig структурка, описывающая конфиг. +type MyConfig struct { + Server string `json:"server,omitempty"` + Port int `json:"port,omitempty"` + Timeout int `json:"timeout,omitempty"` + Loglevel string `json:"loglevel,omitempty"` + Log string `json:"log,omitempty"` + Channel string `json:"channel,omitempty"` + DataDir string `json:"datadir,omitempty"` + Csign string `json:"csign,omitempty"` + ForwardsMax int64 `json:"forwards_max,omitempty"` + OpenWeatherMap struct { + Enabled bool `json:"enabled,omitempty"` + Country bool `json:"country,omitempty"` + Appid string `json:"appid,omitempty"` + } `json:"openweathermap,omitempty"` + Flickr struct { + Enabled bool `json:"enabled,omitempty"` + Key string `json:"key,omitempty"` + Secret string `json:"secret,omitempty"` + OAuthToken string `json:"oauth_token,omitempty"` + OAuthTokenSecret string `json:"oauth_token_secret,omitempty"` + } `json:"flickr,omitempty"` + UserAgents []string `json:"user_agents,omitempty"` + PcacheDB map[string]*pebble.DB + RedisClient *redis.Client +} + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/internal/lib/aleesa-webapp-lib.go b/internal/webapp/MsgParser.go similarity index 87% rename from internal/lib/aleesa-webapp-lib.go rename to internal/webapp/MsgParser.go index 556b647..22123a1 100644 --- a/internal/lib/aleesa-webapp-lib.go +++ b/internal/webapp/MsgParser.go @@ -1,4 +1,4 @@ -package lib +package webapp import ( "aleesa-webapp-go/internal/anekdotru" @@ -13,7 +13,6 @@ import ( "aleesa-webapp-go/internal/prazdnikisegodnyaru" "aleesa-webapp-go/internal/randomfox" "aleesa-webapp-go/internal/thecatapi" - "aleesa-webapp-go/internal/types" "aleesa-webapp-go/internal/xkcdru" "context" "encoding/json" @@ -28,7 +27,7 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { var ( sendTo string answer string - j types.RMsg + j RMsg err error ) @@ -469,7 +468,7 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { } // Настало время формировать json и засылать его в дальше - var message types.SMsg + var message SMsg message.From = j.From message.Userid = j.Userid message.Chatid = j.Chatid @@ -501,62 +500,4 @@ func MsgParser(cfg *config.MyConfig, ctx context.Context, msg string) { } } -// ValidateRmsg валидирует входящее сообщение. -func ValidateRmsg(cfg *config.MyConfig, j types.RMsg, msg string) (types.RMsg, error) { - if exist := j.From; exist == "" { - emsg := fmt.Errorf("incorrect msg from redis, no from field: %s", msg) - - return j, emsg - } - - if exist := j.Chatid; exist == "" { - emsg := fmt.Errorf("incorrect msg from redis, no chatid field: %s", msg) - - return j, emsg - } - - if exist := j.Userid; exist == "" { - emsg := fmt.Errorf("incorrect msg from redis, no userid field: %s", msg) - - return j, emsg - } - - // j.Threadid может быть пустым, значит либо нам его не дали, либо дали пустым. Это нормально. - - if exist := j.Message; exist == "" { - emsg := fmt.Errorf("incorrect msg from redis, no message field: %s", msg) - - return j, emsg - } - - if exist := j.Plugin; exist == "" { - emsg := fmt.Errorf("incorrect msg from redis, no plugin field: %s", msg) - - return j, emsg - } - - if exist := j.Mode; exist == "" { - emsg := fmt.Errorf("incorrect msg from redis, no mode field: %s", msg) - - return j, emsg - } - - // j.Misc.Answer может и не быть, тогда ответа на такое сообщение не будет - // j.Misc.Botnick тоже можно не передавать, тогда будет записана пустая строка - // j.Misc.Csign если нам его не передали, возьмём значение из конфига - if exist := j.Misc.Csign; exist == "" { - j.Misc.Csign = cfg.Csign - } - - // j.Misc.Fwdcnt если нам его не передали, то будет 0 - if exist := j.Misc.Fwdcnt; exist == 0 { - j.Misc.Fwdcnt = 1 - } - - // j.Misc.GoodMorning может быть быть 1 или 0, по-умолчанию 0 - // j.Misc.Msgformat может быть быть 1 или 0, по-умолчанию 0 - // j.Misc.Username можно не передавать, тогда будет пустая строка - return j, nil -} - /* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/internal/config/config.go b/internal/webapp/ReadConfig.go similarity index 60% rename from internal/config/config.go rename to internal/webapp/ReadConfig.go index 1e9019c..d11f07f 100644 --- a/internal/config/config.go +++ b/internal/webapp/ReadConfig.go @@ -1,54 +1,24 @@ -package config +package webapp import ( - "aleesa-webapp-go/internal/defaults" + "aleesa-webapp-go/internal/config" "aleesa-webapp-go/internal/log" - "bufio" "encoding/json" "fmt" "os" "unsafe" "github.com/cockroachdb/pebble" - "github.com/go-redis/redis/v8" "github.com/hjson/hjson-go" ) -// MyConfig структурка, описывающая конфиг. -type MyConfig struct { - Server string `json:"server,omitempty"` - Port int `json:"port,omitempty"` - Timeout int `json:"timeout,omitempty"` - Loglevel string `json:"loglevel,omitempty"` - Log string `json:"log,omitempty"` - Channel string `json:"channel,omitempty"` - DataDir string `json:"datadir,omitempty"` - Csign string `json:"csign,omitempty"` - ForwardsMax int64 `json:"forwards_max,omitempty"` - OpenWeatherMap struct { - Enabled bool `json:"enabled,omitempty"` - Country bool `json:"country,omitempty"` - Appid string `json:"appid,omitempty"` - } `json:"openweathermap,omitempty"` - Flickr struct { - Enabled bool `json:"enabled,omitempty"` - Key string `json:"key,omitempty"` - Secret string `json:"secret,omitempty"` - OAuthToken string `json:"oauth_token,omitempty"` - OAuthTokenSecret string `json:"oauth_token_secret,omitempty"` - } `json:"flickr,omitempty"` - UserAgents []string `json:"user_agents,omitempty"` - PcacheDB map[string]*pebble.DB - RedisClient *redis.Client -} - // ReadConfig читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге // нет. -func ReadConfig() (*MyConfig, error) { - locations, err := defaults.DefaultConfigFileLocations() +func ReadConfig() error { + locations, err := DefaultConfigFileLocations() if err != nil { - return &MyConfig{}, err + return err } for _, location := range locations { @@ -60,7 +30,7 @@ func ReadConfig() (*MyConfig, error) { } // Конфиг-файл длинноват для конфига, попробуем следующего кандидата - if fileInfo.Size() > defaults.ConfigFileSize { + if fileInfo.Size() > ConfigFileSize { log.Warnf("Config file %s is too long for config, skipping", location) continue @@ -79,7 +49,7 @@ func ReadConfig() (*MyConfig, error) { // Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку // сериализуем в json, а потом json преврщааем в стркутурку. Не очень эффективно, но он и не часто требуется. var ( - sampleConfig *MyConfig + sampleConfig *config.MyConfig tmp map[string]interface{} ) @@ -109,19 +79,19 @@ func ReadConfig() (*MyConfig, error) { // Валидируем значения из конфига if sampleConfig.Server == "" { - sampleConfig.Server = defaults.Host + sampleConfig.Server = Host } if sampleConfig.Port == 0 { - sampleConfig.Port = defaults.RedisPort + sampleConfig.Port = RedisPort } if sampleConfig.Timeout == 0 { - sampleConfig.Timeout = defaults.NetworkTimeout + sampleConfig.Timeout = NetworkTimeout } if sampleConfig.Loglevel == "" { - sampleConfig.Loglevel = defaults.Loglevel + sampleConfig.Loglevel = Loglevel } // sampleConfig.Log = "" if not set @@ -133,7 +103,7 @@ func ReadConfig() (*MyConfig, error) { } if sampleConfig.DataDir == "" { - sampleConfig.DataDir = defaults.DataDir + sampleConfig.DataDir = DataDir } if sampleConfig.Csign == "" { @@ -143,7 +113,7 @@ func ReadConfig() (*MyConfig, error) { } if sampleConfig.ForwardsMax == 0 { - sampleConfig.ForwardsMax = defaults.ForwardMax + sampleConfig.ForwardsMax = ForwardMax } if unsafe.Sizeof(sampleConfig.OpenWeatherMap) == 0 { @@ -165,7 +135,7 @@ func ReadConfig() (*MyConfig, error) { uaList, err := ReadLines(uaFile) if err != nil { - return &MyConfig{}, fmt.Errorf("unable to read %s: %w", uaFile, err) + return fmt.Errorf("unable to read %s: %w", uaFile, err) } sampleConfig.UserAgents = uaList @@ -174,37 +144,12 @@ func ReadConfig() (*MyConfig, error) { log.Infof("Using %s as config file", location) - return sampleConfig, nil + Config = sampleConfig + + return nil } - return &MyConfig{}, fmt.Errorf("config was not loaded! Refusing to start") -} - -// ReadLines читает даденный файл построчно в массив строк. -func ReadLines(path string) ([]string, error) { - file, err := os.Open(path) - - if err != nil { - return nil, err - } - - defer func(file *os.File) { - err := file.Close() - if err != nil { - log.Errorf("Unable to close file %s:%s", path, err) - } - }(file) - - var ( - lines []string - scanner = bufio.NewScanner(file) - ) - - for scanner.Scan() { - lines = append(lines, scanner.Text()) - } - - return lines, scanner.Err() + return fmt.Errorf("config was not loaded! Refusing to start") } /* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/internal/webapp/ReadLines.go b/internal/webapp/ReadLines.go new file mode 100644 index 0000000..baecca7 --- /dev/null +++ b/internal/webapp/ReadLines.go @@ -0,0 +1,36 @@ +package webapp + +import ( + "aleesa-webapp-go/internal/log" + "bufio" + "os" +) + +// ReadLines читает даденный файл построчно в массив строк. +func ReadLines(path string) ([]string, error) { + file, err := os.Open(path) + + if err != nil { + return nil, err + } + + defer func(file *os.File) { + err := file.Close() + if err != nil { + log.Errorf("Unable to close file %s:%s", path, err) + } + }(file) + + var ( + lines []string + scanner = bufio.NewScanner(file) + ) + + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + + return lines, scanner.Err() +} + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/internal/webapp/ValidateRmsg.go b/internal/webapp/ValidateRmsg.go new file mode 100644 index 0000000..d0c85fd --- /dev/null +++ b/internal/webapp/ValidateRmsg.go @@ -0,0 +1,66 @@ +package webapp + +import ( + "aleesa-webapp-go/internal/config" + "fmt" +) + +// ValidateRmsg валидирует входящее сообщение. +func ValidateRmsg(cfg *config.MyConfig, j RMsg, msg string) (RMsg, error) { + if exist := j.From; exist == "" { + emsg := fmt.Errorf("incorrect msg from redis, no from field: %s", msg) + + return j, emsg + } + + if exist := j.Chatid; exist == "" { + emsg := fmt.Errorf("incorrect msg from redis, no chatid field: %s", msg) + + return j, emsg + } + + if exist := j.Userid; exist == "" { + emsg := fmt.Errorf("incorrect msg from redis, no userid field: %s", msg) + + return j, emsg + } + + // j.Threadid может быть пустым, значит либо нам его не дали, либо дали пустым. Это нормально. + + if exist := j.Message; exist == "" { + emsg := fmt.Errorf("incorrect msg from redis, no message field: %s", msg) + + return j, emsg + } + + if exist := j.Plugin; exist == "" { + emsg := fmt.Errorf("incorrect msg from redis, no plugin field: %s", msg) + + return j, emsg + } + + if exist := j.Mode; exist == "" { + emsg := fmt.Errorf("incorrect msg from redis, no mode field: %s", msg) + + return j, emsg + } + + // j.Misc.Answer может и не быть, тогда ответа на такое сообщение не будет + // j.Misc.Botnick тоже можно не передавать, тогда будет записана пустая строка + // j.Misc.Csign если нам его не передали, возьмём значение из конфига + if exist := j.Misc.Csign; exist == "" { + j.Misc.Csign = cfg.Csign + } + + // j.Misc.Fwdcnt если нам его не передали, то будет 0 + if exist := j.Misc.Fwdcnt; exist == 0 { + j.Misc.Fwdcnt = 1 + } + + // j.Misc.GoodMorning может быть быть 1 или 0, по-умолчанию 0 + // j.Misc.Msgformat может быть быть 1 или 0, по-умолчанию 0 + // j.Misc.Username можно не передавать, тогда будет пустая строка + return j, nil +} + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/internal/defaults/defaults.go b/internal/webapp/defaults.go similarity index 74% rename from internal/defaults/defaults.go rename to internal/webapp/defaults.go index d6ee987..e6187bd 100644 --- a/internal/defaults/defaults.go +++ b/internal/webapp/defaults.go @@ -1,4 +1,4 @@ -package defaults +package webapp import ( "fmt" @@ -6,14 +6,6 @@ import ( "path/filepath" ) -var ForwardMax int64 = 5 -var ConfigFileSize int64 = 65535 -var DataDir = "data" -var Host = "localhost" -var Loglevel = "info" -var RedisPort = 6379 -var NetworkTimeout = 10 - func DefaultConfigFileLocations() ([]string, error) { var locations []string diff --git a/internal/webapp/globals.go b/internal/webapp/globals.go new file mode 100644 index 0000000..20d18d2 --- /dev/null +++ b/internal/webapp/globals.go @@ -0,0 +1,18 @@ +package webapp + +import ( + "aleesa-webapp-go/internal/config" +) + +var ( + ForwardMax int64 = 5 + ConfigFileSize int64 = 65535 + DataDir = "data" + Host = "localhost" + Loglevel = "info" + RedisPort = 6379 + NetworkTimeout = 10 + Config *config.MyConfig +) + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/internal/types/types.go b/internal/webapp/types.go similarity index 99% rename from internal/types/types.go rename to internal/webapp/types.go index 4d9dd38..7d13d24 100644 --- a/internal/types/types.go +++ b/internal/webapp/types.go @@ -1,4 +1,4 @@ -package types +package webapp // RMsg структурка, описывающая входящее сообщение из pubsub-канала redis-ки. type RMsg struct {