From fb5aba4ed791a064b6490e3733d05cd3f3f804e5 Mon Sep 17 00:00:00 2001 From: Tiago Yamamoto Date: Sun, 15 Feb 2026 13:36:32 -0300 Subject: [PATCH] =?UTF-8?q?backend:=20suportar=20workMode=20em=20vagas=20e?= =?UTF-8?q?=20refor=C3=A7ar=20schema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/dto/requests.go | 6 ++-- backend/internal/handlers/job_handler_test.go | 21 ++++++++++---- backend/internal/services/job_service.go | 12 ++++++-- .../migrations/036_ensure_jobs_work_mode.sql | 28 +++++++++++++++++++ 4 files changed, 57 insertions(+), 10 deletions(-) create mode 100644 backend/migrations/036_ensure_jobs_work_mode.sql diff --git a/backend/internal/dto/requests.go b/backend/internal/dto/requests.go index 4db6f44..0d397a5 100755 --- a/backend/internal/dto/requests.go +++ b/backend/internal/dto/requests.go @@ -11,6 +11,7 @@ type CreateJobRequest struct { Currency *string `json:"currency,omitempty" validate:"omitempty,oneof=BRL USD EUR GBP JPY CNY AED CAD AUD CHF"` SalaryNegotiable bool `json:"salaryNegotiable"` EmploymentType *string `json:"employmentType,omitempty" validate:"omitempty,oneof=full-time part-time dispatch contract temporary training voluntary permanent"` + WorkMode *string `json:"workMode,omitempty" validate:"omitempty,oneof=onsite hybrid remote"` WorkingHours *string `json:"workingHours,omitempty"` Location *string `json:"location,omitempty"` RegionID *int `json:"regionId,omitempty"` @@ -33,6 +34,7 @@ type UpdateJobRequest struct { Currency *string `json:"currency,omitempty" validate:"omitempty,oneof=BRL USD EUR GBP JPY CNY AED CAD AUD CHF"` SalaryNegotiable *bool `json:"salaryNegotiable,omitempty"` EmploymentType *string `json:"employmentType,omitempty" validate:"omitempty,oneof=full-time part-time dispatch contract temporary training voluntary permanent"` + WorkMode *string `json:"workMode,omitempty" validate:"omitempty,oneof=onsite hybrid remote"` WorkingHours *string `json:"workingHours,omitempty"` Location *string `json:"location,omitempty"` RegionID *int `json:"regionId,omitempty"` @@ -202,8 +204,8 @@ type UpdateVideoInterviewRequest struct { // VideoInterviewFeedbackRequest represents submitting interview feedback type VideoInterviewFeedbackRequest struct { InterviewerFeedback *string `json:"interviewerFeedback,omitempty"` - CandidateFeedback *string `json:"candidateFeedback,omitempty"` - Rating *int `json:"rating,omitempty" validate:"omitempty,min=1,max=5"` + CandidateFeedback *string `json:"candidateFeedback,omitempty"` + Rating *int `json:"rating,omitempty" validate:"omitempty,min=1,max=5"` } // CreateJobAlertRequest represents creating a job alert diff --git a/backend/internal/handlers/job_handler_test.go b/backend/internal/handlers/job_handler_test.go index d23be00..edad2bd 100644 --- a/backend/internal/handlers/job_handler_test.go +++ b/backend/internal/handlers/job_handler_test.go @@ -89,7 +89,6 @@ func TestGetJobs_Success(t *testing.T) { assert.Equal(t, 2, response.Pagination.Total) } - func TestGetJobs_ParsesCareerjetAliases(t *testing.T) { mockService := &mockJobService{ getJobsFunc: func(filter dto.JobFilterQuery) ([]models.JobWithCompany, int, error) { @@ -119,10 +118,14 @@ func TestCreateJob_Success(t *testing.T) { mockService := &mockJobService{ createJobFunc: func(req dto.CreateJobRequest, createdBy string) (*models.Job, error) { assert.Equal(t, "user-123", createdBy) + if assert.NotNil(t, req.WorkMode) { + assert.Equal(t, "remote", *req.WorkMode) + } return &models.Job{ - ID: "1", - Title: req.Title, - Status: "open", + ID: "1", + Title: req.Title, + Status: "open", + WorkMode: req.WorkMode, }, nil }, } @@ -134,6 +137,7 @@ func TestCreateJob_Success(t *testing.T) { Title: "Backend Developer", Description: "Build awesome APIs", Status: "open", + WorkMode: func() *string { s := "remote"; return &s }(), } body, _ := json.Marshal(jobReq) @@ -154,6 +158,9 @@ func TestCreateJob_Success(t *testing.T) { err := json.Unmarshal(rr.Body.Bytes(), &job) assert.NoError(t, err) assert.Equal(t, "Backend Developer", job.Title) + if assert.NotNil(t, job.WorkMode) { + assert.Equal(t, "remote", *job.WorkMode) + } } func TestCreateJob_ServiceError(t *testing.T) { @@ -219,8 +226,12 @@ func TestDeleteJob_Success(t *testing.T) { } func TestUpdateJob_Success(t *testing.T) { + workMode := "hybrid" mockService := &mockJobService{ updateJobFunc: func(id string, req dto.UpdateJobRequest) (*models.Job, error) { + if assert.NotNil(t, req.WorkMode) { + assert.Equal(t, workMode, *req.WorkMode) + } return &models.Job{ID: id, Title: "Updated"}, nil }, } @@ -230,7 +241,7 @@ func TestUpdateJob_Success(t *testing.T) { // Cleaner title := "Updated" - reqBody, _ = json.Marshal(dto.UpdateJobRequest{Title: &title}) + reqBody, _ = json.Marshal(dto.UpdateJobRequest{Title: &title, WorkMode: &workMode}) req := httptest.NewRequest("PUT", "/jobs/1", bytes.NewReader(reqBody)) req.SetPathValue("id", "1") diff --git a/backend/internal/services/job_service.go b/backend/internal/services/job_service.go index bf48cdf..addc663 100644 --- a/backend/internal/services/job_service.go +++ b/backend/internal/services/job_service.go @@ -25,9 +25,9 @@ func (s *JobService) CreateJob(req dto.CreateJobRequest, createdBy string) (*mod query := ` INSERT INTO jobs ( company_id, created_by, title, description, salary_min, salary_max, salary_type, currency, - employment_type, working_hours, location, region_id, city_id, + 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) + ) 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 ` @@ -42,6 +42,7 @@ func (s *JobService) CreateJob(req dto.CreateJobRequest, createdBy string) (*mod Currency: req.Currency, SalaryNegotiable: req.SalaryNegotiable, EmploymentType: req.EmploymentType, + WorkMode: req.WorkMode, WorkingHours: req.WorkingHours, Location: req.Location, RegionID: req.RegionID, @@ -63,7 +64,7 @@ func (s *JobService) CreateJob(req dto.CreateJobRequest, createdBy string) (*mod err := s.DB.QueryRow( query, job.CompanyID, job.CreatedBy, job.Title, job.Description, job.SalaryMin, job.SalaryMax, job.SalaryType, job.Currency, - job.EmploymentType, job.WorkingHours, job.Location, job.RegionID, job.CityID, + job.EmploymentType, job.WorkMode, job.WorkingHours, job.Location, job.RegionID, job.CityID, job.Requirements, job.Benefits, job.Questions, job.VisaSupport, job.LanguageLevel, job.Status, job.DatePosted, job.CreatedAt, job.UpdatedAt, job.SalaryNegotiable, ).Scan(&job.ID, &job.DatePosted, &job.CreatedAt, &job.UpdatedAt) @@ -378,6 +379,11 @@ func (s *JobService) UpdateJob(id string, req dto.UpdateJobRequest) (*models.Job args = append(args, *req.EmploymentType) argId++ } + if req.WorkMode != nil { + setClauses = append(setClauses, fmt.Sprintf("work_mode = $%d", argId)) + args = append(args, *req.WorkMode) + argId++ + } if req.WorkingHours != nil { setClauses = append(setClauses, fmt.Sprintf("working_hours = $%d", argId)) args = append(args, *req.WorkingHours) diff --git a/backend/migrations/036_ensure_jobs_work_mode.sql b/backend/migrations/036_ensure_jobs_work_mode.sql new file mode 100644 index 0000000..93e08e1 --- /dev/null +++ b/backend/migrations/036_ensure_jobs_work_mode.sql @@ -0,0 +1,28 @@ +-- Migration: Ensure jobs.work_mode exists and accepts frontend values + +ALTER TABLE jobs + ADD COLUMN IF NOT EXISTS work_mode VARCHAR(20); + +-- Normalize legacy/invalid values before enforcing constraint +UPDATE jobs +SET work_mode = 'onsite' +WHERE work_mode IS NULL + OR work_mode NOT IN ('onsite', 'hybrid', 'remote'); + +ALTER TABLE jobs + ALTER COLUMN work_mode SET DEFAULT 'onsite'; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'jobs_work_mode_check' + ) THEN + ALTER TABLE jobs + ADD CONSTRAINT jobs_work_mode_check + CHECK (work_mode IN ('onsite', 'hybrid', 'remote')); + END IF; +END $$; + +CREATE INDEX IF NOT EXISTS idx_jobs_work_mode ON jobs(work_mode);