package main import ( "database/sql" "errors" "log" "net/http" "os" "strings" "time" _ "github.com/go-sql-driver/mysql" ) var monthNames = []string{ "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", } type App struct { db *sql.DB } func main() { addr := readEnv("APP_ADDR", ":8080") dsn := readEnv("DB_DSN", "farmcal:farmcal@tcp(127.0.0.1:3306)/farmcal?parseTime=true") db, err := sql.Open("mysql", dsn) if err != nil { log.Fatalf("db open failed: %v", err) } defer db.Close() if err := db.Ping(); err != nil { log.Fatalf("db ping failed: %v", err) } if err := ensureSchema(db); err != nil { log.Fatalf("schema setup failed: %v", err) } app := &App{db: db} mux := http.NewServeMux() mux.HandleFunc("/", app.handleDashboard) mux.HandleFunc("/fields", app.handleFieldsPage) mux.HandleFunc("/crops", app.handleCropsPage) mux.HandleFunc("/planning", app.handlePlanningPage) mux.HandleFunc("/general", app.handleGeneralPage) mux.HandleFunc("/settings/days", app.handleSetDaysPerMonth) mux.HandleFunc("/settings/time", app.handleSetCurrentTime) mux.HandleFunc("/fields/create", app.handleCreateField) mux.HandleFunc("/fields/update", app.handleUpdateField) mux.HandleFunc("/fields/delete", app.handleDeleteField) mux.HandleFunc("/field-groups/create", app.handleCreateFieldGroup) mux.HandleFunc("/field-groups/delete", app.handleDeleteFieldGroup) mux.HandleFunc("/crops/create", app.handleCreateCrop) mux.HandleFunc("/crops/update", app.handleUpdateCrop) mux.HandleFunc("/crops/delete", app.handleDeleteCrop) mux.HandleFunc("/products/create", app.handleCreateProduct) mux.HandleFunc("/products/update", app.handleUpdateProduct) mux.HandleFunc("/products/delete", app.handleDeleteProduct) mux.HandleFunc("/crop-steps/create", app.handleCreateCropStep) mux.HandleFunc("/crop-steps/delete", app.handleDeleteCropStep) mux.HandleFunc("/task-templates/create", app.handleCreateTaskTemplate) mux.HandleFunc("/custom-tasks/create", app.handleCreateCustomTask) mux.HandleFunc("/custom-tasks/delete", app.handleDeleteCustomTask) mux.HandleFunc("/plans/create", app.handleCreatePlan) mux.HandleFunc("/plans/delete", app.handleDeletePlan) mux.HandleFunc("/tasks/toggle", app.handleToggleTaskDone) mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) srv := &http.Server{ Addr: addr, Handler: withLogging(mux), ReadHeaderTimeout: 5 * time.Second, } log.Printf("FarmCal listening on %s", addr) if err := srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { log.Fatalf("server stopped: %v", err) } } func readEnv(key, fallback string) string { v := strings.TrimSpace(os.Getenv(key)) if v == "" { return fallback } return v } func withLogging(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() next.ServeHTTP(w, r) log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start).Round(time.Millisecond)) }) }