package main import ( "context" "encoding/json" "fmt" "log" "net/http" "os" "os/signal" "path/filepath" "syscall" "time" "wikiapiserver/api" "wikiapiserver/db" ) // Config is loaded from config.json at startup. type Config struct { Database struct { Host string `json:"host"` Username string `json:"username"` Password string `json:"password"` Name string `json:"name"` } `json:"database"` Server struct { Port int `json:"port"` } `json:"server"` } func loadConfig() (*Config, error) { // Try CWD first (works for go run), then executable dir (works for built binary) for _, dir := range []string{".", ""} { if dir == "" { if exe, err := os.Executable(); err == nil { dir = filepath.Dir(exe) } else { continue } } path := filepath.Join(dir, "config.json") f, err := os.Open(path) if err != nil { continue } var cfg Config if err := json.NewDecoder(f).Decode(&cfg); err != nil { f.Close() return nil, fmt.Errorf("decode %s: %w", path, err) } f.Close() return &cfg, nil } return nil, fmt.Errorf("config.json not found in . or next to executable") } func buildDSN(cfg *Config) string { return fmt.Sprintf("%s:%s@tcp(%s)/%s?parseTime=true", cfg.Database.Username, cfg.Database.Password, cfg.Database.Host, cfg.Database.Name, ) } func main() { cfg, err := loadConfig() if err != nil { log.Fatalf("config: %v", err) } database, err := db.Connect(buildDSN(cfg)) if err != nil { log.Fatalf("db: %v", err) } defer database.Close() handler := api.NewHandler(database) mux := http.NewServeMux() mux.HandleFunc("POST /register", handler.Register) mux.HandleFunc("POST /login", handler.Login) mux.HandleFunc("POST /refresh", handler.Refresh) mux.HandleFunc("GET /health", handler.Health) mux.HandleFunc("GET /token", handler.GetToken) mux.HandleFunc("GET /article", handler.GetArticle) addr := fmt.Sprintf(":%d", cfg.Server.Port) srv := &http.Server{ Addr: addr, Handler: mux, ReadTimeout: 5 * time.Second, WriteTimeout: 45 * time.Second, IdleTimeout: 120 * time.Second, } // Graceful shutdown on SIGINT / SIGTERM ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() go func() { <-ctx.Done() log.Println("shutting down...") shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(shutdownCtx); err != nil { log.Printf("shutdown error: %v", err) } }() log.Printf("listening on %s", addr) if err := srv.ListenAndServe(); err != http.ErrServerClosed { log.Fatalf("server: %v", err) } log.Println("stopped") }