281 lines
6.8 KiB
Go
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)
|
|
}
|