Initial implementation

This commit is contained in:
2025-12-10 11:10:52 +01:00
parent cff9defc4a
commit 4cb97a25ba
26 changed files with 2714 additions and 1 deletions

144
internal/config/config.go Normal file
View File

@@ -0,0 +1,144 @@
// Package config handles loading and validation of application configuration.
package config
import (
"fmt"
"os"
"path/filepath"
"gopkg.in/ini.v1"
)
// Config holds all application configuration.
type Config struct {
Server ServerConfig
Calendar CalendarConfig
Diary DiaryConfig
CalDAV CalDAVConfig
Photos PhotosConfig
}
// ServerConfig holds HTTP server settings.
type ServerConfig struct {
Port int
}
// CalendarConfig holds calendar ICS file settings.
type CalendarConfig struct {
LocalICS string
}
// DiaryConfig holds diary ICS file settings.
type DiaryConfig struct {
ICSFile string
}
// CalDAVConfig holds CalDAV remote calendar settings.
type CalDAVConfig struct {
Enabled bool
URL string
Username string
Password string
}
// PhotosConfig holds photo storage settings.
type PhotosConfig struct {
RootFolder string
}
// Load reads configuration from the specified INI file.
func Load(path string) (*Config, error) {
cfg, err := ini.Load(path)
if err != nil {
return nil, fmt.Errorf("failed to load config file: %w", err)
}
config := &Config{
Server: ServerConfig{
Port: cfg.Section("server").Key("port").MustInt(8080),
},
Calendar: CalendarConfig{
LocalICS: cfg.Section("calendar").Key("local_ics").String(),
},
Diary: DiaryConfig{
ICSFile: cfg.Section("diary").Key("ics_file").String(),
},
CalDAV: CalDAVConfig{
Enabled: cfg.Section("caldav").Key("enabled").MustBool(false),
URL: cfg.Section("caldav").Key("url").String(),
Username: cfg.Section("caldav").Key("username").String(),
Password: cfg.Section("caldav").Key("password").String(),
},
Photos: PhotosConfig{
RootFolder: cfg.Section("photos").Key("root_folder").String(),
},
}
return config, nil
}
// Validate checks that all required configuration values are set and valid.
func (c *Config) Validate() error {
if c.Server.Port <= 0 || c.Server.Port > 65535 {
return fmt.Errorf("invalid server port: %d", c.Server.Port)
}
// Validate calendar ICS path if specified
if c.Calendar.LocalICS != "" {
if err := validateFileReadable(c.Calendar.LocalICS); err != nil {
return fmt.Errorf("calendar ICS file: %w", err)
}
}
// Validate diary ICS path if specified
if c.Diary.ICSFile != "" {
dir := filepath.Dir(c.Diary.ICSFile)
if err := validateDirExists(dir); err != nil {
return fmt.Errorf("diary ICS directory: %w", err)
}
}
// Validate CalDAV settings if enabled
if c.CalDAV.Enabled {
if c.CalDAV.URL == "" {
return fmt.Errorf("caldav URL is required when caldav is enabled")
}
}
// Validate photos folder if specified
if c.Photos.RootFolder != "" {
if err := validateDirExists(c.Photos.RootFolder); err != nil {
return fmt.Errorf("photos root folder: %w", err)
}
}
return nil
}
func validateFileReadable(path string) error {
info, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("file does not exist: %s", path)
}
return fmt.Errorf("cannot access file: %w", err)
}
if info.IsDir() {
return fmt.Errorf("path is a directory, not a file: %s", path)
}
return nil
}
func validateDirExists(path string) error {
info, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("directory does not exist: %s", path)
}
return fmt.Errorf("cannot access directory: %w", err)
}
if !info.IsDir() {
return fmt.Errorf("path is not a directory: %s", path)
}
return nil
}