- 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
91 lines
2.5 KiB
Go
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)
|
|
}
|