Replace the silent hard-delete orphan cleanup (which logged only through ScannerLogger file logs and whose failure paths left rows undetected) with an archive lifecycle that preserves reading history: - A file missing in one scan is marked (missing_scan_count = 1); missing in a second consecutive scan archives it (archived_at, hidden from browsing, progress/notes/highlights survive). Every branch logs to stdout with an [ARCHIVE] prefix so skips are always visible. - When a file reappears - same path, or identical content at a new path via the SHA-256 dedup match - the archived state clears automatically and the item returns with its history intact. - Archived rows older than ARCHIVE_RETENTION_DAYS are hard-purged at scan time (cascading deletes); 0 disables auto-purge for manual-only management. Retention is read from the environment in NewMediaScanner.
98 lines
2.9 KiB
Go
98 lines
2.9 KiB
Go
package config
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
)
|
|
|
|
type Config struct {
|
|
ServerPort string
|
|
BaseURL string
|
|
JWTSecret string
|
|
UploadPath string
|
|
DatabaseHost string
|
|
DatabasePort string
|
|
DatabaseUser string
|
|
DatabasePassword string
|
|
DatabaseName string
|
|
TestMode bool
|
|
RateLimitEnabled bool
|
|
RequestsPerMinute int
|
|
}
|
|
|
|
func LoadConfig() *Config {
|
|
port := getEnv("SERVER_PORT", "8765")
|
|
return &Config{
|
|
ServerPort: port,
|
|
BaseURL: getEnv("BASE_URL", "http://localhost:"+port),
|
|
DatabaseHost: getEnv("DATABASE_HOST", "localhost"),
|
|
DatabasePort: getEnv("DATABASE_PORT", "5432"),
|
|
DatabaseUser: getEnv("DATABASE_USER", "postgres"),
|
|
DatabasePassword: getEnv("DATABASE_PASSWORD", "password"),
|
|
DatabaseName: getEnv("DATABASE_NAME", "bookhoard"),
|
|
JWTSecret: getEnv("JWT_SECRET", "your-secret-key"),
|
|
UploadPath: getEnv("UPLOAD_PATH", "./uploads"),
|
|
TestMode: getEnvBool("TEST_MODE", false),
|
|
RateLimitEnabled: getEnvBool("RATE_LIMIT_ENABLED", true),
|
|
RequestsPerMinute: getEnvInt("REQUESTS_PER_MINUTE", 10),
|
|
}
|
|
}
|
|
|
|
func (c *Config) DatabaseURL() string {
|
|
return fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable",
|
|
c.DatabaseUser, c.DatabasePassword, c.DatabaseHost, c.DatabasePort, c.DatabaseName)
|
|
}
|
|
|
|
// SystemConfigGetter returns the value for a system config key, or an error.
|
|
type SystemConfigGetter func(ctx context.Context, key string) (string, error)
|
|
|
|
// GetBaseURL returns the base URL from system configuration database, or empty
|
|
// string if not set. The getter abstraction avoids importing the database package.
|
|
func GetBaseURL(ctx context.Context, getter SystemConfigGetter) string {
|
|
val, err := getter(ctx, "base_url")
|
|
if err == nil && val != "" {
|
|
return val
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func getEnv(key, defaultValue string) string {
|
|
if value := os.Getenv(key); value != "" {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func getEnvBool(key string, defaultValue bool) bool {
|
|
if value := os.Getenv(key); value != "" {
|
|
boolVal, err := strconv.ParseBool(value)
|
|
if err == nil {
|
|
return boolVal
|
|
}
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func getEnvInt(key string, defaultValue int) int {
|
|
if value := os.Getenv(key); value != "" {
|
|
intVal, err := strconv.Atoi(value)
|
|
if err == nil {
|
|
return intVal
|
|
}
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
// ArchiveRetentionDays returns how many days a media item stays archived
|
|
// (file missing from disk for two consecutive scans) before library scans
|
|
// purge it for good. Reading progress, notes, and highlights survive the
|
|
// archive window and are restored if the file returns; the purge deletes
|
|
// them along with the row.
|
|
// Configure via ARCHIVE_RETENTION_DAYS (default 90); 0 keeps archived items
|
|
// until an admin purges them manually from the library admin page.
|
|
func ArchiveRetentionDays() int {
|
|
return getEnvInt("ARCHIVE_RETENTION_DAYS", 90)
|
|
}
|