- 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
100 lines
2.4 KiB
Go
100 lines
2.4 KiB
Go
package services
|
|
|
|
import (
|
|
"database/sql"
|
|
|
|
"github.com/rede5/gohorsejobs/backend/internal/models"
|
|
)
|
|
|
|
// MetricsService handles job analytics
|
|
type MetricsService struct {
|
|
DB *sql.DB
|
|
}
|
|
|
|
// NewMetricsService creates a new metrics service
|
|
func NewMetricsService(db *sql.DB) *MetricsService {
|
|
return &MetricsService{DB: db}
|
|
}
|
|
|
|
// RecordView records a job view and increments the counter
|
|
func (s *MetricsService) RecordView(jobID int, userID *int, ip *string, userAgent *string) error {
|
|
tx, err := s.DB.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
// Insert view record
|
|
_, err = tx.Exec(`
|
|
INSERT INTO job_views (job_id, user_id, ip_address, user_agent)
|
|
VALUES ($1, $2, $3, $4)
|
|
`, jobID, userID, ip, userAgent)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Increment cached view count
|
|
_, err = tx.Exec(`
|
|
UPDATE jobs SET view_count = view_count + 1 WHERE id = $1
|
|
`, jobID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
// GetJobMetrics returns analytics data for a job
|
|
func (s *MetricsService) GetJobMetrics(jobID int) (*models.JobMetrics, error) {
|
|
metrics := &models.JobMetrics{JobID: jobID}
|
|
|
|
// Get view count from jobs table
|
|
err := s.DB.QueryRow(`
|
|
SELECT COALESCE(view_count, 0) FROM jobs WHERE id = $1
|
|
`, jobID).Scan(&metrics.ViewCount)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Get unique viewers
|
|
err = s.DB.QueryRow(`
|
|
SELECT COUNT(DISTINCT COALESCE(user_id::text, ip_address))
|
|
FROM job_views WHERE job_id = $1
|
|
`, jobID).Scan(&metrics.UniqueViewers)
|
|
if err != nil {
|
|
metrics.UniqueViewers = 0 // Don't fail if table doesn't exist yet
|
|
}
|
|
|
|
// Get application count
|
|
err = s.DB.QueryRow(`
|
|
SELECT COUNT(*) FROM applications WHERE job_id = $1
|
|
`, jobID).Scan(&metrics.ApplicationCount)
|
|
if err != nil {
|
|
metrics.ApplicationCount = 0
|
|
}
|
|
|
|
// Calculate conversion rate
|
|
if metrics.ViewCount > 0 {
|
|
metrics.ConversionRate = float64(metrics.ApplicationCount) / float64(metrics.ViewCount) * 100
|
|
}
|
|
|
|
// Get views last 7 days
|
|
err = s.DB.QueryRow(`
|
|
SELECT COUNT(*) FROM job_views
|
|
WHERE job_id = $1 AND viewed_at > NOW() - INTERVAL '7 days'
|
|
`, jobID).Scan(&metrics.ViewsLast7Days)
|
|
if err != nil {
|
|
metrics.ViewsLast7Days = 0
|
|
}
|
|
|
|
// Get views last 30 days
|
|
err = s.DB.QueryRow(`
|
|
SELECT COUNT(*) FROM job_views
|
|
WHERE job_id = $1 AND viewed_at > NOW() - INTERVAL '30 days'
|
|
`, jobID).Scan(&metrics.ViewsLast30Days)
|
|
if err != nil {
|
|
metrics.ViewsLast30Days = 0
|
|
}
|
|
|
|
return metrics, nil
|
|
}
|