Complete foundational data layer with repository implementation
- 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
This commit is contained in:
@@ -20,8 +20,22 @@ type ServerConfig struct {
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
Driver string `mapstructure:"driver"`
|
||||
Path string `mapstructure:"path"`
|
||||
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 {
|
||||
@@ -60,8 +74,26 @@ func (c *Config) Validate() error {
|
||||
errs = append(errs, fmt.Errorf("server.host: %q is not a valid IP address", c.Server.Host))
|
||||
}
|
||||
|
||||
if c.Database.Path == "" {
|
||||
errs = append(errs, errors.New("database.path: must not be empty"))
|
||||
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 == "" {
|
||||
|
||||
@@ -13,7 +13,13 @@ func defaults(v *viper.Viper) {
|
||||
v.SetDefault("server.port", 10086)
|
||||
|
||||
v.SetDefault("database.driver", "sqlite3")
|
||||
v.SetDefault("database.path", "data/mygo.db")
|
||||
v.SetDefault("database.sqlite.path", "data/mygo.db")
|
||||
v.SetDefault("database.postgres.host", "localhost")
|
||||
v.SetDefault("database.postgres.port", 5432)
|
||||
v.SetDefault("database.postgres.user", "mygo")
|
||||
v.SetDefault("database.postgres.password", "")
|
||||
v.SetDefault("database.postgres.dbname", "mygo")
|
||||
v.SetDefault("database.postgres.sslmode", "disable")
|
||||
|
||||
v.SetDefault("storage.driver", "local")
|
||||
v.SetDefault("storage.local.path", "data/files")
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestDefaults(t *testing.T) {
|
||||
{"server.host", cfg.Server.Host, "0.0.0.0"},
|
||||
{"server.port", cfg.Server.Port, 10086},
|
||||
{"database.driver", cfg.Database.Driver, "sqlite3"},
|
||||
{"database.path", cfg.Database.Path, "data/mygo.db"},
|
||||
{"database.sqlite.path", cfg.Database.SQLite.Path, "data/mygo.db"},
|
||||
{"storage.driver", cfg.Storage.Driver, "local"},
|
||||
{"storage.local.path", cfg.Storage.Local.Path, "data/files"},
|
||||
{"jwt.access_ttl", cfg.JWT.AccessTTL, "15m"},
|
||||
@@ -49,7 +49,8 @@ server:
|
||||
|
||||
database:
|
||||
driver: sqlite3
|
||||
path: /tmp/mygo.db
|
||||
sqlite:
|
||||
path: /tmp/mygo.db
|
||||
|
||||
storage:
|
||||
driver: local
|
||||
@@ -77,8 +78,8 @@ jwt:
|
||||
if cfg.Server.Port != 9090 {
|
||||
t.Errorf("server.port = %d, want %d", cfg.Server.Port, 9090)
|
||||
}
|
||||
if cfg.Database.Path != "/tmp/mygo.db" {
|
||||
t.Errorf("database.path = %q, want %q", cfg.Database.Path, "/tmp/mygo.db")
|
||||
if cfg.Database.SQLite.Path != "/tmp/mygo.db" {
|
||||
t.Errorf("database.sqlite.path = %q, want %q", cfg.Database.SQLite.Path, "/tmp/mygo.db")
|
||||
}
|
||||
if cfg.Storage.Local.Path != "/tmp/mygo-storage" {
|
||||
t.Errorf("storage.local.path = %q, want %q", cfg.Storage.Local.Path, "/tmp/mygo-storage")
|
||||
@@ -98,7 +99,7 @@ func TestEnvOverride(t *testing.T) {
|
||||
t.Setenv("MYGO_SERVER_PORT", "8080")
|
||||
t.Setenv("MYGO_SERVER_HOST", "192.168.1.1")
|
||||
t.Setenv("MYGO_JWT_SECRET", "env-secret")
|
||||
t.Setenv("MYGO_DATABASE_PATH", "/env/path/db.sqlite")
|
||||
t.Setenv("MYGO_DATABASE_SQLITE_PATH", "/env/path/db.sqlite")
|
||||
|
||||
v := New()
|
||||
cfg, err := Load(v, "")
|
||||
@@ -115,8 +116,8 @@ func TestEnvOverride(t *testing.T) {
|
||||
if cfg.JWT.Secret != "env-secret" {
|
||||
t.Errorf("jwt.secret = %q, want %q", cfg.JWT.Secret, "env-secret")
|
||||
}
|
||||
if cfg.Database.Path != "/env/path/db.sqlite" {
|
||||
t.Errorf("database.path = %q, want %q", cfg.Database.Path, "/env/path/db.sqlite")
|
||||
if cfg.Database.SQLite.Path != "/env/path/db.sqlite" {
|
||||
t.Errorf("database.sqlite.path = %q, want %q", cfg.Database.SQLite.Path, "/env/path/db.sqlite")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user