feat: optimize jobs database querying and immutable indexes

This commit is contained in:
Tiago Yamamoto 2026-02-25 07:23:45 -06:00
parent fd9af24e22
commit 9ccb15882e
9 changed files with 612 additions and 99 deletions

View file

@ -163,10 +163,10 @@ func TestAdminService_GetUser(t *testing.T) {
t.Run("returns user by id", func(t *testing.T) { t.Run("returns user by id", func(t *testing.T) {
userID := "019b5290-9680-7c06-9ee3-c9e0e117251b" userID := "019b5290-9680-7c06-9ee3-c9e0e117251b"
now := time.Now() now := time.Now()
mock.ExpectQuery("SELECT id, full_name, email, role, created_at FROM users WHERE id"). mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, full_name, email, role, COALESCE(status, 'active'), created_at, phone, bio, avatar_url FROM users WHERE id = $1`)).
WithArgs(userID). WithArgs(userID).
WillReturnRows(sqlmock.NewRows([]string{"id", "name", "email", "role", "created_at"}). WillReturnRows(sqlmock.NewRows([]string{"id", "full_name", "email", "role", "status", "created_at", "phone", "bio", "avatar_url"}).
AddRow(userID, "Test User", "test@example.com", "admin", now)) AddRow(userID, "Test User", "test@example.com", "admin", "active", now, "123", "Bio", "url"))
user, err := service.GetUser(ctx, userID) user, err := service.GetUser(ctx, userID)
if err != nil { if err != nil {
@ -472,93 +472,92 @@ func TestIsActiveApplicationStatus(t *testing.T) {
} }
} }
// Tests for GetCompanyByUserID // Tests for GetCompanyByUserID
func TestAdminService_GetCompanyByUserID(t *testing.T) { func TestAdminService_GetCompanyByUserID(t *testing.T) {
db, mock, err := sqlmock.New() db, mock, err := sqlmock.New()
if err != nil { if err != nil {
t.Fatalf("Error: %v", err) t.Fatalf("Error: %v", err)
} }
defer db.Close() defer db.Close()
svc := NewAdminService(db) svc := NewAdminService(db)
now := time.Now() now := time.Now()
// Exact query regex match // Exact query regex match
mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, name, slug, description, logo_url, website, location, active, verified, created_at, updated_at FROM companies WHERE user_id=$1`)). mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, name, slug, description, logo_url, website, location, active, verified, created_at, updated_at FROM companies WHERE user_id=$1`)).
WithArgs("user-1"). WithArgs("user-1").
WillReturnRows(sqlmock.NewRows([]string{ WillReturnRows(sqlmock.NewRows([]string{
"id", "name", "slug", "description", "logo_url", "website", "location", "id", "name", "slug", "description", "logo_url", "website", "location",
"active", "verified", "created_at", "updated_at", "active", "verified", "created_at", "updated_at",
}).AddRow( }).AddRow(
"comp-1", "Company", "company-slug", nil, nil, nil, nil, "comp-1", "Company", "company-slug", nil, nil, nil, nil,
true, true, now, now, true, true, now, now,
)) ))
// Note: implementation might be using "owner_id" or "user_id". Check failure if any. // Note: implementation might be using "owner_id" or "user_id". Check failure if any.
_, err = svc.GetCompanyByUserID(context.Background(), "user-1") _, err = svc.GetCompanyByUserID(context.Background(), "user-1")
if err != nil { if err != nil {
t.Logf("GetCompanyByUserID error (likely query mismatch): %v", err) t.Logf("GetCompanyByUserID error (likely query mismatch): %v", err)
} }
} }
func TestAdminService_DeleteCompanyBasic(t *testing.T) { func TestAdminService_DeleteCompanyBasic(t *testing.T) {
db, mock, err := sqlmock.New() db, mock, err := sqlmock.New()
if err != nil { if err != nil {
t.Fatalf("Error: %v", err) t.Fatalf("Error: %v", err)
} }
defer db.Close() defer db.Close()
svc := NewAdminService(db) svc := NewAdminService(db)
// Expect GetCompanyByID check first // Expect GetCompanyByID check first
mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, name, slug, description, logo_url, website, location, active, verified, created_at, updated_at FROM companies WHERE id=$1`)). mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, name, slug, description, logo_url, website, location, active, verified, created_at, updated_at FROM companies WHERE id=$1`)).
WithArgs("comp-1"). WithArgs("comp-1").
WillReturnRows(sqlmock.NewRows([]string{"id", "company_id", "name"}).AddRow("comp-1", "user-1", "Test Co")) WillReturnRows(sqlmock.NewRows([]string{"id", "company_id", "name"}).AddRow("comp-1", "user-1", "Test Co"))
mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM companies WHERE id=$1`)). mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM companies WHERE id=$1`)).
WithArgs("comp-1"). WithArgs("comp-1").
WillReturnResult(sqlmock.NewResult(0, 1)) WillReturnResult(sqlmock.NewResult(0, 1))
_ = svc.DeleteCompany(context.Background(), "comp-1") _ = svc.DeleteCompany(context.Background(), "comp-1")
} }
func TestAdminService_DeleteEmailTemplateBasic(t *testing.T) { func TestAdminService_DeleteEmailTemplateBasic(t *testing.T) {
db, mock, err := sqlmock.New() db, mock, err := sqlmock.New()
if err != nil { if err != nil {
t.Fatalf("Error: %v", err) t.Fatalf("Error: %v", err)
} }
defer db.Close() defer db.Close()
svc := NewAdminService(db) svc := NewAdminService(db)
mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM email_templates WHERE slug=$1`)). mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM email_templates WHERE slug=$1`)).
WithArgs("welcome"). WithArgs("welcome").
WillReturnResult(sqlmock.NewResult(0, 1)) WillReturnResult(sqlmock.NewResult(0, 1))
_ = svc.DeleteEmailTemplate(context.Background(), "welcome") _ = svc.DeleteEmailTemplate(context.Background(), "welcome")
} }
func TestAdminService_GetEmailSettingsBasic(t *testing.T) { func TestAdminService_GetEmailSettingsBasic(t *testing.T) {
db, mock, err := sqlmock.New() db, mock, err := sqlmock.New()
if err != nil { if err != nil {
t.Fatalf("Error: %v", err) t.Fatalf("Error: %v", err)
} }
defer db.Close() defer db.Close()
svc := NewAdminService(db) svc := NewAdminService(db)
query := `SELECT id, provider, smtp_host, smtp_port, smtp_user, smtp_pass, smtp_secure, sender_name, sender_email, amqp_url, is_active, updated_at query := `SELECT id, provider, smtp_host, smtp_port, smtp_user, smtp_pass, smtp_secure, sender_name, sender_email, amqp_url, is_active, updated_at
FROM email_settings WHERE is_active = true ORDER BY updated_at DESC LIMIT 1` FROM email_settings WHERE is_active = true ORDER BY updated_at DESC LIMIT 1`
mock.ExpectQuery(regexp.QuoteMeta(query)). mock.ExpectQuery(regexp.QuoteMeta(query)).
WillReturnRows(sqlmock.NewRows([]string{"id", "provider", "smtp_host", "smtp_port", "smtp_user", "smtp_pass", "smtp_secure", "sender_name", "sender_email", "amqp_url", "is_active", "updated_at"}). WillReturnRows(sqlmock.NewRows([]string{"id", "provider", "smtp_host", "smtp_port", "smtp_user", "smtp_pass", "smtp_secure", "sender_name", "sender_email", "amqp_url", "is_active", "updated_at"}).
AddRow("1", "smtp", "smtp.test.com", 587, "user", "pass", false, "Sender", "sender@test.com", "amqp://", true, time.Now())) AddRow("1", "smtp", "smtp.test.com", 587, "user", "pass", false, "Sender", "sender@test.com", "amqp://", true, time.Now()))
settings, err := svc.GetEmailSettings(context.Background()) settings, err := svc.GetEmailSettings(context.Background())
if err == nil { if err == nil {
if settings.SMTPHost == nil || *settings.SMTPHost != "smtp.test.com" { if settings.SMTPHost == nil || *settings.SMTPHost != "smtp.test.com" {
t.Errorf("Expected smtp.test.com") t.Errorf("Expected smtp.test.com")
} }
} }
} }

View file

@ -126,7 +126,7 @@ func TestApplicationService_CreateApplication_Full(t *testing.T) {
mock.ExpectQuery("INSERT INTO applications"). mock.ExpectQuery("INSERT INTO applications").
WithArgs( WithArgs(
req.JobID, req.UserID, req.Name, req.Phone, req.LineID, req.WhatsApp, req.Email, req.JobID, req.UserID, req.Name, req.Phone, req.LineID, req.WhatsApp, req.Email,
req.Message, req.ResumeURL, sqlmock.AnyArg(), "pending", sqlmock.AnyArg(), sqlmock.AnyArg(), req.Message, req.ResumeURL, sqlmock.AnyArg(), sqlmock.AnyArg(), "pending", sqlmock.AnyArg(), sqlmock.AnyArg(),
). ).
WillReturnRows(sqlmock.NewRows([]string{"id", "created_at", "updated_at"}). WillReturnRows(sqlmock.NewRows([]string{"id", "created_at", "updated_at"}).
AddRow("app-789", time.Now(), time.Now())) AddRow("app-789", time.Now(), time.Now()))
@ -156,10 +156,10 @@ func TestApplicationService_GetApplications(t *testing.T) {
rows := sqlmock.NewRows([]string{ rows := sqlmock.NewRows([]string{
"id", "job_id", "user_id", "name", "phone", "line_id", "whatsapp", "email", "id", "job_id", "user_id", "name", "phone", "line_id", "whatsapp", "email",
"message", "resume_url", "status", "created_at", "updated_at", "message", "resume_url", "documents", "answers", "status", "created_at", "updated_at",
}).AddRow( }).AddRow(
"app-1", jobID, "user-1", "John Doe", "123", nil, nil, "john@test.com", "app-1", jobID, "user-1", "John Doe", "123", nil, nil, "john@test.com",
"Hello", "http://resume.pdf", "pending", time.Now(), time.Now(), "Hello", "http://resume.pdf", nil, nil, "pending", time.Now(), time.Now(),
) )
mock.ExpectQuery("SELECT id, job_id, user_id, name, phone, line_id, whatsapp, email"). mock.ExpectQuery("SELECT id, job_id, user_id, name, phone, line_id, whatsapp, email").
@ -191,10 +191,10 @@ func TestApplicationService_GetApplicationByID(t *testing.T) {
rows := sqlmock.NewRows([]string{ rows := sqlmock.NewRows([]string{
"id", "job_id", "user_id", "name", "phone", "line_id", "whatsapp", "email", "id", "job_id", "user_id", "name", "phone", "line_id", "whatsapp", "email",
"message", "resume_url", "documents", "status", "created_at", "updated_at", "message", "resume_url", "documents", "answers", "status", "created_at", "updated_at",
}).AddRow( }).AddRow(
appID, "job-1", "user-1", "Jane Doe", "456", nil, nil, "jane@test.com", appID, "job-1", "user-1", "Jane Doe", "456", nil, nil, "jane@test.com",
"Hi", "http://cv.pdf", nil, "pending", time.Now(), time.Now(), "Hi", "http://cv.pdf", nil, nil, "pending", time.Now(), time.Now(),
) )
mock.ExpectQuery("SELECT id, job_id, user_id, name, phone, line_id, whatsapp, email"). mock.ExpectQuery("SELECT id, job_id, user_id, name, phone, line_id, whatsapp, email").

View file

@ -21,13 +21,18 @@ func TestNewEmailService(t *testing.T) {
} }
service := NewEmailService(db, credsSvc) service := NewEmailService(db, credsSvc)
if service == nil { concreteService, ok := service.(*emailServiceImpl)
if !ok {
t.Fatal("Expected NewEmailService to return *emailServiceImpl")
}
if concreteService == nil {
t.Error("Expected service, got nil") t.Error("Expected service, got nil")
} }
if service.db != db { if concreteService.db != db {
t.Error("Expected db to be set") t.Error("Expected db to be set")
} }
if service.credentialsService != credsSvc { if concreteService.credentialsService != credsSvc {
t.Error("Expected credentialsService to be set") t.Error("Expected credentialsService to be set")
} }
} }

View file

@ -91,11 +91,12 @@ func (s *JobService) GetJobs(filter dto.JobFilterQuery) ([]models.JobWithCompany
j.view_count, j.featured_until, j.view_count, j.featured_until,
(SELECT COUNT(*) FROM applications a WHERE a.job_id = j.id) as applications_count (SELECT COUNT(*) FROM applications a WHERE a.job_id = j.id) as applications_count
FROM jobs j FROM jobs j
LEFT JOIN companies c ON j.company_id::text = c.id::text LEFT JOIN companies c ON j.company_id = c.id
LEFT JOIN states r ON j.region_id::text = r.id::text LEFT JOIN states r ON j.region_id = r.id
LEFT JOIN cities ci ON j.city_id::text = ci.id::text LEFT JOIN cities ci ON j.city_id = ci.id
WHERE 1=1` WHERE 1=1`
countQuery := `SELECT COUNT(*) FROM jobs j LEFT JOIN companies c ON j.company_id::text = c.id::text WHERE 1=1`
countQuery := `SELECT COUNT(*) FROM jobs j WHERE 1=1`
var args []interface{} var args []interface{}
argId := 1 argId := 1
@ -105,7 +106,11 @@ func (s *JobService) GetJobs(filter dto.JobFilterQuery) ([]models.JobWithCompany
searchTerm := fmt.Sprintf("%%%s%%", *filter.Search) searchTerm := fmt.Sprintf("%%%s%%", *filter.Search)
clause := fmt.Sprintf(" AND (j.title ILIKE $%d OR j.description ILIKE $%d OR c.name ILIKE $%d)", argId, argId, argId) clause := fmt.Sprintf(" AND (j.title ILIKE $%d OR j.description ILIKE $%d OR c.name ILIKE $%d)", argId, argId, argId)
baseQuery += clause baseQuery += clause
countQuery += clause
// Se tem busca textual que checa c.name, a query de count *precisa* do JOIN
countQueryBase := `SELECT COUNT(*) FROM jobs j LEFT JOIN companies c ON j.company_id = c.id WHERE 1=1` // Usando count interno modificado apenas se tiver c.name
countQuery = countQueryBase + clause
args = append(args, searchTerm) args = append(args, searchTerm)
argId++ argId++
} }

View file

@ -35,8 +35,8 @@ func TestCreateJob(t *testing.T) {
}, },
mockRun: func() { mockRun: func() {
mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO jobs`)). mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO jobs`)).
WithArgs("1", "user-123", "Go Developer", sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), "published", sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg()). WithArgs("1", "user-123", "Go Developer", sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), "published", sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg()).
WillReturnRows(sqlmock.NewRows([]string{"id", "created_at", "updated_at"}).AddRow("100", time.Now(), time.Now())) WillReturnRows(sqlmock.NewRows([]string{"id", "date_posted", "created_at", "updated_at"}).AddRow("100", time.Now(), time.Now(), time.Now()))
}, },
wantErr: false, wantErr: false,
}, },
@ -93,17 +93,26 @@ func TestGetJobs(t *testing.T) {
// List query // List query
mock.ExpectQuery(regexp.QuoteMeta(`SELECT mock.ExpectQuery(regexp.QuoteMeta(`SELECT
j.id, j.company_id, j.title, j.description, j.salary_min, j.salary_max, j.salary_type, j.id, j.company_id, j.title, j.description, j.salary_min, j.salary_max, j.salary_type,
j.employment_type, j.work_mode, j.working_hours, j.location, j.status, j.salary_negotiable, j.is_featured, j.created_at, j.updated_at, j.employment_type, j.work_mode, j.working_hours, j.location, j.status, j.salary_negotiable, j.is_featured, COALESCE(j.date_posted, j.created_at) AS date_posted, j.created_at, j.updated_at,
COALESCE(c.name, '') as company_name, c.logo_url as company_logo_url, CASE
r.name as region_name, ci.name as city_name WHEN c.type = 'CANDIDATE_WORKSPACE' OR c.name LIKE 'Candidate - %' THEN ''
FROM jobs j`)). ELSE COALESCE(c.name, '')
END as company_name, c.logo_url as company_logo_url,
r.name as region_name, ci.name as city_name,
j.view_count, j.featured_until,
(SELECT COUNT(*) FROM applications a WHERE a.job_id = j.id) as applications_count
FROM jobs j
LEFT JOIN companies c ON j.company_id = c.id
LEFT JOIN states r ON j.region_id = r.id
LEFT JOIN cities ci ON j.city_id = ci.id
WHERE 1=1`)).
WillReturnRows(sqlmock.NewRows([]string{ WillReturnRows(sqlmock.NewRows([]string{
"id", "company_id", "title", "description", "salary_min", "salary_max", "salary_type", "id", "company_id", "title", "description", "salary_min", "salary_max", "salary_type",
"employment_type", "work_mode", "working_hours", "location", "status", "salary_negotiable", "is_featured", "created_at", "updated_at", "employment_type", "work_mode", "working_hours", "location", "status", "salary_negotiable", "is_featured", "date_posted", "created_at", "updated_at",
"company_name", "company_logo_url", "region_name", "city_name", "company_name", "company_logo_url", "region_name", "city_name", "view_count", "featured_until", "applications_count",
}).AddRow( }).AddRow(
"1", "10", "Dev", "Desc", 100, 200, "m", "ft", "Remote", "40h", "Remote", "open", true, false, time.Now(), time.Now(), "1", "10", "Dev", "Desc", 100, 200, "m", "ft", "Remote", "40h", "Remote", "open", true, false, time.Now(), time.Now(), time.Now(),
"Acme", "url", "Region", "City", "Acme", "url", "Region", "City", 0, nil, 0,
)) ))
// Count query // Count query
@ -136,15 +145,16 @@ func TestGetJobByID(t *testing.T) {
jobID := "100" jobID := "100"
// Mocking row for GetJobByID (20 columns)
rows := sqlmock.NewRows([]string{ rows := sqlmock.NewRows([]string{
"id", "company_id", "title", "description", "salary_min", "salary_max", "salary_type", "id", "company_id", "title", "description", "salary_min", "salary_max", "salary_type",
"employment_type", "working_hours", "location", "region_id", "city_id", "salary_negotiable", "employment_type", "working_hours", "location", "region_id", "city_id",
"requirements", "benefits", "visa_support", "language_level", "status", "created_at", "updated_at", "requirements", "benefits", "visa_support", "language_level", "status", "is_featured", "featured_until", "view_count", "date_posted", "created_at", "updated_at",
"salary_negotiable", "currency", "work_mode",
}).AddRow( }).AddRow(
jobID, 1, "Title", "Desc", 100, 200, "m", jobID, 1, "Title", "Desc", 100, 200, "m",
"ft", "40h", "Remote", 0, 0, false, "ft", "40h", "Remote", 0, 0,
nil, nil, false, "N2", "open", time.Now(), time.Now(), nil, nil, false, "N2", "open", false, nil, 0, time.Now(), time.Now(), time.Now(),
false, "USD", "remote",
) )
mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, company_id`)). mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, company_id`)).
@ -181,15 +191,16 @@ func TestUpdateJob(t *testing.T) {
WithArgs(newTitle, jobID). WithArgs(newTitle, jobID).
WillReturnRows(sqlmock.NewRows([]string{"id", "updated_at"}).AddRow(jobID, time.Now())) WillReturnRows(sqlmock.NewRows([]string{"id", "updated_at"}).AddRow(jobID, time.Now()))
// Expect subsequent SELECT to fetch updated job (20 columns)
rows := sqlmock.NewRows([]string{ rows := sqlmock.NewRows([]string{
"id", "company_id", "title", "description", "salary_min", "salary_max", "salary_type", "id", "company_id", "title", "description", "salary_min", "salary_max", "salary_type",
"employment_type", "working_hours", "location", "region_id", "city_id", "salary_negotiable", "employment_type", "working_hours", "location", "region_id", "city_id",
"requirements", "benefits", "visa_support", "language_level", "status", "created_at", "updated_at", "requirements", "benefits", "visa_support", "language_level", "status", "is_featured", "featured_until", "view_count", "date_posted", "created_at", "updated_at",
"salary_negotiable", "currency", "work_mode",
}).AddRow( }).AddRow(
jobID, 1, newTitle, "Desc", 100, 200, "m", jobID, 1, newTitle, "Desc", 100, 200, "m",
"ft", "40h", "Remote", 0, 0, false, "ft", "40h", "Remote", 0, 0,
nil, nil, false, "N2", "open", time.Now(), time.Now(), nil, nil, false, "N2", "open", false, nil, 0, time.Now(), time.Now(), time.Now(),
false, "USD", "remote",
) )
mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, company_id`)). mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, company_id`)).

View file

@ -0,0 +1,265 @@
=== RUN TestAdminService_ListCompanies
=== RUN TestAdminService_ListCompanies/returns_empty_list_when_no_companies
=== RUN TestAdminService_ListCompanies/filters_by_verified_status
--- PASS: TestAdminService_ListCompanies (0.00s)
--- PASS: TestAdminService_ListCompanies/returns_empty_list_when_no_companies (0.00s)
--- PASS: TestAdminService_ListCompanies/filters_by_verified_status (0.00s)
=== RUN TestAdminService_ListTags
=== RUN TestAdminService_ListTags/returns_empty_list_when_no_tags
=== RUN TestAdminService_ListTags/filters_by_category
--- PASS: TestAdminService_ListTags (0.00s)
--- PASS: TestAdminService_ListTags/returns_empty_list_when_no_tags (0.00s)
--- PASS: TestAdminService_ListTags/filters_by_category (0.00s)
=== RUN TestAdminService_CreateTag
=== RUN TestAdminService_CreateTag/creates_a_new_tag
=== RUN TestAdminService_CreateTag/rejects_empty_tag_name
--- PASS: TestAdminService_CreateTag (0.00s)
--- PASS: TestAdminService_CreateTag/creates_a_new_tag (0.00s)
--- PASS: TestAdminService_CreateTag/rejects_empty_tag_name (0.00s)
=== RUN TestAdminService_GetUser
=== RUN TestAdminService_GetUser/returns_user_by_id
admin_service_test.go:173: Unexpected error: Query: could not match actual sql: "SELECT id, full_name, email, role, COALESCE(status, 'active'), created_at, phone, bio, avatar_url FROM users WHERE id = $1" with expected regexp "SELECT id, full_name, email, role, created_at FROM users WHERE id"
--- FAIL: TestAdminService_GetUser (0.00s)
--- FAIL: TestAdminService_GetUser/returns_user_by_id (0.00s)
=== RUN TestAdminService_UpdateCompanyStatus
=== RUN TestAdminService_UpdateCompanyStatus/updates_active_status_successfully
=== RUN TestAdminService_UpdateCompanyStatus/updates_verified_status_successfully
=== RUN TestAdminService_UpdateCompanyStatus/returns_error_when_company_not_found
--- PASS: TestAdminService_UpdateCompanyStatus (0.00s)
--- PASS: TestAdminService_UpdateCompanyStatus/updates_active_status_successfully (0.00s)
--- PASS: TestAdminService_UpdateCompanyStatus/updates_verified_status_successfully (0.00s)
--- PASS: TestAdminService_UpdateCompanyStatus/returns_error_when_company_not_found (0.00s)
=== RUN TestAdminService_ListUsers
=== RUN TestAdminService_ListUsers/returns_users_list
--- PASS: TestAdminService_ListUsers (0.00s)
--- PASS: TestAdminService_ListUsers/returns_users_list (0.00s)
=== RUN TestAdminService_DuplicateJob
--- PASS: TestAdminService_DuplicateJob (0.00s)
=== RUN TestAdminService_EmailTemplates
=== RUN TestAdminService_EmailTemplates/ListEmailTemplates
=== RUN TestAdminService_EmailTemplates/CreateEmailTemplate
--- PASS: TestAdminService_EmailTemplates (0.00s)
--- PASS: TestAdminService_EmailTemplates/ListEmailTemplates (0.00s)
--- PASS: TestAdminService_EmailTemplates/CreateEmailTemplate (0.00s)
=== RUN TestStringOrNil
=== RUN TestStringOrNil/nil_for_invalid
=== RUN TestStringOrNil/pointer_for_valid
--- PASS: TestStringOrNil (0.00s)
--- PASS: TestStringOrNil/nil_for_invalid (0.00s)
--- PASS: TestStringOrNil/pointer_for_valid (0.00s)
=== RUN TestBuildLocation
=== RUN TestBuildLocation/nil_when_both_empty
=== RUN TestBuildLocation/city,_state_format
=== RUN TestBuildLocation/city_only
--- PASS: TestBuildLocation (0.00s)
--- PASS: TestBuildLocation/nil_when_both_empty (0.00s)
--- PASS: TestBuildLocation/city,_state_format (0.00s)
--- PASS: TestBuildLocation/city_only (0.00s)
=== RUN TestNormalizeSkills
--- PASS: TestNormalizeSkills (0.00s)
=== RUN TestIsActiveApplicationStatus
--- PASS: TestIsActiveApplicationStatus (0.00s)
=== RUN TestAdminService_GetCompanyByUserID
admin_service_test.go:501: GetCompanyByUserID error (likely query mismatch): Query: could not match actual sql: "SELECT id, full_name, email, role, COALESCE(status, 'active'), created_at, phone, bio, avatar_url FROM users WHERE id = $1" with expected regexp "SELECT id, name, slug, description, logo_url, website, location, active, verified, created_at, updated_at FROM companies WHERE user_id=\$1"
--- PASS: TestAdminService_GetCompanyByUserID (0.00s)
=== RUN TestAdminService_DeleteCompanyBasic
--- PASS: TestAdminService_DeleteCompanyBasic (0.00s)
=== RUN TestAdminService_DeleteEmailTemplateBasic
--- PASS: TestAdminService_DeleteEmailTemplateBasic (0.00s)
=== RUN TestAdminService_GetEmailSettingsBasic
--- PASS: TestAdminService_GetEmailSettingsBasic (0.00s)
=== RUN TestAuditService_RecordLogin
=== RUN TestAuditService_RecordLogin/records_login_successfully
=== RUN TestAuditService_RecordLogin/records_login_without_optional_fields
--- PASS: TestAuditService_RecordLogin (0.00s)
--- PASS: TestAuditService_RecordLogin/records_login_successfully (0.00s)
--- PASS: TestAuditService_RecordLogin/records_login_without_optional_fields (0.00s)
=== RUN TestAuditService_ListLogins
=== RUN TestAuditService_ListLogins/returns_empty_list_when_no_audits
=== RUN TestAuditService_ListLogins/respects_custom_limit
--- PASS: TestAuditService_ListLogins (0.00s)
--- PASS: TestAuditService_ListLogins/returns_empty_list_when_no_audits (0.00s)
--- PASS: TestAuditService_ListLogins/respects_custom_limit (0.00s)
=== RUN TestNewChatService
--- PASS: TestNewChatService (0.00s)
=== RUN TestChatService_SendMessage
chat_service_test.go:37: Skipping due to async goroutine requiring real dependencies
--- SKIP: TestChatService_SendMessage (0.00s)
=== RUN TestChatService_ListMessages
=== RUN TestChatService_ListMessages/returns_messages_list
=== RUN TestChatService_ListMessages/returns_empty_list_when_no_messages
--- PASS: TestChatService_ListMessages (0.00s)
--- PASS: TestChatService_ListMessages/returns_messages_list (0.00s)
--- PASS: TestChatService_ListMessages/returns_empty_list_when_no_messages (0.00s)
=== RUN TestChatService_ListConversations
=== RUN TestChatService_ListConversations/lists_conversations_for_candidate
=== RUN TestChatService_ListConversations/lists_conversations_for_company
=== RUN TestChatService_ListConversations/returns_error_for_invalid_context
--- PASS: TestChatService_ListConversations (0.00s)
--- PASS: TestChatService_ListConversations/lists_conversations_for_candidate (0.00s)
--- PASS: TestChatService_ListConversations/lists_conversations_for_company (0.00s)
--- PASS: TestChatService_ListConversations/returns_error_for_invalid_context (0.00s)
=== RUN TestMessage_Struct
--- PASS: TestMessage_Struct (0.00s)
=== RUN TestConversation_Struct
--- PASS: TestConversation_Struct (0.00s)
=== RUN TestNewEmailService
--- PASS: TestNewEmailService (0.00s)
=== RUN TestEmailService_SendTemplateEmail_NoAMQPURL
=== RUN TestEmailService_SendTemplateEmail_NoAMQPURL/returns_error_when_AMQP_URL_not_configured
--- PASS: TestEmailService_SendTemplateEmail_NoAMQPURL (0.00s)
--- PASS: TestEmailService_SendTemplateEmail_NoAMQPURL/returns_error_when_AMQP_URL_not_configured (0.00s)
=== RUN TestEmailService_SendTemplateEmail_DBError
=== RUN TestEmailService_SendTemplateEmail_DBError/handles_db_error_gracefully
2026/02/25 06:02:13 [EmailService] Failed to fetch AMQP URL: sql: connection is already closed
--- PASS: TestEmailService_SendTemplateEmail_DBError (0.00s)
--- PASS: TestEmailService_SendTemplateEmail_DBError/handles_db_error_gracefully (0.00s)
=== RUN TestEmailJob_Struct
--- PASS: TestEmailJob_Struct (0.00s)
=== RUN TestNotificationService_ListNotifications
=== RUN TestNotificationService_ListNotifications/returns_empty_list_when_no_notifications
--- PASS: TestNotificationService_ListNotifications (0.00s)
--- PASS: TestNotificationService_ListNotifications/returns_empty_list_when_no_notifications (0.00s)
=== RUN TestNotificationService_CreateNotification
=== RUN TestNotificationService_CreateNotification/creates_a_new_notification
--- PASS: TestNotificationService_CreateNotification (0.00s)
--- PASS: TestNotificationService_CreateNotification/creates_a_new_notification (0.00s)
=== RUN TestNotificationService_MarkAsRead
=== RUN TestNotificationService_MarkAsRead/marks_notification_as_read
--- PASS: TestNotificationService_MarkAsRead (0.00s)
--- PASS: TestNotificationService_MarkAsRead/marks_notification_as_read (0.00s)
=== RUN TestNotificationService_MarkAllAsRead
=== RUN TestNotificationService_MarkAllAsRead/marks_all_notifications_as_read
--- PASS: TestNotificationService_MarkAllAsRead (0.00s)
--- PASS: TestNotificationService_MarkAllAsRead/marks_all_notifications_as_read (0.00s)
=== RUN TestNewNotificationService
--- PASS: TestNewNotificationService (0.00s)
=== RUN TestNewSettingsService
--- PASS: TestNewSettingsService (0.00s)
=== RUN TestSettingsService_GetSettings
=== RUN TestSettingsService_GetSettings/returns_setting_value
=== RUN TestSettingsService_GetSettings/returns_nil_when_not_found
=== RUN TestSettingsService_GetSettings/returns_error_on_db_failure
--- PASS: TestSettingsService_GetSettings (0.00s)
--- PASS: TestSettingsService_GetSettings/returns_setting_value (0.00s)
--- PASS: TestSettingsService_GetSettings/returns_nil_when_not_found (0.00s)
--- PASS: TestSettingsService_GetSettings/returns_error_on_db_failure (0.00s)
=== RUN TestSettingsService_SaveSettings
=== RUN TestSettingsService_SaveSettings/saves_setting_successfully
=== RUN TestSettingsService_SaveSettings/upserts_existing_setting
=== RUN TestSettingsService_SaveSettings/returns_error_on_db_failure
=== RUN TestSettingsService_SaveSettings/handles_unmarshalable_value
--- PASS: TestSettingsService_SaveSettings (0.00s)
--- PASS: TestSettingsService_SaveSettings/saves_setting_successfully (0.00s)
--- PASS: TestSettingsService_SaveSettings/upserts_existing_setting (0.00s)
--- PASS: TestSettingsService_SaveSettings/returns_error_on_db_failure (0.00s)
--- PASS: TestSettingsService_SaveSettings/handles_unmarshalable_value (0.00s)
=== RUN TestNewStorageService
--- PASS: TestNewStorageService (0.00s)
=== RUN TestUploadConfig_Validation
--- PASS: TestUploadConfig_Validation (0.00s)
=== RUN TestUploadConfig_DefaultRegion
--- PASS: TestUploadConfig_DefaultRegion (0.00s)
=== RUN TestUploadConfig_IncompleteFields
storage_service_test.go:90: Correctly identified incomplete credentials
--- PASS: TestUploadConfig_IncompleteFields (0.00s)
=== RUN TestAdminService_Extra_Unit
--- PASS: TestAdminService_Extra_Unit (0.00s)
=== RUN TestAdminService_DuplicateJob
--- PASS: TestAdminService_DuplicateJob (0.00s)
=== RUN TestAdminService_ListMethods
--- PASS: TestAdminService_ListMethods (0.00s)
=== RUN TestCreateApplication_Success
--- PASS: TestCreateApplication_Success (0.10s)
=== RUN TestApplicationService_DeleteApplication
--- PASS: TestApplicationService_DeleteApplication (0.00s)
=== RUN TestApplicationService_CreateApplication_Full
application_service_test.go:136: CreateApplication failed: Query '
INSERT INTO applications (
job_id, user_id, name, phone, line_id, whatsapp, email,
message, resume_url, documents, answers, status, 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
', arguments do not match: expected 13, but got 14 arguments
--- FAIL: TestApplicationService_CreateApplication_Full (0.00s)
=== RUN TestApplicationService_GetApplications
application_service_test.go:171: GetApplications failed: sql: expected 13 destination arguments in Scan, not 15
--- FAIL: TestApplicationService_GetApplications (0.00s)
=== RUN TestApplicationService_GetApplicationByID
application_service_test.go:206: GetApplicationByID failed: sql: expected 14 destination arguments in Scan, not 15
--- FAIL: TestApplicationService_GetApplicationByID (0.00s)
=== RUN TestAuxiliaryServices_WithMockDB
--- PASS: TestAuxiliaryServices_WithMockDB (0.00s)
=== RUN TestLocationService_WithMockRepo
--- PASS: TestLocationService_WithMockRepo (0.00s)
=== RUN TestNotificationService_SaveFCMToken
--- PASS: TestNotificationService_SaveFCMToken (0.00s)
=== RUN TestChatService_Constructors
--- PASS: TestChatService_Constructors (0.00s)
=== RUN TestSaveCredentials
--- PASS: TestSaveCredentials (0.00s)
=== RUN TestGetDecryptedKey
--- PASS: TestGetDecryptedKey (0.48s)
=== RUN TestListConfiguredServices
--- PASS: TestListConfiguredServices (0.00s)
=== RUN TestDeleteCredentials
--- PASS: TestDeleteCredentials (0.00s)
=== RUN TestEncryptPayload
--- PASS: TestEncryptPayload (0.39s)
=== RUN TestCreateJob
=== RUN TestCreateJob/Success
[JOB_SERVICE DEBUG] === CreateJob Started ===
[JOB_SERVICE DEBUG] CompanyID=1, CreatedBy=user-123, Title=Go Developer, Status=published
[JOB_SERVICE DEBUG] Executing INSERT query...
[JOB_SERVICE DEBUG] Job struct: &{ID: CompanyID:1 CreatedBy:user-123 Title:Go Developer Description: SalaryMin:<nil> SalaryMax:<nil> SalaryType:<nil> Currency:<nil> SalaryNegotiable:false EmploymentType:<nil> WorkMode:<nil> WorkingHours:<nil> Location:<nil> RegionID:<nil> CityID:<nil> Requirements:map[] Benefits:map[] Questions:map[] VisaSupport:false LanguageLevel:<nil> Status:published IsFeatured:false ViewCount:0 FeaturedUntil:<nil> DatePosted:2026-02-25 06:02:14.4861294 -0600 CST m=+1.036940001 CreatedAt:2026-02-25 06:02:14.4861294 -0600 CST m=+1.036940001 UpdatedAt:2026-02-25 06:02:14.4861294 -0600 CST m=+1.036940001}
[JOB_SERVICE ERROR] INSERT query failed: Query '
INSERT INTO jobs (
company_id, created_by, title, description, salary_min, salary_max, salary_type, currency,
employment_type, work_mode, working_hours, location, region_id, city_id,
requirements, benefits, questions, visa_support, language_level, status, date_posted, created_at, updated_at, salary_negotiable
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)
RETURNING id, date_posted, created_at, updated_at
', arguments do not match: expected 21, but got 24 arguments
job_service_test.go:62: JobService.CreateJob() error = Query '
INSERT INTO jobs (
company_id, created_by, title, description, salary_min, salary_max, salary_type, currency,
employment_type, work_mode, working_hours, location, region_id, city_id,
requirements, benefits, questions, visa_support, language_level, status, date_posted, created_at, updated_at, salary_negotiable
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)
RETURNING id, date_posted, created_at, updated_at
', arguments do not match: expected 21, but got 24 arguments, wantErr false
=== RUN TestCreateJob/DB_Error
[JOB_SERVICE DEBUG] === CreateJob Started ===
[JOB_SERVICE DEBUG] CompanyID=1, CreatedBy=user-123, Title=Go Developer, Status=
[JOB_SERVICE DEBUG] Executing INSERT query...
[JOB_SERVICE DEBUG] Job struct: &{ID: CompanyID:1 CreatedBy:user-123 Title:Go Developer Description: SalaryMin:<nil> SalaryMax:<nil> SalaryType:<nil> Currency:<nil> SalaryNegotiable:false EmploymentType:<nil> WorkMode:<nil> WorkingHours:<nil> Location:<nil> RegionID:<nil> CityID:<nil> Requirements:map[] Benefits:map[] Questions:map[] VisaSupport:false LanguageLevel:<nil> Status: IsFeatured:false ViewCount:0 FeaturedUntil:<nil> DatePosted:2026-02-25 06:02:14.4879161 -0600 CST m=+1.038726701 CreatedAt:2026-02-25 06:02:14.4879161 -0600 CST m=+1.038726701 UpdatedAt:2026-02-25 06:02:14.4879161 -0600 CST m=+1.038726701}
[JOB_SERVICE ERROR] INSERT query failed: Query '
INSERT INTO jobs (
company_id, created_by, title, description, salary_min, salary_max, salary_type, currency,
employment_type, work_mode, working_hours, location, region_id, city_id,
requirements, benefits, questions, visa_support, language_level, status, date_posted, created_at, updated_at, salary_negotiable
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)
RETURNING id, date_posted, created_at, updated_at
', arguments do not match: expected 21, but got 24 arguments
--- FAIL: TestCreateJob (0.00s)
--- FAIL: TestCreateJob/Success (0.00s)
--- PASS: TestCreateJob/DB_Error (0.00s)
=== RUN TestGetJobs
=== RUN TestGetJobs/List_All
--- PASS: TestGetJobs (0.00s)
--- PASS: TestGetJobs/List_All (0.00s)
=== RUN TestGetJobByID
job_service_test.go:165: GetJobByID() error = sql: expected 20 destination arguments in Scan, not 26
--- FAIL: TestGetJobByID (0.00s)
=== RUN TestUpdateJob
job_service_test.go:210: UpdateJob() error = sql: expected 20 destination arguments in Scan, not 26
--- FAIL: TestUpdateJob (0.00s)
=== RUN TestDeleteJob
--- PASS: TestDeleteJob (0.00s)
=== RUN TestTicketService_CRUD
--- PASS: TestTicketService_CRUD (0.00s)
=== RUN TestTicketService_Extended
--- PASS: TestTicketService_Extended (0.00s)
FAIL
FAIL github.com/rede5/gohorsejobs/backend/internal/services 1.143s
FAIL

View file

@ -0,0 +1,57 @@
--- FAIL: TestAdminService_GetUser (0.00s)
--- FAIL: TestAdminService_GetUser/returns_user_by_id (0.00s)
admin_service_test.go:173: Unexpected error: Query: could not match actual sql: "SELECT id, full_name, email, role, COALESCE(status, 'active'), created_at, phone, bio, avatar_url FROM users WHERE id = $1" with expected regexp "SELECT id, full_name, email, role, created_at FROM users WHERE id"
2026/02/25 06:11:52 [EmailService] Failed to fetch AMQP URL: sql: connection is already closed
--- FAIL: TestApplicationService_CreateApplication_Full (0.00s)
application_service_test.go:136: CreateApplication failed: Query '
INSERT INTO applications (
job_id, user_id, name, phone, line_id, whatsapp, email,
message, resume_url, documents, answers, status, 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
', arguments do not match: expected 13, but got 14 arguments
--- FAIL: TestApplicationService_GetApplications (0.00s)
application_service_test.go:171: GetApplications failed: sql: expected 13 destination arguments in Scan, not 15
--- FAIL: TestApplicationService_GetApplicationByID (0.00s)
application_service_test.go:206: GetApplicationByID failed: sql: expected 14 destination arguments in Scan, not 15
[JOB_SERVICE DEBUG] === CreateJob Started ===
[JOB_SERVICE DEBUG] CompanyID=1, CreatedBy=user-123, Title=Go Developer, Status=published
[JOB_SERVICE DEBUG] Executing INSERT query...
[JOB_SERVICE DEBUG] Job struct: &{ID: CompanyID:1 CreatedBy:user-123 Title:Go Developer Description: SalaryMin:<nil> SalaryMax:<nil> SalaryType:<nil> Currency:<nil> SalaryNegotiable:false EmploymentType:<nil> WorkMode:<nil> WorkingHours:<nil> Location:<nil> RegionID:<nil> CityID:<nil> Requirements:map[] Benefits:map[] Questions:map[] VisaSupport:false LanguageLevel:<nil> Status:published IsFeatured:false ViewCount:0 FeaturedUntil:<nil> DatePosted:2026-02-25 06:11:52.3397837 -0600 CST m=+0.337955101 CreatedAt:2026-02-25 06:11:52.3397837 -0600 CST m=+0.337955101 UpdatedAt:2026-02-25 06:11:52.3397837 -0600 CST m=+0.337955101}
[JOB_SERVICE ERROR] INSERT query failed: Query '
INSERT INTO jobs (
company_id, created_by, title, description, salary_min, salary_max, salary_type, currency,
employment_type, work_mode, working_hours, location, region_id, city_id,
requirements, benefits, questions, visa_support, language_level, status, date_posted, created_at, updated_at, salary_negotiable
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)
RETURNING id, date_posted, created_at, updated_at
', arguments do not match: expected 21, but got 24 arguments
[JOB_SERVICE DEBUG] === CreateJob Started ===
[JOB_SERVICE DEBUG] CompanyID=1, CreatedBy=user-123, Title=Go Developer, Status=
[JOB_SERVICE DEBUG] Executing INSERT query...
[JOB_SERVICE DEBUG] Job struct: &{ID: CompanyID:1 CreatedBy:user-123 Title:Go Developer Description: SalaryMin:<nil> SalaryMax:<nil> SalaryType:<nil> Currency:<nil> SalaryNegotiable:false EmploymentType:<nil> WorkMode:<nil> WorkingHours:<nil> Location:<nil> RegionID:<nil> CityID:<nil> Requirements:map[] Benefits:map[] Questions:map[] VisaSupport:false LanguageLevel:<nil> Status: IsFeatured:false ViewCount:0 FeaturedUntil:<nil> DatePosted:2026-02-25 06:11:52.3397837 -0600 CST m=+0.337955101 CreatedAt:2026-02-25 06:11:52.3397837 -0600 CST m=+0.337955101 UpdatedAt:2026-02-25 06:11:52.3397837 -0600 CST m=+0.337955101}
[JOB_SERVICE ERROR] INSERT query failed: Query '
INSERT INTO jobs (
company_id, created_by, title, description, salary_min, salary_max, salary_type, currency,
employment_type, work_mode, working_hours, location, region_id, city_id,
requirements, benefits, questions, visa_support, language_level, status, date_posted, created_at, updated_at, salary_negotiable
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)
RETURNING id, date_posted, created_at, updated_at
', arguments do not match: expected 21, but got 24 arguments
--- FAIL: TestCreateJob (0.00s)
--- FAIL: TestCreateJob/Success (0.00s)
job_service_test.go:62: JobService.CreateJob() error = Query '
INSERT INTO jobs (
company_id, created_by, title, description, salary_min, salary_max, salary_type, currency,
employment_type, work_mode, working_hours, location, region_id, city_id,
requirements, benefits, questions, visa_support, language_level, status, date_posted, created_at, updated_at, salary_negotiable
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)
RETURNING id, date_posted, created_at, updated_at
', arguments do not match: expected 21, but got 24 arguments, wantErr false
--- FAIL: TestGetJobByID (0.00s)
job_service_test.go:165: GetJobByID() error = sql: expected 20 destination arguments in Scan, not 26
--- FAIL: TestUpdateJob (0.00s)
job_service_test.go:210: UpdateJob() error = sql: expected 20 destination arguments in Scan, not 26
FAIL
FAIL github.com/rede5/gohorsejobs/backend/internal/services 0.441s
FAIL

View file

@ -0,0 +1,152 @@
2026/02/25 06:54:46 Current Working Directory: C:\dev\gohorsejobs\backend
2026/02/25 06:54:46 Processing migration: 000_init_uuid_v7.sql (from migrations/000_init_uuid_v7.sql)
2026/02/25 06:54:46 Executing migration file migrations/000_init_uuid_v7.sql
2026/02/25 06:54:46 Migration 000_init_uuid_v7.sql applied successfully
2026/02/25 06:54:46 Processing migration: 001_create_users_table.sql (from migrations/001_create_users_table.sql)
2026/02/25 06:54:46 Executing migration file migrations/001_create_users_table.sql
2026/02/25 06:54:46 Warning while applying 001_create_users_table.sql: pq: relation "idx_users_identifier" already exists
2026/02/25 06:54:46 Processing migration: 002_create_companies_table.sql (from migrations/002_create_companies_table.sql)
2026/02/25 06:54:46 Executing migration file migrations/002_create_companies_table.sql
2026/02/25 06:54:46 Warning while applying 002_create_companies_table.sql: pq: relation "idx_companies_active" already exists
2026/02/25 06:54:46 Processing migration: 003_create_user_companies_table.sql (from migrations/003_create_user_companies_table.sql)
2026/02/25 06:54:46 Executing migration file migrations/003_create_user_companies_table.sql
2026/02/25 06:54:46 Warning while applying 003_create_user_companies_table.sql: pq: relation "idx_user_companies_user" already exists
2026/02/25 06:54:46 Processing migration: 004_create_prefectures_cities_tables.sql (from migrations/004_create_prefectures_cities_tables.sql)
2026/02/25 06:54:46 Executing migration file migrations/004_create_prefectures_cities_tables.sql
2026/02/25 06:54:46 Migration 004_create_prefectures_cities_tables.sql applied successfully
2026/02/25 06:54:46 Processing migration: 005_create_jobs_table.sql (from migrations/005_create_jobs_table.sql)
2026/02/25 06:54:46 Executing migration file migrations/005_create_jobs_table.sql
2026/02/25 06:54:46 Warning while applying 005_create_jobs_table.sql: pq: relation "idx_jobs_company" already exists
2026/02/25 06:54:46 Processing migration: 006_create_applications_table.sql (from migrations/006_create_applications_table.sql)
2026/02/25 06:54:46 Executing migration file migrations/006_create_applications_table.sql
2026/02/25 06:54:46 Warning while applying 006_create_applications_table.sql: pq: relation "idx_applications_job" already exists
2026/02/25 06:54:46 Processing migration: 007_create_favorite_jobs_table.sql (from migrations/007_create_favorite_jobs_table.sql)
2026/02/25 06:54:46 Executing migration file migrations/007_create_favorite_jobs_table.sql
2026/02/25 06:54:46 Warning while applying 007_create_favorite_jobs_table.sql: pq: relation "idx_favorite_jobs_user" already exists
2026/02/25 06:54:46 Processing migration: 008_create_password_resets_table.sql (from migrations/008_create_password_resets_table.sql)
2026/02/25 06:54:46 Executing migration file migrations/008_create_password_resets_table.sql
2026/02/25 06:54:46 Warning while applying 008_create_password_resets_table.sql: pq: relation "idx_password_resets_token" already exists
2026/02/25 06:54:46 Processing migration: 009_unify_schema.sql (from migrations/009_unify_schema.sql)
2026/02/25 06:54:46 Executing migration file migrations/009_unify_schema.sql
2026/02/25 06:54:46 Migration 009_unify_schema.sql applied successfully
2026/02/25 06:54:46 Processing migration: 010_seed_super_admin.sql (from migrations/010_seed_super_admin.sql)
2026/02/25 06:54:46 Executing migration file migrations/010_seed_super_admin.sql
2026/02/25 06:54:47 Migration 010_seed_super_admin.sql applied successfully
2026/02/25 06:54:47 Processing migration: 011_add_is_featured_to_jobs.sql (from migrations/011_add_is_featured_to_jobs.sql)
2026/02/25 06:54:47 Executing migration file migrations/011_add_is_featured_to_jobs.sql
2026/02/25 06:54:47 Migration 011_add_is_featured_to_jobs.sql applied successfully
2026/02/25 06:54:47 Processing migration: 012_add_work_mode.sql (from migrations/012_add_work_mode.sql)
2026/02/25 06:54:47 Executing migration file migrations/012_add_work_mode.sql
2026/02/25 06:54:47 Migration 012_add_work_mode.sql applied successfully
2026/02/25 06:54:47 Processing migration: 013_create_backoffice_tables.sql (from migrations/013_create_backoffice_tables.sql)
2026/02/25 06:54:47 Executing migration file migrations/013_create_backoffice_tables.sql
2026/02/25 06:54:47 Migration 013_create_backoffice_tables.sql applied successfully
2026/02/25 06:54:47 Processing migration: 014_update_job_status_constraint.sql (from migrations/014_update_job_status_constraint.sql)
2026/02/25 06:54:47 Executing migration file migrations/014_update_job_status_constraint.sql
2026/02/25 06:54:47 Migration 014_update_job_status_constraint.sql applied successfully
2026/02/25 06:54:47 Processing migration: 015_add_candidate_profile_fields.sql (from migrations/015_add_candidate_profile_fields.sql)
2026/02/25 06:54:47 Executing migration file migrations/015_add_candidate_profile_fields.sql
2026/02/25 06:54:47 Migration 015_add_candidate_profile_fields.sql applied successfully
2026/02/25 06:54:47 Processing migration: 016_create_notifications_table.sql (from migrations/016_create_notifications_table.sql)
2026/02/25 06:54:47 Executing migration file migrations/016_create_notifications_table.sql
2026/02/25 06:54:47 Warning while applying 016_create_notifications_table.sql: pq: relation "idx_notifications_user_id" already exists
2026/02/25 06:54:47 Processing migration: 017_create_tickets_table.sql (from migrations/017_create_tickets_table.sql)
2026/02/25 06:54:47 Executing migration file migrations/017_create_tickets_table.sql
2026/02/25 06:54:47 Warning while applying 017_create_tickets_table.sql: pq: relation "tickets" already exists
2026/02/25 06:54:47 Processing migration: 018_add_currency_to_jobs.sql (from migrations/018_add_currency_to_jobs.sql)
2026/02/25 06:54:47 Executing migration file migrations/018_add_currency_to_jobs.sql
2026/02/25 06:54:47 Migration 018_add_currency_to_jobs.sql applied successfully
2026/02/25 06:54:47 Processing migration: 019_create_job_payments_table.sql (from migrations/019_create_job_payments_table.sql)
2026/02/25 06:54:47 Executing migration file migrations/019_create_job_payments_table.sql
2026/02/25 06:54:47 Warning while applying 019_create_job_payments_table.sql: pq: relation "idx_job_payments_job_id" already exists
2026/02/25 06:54:47 Processing migration: 020_create_fcm_tokens_table.sql (from migrations/020_create_fcm_tokens_table.sql)
2026/02/25 06:54:47 Executing migration file migrations/020_create_fcm_tokens_table.sql
2026/02/25 06:54:47 Migration 020_create_fcm_tokens_table.sql applied successfully
2026/02/25 06:54:47 Processing migration: 021_location_hierarchy.sql (from migrations/021_location_hierarchy.sql)
2026/02/25 06:54:47 Executing migration file migrations/021_location_hierarchy.sql
2026/02/25 06:54:47 Warning while applying 021_location_hierarchy.sql: pq: relation "regions_old" already exists
2026/02/25 06:54:47 Processing migration: 022_fix_superadmin_role.sql (from migrations/022_fix_superadmin_role.sql)
2026/02/25 06:54:47 Executing migration file migrations/022_fix_superadmin_role.sql
2026/02/25 06:54:47 Migration 022_fix_superadmin_role.sql applied successfully
2026/02/25 06:54:47 Processing migration: 023_ensure_seeded_admins_roles.sql (from migrations/023_ensure_seeded_admins_roles.sql)
2026/02/25 06:54:47 Executing migration file migrations/023_ensure_seeded_admins_roles.sql
2026/02/25 06:54:47 Migration 023_ensure_seeded_admins_roles.sql applied successfully
2026/02/25 06:54:47 Processing migration: 024_create_external_services_credentials.sql (from migrations/024_create_external_services_credentials.sql)
2026/02/25 06:54:47 Executing migration file migrations/024_create_external_services_credentials.sql
2026/02/25 06:54:47 Warning while applying 024_create_external_services_credentials.sql: pq: relation "idx_service_name" already exists
2026/02/25 06:54:47 Processing migration: 025_create_chat_tables.sql (from migrations/025_create_chat_tables.sql)
2026/02/25 06:54:47 Executing migration file migrations/025_create_chat_tables.sql
2026/02/25 06:54:47 Warning while applying 025_create_chat_tables.sql: pq: relation "idx_conversations_candidate" already exists
2026/02/25 06:54:47 Processing migration: 026_create_system_settings.sql (from migrations/026_create_system_settings.sql)
2026/02/25 06:54:47 Executing migration file migrations/026_create_system_settings.sql
2026/02/25 06:54:47 Migration 026_create_system_settings.sql applied successfully
2026/02/25 06:54:47 Processing migration: 027_create_email_system.sql (from migrations/027_create_email_system.sql)
2026/02/25 06:54:47 Executing migration file migrations/027_create_email_system.sql
2026/02/25 06:54:47 Migration 027_create_email_system.sql applied successfully
2026/02/25 06:54:47 Processing migration: 028_add_avatar_url_to_users.sql (from migrations/028_add_avatar_url_to_users.sql)
2026/02/25 06:54:47 Executing migration file migrations/028_add_avatar_url_to_users.sql
2026/02/25 06:54:47 Migration 028_add_avatar_url_to_users.sql applied successfully
2026/02/25 06:54:47 Processing migration: 029_expand_employment_types.sql (from migrations/029_expand_employment_types.sql)
2026/02/25 06:54:47 Executing migration file migrations/029_expand_employment_types.sql
2026/02/25 06:54:47 Migration 029_expand_employment_types.sql applied successfully
2026/02/25 06:54:47 Processing migration: 030_add_salary_negotiable.sql (from migrations/030_add_salary_negotiable.sql)
2026/02/25 06:54:47 Executing migration file migrations/030_add_salary_negotiable.sql
2026/02/25 06:54:47 Migration 030_add_salary_negotiable.sql applied successfully
2026/02/25 06:54:47 Processing migration: 031_add_company_profile_fields.sql (from migrations/031_add_company_profile_fields.sql)
2026/02/25 06:54:47 Executing migration file migrations/031_add_company_profile_fields.sql
2026/02/25 06:54:47 Migration 031_add_company_profile_fields.sql applied successfully
2026/02/25 06:54:47 Processing migration: 032_update_superadmin_lol.sql (from migrations/032_update_superadmin_lol.sql)
2026/02/25 06:54:47 Executing migration file migrations/032_update_superadmin_lol.sql
2026/02/25 06:54:47 Migration 032_update_superadmin_lol.sql applied successfully
2026/02/25 06:54:47 Processing migration: 033_add_refactor_columns.sql (from migrations/033_add_refactor_columns.sql)
2026/02/25 06:54:47 Executing migration file migrations/033_add_refactor_columns.sql
2026/02/25 06:54:47 Migration 033_add_refactor_columns.sql applied successfully
2026/02/25 06:54:47 Processing migration: 034_create_video_interviews.sql (from migrations/034_create_video_interviews.sql)
2026/02/25 06:54:47 Executing migration file migrations/034_create_video_interviews.sql
2026/02/25 06:54:47 Migration 034_create_video_interviews.sql applied successfully
2026/02/25 06:54:47 Processing migration: 035_create_job_alerts.sql (from migrations/035_create_job_alerts.sql)
2026/02/25 06:54:47 Executing migration file migrations/035_create_job_alerts.sql
2026/02/25 06:54:47 Migration 035_create_job_alerts.sql applied successfully
2026/02/25 06:54:47 Processing migration: 036_create_company_followers.sql (from migrations/036_create_company_followers.sql)
2026/02/25 06:54:47 Executing migration file migrations/036_create_company_followers.sql
2026/02/25 06:54:47 Migration 036_create_company_followers.sql applied successfully
2026/02/25 06:54:47 Processing migration: 036_ensure_jobs_work_mode.sql (from migrations/036_ensure_jobs_work_mode.sql)
2026/02/25 06:54:47 Executing migration file migrations/036_ensure_jobs_work_mode.sql
2026/02/25 06:54:47 Migration 036_ensure_jobs_work_mode.sql applied successfully
2026/02/25 06:54:47 Processing migration: 037_add_date_posted_to_jobs.sql (from migrations/037_add_date_posted_to_jobs.sql)
2026/02/25 06:54:47 Executing migration file migrations/037_add_date_posted_to_jobs.sql
2026/02/25 06:54:47 Migration 037_add_date_posted_to_jobs.sql applied successfully
2026/02/25 06:54:47 Processing migration: 037_add_profile_fields_to_users.sql (from migrations/037_add_profile_fields_to_users.sql)
2026/02/25 06:54:47 Executing migration file migrations/037_add_profile_fields_to_users.sql
2026/02/25 06:54:47 Migration 037_add_profile_fields_to_users.sql applied successfully
2026/02/25 06:54:47 Processing migration: 038_create_password_reset_tokens.sql (from migrations/038_create_password_reset_tokens.sql)
2026/02/25 06:54:47 Executing migration file migrations/038_create_password_reset_tokens.sql
2026/02/25 06:54:47 Warning while applying 038_create_password_reset_tokens.sql: pq: relation "idx_reset_tokens_token" already exists
2026/02/25 06:54:47 Processing migration: 039_create_tickets_table_v2.sql (from migrations/039_create_tickets_table_v2.sql)
2026/02/25 06:54:47 Executing migration file migrations/039_create_tickets_table_v2.sql
2026/02/25 06:54:47 Migration 039_create_tickets_table_v2.sql applied successfully
2026/02/25 06:54:47 Processing migration: 040_create_activity_logs_table.sql (from migrations/040_create_activity_logs_table.sql)
2026/02/25 06:54:47 Executing migration file migrations/040_create_activity_logs_table.sql
2026/02/25 06:54:47 Migration 040_create_activity_logs_table.sql applied successfully
2026/02/25 06:54:47 Processing migration: 041_create_notifications_table_v2.sql (from migrations/041_create_notifications_table_v2.sql)
2026/02/25 06:54:47 Executing migration file migrations/041_create_notifications_table_v2.sql
2026/02/25 06:54:47 Migration 041_create_notifications_table_v2.sql applied successfully
2026/02/25 06:54:47 Processing migration: 042_add_view_count_and_job_views.sql (from migrations/042_add_view_count_and_job_views.sql)
2026/02/25 06:54:47 Executing migration file migrations/042_add_view_count_and_job_views.sql
2026/02/25 06:54:47 Migration 042_add_view_count_and_job_views.sql applied successfully
2026/02/25 06:54:47 Processing migration: 043_add_company_subscription.sql (from migrations/043_add_company_subscription.sql)
2026/02/25 06:54:47 Executing migration file migrations/043_add_company_subscription.sql
2026/02/25 06:54:47 Migration 043_add_company_subscription.sql applied successfully
2026/02/25 06:54:47 Processing migration: 044_add_category_to_tickets.sql (from migrations/044_add_category_to_tickets.sql)
2026/02/25 06:54:47 Executing migration file migrations/044_add_category_to_tickets.sql
2026/02/25 06:54:47 Migration 044_add_category_to_tickets.sql applied successfully
2026/02/25 06:54:47 Processing migration: 045_add_answers_to_applications.sql (from migrations/045_add_answers_to_applications.sql)
2026/02/25 06:54:47 Executing migration file migrations/045_add_answers_to_applications.sql
2026/02/25 06:54:47 Migration 045_add_answers_to_applications.sql applied successfully
2026/02/25 06:54:47 Processing migration: 046_fix_user_status_varchar.sql (from migrations/046_fix_user_status_varchar.sql)
2026/02/25 06:54:47 Executing migration file migrations/046_fix_user_status_varchar.sql
2026/02/25 06:54:47 Migration 046_fix_user_status_varchar.sql applied successfully
2026/02/25 06:54:47 Processing migration: 047_optimize_job_indexes.sql (from migrations/047_optimize_job_indexes.sql)
2026/02/25 06:54:47 Executing migration file migrations/047_optimize_job_indexes.sql
2026/02/25 06:54:47 Failed applying migration 047_optimize_job_indexes.sql: pq: function immutable_coalesce_dates(timestamp with time zone, timestamp without time zone) does not exist
exit status 1

View file

@ -0,0 +1,19 @@
-- Migration: 047_optimize_job_indexes
-- Description: Creates an immutable wrapper function and a compound index to dramatically optimize the default sorting on the Homepage.
-- Removing the heavy Table Scan calculations for `ORDER BY j.is_featured DESC, COALESCE(j.date_posted, j.created_at) DESC`.
-- 1. Create an immutable function wrapper for COALESCE because PostgreSQL forbids volatile timestamps in indexes
CREATE OR REPLACE FUNCTION immutable_coalesce_dates(d1 timestamp with time zone, d2 timestamp without time zone)
RETURNS timestamp with time zone AS $$
BEGIN
RETURN COALESCE(d1, d2 AT TIME ZONE 'UTC');
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- 2. Create the precise index using the new immutable function
CREATE INDEX IF NOT EXISTS idx_jobs_featured_date_posted
ON jobs (is_featured DESC, immutable_coalesce_dates(date_posted, created_at) DESC);
-- 3. Also index jobs creation date explicitly if they search strictly by recents without features
CREATE INDEX IF NOT EXISTS idx_jobs_coalesce_date_posted
ON jobs (immutable_coalesce_dates(date_posted, created_at) DESC);