Files
hireme/internal/ui/handlers.go
T

281 lines
6.8 KiB
Go

package ui
import (
"context"
"log"
"net/http"
"strconv"
"strings"
"github.com/hireme/hireme/internal/config"
"github.com/hireme/hireme/internal/db"
"github.com/hireme/hireme/internal/service"
)
type Server struct {
cfg *config.Config
db *db.DB
pipeline *service.PipelineService
}
func NewServer(cfg *config.Config, database *db.DB, pipeline *service.PipelineService) *Server {
return &Server{
cfg: cfg,
db: database,
pipeline: pipeline,
}
}
func (s *Server) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("GET /", s.handleIndex)
mux.HandleFunc("GET /targets", s.handleGetTargets)
mux.HandleFunc("POST /targets", s.handleCreateTarget)
mux.HandleFunc("POST /targets/{id}/toggle", s.handleToggleTarget)
mux.HandleFunc("GET /jobs", s.handleGetJobs)
mux.HandleFunc("GET /jobs/{id}", s.handleGetJobDetail)
mux.HandleFunc("POST /jobs/{id}/analyze", s.handleAnalyzeJob)
mux.HandleFunc("POST /applications", s.handleCreateApplication)
mux.HandleFunc("PATCH /applications/{id}/status", s.handleUpdateAppStatus)
mux.HandleFunc("POST /pipeline/run", s.handleRunPipeline)
}
func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
targets, err := s.db.GetAllTargets()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
jobs, err := s.db.GetJobsWithAnalysis()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
apps, err := s.db.GetApplications()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
qualifiedCount := 0
for _, j := range jobs {
if j.Analysis != nil && j.Analysis.IsQualified {
qualifiedCount++
}
}
data := PageData{
Targets: targets,
Jobs: jobs,
Applications: apps,
ActiveTab: "triage",
LLMProvider: s.cfg.LLMProvider,
QualifiedJobsCount: qualifiedCount,
TotalIngestedCount: len(jobs),
ActiveApplications: len(apps),
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := RenderPage(w, data); err != nil {
log.Printf("[UI] RenderPage error: %v", err)
}
}
func (s *Server) handleGetTargets(w http.ResponseWriter, r *http.Request) {
targets, err := s.db.GetAllTargets()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_ = RenderTargetList(w, targets)
}
func (s *Server) handleCreateTarget(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
name := r.FormValue("name")
targetURL := r.FormValue("target_url")
platform := r.FormValue("platform")
if name == "" || targetURL == "" {
http.Error(w, "name and target_url are required", http.StatusBadRequest)
return
}
_, err := s.db.CreateTarget(name, targetURL, platform)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
s.handleGetTargets(w, r)
}
func (s *Server) handleToggleTarget(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
http.Error(w, "invalid target id", http.StatusBadRequest)
return
}
if err := s.db.ToggleTargetStatus(id); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
s.handleGetTargets(w, r)
}
func (s *Server) handleGetJobs(w http.ResponseWriter, r *http.Request) {
jobs, err := s.db.GetJobsWithAnalysis()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_ = RenderJobList(w, jobs)
}
func (s *Server) handleGetJobDetail(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
http.Error(w, "invalid job id", http.StatusBadRequest)
return
}
job, err := s.db.GetJobByID(id)
if err != nil {
http.Error(w, "job not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_ = RenderJobDetail(w, job)
}
func (s *Server) handleAnalyzeJob(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
http.Error(w, "invalid job id", http.StatusBadRequest)
return
}
job, err := s.pipeline.AnalyzeJobByID(r.Context(), id)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_ = RenderJobDetail(w, job)
}
func (s *Server) handleCreateApplication(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
rawJobIDStr := r.FormValue("raw_job_id")
status := r.FormValue("status")
if status == "" {
status = "SAVED"
}
rawJobID, err := strconv.ParseInt(rawJobIDStr, 10, 64)
if err != nil {
http.Error(w, "invalid raw_job_id", http.StatusBadRequest)
return
}
_, err = s.db.CreateOrUpdateApplication(rawJobID, status, "")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if strings.Contains(r.Header.Get("HX-Target"), "kanban") {
apps, _ := s.db.GetApplications()
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_ = RenderKanbanBoard(w, apps)
return
}
s.handleGetJobs(w, r)
}
func (s *Server) handleUpdateAppStatus(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
appID, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
http.Error(w, "invalid app id", http.StatusBadRequest)
return
}
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
newStatus := r.FormValue("status")
if newStatus == "" {
http.Error(w, "status required", http.StatusBadRequest)
return
}
// Fetch rawJobID for appID
apps, err := s.db.GetApplications()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var targetRawJobID int64 = -1
for _, a := range apps {
if a.App != nil && a.App.ID == appID {
targetRawJobID = a.ID
break
}
}
if targetRawJobID == -1 {
http.Error(w, "application not found", http.StatusNotFound)
return
}
_, err = s.db.CreateOrUpdateApplication(targetRawJobID, newStatus, "")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
updatedApps, _ := s.db.GetApplications()
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_ = RenderKanbanBoard(w, updatedApps)
}
func (s *Server) handleRunPipeline(w http.ResponseWriter, r *http.Request) {
ctx := context.Background()
_, _, err := s.pipeline.RunPipeline(ctx)
if err != nil {
log.Printf("[Server] Pipeline run error: %v", err)
}
s.handleGetJobs(w, r)
}