gohorsejobs/backend/internal/handlers/metrics_handler.go
Tiago Yamamoto b23393bf35 feat: implement stripe subscriptions, google analytics, and user crud
- Backend:
  - Add Stripe subscription fields to companies (migration 019)
  - Implement Stripe Checkout and Webhook handlers
  - Add Metrics API (view count, recording)
  - Update Company and Job models
- Frontend:
  - Add Google Analytics component
  - Implement User CRUD in Backoffice (Dashboard)
  - Add 'Featured' badge to JobCard
- Docs: Update Roadmap and artifacts
2025-12-27 12:06:54 -03:00

91 lines
2.5 KiB
Go

package handlers
import (
"encoding/json"
"net/http"
"strconv"
"github.com/rede5/gohorsejobs/backend/internal/services"
)
// MetricsHandler handles job metrics endpoints
type MetricsHandler struct {
Service *services.MetricsService
}
// NewMetricsHandler creates a new metrics handler
func NewMetricsHandler(service *services.MetricsService) *MetricsHandler {
return &MetricsHandler{Service: service}
}
// GetJobMetrics godoc
// @Summary Get job metrics
// @Description Get analytics data for a job including views, applications, and conversion rate
// @Tags Metrics
// @Accept json
// @Produce json
// @Param id path int true "Job ID"
// @Success 200 {object} models.JobMetrics
// @Failure 400 {string} string "Bad Request"
// @Failure 404 {string} string "Not Found"
// @Router /api/v1/jobs/{id}/metrics [get]
func (h *MetricsHandler) GetJobMetrics(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid job ID", http.StatusBadRequest)
return
}
metrics, err := h.Service.GetJobMetrics(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(metrics)
}
// RecordJobView godoc
// @Summary Record a job view
// @Description Record that a user viewed a job (called internally or by frontend)
// @Tags Metrics
// @Accept json
// @Produce json
// @Param id path int true "Job ID"
// @Success 204 "No Content"
// @Failure 400 {string} string "Bad Request"
// @Failure 500 {string} string "Internal Server Error"
// @Router /api/v1/jobs/{id}/view [post]
func (h *MetricsHandler) RecordJobView(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid job ID", http.StatusBadRequest)
return
}
// Get user ID from context if authenticated
var userID *int
if uid := r.Context().Value("user_id"); uid != nil {
if id, ok := uid.(int); ok {
userID = &id
}
}
// Get IP and User-Agent for analytics
ip := r.Header.Get("X-Forwarded-For")
if ip == "" {
ip = r.RemoteAddr
}
userAgent := r.Header.Get("User-Agent")
err = h.Service.RecordView(id, userID, &ip, &userAgent)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
}