- Remove marcadores de conflito git em admin_service que causavam erro 500 em ListCompanies. - Implementa SeederService no backend Go com streaming SSE para logs em tempo real. - Expõe endpoints: GET /api/v1/seeder/seed/stream e POST /api/v1/seeder/reset. - Atualiza config do frontend para apontar URL do seeder para a API backend. - Corrige erros de sintaxe na UI do dashboard Backoffice e implementa busca de estatísticas. - Garante lógica correta de UPSERT no seeder (RETURNING id) usando colunas 'identifier' e 'full_name' para evitar abortar transações. - Corrige constraint de role em user_companies no seeder para usar 'admin'.
210 lines
6.2 KiB
Go
210 lines
6.2 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"math/rand"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
type SeederService struct {
|
|
DB *sql.DB
|
|
}
|
|
|
|
func NewSeederService(db *sql.DB) *SeederService {
|
|
return &SeederService{DB: db}
|
|
}
|
|
|
|
// SendEvent is a helper to stream logs via SSE
|
|
func (s *SeederService) SendEvent(logChan chan string, msg string) {
|
|
if logChan != nil {
|
|
logChan <- msg
|
|
} else {
|
|
fmt.Println("[SEEDER]", msg)
|
|
}
|
|
}
|
|
|
|
func (s *SeederService) Reset(ctx context.Context) error {
|
|
// Dangerous operation: Truncate tables
|
|
queries := []string{
|
|
"TRUNCATE TABLE applications CASCADE",
|
|
"TRUNCATE TABLE jobs CASCADE",
|
|
"TRUNCATE TABLE companies CASCADE",
|
|
"TRUNCATE TABLE users CASCADE",
|
|
// Add other tables as needed
|
|
}
|
|
|
|
tx, err := s.DB.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
for _, q := range queries {
|
|
if _, err := tx.ExecContext(ctx, q); err != nil {
|
|
// Ignore if table doesn't exist, but log it
|
|
fmt.Printf("Error executing %s: %v\n", q, err)
|
|
}
|
|
}
|
|
|
|
// Re-create SuperAdmin if needed, or leave it to manual registration
|
|
// For dev, it's nice to have a default admin.
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
func (s *SeederService) Seed(ctx context.Context, logChan chan string) error {
|
|
s.SendEvent(logChan, "🚀 Starting Database Seed...")
|
|
|
|
// Create Random Source
|
|
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
|
|
tx, err := s.DB.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
// 1. Create Users (Candidates & Recruiters)
|
|
s.SendEvent(logChan, "👤 Creating Users...")
|
|
|
|
candidates := []string{"Alice Johnson", "Bob Smith", "Charlie Brown", "Diana Prince", "Evan Wright"}
|
|
var candidateIDs []string
|
|
|
|
passwordHash, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
|
|
|
|
for _, name := range candidates {
|
|
id := uuid.New().String()
|
|
// Using email format as identifier
|
|
identifier := strings.ToLower(strings.ReplaceAll(name, " ", ".")) + "@example.com"
|
|
|
|
// UPSERT to ensure we get the correct ID back if it exists
|
|
row := tx.QueryRowContext(ctx, `
|
|
INSERT INTO users (id, identifier, full_name, password_hash, role, created_at, updated_at)
|
|
VALUES ($1, $2, $3, $4, 'candidate', NOW(), NOW())
|
|
ON CONFLICT (identifier) DO UPDATE SET updated_at = NOW()
|
|
RETURNING id
|
|
`, id, identifier, name, string(passwordHash))
|
|
|
|
if err := row.Scan(&id); err != nil {
|
|
s.SendEvent(logChan, fmt.Sprintf("❌ Error creating candidate %s: %v", name, err))
|
|
continue
|
|
}
|
|
candidateIDs = append(candidateIDs, id)
|
|
s.SendEvent(logChan, fmt.Sprintf(" - Created Candidate: %s (%s)", name, identifier))
|
|
}
|
|
|
|
// 2. Create Companies & Recruiters
|
|
s.SendEvent(logChan, "🏢 Creating Companies...")
|
|
companyNames := []string{"TechCorp", "InnovateX", "GlobalSolutions", "CodeFactory", "DesignStudio"}
|
|
var companyIDs []string
|
|
|
|
for _, compName := range companyNames {
|
|
// Create Recruiter
|
|
recruiterID := uuid.New().String()
|
|
recIdentifier := "hr@" + strings.ToLower(compName) + ".com"
|
|
|
|
row := tx.QueryRowContext(ctx, `
|
|
INSERT INTO users (id, identifier, full_name, password_hash, role, created_at, updated_at)
|
|
VALUES ($1, $2, $3, $4, 'recruiter', NOW(), NOW())
|
|
ON CONFLICT (identifier) DO UPDATE SET updated_at = NOW()
|
|
RETURNING id
|
|
`, recruiterID, recIdentifier, compName+" Recruiter", string(passwordHash))
|
|
|
|
if err := row.Scan(&recruiterID); err != nil {
|
|
s.SendEvent(logChan, fmt.Sprintf("❌ Error creating recruiter %s: %v", recIdentifier, err))
|
|
continue
|
|
}
|
|
|
|
// Create Company
|
|
compID := uuid.New().String()
|
|
slug := strings.ToLower(compName)
|
|
|
|
row = tx.QueryRowContext(ctx, `
|
|
INSERT INTO companies (id, name, slug, type, verified, active, created_at, updated_at)
|
|
VALUES ($1, $2, $3, 'COMPANY', true, true, NOW(), NOW())
|
|
ON CONFLICT (slug) DO UPDATE SET updated_at = NOW()
|
|
RETURNING id
|
|
`, compID, compName, slug)
|
|
|
|
if err := row.Scan(&compID); err != nil {
|
|
s.SendEvent(logChan, fmt.Sprintf("❌ Error creating company %s: %v", compName, err))
|
|
continue
|
|
}
|
|
companyIDs = append(companyIDs, compID)
|
|
|
|
// Link Recruiter - Use 'admin' as role (per schema constraint: 'admin', 'recruiter')
|
|
_, err = tx.ExecContext(ctx, `
|
|
INSERT INTO user_companies (user_id, company_id, role)
|
|
VALUES ($1, $2, 'admin')
|
|
ON CONFLICT DO NOTHING
|
|
`, recruiterID, compID)
|
|
|
|
if err != nil {
|
|
s.SendEvent(logChan, fmt.Sprintf("⚠️ Failed to link recruiter: %v", err))
|
|
}
|
|
|
|
s.SendEvent(logChan, fmt.Sprintf(" - Created Company: %s (HR: %s)", compName, recIdentifier))
|
|
}
|
|
|
|
// 3. Create Jobs
|
|
s.SendEvent(logChan, "💼 Creating Jobs...")
|
|
jobTitles := []string{"Software Engineer", "Frontend Developer", "Backend Developer", "Product Manager", "UX Designer"}
|
|
var jobIDs []string
|
|
|
|
for _, compID := range companyIDs {
|
|
// Create 2-3 jobs per company
|
|
numJobs := rnd.Intn(2) + 2
|
|
for i := 0; i < numJobs; i++ {
|
|
jobID := uuid.New().String()
|
|
title := jobTitles[rnd.Intn(len(jobTitles))]
|
|
_, err := tx.ExecContext(ctx, `
|
|
INSERT INTO jobs (id, company_id, title, description, location, type, status, created_at, updated_at)
|
|
VALUES ($1, $2, $3, 'This is a great job opportunity.', 'Remote', 'full-time', 'published', NOW(), NOW())
|
|
ON CONFLICT DO NOTHING
|
|
`, jobID, compID, title)
|
|
if err != nil {
|
|
s.SendEvent(logChan, fmt.Sprintf("❌ Error creating job %s: %v", title, err))
|
|
continue
|
|
}
|
|
jobIDs = append(jobIDs, jobID)
|
|
s.SendEvent(logChan, fmt.Sprintf(" - Posted Job: %s at %s", title, compID[:8]))
|
|
}
|
|
}
|
|
|
|
// 4. Create Applications
|
|
s.SendEvent(logChan, "📝 Creating Applications...")
|
|
for _, candID := range candidateIDs {
|
|
// Apply to 1-3 random jobs
|
|
numApps := rnd.Intn(3) + 1
|
|
for i := 0; i < numApps; i++ {
|
|
if len(jobIDs) == 0 {
|
|
break
|
|
}
|
|
jobID := jobIDs[rnd.Intn(len(jobIDs))]
|
|
appID := uuid.New().String()
|
|
|
|
_, err := tx.ExecContext(ctx, `
|
|
INSERT INTO applications (id, job_id, user_id, status, created_at, updated_at)
|
|
VALUES ($1, $2, $3, 'applied', NOW(), NOW())
|
|
ON CONFLICT DO NOTHING
|
|
`, appID, jobID, candID)
|
|
|
|
if err == nil {
|
|
s.SendEvent(logChan, fmt.Sprintf(" - Candidate %s applied to Job %s", candID[:8], jobID[:8]))
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
return err
|
|
}
|
|
|
|
s.SendEvent(logChan, "✅ Seed Completed Successfully!")
|
|
return nil
|
|
}
|