This commit is contained in:
2026-04-11 12:12:07 +02:00
parent 3bc6e2e080
commit 5b3c5ebb2f
92 changed files with 10948 additions and 35 deletions

219
cmd/server/main.go Normal file
View File

@@ -0,0 +1,219 @@
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))
}