- Video Interview system (backend + frontend) - Date Posted filter (24h, 7d, 30d) - Company filter in jobs listing - Recent searches persistence (LocalStorage) - Job Alerts with email confirmation - Favorite jobs with API - Company followers system - Careerjet URL compatibility (s/l aliases)
208 lines
5.6 KiB
Go
208 lines
5.6 KiB
Go
package services
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"database/sql"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/rede5/gohorsejobs/backend/internal/dto"
|
|
"github.com/rede5/gohorsejobs/backend/internal/models"
|
|
)
|
|
|
|
type JobAlertService struct {
|
|
DB *sql.DB
|
|
EmailService EmailService
|
|
}
|
|
|
|
func NewJobAlertService(db *sql.DB, emailService EmailService) *JobAlertService {
|
|
return &JobAlertService{DB: db, EmailService: emailService}
|
|
}
|
|
|
|
func generateToken() string {
|
|
bytes := make([]byte, 32)
|
|
rand.Read(bytes)
|
|
return hex.EncodeToString(bytes)
|
|
}
|
|
|
|
func (s *JobAlertService) CreateAlert(req dto.CreateJobAlertRequest, userID *string) (*models.JobAlert, error) {
|
|
frequency := "daily"
|
|
if req.Frequency != nil && *req.Frequency == "weekly" {
|
|
frequency = *req.Frequency
|
|
}
|
|
|
|
currency := "BRL"
|
|
if req.Currency != nil {
|
|
currency = *req.Currency
|
|
}
|
|
|
|
token := generateToken()
|
|
nextSend := time.Now().Add(24 * time.Hour)
|
|
if frequency == "weekly" {
|
|
nextSend = time.Now().Add(7 * 24 * time.Hour)
|
|
}
|
|
|
|
query := `
|
|
INSERT INTO job_alerts (
|
|
user_id, search_query, location, employment_type, work_mode,
|
|
salary_min, salary_max, currency, frequency, is_active,
|
|
confirmation_token, next_send_at, created_at, updated_at
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
|
RETURNING id, created_at, updated_at
|
|
`
|
|
|
|
alert := &models.JobAlert{
|
|
UserID: userID,
|
|
SearchQuery: req.SearchQuery,
|
|
Location: req.Location,
|
|
EmploymentType: req.EmploymentType,
|
|
WorkMode: req.WorkMode,
|
|
SalaryMin: req.SalaryMin,
|
|
SalaryMax: req.SalaryMax,
|
|
Currency: currency,
|
|
Frequency: frequency,
|
|
IsActive: false, // Requires email confirmation
|
|
ConfirmationToken: &token,
|
|
NextSendAt: &nextSend,
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
err := s.DB.QueryRow(
|
|
query,
|
|
alert.UserID, alert.SearchQuery, alert.Location, alert.EmploymentType, alert.WorkMode,
|
|
alert.SalaryMin, alert.SalaryMax, alert.Currency, alert.Frequency, alert.IsActive,
|
|
alert.ConfirmationToken, alert.NextSendAt, alert.CreatedAt, alert.UpdatedAt,
|
|
).Scan(&alert.ID, &alert.CreatedAt, &alert.UpdatedAt)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create alert: %w", err)
|
|
}
|
|
|
|
// Send confirmation email
|
|
email := ""
|
|
if userID == nil && req.Email != nil {
|
|
email = *req.Email
|
|
} else if userID != nil {
|
|
// Get user email
|
|
var userEmail string
|
|
err := s.DB.QueryRow("SELECT email FROM users WHERE id = $1", userID).Scan(&userEmail)
|
|
if err == nil {
|
|
email = userEmail
|
|
}
|
|
}
|
|
|
|
if email != "" {
|
|
confirmLink := fmt.Sprintf("https://gohorsejobs.com/alerts/confirm?token=%s", token)
|
|
subject := "Confirme seu alerta de vagas no GoHorse Jobs"
|
|
body := fmt.Sprintf(`
|
|
Olá,
|
|
|
|
Confirme seu alerta de vagas para receber as melhores oportunidades.
|
|
|
|
Link de confirmação: %s
|
|
|
|
Se você não criou este alerta, ignore este email.
|
|
`, confirmLink)
|
|
go s.EmailService.SendEmail(email, subject, body)
|
|
}
|
|
|
|
return alert, nil
|
|
}
|
|
|
|
func (s *JobAlertService) ConfirmAlert(token string) (*models.JobAlert, error) {
|
|
var alert models.JobAlert
|
|
query := `
|
|
SELECT id, user_id, search_query, location, employment_type, work_mode,
|
|
salary_min, salary_max, currency, frequency, is_active,
|
|
confirmation_token, confirmed_at, created_at, updated_at
|
|
FROM job_alerts WHERE confirmation_token = $1
|
|
`
|
|
|
|
err := s.DB.QueryRow(query, token).Scan(
|
|
&alert.ID, &alert.UserID, &alert.SearchQuery, &alert.Location, &alert.EmploymentType, &alert.WorkMode,
|
|
&alert.SalaryMin, &alert.SalaryMax, &alert.Currency, &alert.Frequency, &alert.IsActive,
|
|
&alert.ConfirmationToken, &alert.ConfirmedAt, &alert.CreatedAt, &alert.UpdatedAt,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if alert.ConfirmedAt != nil {
|
|
return &alert, nil // Already confirmed
|
|
}
|
|
|
|
now := time.Now()
|
|
nextSend := now.Add(24 * time.Hour)
|
|
if alert.Frequency == "weekly" {
|
|
nextSend = now.Add(7 * 24 * time.Hour)
|
|
}
|
|
|
|
_, err = s.DB.Exec(
|
|
"UPDATE job_alerts SET is_active = true, confirmed_at = $1, next_send_at = $2 WHERE id = $3",
|
|
now, nextSend, alert.ID,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
alert.IsActive = true
|
|
alert.ConfirmedAt = &now
|
|
alert.NextSendAt = &nextSend
|
|
|
|
return &alert, nil
|
|
}
|
|
|
|
func (s *JobAlertService) GetAlertsByUser(userID string) ([]models.JobAlert, error) {
|
|
query := `
|
|
SELECT id, user_id, search_query, location, employment_type, work_mode,
|
|
salary_min, salary_max, currency, frequency, is_active,
|
|
last_sent_at, next_send_at, confirmed_at, created_at, updated_at
|
|
FROM job_alerts WHERE user_id = $1 ORDER BY created_at DESC
|
|
`
|
|
|
|
rows, err := s.DB.Query(query, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var alerts []models.JobAlert
|
|
for rows.Next() {
|
|
var alert models.JobAlert
|
|
err := rows.Scan(
|
|
&alert.ID, &alert.UserID, &alert.SearchQuery, &alert.Location, &alert.EmploymentType, &alert.WorkMode,
|
|
&alert.SalaryMin, &alert.SalaryMax, &alert.Currency, &alert.Frequency, &alert.IsActive,
|
|
&alert.LastSentAt, &alert.NextSendAt, &alert.ConfirmedAt, &alert.CreatedAt, &alert.UpdatedAt,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
alerts = append(alerts, alert)
|
|
}
|
|
|
|
return alerts, nil
|
|
}
|
|
|
|
func (s *JobAlertService) DeleteAlert(id string, userID string) error {
|
|
result, err := s.DB.Exec("DELETE FROM job_alerts WHERE id = $1 AND user_id = $2", id, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rowsAffected, _ := result.RowsAffected()
|
|
if rowsAffected == 0 {
|
|
return sql.ErrNoRows
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *JobAlertService) ToggleAlert(id string, userID string, active bool) error {
|
|
_, err := s.DB.Exec("UPDATE job_alerts SET is_active = $1, updated_at = $2 WHERE id = $3 AND user_id = $4",
|
|
active, time.Now(), id, userID)
|
|
return err
|
|
}
|