- Add GORM dependencies for SQLite and PostgreSQL - Create domain models (User, Session, File) with common errors - Implement repository interfaces and database layer with migrations - Update WebApp to bootstrap with database and repositories - Add comprehensive unit tests for repository methods - Update config structure to support multiple database drivers - Extend AGENTS.md with debugging principles and dependency rules
117 lines
3.2 KiB
Go
117 lines
3.2 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"time"
|
|
)
|
|
|
|
type Config struct {
|
|
Server ServerConfig `mapstructure:"server"`
|
|
Database DatabaseConfig `mapstructure:"database"`
|
|
Storage StorageConfig `mapstructure:"storage"`
|
|
JWT JWTConfig `mapstructure:"jwt"`
|
|
}
|
|
|
|
type ServerConfig struct {
|
|
Host string `mapstructure:"host"`
|
|
Port int `mapstructure:"port"`
|
|
}
|
|
|
|
type DatabaseConfig struct {
|
|
Driver string `mapstructure:"driver"`
|
|
SQLite SQLiteConfig `mapstructure:"sqlite"`
|
|
Postgres PostgresConfig `mapstructure:"postgres"`
|
|
}
|
|
|
|
type SQLiteConfig struct {
|
|
Path string `mapstructure:"path"`
|
|
}
|
|
|
|
type PostgresConfig struct {
|
|
Host string `mapstructure:"host"`
|
|
Port int `mapstructure:"port"`
|
|
User string `mapstructure:"user"`
|
|
Password string `mapstructure:"password"`
|
|
DBName string `mapstructure:"dbname"`
|
|
SSLMode string `mapstructure:"sslmode"`
|
|
}
|
|
|
|
type StorageConfig struct {
|
|
Driver string `mapstructure:"driver"`
|
|
Local LocalStorageConfig `mapstructure:"local"`
|
|
}
|
|
|
|
type LocalStorageConfig struct {
|
|
Path string `mapstructure:"path"`
|
|
}
|
|
|
|
type JWTConfig struct {
|
|
Secret string `mapstructure:"secret"`
|
|
AccessTTL string `mapstructure:"access_ttl"`
|
|
RefreshTTL string `mapstructure:"refresh_ttl"`
|
|
}
|
|
|
|
func (j JWTConfig) AccessDuration() time.Duration {
|
|
d, _ := time.ParseDuration(j.AccessTTL)
|
|
return d
|
|
}
|
|
|
|
func (j JWTConfig) RefreshDuration() time.Duration {
|
|
d, _ := time.ParseDuration(j.RefreshTTL)
|
|
return d
|
|
}
|
|
|
|
func (c *Config) Validate() error {
|
|
var errs []error
|
|
|
|
if c.Server.Port < 1 || c.Server.Port > 65535 {
|
|
errs = append(errs, fmt.Errorf("server.port: %d out of range [1, 65535]", c.Server.Port))
|
|
}
|
|
|
|
if addr := net.ParseIP(c.Server.Host); c.Server.Host != "" && addr == nil {
|
|
errs = append(errs, fmt.Errorf("server.host: %q is not a valid IP address", c.Server.Host))
|
|
}
|
|
|
|
switch c.Database.Driver {
|
|
case "sqlite3":
|
|
if c.Database.SQLite.Path == "" {
|
|
errs = append(errs, errors.New("database.sqlite.path: must not be empty"))
|
|
}
|
|
case "postgres":
|
|
if c.Database.Postgres.Host == "" {
|
|
errs = append(errs, errors.New("database.postgres.host: must not be empty"))
|
|
}
|
|
if c.Database.Postgres.Port < 1 || c.Database.Postgres.Port > 65535 {
|
|
errs = append(errs, fmt.Errorf("database.postgres.port: %d out of range [1, 65535]", c.Database.Postgres.Port))
|
|
}
|
|
if c.Database.Postgres.User == "" {
|
|
errs = append(errs, errors.New("database.postgres.user: must not be empty"))
|
|
}
|
|
if c.Database.Postgres.DBName == "" {
|
|
errs = append(errs, errors.New("database.postgres.dbname: must not be empty"))
|
|
}
|
|
default:
|
|
errs = append(errs, fmt.Errorf("database.driver: %q is not supported (use sqlite3 or postgres)", c.Database.Driver))
|
|
}
|
|
|
|
if c.Storage.Local.Path == "" {
|
|
errs = append(errs, errors.New("storage.local.path: must not be empty"))
|
|
}
|
|
|
|
if c.JWT.Secret == "" {
|
|
errs = append(errs, errors.New("jwt.secret: must not be empty"))
|
|
}
|
|
|
|
if _, err := time.ParseDuration(c.JWT.AccessTTL); err != nil {
|
|
errs = append(errs, fmt.Errorf("jwt.access_ttl: %w", err))
|
|
}
|
|
|
|
if _, err := time.ParseDuration(c.JWT.RefreshTTL); err != nil {
|
|
errs = append(errs, fmt.Errorf("jwt.refresh_ttl: %w", err))
|
|
}
|
|
|
|
return errors.Join(errs...)
|
|
}
|