All configuration (including secrets) now lives in a single file: bin/config.yaml. The separate .env file has been eliminated. Changes: - config.go: Added SMTPSettings struct + AI.APIKey to Config; removed godotenv import, Environment struct, and all os.Getenv() calls - config.yaml: Added smtp section (host/port/username/password) and ai.api_key field with placeholder values - main.go: Reads SMTP and API key from cfg instead of env - smtp.go: Changed Port field from string to int - otp_test.go: Updated Port values to int - .env.example: Deleted (all config is in config.yaml) - .gitignore: Removed .env.example; kept .env for safety - go.mod/go.sum: Removed github.com/joho/godotenv dependency - install.sh: No longer creates .env or uses EnvironmentFile; warns about placeholder values in config.yaml instead
178 lines
No EOL
4.8 KiB
Go
178 lines
No EOL
4.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"inboxer/src/internal/ai"
|
|
"inboxer/src/internal/auth"
|
|
"inboxer/src/internal/db"
|
|
"inboxer/src/internal/web"
|
|
"inboxer/src/internal/worker"
|
|
"inboxer/src/pkg/config"
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
func main() {
|
|
// Load configuration
|
|
configPath, err := config.GetDefaultConfigPath()
|
|
if err != nil {
|
|
configPath = "bin/config.yaml"
|
|
}
|
|
|
|
cfg, err := config.LoadConfig(configPath)
|
|
if err != nil {
|
|
log.Fatalf("Failed to load configuration: %v", err)
|
|
}
|
|
|
|
// Initialize database
|
|
database, err := db.NewDatabase(cfg.Database.Path, cfg.Server.SessionSecret)
|
|
if err != nil {
|
|
log.Fatalf("Failed to initialize database: %v", err)
|
|
}
|
|
defer database.Close()
|
|
|
|
// Initialize SMTP sender (credentials from config.yaml)
|
|
smtpConfig := auth.SMTPConfig{
|
|
Host: cfg.SMTP.Host,
|
|
Port: cfg.SMTP.Port,
|
|
Username: cfg.SMTP.Username,
|
|
Password: cfg.SMTP.Password,
|
|
From: cfg.SMTP.Username, // From address matches SMTP username
|
|
}
|
|
|
|
// Validate SMTP config (but don't fail if not set — user might configure later)
|
|
if err := smtpConfig.ValidateConfig(); err != nil {
|
|
log.Printf("Warning: SMTP configuration incomplete: %v", err)
|
|
log.Println("OTP emails will not be sent until SMTP is configured in config.yaml")
|
|
}
|
|
|
|
smtpSender := auth.NewSMTPSender(smtpConfig)
|
|
|
|
// Initialize session manager
|
|
sessionManager := auth.NewSessionManager(cfg.Server.SessionSecret)
|
|
// In development, allow non-HTTPS
|
|
if os.Getenv("APP_ENV") == "development" {
|
|
sessionManager.UpdateSessionOptions(false, 86400*7)
|
|
}
|
|
|
|
// Initialize OTP store (database-backed)
|
|
otpStore := db.NewDatabaseOTPStore(database)
|
|
|
|
// Initialize auth service
|
|
authService := auth.NewAuthService(smtpSender, sessionManager, otpStore)
|
|
|
|
// Initialize AI classifier
|
|
deepSeekAPI := ai.NewDeepSeekAPI(
|
|
cfg.AI.APIKey,
|
|
cfg.AI.Model,
|
|
cfg.AI.MaxTokens,
|
|
cfg.AI.Temperature,
|
|
)
|
|
var classifier worker.AIClassifier
|
|
classifier, err = ai.NewClassifier(deepSeekAPI, cfg.AI.PromptFile)
|
|
if err != nil {
|
|
log.Printf("Warning: AI classifier initialization failed: %v", err)
|
|
log.Println("Falling back to placeholder classifier (all emails -> Other)")
|
|
classifier = worker.NewPlaceholderClassifier(cfg.Folders.Other)
|
|
}
|
|
|
|
// Create background worker (not started yet — handlers need a reference first)
|
|
bgWorker := worker.NewWorker(database, cfg, classifier)
|
|
|
|
// Initialize web handlers (needs worker for ProcessNow)
|
|
handler, err := web.NewHandler(authService, database, cfg, bgWorker)
|
|
if err != nil {
|
|
log.Fatalf("Failed to initialize web handlers: %v", err)
|
|
}
|
|
|
|
// Setup router
|
|
router := mux.NewRouter()
|
|
handler.RegisterRoutes(router)
|
|
|
|
// Add middleware
|
|
router.Use(loggingMiddleware)
|
|
router.Use(authMiddleware(authService))
|
|
|
|
// Create HTTP server
|
|
server := &http.Server{
|
|
Addr: fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port),
|
|
Handler: router,
|
|
ReadTimeout: 10 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
IdleTimeout: 60 * time.Second,
|
|
}
|
|
|
|
// Start background worker (now that everything is wired up)
|
|
bgWorker.Start()
|
|
|
|
// Start server in goroutine
|
|
go func() {
|
|
log.Printf("Starting server on %s", server.Addr)
|
|
log.Printf("Access the application at http://localhost:%d", cfg.Server.Port)
|
|
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
log.Fatalf("Server error: %v", err)
|
|
}
|
|
}()
|
|
|
|
// Wait for interrupt signal
|
|
stop := make(chan os.Signal, 1)
|
|
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
|
|
<-stop
|
|
|
|
log.Println("Shutting down...")
|
|
|
|
// Stop background worker first
|
|
bgWorker.Stop()
|
|
|
|
// Give server time to shutdown gracefully
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
if err := server.Shutdown(ctx); err != nil {
|
|
log.Printf("Server shutdown error: %v", err)
|
|
}
|
|
|
|
log.Println("Server stopped")
|
|
}
|
|
|
|
// loggingMiddleware logs HTTP requests
|
|
func loggingMiddleware(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 %v", r.Method, r.URL.Path, time.Since(start))
|
|
})
|
|
}
|
|
|
|
// authMiddleware checks authentication for protected routes
|
|
func authMiddleware(authService *auth.AuthService) mux.MiddlewareFunc {
|
|
protectedPaths := map[string]bool{
|
|
"/dashboard": true,
|
|
"/settings": true,
|
|
"/logout": true,
|
|
"/toggle-test-mode": true,
|
|
"/test-connection": true,
|
|
"/process-now": true,
|
|
}
|
|
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Check if path is protected
|
|
if protectedPaths[r.URL.Path] {
|
|
// Check if user is logged in
|
|
if !authService.GetSessionManager().IsLoggedIn(r) {
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
} |