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) {
userID := "019b5290-9680-7c06-9ee3-c9e0e117251b"
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).
WillReturnRows(sqlmock.NewRows([]string{"id", "name", "email", "role", "created_at"}).
AddRow(userID, "Test User", "test@example.com", "admin", now))
WillReturnRows(sqlmock.NewRows([]string{"id", "full_name", "email", "role", "status", "created_at", "phone", "bio", "avatar_url"}).
AddRow(userID, "Test User", "test@example.com", "admin", "active", now, "123", "Bio", "url"))
user, err := service.GetUser(ctx, userID)
if err != nil {
@ -472,7 +472,6 @@ func TestIsActiveApplicationStatus(t *testing.T) {
}
}
// Tests for GetCompanyByUserID
func TestAdminService_GetCompanyByUserID(t *testing.T) {
db, mock, err := sqlmock.New()

View file

@ -126,7 +126,7 @@ func TestApplicationService_CreateApplication_Full(t *testing.T) {
mock.ExpectQuery("INSERT INTO applications").
WithArgs(
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"}).
AddRow("app-789", time.Now(), time.Now()))
@ -156,10 +156,10 @@ func TestApplicationService_GetApplications(t *testing.T) {
rows := sqlmock.NewRows([]string{
"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(
"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").
@ -191,10 +191,10 @@ func TestApplicationService_GetApplicationByID(t *testing.T) {
rows := sqlmock.NewRows([]string{
"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(
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").

View file

@ -21,13 +21,18 @@ func TestNewEmailService(t *testing.T) {
}
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")
}
if service.db != db {
if concreteService.db != db {
t.Error("Expected db to be set")
}
if service.credentialsService != credsSvc {
if concreteService.credentialsService != credsSvc {
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,
(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::text = c.id::text
LEFT JOIN states r ON j.region_id::text = r.id::text
LEFT JOIN cities ci ON j.city_id::text = ci.id::text
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`
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{}
argId := 1
@ -105,7 +106,11 @@ func (s *JobService) GetJobs(filter dto.JobFilterQuery) ([]models.JobWithCompany
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)
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)
argId++
}

View file

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

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);