initial commit
This commit is contained in:
0
internal/api/api.swagger.yaml
Normal file
0
internal/api/api.swagger.yaml
Normal file
217
internal/config/config.go
Normal file
217
internal/config/config.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/knadh/koanf/parsers/toml"
|
||||
"github.com/knadh/koanf/providers/env"
|
||||
"github.com/knadh/koanf/providers/file"
|
||||
"github.com/knadh/koanf/v2"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Logiflow struct {
|
||||
LogLevel slog.Level `koanf:"logLevel"`
|
||||
} `koanf:"logiflow"`
|
||||
|
||||
Database struct {
|
||||
Host string `koanf:"host"`
|
||||
Port int `koanf:"port"`
|
||||
User string `koanf:"user"`
|
||||
Password string `koanf:"password"`
|
||||
Name string `koanf:"name"`
|
||||
SslMode string `koanf:"sslmode"`
|
||||
URL string
|
||||
} `koanf:"database"`
|
||||
|
||||
Server struct {
|
||||
Host string `koanf:"host"`
|
||||
Port int `koanf:"port"`
|
||||
ReadTimeout string `koanf:"readTimeout"`
|
||||
WriteTimeout string `koanf:"writeTimeout"`
|
||||
IdleTimeout string `koanf:"idleTimeout"`
|
||||
ReadTimeoutDur time.Duration
|
||||
WriteTimeoutDur time.Duration
|
||||
IdleTimeoutDur time.Duration
|
||||
URL string
|
||||
} `koanf:"server"`
|
||||
|
||||
Redis struct {
|
||||
Addr string `koanf:"addr"`
|
||||
Password string `koanf:"password"`
|
||||
DB int `koanf:"db"`
|
||||
RefreshTokenTTL string `koanf:"refreshTokenTTL"`
|
||||
AccessTokenTTL string `koanf:"accessTokenTTL"`
|
||||
RefreshTokenDur time.Duration
|
||||
AccessTokenDur time.Duration
|
||||
} `koanf:"redis"`
|
||||
|
||||
JwtOpt struct {
|
||||
Key string `koanf:"key"`
|
||||
Issuer string `koanf:"issuer"`
|
||||
Audience string `koanf:"audience"`
|
||||
} `koanf:"jwt"`
|
||||
}
|
||||
|
||||
func NewConfig(ctx context.Context, configPath string) (*Config, error) {
|
||||
if err := godotenv.Load(); err != nil && !os.IsNotExist(err) {
|
||||
slog.WarnContext(ctx, "Не удалось загрузить .env (возможно, файла нет)", "error", err)
|
||||
}
|
||||
|
||||
k := koanf.New(".")
|
||||
|
||||
if err := k.Load(env.Provider("HANDBOOKS_", ".", func(s string) string {
|
||||
return strings.ReplaceAll(strings.ToLower(strings.TrimPrefix(s, "HANDBOOKS_")), "_", ".")
|
||||
}), nil); err != nil {
|
||||
return nil, fmt.Errorf("ошибка загрузки ENV: %w", err)
|
||||
}
|
||||
|
||||
if err := k.Load(file.Provider(configPath), toml.Parser()); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("не удалось прочитать config.toml: %w", err)
|
||||
}
|
||||
slog.InfoContext(ctx, "config.toml не найден — используем только ENV и дефолты")
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
if err := k.Unmarshal("", &cfg); err != nil {
|
||||
return nil, fmt.Errorf("не удалось размапить конфигурацию: %w", err)
|
||||
}
|
||||
|
||||
cfg.setDefaults()
|
||||
|
||||
if err := cfg.parseDurations(); err != nil {
|
||||
return nil, fmt.Errorf("ошибка парсинга длительностей: %w", err)
|
||||
}
|
||||
|
||||
if err := cfg.validate(); err != nil {
|
||||
return nil, fmt.Errorf("конфигурация невалидна: %w", err)
|
||||
}
|
||||
|
||||
cfg.Server.URL = fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)
|
||||
cfg.Database.URL = cfg.makePostgresURL()
|
||||
|
||||
slog.InfoContext(ctx, "Конфигурация загружена успешно",
|
||||
slog.String("db_host", cfg.Database.Host),
|
||||
slog.String("db_user", cfg.Database.User),
|
||||
slog.String("db_pass", maskSecret(cfg.Database.Password)),
|
||||
slog.String("db_name", cfg.Database.Name),
|
||||
slog.String("redis_addr", cfg.Redis.Addr),
|
||||
slog.String("log_level", cfg.Logiflow.LogLevel.String()),
|
||||
)
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func maskSecret(s string) string {
|
||||
if s == "" {
|
||||
return "<empty>"
|
||||
}
|
||||
if len(s) <= 4 {
|
||||
return "****"
|
||||
}
|
||||
return s[:2] + strings.Repeat("*", len(s)-4) + s[len(s)-2:]
|
||||
}
|
||||
|
||||
func (c *Config) makePostgresURL() string {
|
||||
return fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s",
|
||||
c.Database.User,
|
||||
c.Database.Password,
|
||||
c.Database.Host,
|
||||
c.Database.Port,
|
||||
c.Database.Name,
|
||||
c.Database.SslMode,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Config) setDefaults() {
|
||||
if c.Logiflow.LogLevel == 0 {
|
||||
c.Logiflow.LogLevel = slog.LevelInfo
|
||||
}
|
||||
if c.Database.Host == "" {
|
||||
c.Database.Host = "localhost"
|
||||
}
|
||||
if c.Database.Port == 0 {
|
||||
c.Database.Port = 5432
|
||||
}
|
||||
if c.Database.SslMode == "" {
|
||||
c.Database.SslMode = "disable"
|
||||
}
|
||||
if c.Server.Host == "" {
|
||||
c.Server.Host = "0.0.0.0"
|
||||
}
|
||||
if c.Server.Port == 0 {
|
||||
c.Server.Port = 3001
|
||||
}
|
||||
if c.Redis.Addr == "" {
|
||||
c.Redis.Addr = "localhost:6379"
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) parseDurations() error {
|
||||
var err error
|
||||
|
||||
parse := func(name, s string) (time.Duration, error) {
|
||||
d, e := time.ParseDuration(s)
|
||||
if e != nil {
|
||||
return 0, fmt.Errorf("%s %q: %w", name, s, e)
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
c.Server.ReadTimeoutDur, err = parse("readTimeout", c.Server.ReadTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Server.WriteTimeoutDur, err = parse("writeTimeout", c.Server.WriteTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Server.IdleTimeoutDur, err = parse("idleTimeout", c.Server.IdleTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Redis.RefreshTokenDur, err = parse("refreshTokenTTL", c.Redis.RefreshTokenTTL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Redis.AccessTokenDur, err = parse("accessTokenTTL", c.Redis.AccessTokenTTL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) validate() error {
|
||||
if c.Database.Host == "" {
|
||||
return fmt.Errorf("database.host обязателен")
|
||||
}
|
||||
if c.Database.User == "" {
|
||||
return fmt.Errorf("database.user обязателен")
|
||||
}
|
||||
if c.Database.Password == "" {
|
||||
return fmt.Errorf("database.password обязателен")
|
||||
}
|
||||
if c.Database.Name == "" {
|
||||
return fmt.Errorf("database.name обязателен")
|
||||
}
|
||||
if c.JwtOpt.Key == "" {
|
||||
return fmt.Errorf("jwt.key обязателен")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) DatabaseURL() string { return c.Database.URL }
|
||||
func (c *Config) ServerURL() string { return c.Server.URL }
|
||||
func (c *Config) ReadTimeout() time.Duration { return c.Server.ReadTimeoutDur }
|
||||
func (c *Config) WriteTimeout() time.Duration { return c.Server.WriteTimeoutDur }
|
||||
func (c *Config) IdleTimeout() time.Duration { return c.Server.IdleTimeoutDur }
|
||||
func (c *Config) RedisAccessTokenDur() time.Duration { return c.Redis.AccessTokenDur }
|
||||
func (c *Config) RedisRefreshTokenDur() time.Duration { return c.Redis.RefreshTokenDur }
|
||||
5
internal/database/postgres.go
Normal file
5
internal/database/postgres.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package database
|
||||
|
||||
import "github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
func NewConnectionPool(connectionString string) (*pgxpool.Pool, error)
|
||||
26
internal/database/redis.go
Normal file
26
internal/database/redis.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"github.com/anxi0uz/logiflow/internal/config"
|
||||
"github.com/redis/go-redis"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func NewRedisConnection(ctx context.Context, cfg *config.Config) (*redis.Client, error) {
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: cfg.Redis.Addr,
|
||||
Password: cfg.Redis.Password,
|
||||
DB: cfg.Redis.DB,
|
||||
})
|
||||
|
||||
_, err := rdb.Ping().Result()
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Cant ping redis", slog.String("Error", err.Error()))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rdb, nil
|
||||
}
|
||||
Reference in New Issue
Block a user