package main import ( "encoding/json" "log" "net/http" "os" "strings" "github.com/gorilla/mux" "github.com/f4bpo/rental-manager/internal/auth" "github.com/f4bpo/rental-manager/internal/calendar" "github.com/f4bpo/rental-manager/internal/category" "github.com/f4bpo/rental-manager/internal/db" "github.com/f4bpo/rental-manager/internal/document" "github.com/f4bpo/rental-manager/internal/fiscal" "github.com/f4bpo/rental-manager/internal/ical" "github.com/f4bpo/rental-manager/internal/importer" "github.com/f4bpo/rental-manager/internal/loan" "github.com/f4bpo/rental-manager/internal/property" "github.com/f4bpo/rental-manager/internal/transaction" "github.com/f4bpo/rental-manager/web" ) func corsMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { origin := r.Header.Get("Origin") if strings.Contains(origin, "localhost") { w.Header().Set("Access-Control-Allow-Origin", origin) w.Header().Set("Access-Control-Allow-Credentials", "true") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") } if r.Method == "OPTIONS" { w.WriteHeader(http.StatusNoContent) return } next.ServeHTTP(w, r) }) } func main() { database, err := db.Init("./data/rental.db") if err != nil { log.Fatalf("failed to init database: %v", err) } defer database.Close() if err := db.Migrate(database); err != nil { log.Fatalf("failed to run migrations: %v", err) } propertyStore := property.NewStore(database) transactionStore := transaction.NewStore(database) documentStore := document.NewStore(database) documentStore.Migrate() calendarStore := calendar.NewStore(database) userStore := auth.NewStore(database) categoryStore := category.NewStore(database) if userStore.Count() == 0 { u, err := userStore.Create("admin@rental.local", "Administrateur", "admin1234") if err != nil { log.Printf("⚠ impossible de créer l'utilisateur par défaut: %v", err) } else { log.Printf("✓ Utilisateur par défaut créé — email: %s / mdp: admin1234", u.Email) } } loanStore := loan.NewStore(database) loanStore.Migrate() // Seed tableaux d'amortissement si pas encore importés loans, _ := loanStore.ListLoans("") if len(loans) == 0 { log.Println("ℹ Tableaux d'amortissement non encore configurés.") log.Println(" Ajoutez vos prêts dans la page Prêts et uploadez les échéances.") } icalService := ical.NewService(calendarStore, propertyStore) icalService.StartSync() authHandler := auth.NewHandler(userStore) propertyHandler := property.NewHandler(propertyStore) transactionHandler := transaction.NewHandler(transactionStore) documentHandler := document.NewHandler(documentStore, "./data/documents") calendarHandler := calendar.NewHandler(calendarStore) fiscalHandler := fiscal.NewHandler(transactionStore, documentStore) categoryHandler := category.NewHandler(categoryStore) importHandler := importer.NewHandler(database) loanHandler := loan.NewHandler(loanStore) r := mux.NewRouter() r.Use(corsMiddleware) api := r.PathPrefix("/api").Subrouter() api.HandleFunc("/auth/login", authHandler.Login).Methods("POST", "OPTIONS") api.HandleFunc("/auth/logout", authHandler.Logout).Methods("POST", "OPTIONS") api.HandleFunc("/auth/register", func(w http.ResponseWriter, r *http.Request) { if userStore.Count() > 0 { auth.Middleware(userStore)(http.HandlerFunc(authHandler.Register)).ServeHTTP(w, r) return } authHandler.Register(w, r) }).Methods("POST", "OPTIONS") protected := api.NewRoute().Subrouter() protected.Use(auth.Middleware(userStore)) // Profil & utilisateurs protected.HandleFunc("/me", authHandler.Me).Methods("GET") protected.HandleFunc("/me", authHandler.UpdateProfile).Methods("PUT") protected.HandleFunc("/me/password", authHandler.UpdatePassword).Methods("PUT") protected.HandleFunc("/users", authHandler.ListUsers).Methods("GET") protected.HandleFunc("/users/{id}", authHandler.DeleteUser).Methods("DELETE") // Catégories protected.HandleFunc("/categories", categoryHandler.List).Methods("GET") protected.HandleFunc("/categories", categoryHandler.Create).Methods("POST") protected.HandleFunc("/categories/{id}", categoryHandler.Update).Methods("PUT") protected.HandleFunc("/categories/{id}", categoryHandler.Delete).Methods("DELETE") // Biens protected.HandleFunc("/properties", propertyHandler.List).Methods("GET") protected.HandleFunc("/properties", propertyHandler.Create).Methods("POST") protected.HandleFunc("/properties/{id}", propertyHandler.Get).Methods("GET") protected.HandleFunc("/properties/{id}", propertyHandler.Update).Methods("PUT") protected.HandleFunc("/properties/{id}", propertyHandler.Delete).Methods("DELETE") // Transactions protected.HandleFunc("/transactions", transactionHandler.List).Methods("GET") protected.HandleFunc("/transactions", transactionHandler.Create).Methods("POST") protected.HandleFunc("/transactions/summary", transactionHandler.Summary).Methods("GET") protected.HandleFunc("/transactions/monthly", transactionHandler.Monthly).Methods("GET") protected.HandleFunc("/transactions/categories", transactionHandler.CategoryBreakdown).Methods("GET") protected.HandleFunc("/transactions/{id}", transactionHandler.Get).Methods("GET") protected.HandleFunc("/transactions/{id}", transactionHandler.Update).Methods("PUT") protected.HandleFunc("/transactions/{id}", transactionHandler.Delete).Methods("DELETE") protected.HandleFunc("/transactions/{id}/split", transactionHandler.SplitTransaction).Methods("POST") // Import QIF protected.HandleFunc("/import/preview", importHandler.Preview).Methods("POST") protected.HandleFunc("/import/check", importHandler.Check).Methods("POST") protected.HandleFunc("/import/qif", importHandler.Import).Methods("POST") // Documents protected.HandleFunc("/documents", documentHandler.List).Methods("GET") protected.HandleFunc("/documents", documentHandler.Upload).Methods("POST") protected.HandleFunc("/documents/export", documentHandler.Export).Methods("GET") protected.HandleFunc("/documents/{id}", documentHandler.Get).Methods("GET") protected.HandleFunc("/documents/{id}", documentHandler.Delete).Methods("DELETE") protected.HandleFunc("/documents/{id}/download", documentHandler.Download).Methods("GET") // Calendrier protected.HandleFunc("/calendar", calendarHandler.List).Methods("GET") protected.HandleFunc("/calendar", calendarHandler.CreateEvent).Methods("POST") protected.HandleFunc("/calendar/stats", calendarHandler.Stats).Methods("GET") protected.HandleFunc("/calendar/sync", func(w http.ResponseWriter, r *http.Request) { results := icalService.SyncAll() if results == nil { results = []ical.SyncResult{} } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(results) }).Methods("POST") protected.HandleFunc("/calendar/{id}", calendarHandler.UpdateEvent).Methods("PUT") protected.HandleFunc("/calendar/{id}", calendarHandler.DeleteEvent).Methods("DELETE") // Prêts immobiliers protected.HandleFunc("/loans", loanHandler.ListLoans).Methods("GET") protected.HandleFunc("/loans", loanHandler.CreateLoan).Methods("POST") // Routes statiques AVANT les routes avec {id} protected.HandleFunc("/loans/split", loanHandler.GetSplitForAmount).Methods("GET") protected.HandleFunc("/loans/upload-pdf", loanHandler.UploadPDF).Methods("POST") protected.HandleFunc("/loans/create", loanHandler.CreateLoanManual).Methods("POST") // Routes avec paramètre {id} protected.HandleFunc("/loans/{id}", loanHandler.DeleteLoan).Methods("DELETE") protected.HandleFunc("/loans/{id}/lines", loanHandler.GetLines).Methods("GET") protected.HandleFunc("/loans/{id}/lines", loanHandler.UploadLines).Methods("POST") protected.HandleFunc("/loans/{id}/split", loanHandler.SplitByDate).Methods("GET") protected.HandleFunc("/loans/{id}/summary", loanHandler.AnnualSummary).Methods("GET") protected.HandleFunc("/loans/{id}/reload", loanHandler.ReloadLines).Methods("POST") // Export fiscal protected.HandleFunc("/fiscal/export", fiscalHandler.Export).Methods("GET") protected.HandleFunc("/fiscal/summary", fiscalHandler.Summary).Methods("GET") // Arrêt du serveur protected.HandleFunc("/shutdown", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) go func() { os.Exit(0) }() }).Methods("POST") // Frontend embarqué assets, err := web.Assets() if err != nil { log.Fatalf("embed frontend: %v", err) } fileServer := http.FileServer(http.FS(assets)) r.PathPrefix("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { path := strings.TrimPrefix(r.URL.Path, "/") if path == "" { path = "index.html" } if _, err := assets.Open(path); err != nil { r.URL.Path = "/" } fileServer.ServeHTTP(w, r) }) port := os.Getenv("PORT") if port == "" { port = "9000" } log.Printf("🏠 Rental Manager démarré sur http://localhost:%s", port) log.Fatal(http.ListenAndServe(":"+port, r)) }