backend: suportar workMode em vagas e reforçar schema
This commit is contained in:
parent
bdbc6f6b5b
commit
fb5aba4ed7
4 changed files with 57 additions and 10 deletions
|
|
@ -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"`
|
Currency *string `json:"currency,omitempty" validate:"omitempty,oneof=BRL USD EUR GBP JPY CNY AED CAD AUD CHF"`
|
||||||
SalaryNegotiable bool `json:"salaryNegotiable"`
|
SalaryNegotiable bool `json:"salaryNegotiable"`
|
||||||
EmploymentType *string `json:"employmentType,omitempty" validate:"omitempty,oneof=full-time part-time dispatch contract temporary training voluntary permanent"`
|
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"`
|
WorkingHours *string `json:"workingHours,omitempty"`
|
||||||
Location *string `json:"location,omitempty"`
|
Location *string `json:"location,omitempty"`
|
||||||
RegionID *int `json:"regionId,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"`
|
Currency *string `json:"currency,omitempty" validate:"omitempty,oneof=BRL USD EUR GBP JPY CNY AED CAD AUD CHF"`
|
||||||
SalaryNegotiable *bool `json:"salaryNegotiable,omitempty"`
|
SalaryNegotiable *bool `json:"salaryNegotiable,omitempty"`
|
||||||
EmploymentType *string `json:"employmentType,omitempty" validate:"omitempty,oneof=full-time part-time dispatch contract temporary training voluntary permanent"`
|
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"`
|
WorkingHours *string `json:"workingHours,omitempty"`
|
||||||
Location *string `json:"location,omitempty"`
|
Location *string `json:"location,omitempty"`
|
||||||
RegionID *int `json:"regionId,omitempty"`
|
RegionID *int `json:"regionId,omitempty"`
|
||||||
|
|
@ -202,8 +204,8 @@ type UpdateVideoInterviewRequest struct {
|
||||||
// VideoInterviewFeedbackRequest represents submitting interview feedback
|
// VideoInterviewFeedbackRequest represents submitting interview feedback
|
||||||
type VideoInterviewFeedbackRequest struct {
|
type VideoInterviewFeedbackRequest struct {
|
||||||
InterviewerFeedback *string `json:"interviewerFeedback,omitempty"`
|
InterviewerFeedback *string `json:"interviewerFeedback,omitempty"`
|
||||||
CandidateFeedback *string `json:"candidateFeedback,omitempty"`
|
CandidateFeedback *string `json:"candidateFeedback,omitempty"`
|
||||||
Rating *int `json:"rating,omitempty" validate:"omitempty,min=1,max=5"`
|
Rating *int `json:"rating,omitempty" validate:"omitempty,min=1,max=5"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateJobAlertRequest represents creating a job alert
|
// CreateJobAlertRequest represents creating a job alert
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,6 @@ func TestGetJobs_Success(t *testing.T) {
|
||||||
assert.Equal(t, 2, response.Pagination.Total)
|
assert.Equal(t, 2, response.Pagination.Total)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestGetJobs_ParsesCareerjetAliases(t *testing.T) {
|
func TestGetJobs_ParsesCareerjetAliases(t *testing.T) {
|
||||||
mockService := &mockJobService{
|
mockService := &mockJobService{
|
||||||
getJobsFunc: func(filter dto.JobFilterQuery) ([]models.JobWithCompany, int, error) {
|
getJobsFunc: func(filter dto.JobFilterQuery) ([]models.JobWithCompany, int, error) {
|
||||||
|
|
@ -119,10 +118,14 @@ func TestCreateJob_Success(t *testing.T) {
|
||||||
mockService := &mockJobService{
|
mockService := &mockJobService{
|
||||||
createJobFunc: func(req dto.CreateJobRequest, createdBy string) (*models.Job, error) {
|
createJobFunc: func(req dto.CreateJobRequest, createdBy string) (*models.Job, error) {
|
||||||
assert.Equal(t, "user-123", createdBy)
|
assert.Equal(t, "user-123", createdBy)
|
||||||
|
if assert.NotNil(t, req.WorkMode) {
|
||||||
|
assert.Equal(t, "remote", *req.WorkMode)
|
||||||
|
}
|
||||||
return &models.Job{
|
return &models.Job{
|
||||||
ID: "1",
|
ID: "1",
|
||||||
Title: req.Title,
|
Title: req.Title,
|
||||||
Status: "open",
|
Status: "open",
|
||||||
|
WorkMode: req.WorkMode,
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -134,6 +137,7 @@ func TestCreateJob_Success(t *testing.T) {
|
||||||
Title: "Backend Developer",
|
Title: "Backend Developer",
|
||||||
Description: "Build awesome APIs",
|
Description: "Build awesome APIs",
|
||||||
Status: "open",
|
Status: "open",
|
||||||
|
WorkMode: func() *string { s := "remote"; return &s }(),
|
||||||
}
|
}
|
||||||
body, _ := json.Marshal(jobReq)
|
body, _ := json.Marshal(jobReq)
|
||||||
|
|
||||||
|
|
@ -154,6 +158,9 @@ func TestCreateJob_Success(t *testing.T) {
|
||||||
err := json.Unmarshal(rr.Body.Bytes(), &job)
|
err := json.Unmarshal(rr.Body.Bytes(), &job)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "Backend Developer", job.Title)
|
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) {
|
func TestCreateJob_ServiceError(t *testing.T) {
|
||||||
|
|
@ -219,8 +226,12 @@ func TestDeleteJob_Success(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateJob_Success(t *testing.T) {
|
func TestUpdateJob_Success(t *testing.T) {
|
||||||
|
workMode := "hybrid"
|
||||||
mockService := &mockJobService{
|
mockService := &mockJobService{
|
||||||
updateJobFunc: func(id string, req dto.UpdateJobRequest) (*models.Job, error) {
|
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
|
return &models.Job{ID: id, Title: "Updated"}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -230,7 +241,7 @@ func TestUpdateJob_Success(t *testing.T) {
|
||||||
|
|
||||||
// Cleaner
|
// Cleaner
|
||||||
title := "Updated"
|
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 := httptest.NewRequest("PUT", "/jobs/1", bytes.NewReader(reqBody))
|
||||||
req.SetPathValue("id", "1")
|
req.SetPathValue("id", "1")
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,9 @@ func (s *JobService) CreateJob(req dto.CreateJobRequest, createdBy string) (*mod
|
||||||
query := `
|
query := `
|
||||||
INSERT INTO jobs (
|
INSERT INTO jobs (
|
||||||
company_id, created_by, title, description, salary_min, salary_max, salary_type, currency,
|
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
|
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
|
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,
|
Currency: req.Currency,
|
||||||
SalaryNegotiable: req.SalaryNegotiable,
|
SalaryNegotiable: req.SalaryNegotiable,
|
||||||
EmploymentType: req.EmploymentType,
|
EmploymentType: req.EmploymentType,
|
||||||
|
WorkMode: req.WorkMode,
|
||||||
WorkingHours: req.WorkingHours,
|
WorkingHours: req.WorkingHours,
|
||||||
Location: req.Location,
|
Location: req.Location,
|
||||||
RegionID: req.RegionID,
|
RegionID: req.RegionID,
|
||||||
|
|
@ -63,7 +64,7 @@ func (s *JobService) CreateJob(req dto.CreateJobRequest, createdBy string) (*mod
|
||||||
err := s.DB.QueryRow(
|
err := s.DB.QueryRow(
|
||||||
query,
|
query,
|
||||||
job.CompanyID, job.CreatedBy, job.Title, job.Description, job.SalaryMin, job.SalaryMax, job.SalaryType, job.Currency,
|
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,
|
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)
|
).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)
|
args = append(args, *req.EmploymentType)
|
||||||
argId++
|
argId++
|
||||||
}
|
}
|
||||||
|
if req.WorkMode != nil {
|
||||||
|
setClauses = append(setClauses, fmt.Sprintf("work_mode = $%d", argId))
|
||||||
|
args = append(args, *req.WorkMode)
|
||||||
|
argId++
|
||||||
|
}
|
||||||
if req.WorkingHours != nil {
|
if req.WorkingHours != nil {
|
||||||
setClauses = append(setClauses, fmt.Sprintf("working_hours = $%d", argId))
|
setClauses = append(setClauses, fmt.Sprintf("working_hours = $%d", argId))
|
||||||
args = append(args, *req.WorkingHours)
|
args = append(args, *req.WorkingHours)
|
||||||
|
|
|
||||||
28
backend/migrations/036_ensure_jobs_work_mode.sql
Normal file
28
backend/migrations/036_ensure_jobs_work_mode.sql
Normal file
|
|
@ -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);
|
||||||
Loading…
Reference in a new issue