package handler import ( "database/sql" "net/http" "dealroom/internal/fireworks" "dealroom/internal/model" "dealroom/internal/worker" ) type Handler struct { db *sql.DB config *Config extractor *worker.Extractor fw *fireworks.Client } type Config struct { BaseURL string SessionKey string K25APIURL string K25APIKey string SMTPHost string SMTPUser string SMTPPass string } func New(db *sql.DB, _ interface{}, config *Config) *Handler { fw := fireworks.NewClient() ext := worker.NewExtractor(db, fw) ext.Start() return &Handler{ db: db, config: config, extractor: ext, fw: fw, } } // enqueueExtraction submits a job to the background extraction worker. func (h *Handler) enqueueExtraction(responseID, filePath, dealID string) { if h.extractor != nil { h.extractor.Enqueue(worker.ExtractionJob{ ResponseID: responseID, FilePath: filePath, DealID: dealID, }) } } func (h *Handler) RegisterRoutes(mux *http.ServeMux) { // Static files mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) // Auth mux.HandleFunc("/login", h.handleLoginPage) mux.HandleFunc("/signup", h.handleSignupPage) mux.HandleFunc("/auth/login", h.handleLogin) mux.HandleFunc("/auth/signup", h.handleSignup) mux.HandleFunc("/auth/logout", h.handleLogout) mux.HandleFunc("/auth/view-toggle", h.requireAuth(h.handleViewToggle)) // Pages (auth required) mux.HandleFunc("/", h.requireAuth(h.handleDashboard)) mux.HandleFunc("/deals", h.requireAuth(h.handleDealRooms)) mux.HandleFunc("/deals/", h.requireAuth(h.handleDealRoom)) mux.HandleFunc("/requests", h.requireAuth(h.handleRequestList)) mux.HandleFunc("/contacts", h.requireAuth(h.handleContacts)) mux.HandleFunc("/audit", h.requireAuth(h.handleAuditLog)) mux.HandleFunc("/analytics", h.requireAuth(h.handleAnalytics)) mux.HandleFunc("/subscription", h.requireAuth(h.handleSubscription)) // Admin CRUD mux.HandleFunc("/admin", h.requireAdmin(h.handleAdmin)) mux.HandleFunc("/admin/contacts", h.requireAdmin(h.handleAdminContacts)) mux.HandleFunc("/admin/contacts/edit", h.requireAdmin(h.handleAdminContactForm)) mux.HandleFunc("/admin/contacts/save", h.requireAdmin(h.handleAdminContactSave)) mux.HandleFunc("/admin/contacts/delete", h.requireAdmin(h.handleAdminContactDelete)) mux.HandleFunc("/admin/deals", h.requireAdmin(h.handleAdminDeals)) mux.HandleFunc("/admin/deals/edit", h.requireAdmin(h.handleAdminDealForm)) mux.HandleFunc("/admin/deals/save", h.requireAdmin(h.handleAdminDealSave)) mux.HandleFunc("/admin/deals/delete", h.requireAdmin(h.handleAdminDealDelete)) mux.HandleFunc("/admin/users", h.requireAdmin(h.handleAdminUsers)) mux.HandleFunc("/admin/users/edit", h.requireAdmin(h.handleAdminUserForm)) mux.HandleFunc("/admin/users/save", h.requireAdmin(h.handleAdminUserSave)) mux.HandleFunc("/admin/users/delete", h.requireAdmin(h.handleAdminUserDelete)) mux.HandleFunc("/admin/organizations", h.requireAdmin(h.handleAdminOrgs)) mux.HandleFunc("/admin/organizations/edit", h.requireAdmin(h.handleAdminOrgForm)) mux.HandleFunc("/admin/organizations/save", h.requireAdmin(h.handleAdminOrgSave)) mux.HandleFunc("/admin/organizations/delete", h.requireAdmin(h.handleAdminOrgDelete)) // Team & Invites mux.HandleFunc("/team", h.requireAuth(h.handleTeam)) mux.HandleFunc("/invites/create", h.requireAuth(h.handleInviteCreate)) mux.HandleFunc("/invites/accept", h.handleInviteAcceptPage) mux.HandleFunc("/invites/accept-submit", h.handleInviteAccept) // Deal creation & folder management mux.HandleFunc("/deals/create", h.requireAuth(h.handleCreateDeal)) mux.HandleFunc("/deals/folders/create", h.requireAuth(h.handleFolderCreate)) mux.HandleFunc("/deals/folders/rename", h.requireAuth(h.handleFolderRename)) mux.HandleFunc("/deals/folders/delete", h.requireAuth(h.handleFolderDelete)) mux.HandleFunc("/deals/folders/reorder", h.requireAuth(h.handleFolderReorder)) mux.HandleFunc("/deals/folders/reparent", h.requireAuth(h.handleFolderReparent)) // File management mux.HandleFunc("/deals/files/upload", h.requireAuth(h.handleFileUpload)) mux.HandleFunc("/deals/files/delete", h.requireAuth(h.handleFileDelete)) mux.HandleFunc("/deals/files/download/", h.requireAuth(h.handleFileDownload)) mux.HandleFunc("/deals/files/comments/", h.requireAuth(h.handleFileComments)) mux.HandleFunc("/deals/search/", h.requireAuth(h.handleDealSearch)) mux.HandleFunc("/analytics/buyers", h.requireAuth(h.handleAnalyticsBuyers)) // Deal update mux.HandleFunc("/deals/update", h.requireAuth(h.handleDealUpdate)) // API endpoints mux.HandleFunc("/api/contacts/search", h.requireAuth(h.handleContactSearch)) mux.HandleFunc("/api/industries", h.requireAuth(h.handleIndustryList)) // Request list upload mux.HandleFunc("/deals/requests/upload", h.requireAuth(h.handleRequestListUpload)) // HTMX partials mux.HandleFunc("/htmx/request-comment", h.requireAuth(h.handleUpdateComment)) // Responses & AI matching mux.HandleFunc("/deals/responses/statement", h.requireAuth(h.handleCreateStatement)) mux.HandleFunc("/deals/responses/confirm", h.requireAuth(h.handleConfirmLink)) mux.HandleFunc("/deals/responses/reject", h.requireAuth(h.handleRejectLink)) mux.HandleFunc("/deals/responses/pending/", h.requireAuth(h.handlePendingLinks)) mux.HandleFunc("/deals/assignment-rules/save", h.requireAuth(h.handleSaveAssignmentRules)) mux.HandleFunc("/deals/assignment-rules/", h.requireAuth(h.handleGetAssignmentRules)) } // Middleware func (h *Handler) requireAuth(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie("session") if err != nil { http.Redirect(w, r, "/login", http.StatusSeeOther) return } var profile model.Profile err = h.db.QueryRow(` SELECT p.id, p.email, p.full_name, p.avatar_url, p.organization_id, p.role FROM sessions s JOIN profiles p ON s.user_id = p.id WHERE s.token = ? AND s.expires_at > datetime('now') `, cookie.Value).Scan(&profile.ID, &profile.Email, &profile.FullName, &profile.AvatarURL, &profile.OrganizationID, &profile.Role) if err != nil { http.SetCookie(w, &http.Cookie{Name: "session", Value: "", MaxAge: -1, Path: "/"}) http.Redirect(w, r, "/login", http.StatusSeeOther) return } // Check view-as-buyer toggle (only applies to sellers) if profile.Role == "owner" || profile.Role == "admin" { if c, err := r.Cookie("view_as_buyer"); err == nil && c.Value == "1" { profile.ViewAsBuyer = true } } ctx := setProfile(r.Context(), &profile) next.ServeHTTP(w, r.WithContext(ctx)) } }