Refactor backend to use string IDs for Job, Company, and Application

This commit is contained in:
Tiago Yamamoto 2025-12-23 14:46:17 -03:00
parent 5e714eeb4f
commit 0e265e64b8
16 changed files with 128 additions and 157 deletions

View file

@ -123,12 +123,7 @@ func (h *AdminHandlers) ListCompanies(w http.ResponseWriter, r *http.Request) {
}
func (h *AdminHandlers) UpdateCompanyStatus(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid company ID", http.StatusBadRequest)
return
}
id := r.PathValue("id")
var req UpdateCompanyStatusRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@ -182,12 +177,7 @@ func (h *AdminHandlers) ListJobs(w http.ResponseWriter, r *http.Request) {
}
func (h *AdminHandlers) UpdateJobStatus(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid job ID", http.StatusBadRequest)
return
}
id := r.PathValue("id")
var req UpdateJobStatusRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@ -211,12 +201,7 @@ func (h *AdminHandlers) UpdateJobStatus(w http.ResponseWriter, r *http.Request)
}
func (h *AdminHandlers) DuplicateJob(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid job ID", http.StatusBadRequest)
return
}
id := r.PathValue("id")
job, err := h.adminService.DuplicateJob(r.Context(), id)
if err != nil {

View file

@ -2,7 +2,7 @@ package dto
// CreateJobRequest represents the request to create a new job
type CreateJobRequest struct {
CompanyID int `json:"companyId" validate:"required"`
CompanyID string `json:"companyId" validate:"required"`
Title string `json:"title" validate:"required,min=5,max=255"`
Description string `json:"description" validate:"required,min=20"`
SalaryMin *float64 `json:"salaryMin,omitempty"`
@ -41,8 +41,8 @@ type UpdateJobRequest struct {
// CreateApplicationRequest represents a job application (guest or logged user)
type CreateApplicationRequest struct {
JobID int `json:"jobId" validate:"required"`
UserID *int `json:"userId,omitempty"`
JobID string `json:"jobId" validate:"required"`
UserID *string `json:"userId,omitempty"`
Name *string `json:"name,omitempty"`
Phone *string `json:"phone,omitempty"`
LineID *string `json:"lineId,omitempty"`
@ -95,8 +95,8 @@ type UpdateCompanyRequest struct {
// AssignUserToCompanyRequest represents assigning a user to a company
type AssignUserToCompanyRequest struct {
UserID int `json:"userId" validate:"required"`
CompanyID int `json:"companyId" validate:"required"`
UserID string `json:"userId" validate:"required"`
CompanyID string `json:"companyId" validate:"required"`
Role string `json:"role" validate:"required,oneof=companyAdmin recruiter"`
Permissions map[string]interface{} `json:"permissions,omitempty"`
}
@ -110,7 +110,7 @@ type PaginationQuery struct {
// JobFilterQuery represents job filtering parameters
type JobFilterQuery struct {
PaginationQuery
CompanyID *int `form:"companyId"`
CompanyID *string `form:"companyId"`
RegionID *int `form:"regionId"`
CityID *int `form:"cityId"`
EmploymentType *string `form:"employmentType"`

View file

@ -3,7 +3,6 @@ package handlers
import (
"encoding/json"
"net/http"
"strconv"
"github.com/rede5/gohorsejobs/backend/internal/dto"
"github.com/rede5/gohorsejobs/backend/internal/services"
@ -59,16 +58,11 @@ func (h *ApplicationHandler) CreateApplication(w http.ResponseWriter, r *http.Re
// @Router /api/v1/applications [get]
func (h *ApplicationHandler) GetApplications(w http.ResponseWriter, r *http.Request) {
// For now, simple get by Job ID query param
jobIDStr := r.URL.Query().Get("jobId")
if jobIDStr == "" {
jobID := r.URL.Query().Get("jobId")
if jobID == "" {
http.Error(w, "jobId is required", http.StatusBadRequest)
return
}
jobID, err := strconv.Atoi(jobIDStr)
if err != nil {
http.Error(w, "Invalid jobId", http.StatusBadRequest)
return
}
apps, err := h.Service.GetApplications(jobID)
if err != nil {
@ -92,12 +86,7 @@ func (h *ApplicationHandler) GetApplications(w http.ResponseWriter, r *http.Requ
// @Failure 404 {string} string "Not Found"
// @Router /api/v1/applications/{id} [get]
func (h *ApplicationHandler) GetApplicationByID(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid application ID", http.StatusBadRequest)
return
}
id := r.PathValue("id")
app, err := h.Service.GetApplicationByID(id)
if err != nil {
@ -122,12 +111,7 @@ func (h *ApplicationHandler) GetApplicationByID(w http.ResponseWriter, r *http.R
// @Failure 500 {string} string "Internal Server Error"
// @Router /api/v1/applications/{id}/status [put]
func (h *ApplicationHandler) UpdateApplicationStatus(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid application ID", http.StatusBadRequest)
return
}
id := r.PathValue("id")
var req dto.UpdateApplicationStatusRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {

View file

@ -16,9 +16,9 @@ import (
// mockApplicationService is a mock implementation for testing
type mockApplicationService struct {
createApplicationFunc func(req dto.CreateApplicationRequest) (*models.Application, error)
getApplicationsFunc func(jobID int) ([]models.Application, error)
getApplicationByIDFunc func(id int) (*models.Application, error)
updateApplicationStatusFunc func(id int, req dto.UpdateApplicationStatusRequest) (*models.Application, error)
getApplicationsFunc func(jobID string) ([]models.Application, error)
getApplicationByIDFunc func(id string) (*models.Application, error)
updateApplicationStatusFunc func(id string, req dto.UpdateApplicationStatusRequest) (*models.Application, error)
}
func (m *mockApplicationService) CreateApplication(req dto.CreateApplicationRequest) (*models.Application, error) {
@ -28,21 +28,21 @@ func (m *mockApplicationService) CreateApplication(req dto.CreateApplicationRequ
return nil, nil
}
func (m *mockApplicationService) GetApplications(jobID int) ([]models.Application, error) {
func (m *mockApplicationService) GetApplications(jobID string) ([]models.Application, error) {
if m.getApplicationsFunc != nil {
return m.getApplicationsFunc(jobID)
}
return nil, nil
}
func (m *mockApplicationService) GetApplicationByID(id int) (*models.Application, error) {
func (m *mockApplicationService) GetApplicationByID(id string) (*models.Application, error) {
if m.getApplicationByIDFunc != nil {
return m.getApplicationByIDFunc(id)
}
return nil, nil
}
func (m *mockApplicationService) UpdateApplicationStatus(id int, req dto.UpdateApplicationStatusRequest) (*models.Application, error) {
func (m *mockApplicationService) UpdateApplicationStatus(id string, req dto.UpdateApplicationStatusRequest) (*models.Application, error) {
if m.updateApplicationStatusFunc != nil {
return m.updateApplicationStatusFunc(id, req)
}
@ -52,9 +52,9 @@ func (m *mockApplicationService) UpdateApplicationStatus(id int, req dto.UpdateA
// ApplicationServiceInterface defines the interface for application service
type ApplicationServiceInterface interface {
CreateApplication(req dto.CreateApplicationRequest) (*models.Application, error)
GetApplications(jobID int) ([]models.Application, error)
GetApplicationByID(id int) (*models.Application, error)
UpdateApplicationStatus(id int, req dto.UpdateApplicationStatusRequest) (*models.Application, error)
GetApplications(jobID string) ([]models.Application, error)
GetApplicationByID(id string) (*models.Application, error)
UpdateApplicationStatus(id string, req dto.UpdateApplicationStatusRequest) (*models.Application, error)
}
// testableApplicationHandler wraps an interface for testing
@ -84,7 +84,8 @@ func (h *testableApplicationHandler) CreateApplication(w http.ResponseWriter, r
json.NewEncoder(w).Encode(app)
}
func (h *testableApplicationHandler) GetApplications(w http.ResponseWriter, r *http.Request, jobID int) {
func (h *testableApplicationHandler) GetApplications(w http.ResponseWriter, r *http.Request) {
jobID := r.URL.Query().Get("jobId")
apps, err := h.service.GetApplications(jobID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
@ -95,7 +96,12 @@ func (h *testableApplicationHandler) GetApplications(w http.ResponseWriter, r *h
json.NewEncoder(w).Encode(apps)
}
func (h *testableApplicationHandler) GetApplicationByID(w http.ResponseWriter, r *http.Request, id int) {
func (h *testableApplicationHandler) GetApplicationByID(w http.ResponseWriter, r *http.Request) {
// In real handler we use path value, here we might need to simulate or just call service
// For unit test of handler logic usually we mock the router or simply pass arguments if method signature allows.
// But check original handler: it extracts from r.PathValue("id").
// In tests using httptest.NewRequest with Go 1.22 routing, we need to set path values.
id := r.PathValue("id")
app, err := h.service.GetApplicationByID(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
@ -106,7 +112,8 @@ func (h *testableApplicationHandler) GetApplicationByID(w http.ResponseWriter, r
json.NewEncoder(w).Encode(app)
}
func (h *testableApplicationHandler) UpdateApplicationStatus(w http.ResponseWriter, r *http.Request, id int) {
func (h *testableApplicationHandler) UpdateApplicationStatus(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
var req dto.UpdateApplicationStatusRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
@ -134,7 +141,7 @@ func TestCreateApplication_Success(t *testing.T) {
mockService := &mockApplicationService{
createApplicationFunc: func(req dto.CreateApplicationRequest) (*models.Application, error) {
return &models.Application{
ID: 1,
ID: "1",
JobID: req.JobID,
Name: &name,
Email: &email,
@ -148,7 +155,7 @@ func TestCreateApplication_Success(t *testing.T) {
handler := newTestableApplicationHandler(mockService)
appReq := dto.CreateApplicationRequest{
JobID: 1,
JobID: "1",
Name: &name,
Email: &email,
}
@ -166,7 +173,7 @@ func TestCreateApplication_Success(t *testing.T) {
err := json.Unmarshal(rr.Body.Bytes(), &app)
assert.NoError(t, err)
assert.Equal(t, "pending", app.Status)
assert.Equal(t, 1, app.JobID)
assert.Equal(t, "1", app.JobID)
}
func TestCreateApplication_InvalidJSON(t *testing.T) {
@ -192,7 +199,7 @@ func TestCreateApplication_ServiceError(t *testing.T) {
handler := newTestableApplicationHandler(mockService)
appReq := dto.CreateApplicationRequest{JobID: 1}
appReq := dto.CreateApplicationRequest{JobID: "1"}
body, _ := json.Marshal(appReq)
req := httptest.NewRequest("POST", "/applications", bytes.NewReader(body))
@ -209,17 +216,17 @@ func TestGetApplications_Success(t *testing.T) {
name2 := "Jane Smith"
mockService := &mockApplicationService{
getApplicationsFunc: func(jobID int) ([]models.Application, error) {
getApplicationsFunc: func(jobID string) ([]models.Application, error) {
return []models.Application{
{
ID: 1,
ID: "1",
JobID: jobID,
Name: &name1,
Status: "pending",
CreatedAt: time.Now(),
},
{
ID: 2,
ID: "2",
JobID: jobID,
Name: &name2,
Status: "reviewed",
@ -234,7 +241,7 @@ func TestGetApplications_Success(t *testing.T) {
req := httptest.NewRequest("GET", "/applications?jobId=1", nil)
rr := httptest.NewRecorder()
handler.GetApplications(rr, req, 1)
handler.GetApplications(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
@ -246,7 +253,7 @@ func TestGetApplications_Success(t *testing.T) {
func TestGetApplications_Empty(t *testing.T) {
mockService := &mockApplicationService{
getApplicationsFunc: func(jobID int) ([]models.Application, error) {
getApplicationsFunc: func(jobID string) ([]models.Application, error) {
return []models.Application{}, nil
},
}
@ -256,14 +263,14 @@ func TestGetApplications_Empty(t *testing.T) {
req := httptest.NewRequest("GET", "/applications?jobId=1", nil)
rr := httptest.NewRecorder()
handler.GetApplications(rr, req, 1)
handler.GetApplications(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
}
func TestGetApplications_Error(t *testing.T) {
mockService := &mockApplicationService{
getApplicationsFunc: func(jobID int) ([]models.Application, error) {
getApplicationsFunc: func(jobID string) ([]models.Application, error) {
return nil, assert.AnError
},
}
@ -273,7 +280,7 @@ func TestGetApplications_Error(t *testing.T) {
req := httptest.NewRequest("GET", "/applications?jobId=1", nil)
rr := httptest.NewRecorder()
handler.GetApplications(rr, req, 1)
handler.GetApplications(rr, req)
assert.Equal(t, http.StatusInternalServerError, rr.Code)
}
@ -282,10 +289,10 @@ func TestGetApplicationByID_Success(t *testing.T) {
name := "John Doe"
mockService := &mockApplicationService{
getApplicationByIDFunc: func(id int) (*models.Application, error) {
getApplicationByIDFunc: func(id string) (*models.Application, error) {
return &models.Application{
ID: id,
JobID: 1,
JobID: "1",
Name: &name,
Status: "pending",
CreatedAt: time.Now(),
@ -296,21 +303,22 @@ func TestGetApplicationByID_Success(t *testing.T) {
handler := newTestableApplicationHandler(mockService)
req := httptest.NewRequest("GET", "/applications/1", nil)
req.SetPathValue("id", "1") // Go 1.22 feature
rr := httptest.NewRecorder()
handler.GetApplicationByID(rr, req, 1)
handler.GetApplicationByID(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
var app models.Application
err := json.Unmarshal(rr.Body.Bytes(), &app)
assert.NoError(t, err)
assert.Equal(t, 1, app.ID)
assert.Equal(t, "1", app.ID)
}
func TestGetApplicationByID_NotFound(t *testing.T) {
mockService := &mockApplicationService{
getApplicationByIDFunc: func(id int) (*models.Application, error) {
getApplicationByIDFunc: func(id string) (*models.Application, error) {
return nil, assert.AnError
},
}
@ -318,9 +326,10 @@ func TestGetApplicationByID_NotFound(t *testing.T) {
handler := newTestableApplicationHandler(mockService)
req := httptest.NewRequest("GET", "/applications/999", nil)
req.SetPathValue("id", "999")
rr := httptest.NewRecorder()
handler.GetApplicationByID(rr, req, 999)
handler.GetApplicationByID(rr, req)
assert.Equal(t, http.StatusNotFound, rr.Code)
}
@ -329,10 +338,10 @@ func TestUpdateApplicationStatus_Success(t *testing.T) {
name := "John Doe"
mockService := &mockApplicationService{
updateApplicationStatusFunc: func(id int, req dto.UpdateApplicationStatusRequest) (*models.Application, error) {
updateApplicationStatusFunc: func(id string, req dto.UpdateApplicationStatusRequest) (*models.Application, error) {
return &models.Application{
ID: id,
JobID: 1,
JobID: "1",
Name: &name,
Status: req.Status,
CreatedAt: time.Now(),
@ -349,10 +358,11 @@ func TestUpdateApplicationStatus_Success(t *testing.T) {
body, _ := json.Marshal(statusReq)
req := httptest.NewRequest("PUT", "/applications/1/status", bytes.NewReader(body))
req.SetPathValue("id", "1")
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
handler.UpdateApplicationStatus(rr, req, 1)
handler.UpdateApplicationStatus(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
@ -368,17 +378,18 @@ func TestUpdateApplicationStatus_InvalidJSON(t *testing.T) {
handler := newTestableApplicationHandler(mockService)
req := httptest.NewRequest("PUT", "/applications/1/status", bytes.NewReader([]byte("invalid")))
req.SetPathValue("id", "1")
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
handler.UpdateApplicationStatus(rr, req, 1)
handler.UpdateApplicationStatus(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
}
func TestUpdateApplicationStatus_Error(t *testing.T) {
mockService := &mockApplicationService{
updateApplicationStatusFunc: func(id int, req dto.UpdateApplicationStatusRequest) (*models.Application, error) {
updateApplicationStatusFunc: func(id string, req dto.UpdateApplicationStatusRequest) (*models.Application, error) {
return nil, assert.AnError
},
}
@ -389,10 +400,11 @@ func TestUpdateApplicationStatus_Error(t *testing.T) {
body, _ := json.Marshal(statusReq)
req := httptest.NewRequest("PUT", "/applications/1/status", bytes.NewReader(body))
req.SetPathValue("id", "1")
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
handler.UpdateApplicationStatus(rr, req, 1)
handler.UpdateApplicationStatus(rr, req)
assert.Equal(t, http.StatusInternalServerError, rr.Code)
}

View file

@ -40,7 +40,7 @@ func NewJobHandler(service *services.JobService) *JobHandler {
func (h *JobHandler) GetJobs(w http.ResponseWriter, r *http.Request) {
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
companyID, _ := strconv.Atoi(r.URL.Query().Get("companyId"))
companyID := r.URL.Query().Get("companyId")
isFeaturedStr := r.URL.Query().Get("featured")
// Extraction of filters
@ -55,7 +55,7 @@ func (h *JobHandler) GetJobs(w http.ResponseWriter, r *http.Request) {
Limit: limit,
},
}
if companyID > 0 {
if companyID != "" {
filter.CompanyID = &companyID
}
if isFeaturedStr == "true" {
@ -137,13 +137,7 @@ func (h *JobHandler) CreateJob(w http.ResponseWriter, r *http.Request) {
// @Failure 404 {string} string "Not Found"
// @Router /api/v1/jobs/{id} [get]
func (h *JobHandler) GetJobByID(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id") // Go 1.22+ routing
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid job ID", http.StatusBadRequest)
return
}
id := r.PathValue("id")
job, err := h.Service.GetJobByID(id)
if err != nil {
@ -168,12 +162,7 @@ func (h *JobHandler) GetJobByID(w http.ResponseWriter, r *http.Request) {
// @Failure 500 {string} string "Internal Server Error"
// @Router /api/v1/jobs/{id} [put]
func (h *JobHandler) UpdateJob(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid job ID", http.StatusBadRequest)
return
}
id := r.PathValue("id")
var req dto.UpdateJobRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@ -203,12 +192,7 @@ func (h *JobHandler) UpdateJob(w http.ResponseWriter, r *http.Request) {
// @Failure 500 {string} string "Internal Server Error"
// @Router /api/v1/jobs/{id} [delete]
func (h *JobHandler) DeleteJob(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid job ID", http.StatusBadRequest)
return
}
id := r.PathValue("id")
if err := h.Service.DeleteJob(id); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)

View file

@ -17,9 +17,9 @@ import (
type mockJobService struct {
getJobsFunc func(filter dto.JobFilterQuery) ([]models.JobWithCompany, int, error)
createJobFunc func(req dto.CreateJobRequest) (*models.Job, error)
getJobByIDFunc func(id int) (*models.Job, error)
updateJobFunc func(id int, req dto.UpdateJobRequest) (*models.Job, error)
deleteJobFunc func(id int) error
getJobByIDFunc func(id string) (*models.Job, error)
updateJobFunc func(id string, req dto.UpdateJobRequest) (*models.Job, error)
deleteJobFunc func(id string) error
}
func (m *mockJobService) GetJobs(filter dto.JobFilterQuery) ([]models.JobWithCompany, int, error) {
@ -36,21 +36,21 @@ func (m *mockJobService) CreateJob(req dto.CreateJobRequest) (*models.Job, error
return nil, nil
}
func (m *mockJobService) GetJobByID(id int) (*models.Job, error) {
func (m *mockJobService) GetJobByID(id string) (*models.Job, error) {
if m.getJobByIDFunc != nil {
return m.getJobByIDFunc(id)
}
return nil, nil
}
func (m *mockJobService) UpdateJob(id int, req dto.UpdateJobRequest) (*models.Job, error) {
func (m *mockJobService) UpdateJob(id string, req dto.UpdateJobRequest) (*models.Job, error) {
if m.updateJobFunc != nil {
return m.updateJobFunc(id, req)
}
return nil, nil
}
func (m *mockJobService) DeleteJob(id int) error {
func (m *mockJobService) DeleteJob(id string) error {
if m.deleteJobFunc != nil {
return m.deleteJobFunc(id)
}
@ -61,9 +61,9 @@ func (m *mockJobService) DeleteJob(id int) error {
type JobServiceInterface interface {
GetJobs(filter dto.JobFilterQuery) ([]models.JobWithCompany, int, error)
CreateJob(req dto.CreateJobRequest) (*models.Job, error)
GetJobByID(id int) (*models.Job, error)
UpdateJob(id int, req dto.UpdateJobRequest) (*models.Job, error)
DeleteJob(id int) error
GetJobByID(id string) (*models.Job, error)
UpdateJob(id string, req dto.UpdateJobRequest) (*models.Job, error)
DeleteJob(id string) error
}
// testableJobHandler wraps an interface for testing
@ -112,7 +112,8 @@ func (h *testableJobHandler) CreateJob(w http.ResponseWriter, r *http.Request) {
}
func (h *testableJobHandler) GetJobByID(w http.ResponseWriter, r *http.Request) {
job, err := h.service.GetJobByID(1) // simplified for testing
id := r.PathValue("id")
job, err := h.service.GetJobByID(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
@ -123,7 +124,8 @@ func (h *testableJobHandler) GetJobByID(w http.ResponseWriter, r *http.Request)
}
func (h *testableJobHandler) DeleteJob(w http.ResponseWriter, r *http.Request) {
if err := h.service.DeleteJob(1); err != nil {
id := r.PathValue("id")
if err := h.service.DeleteJob(id); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@ -140,8 +142,8 @@ func TestGetJobs_Success(t *testing.T) {
return []models.JobWithCompany{
{
Job: models.Job{
ID: 1,
CompanyID: 1,
ID: "1",
CompanyID: "1",
Title: "Software Engineer",
Status: "open",
CreatedAt: time.Now(),
@ -151,8 +153,8 @@ func TestGetJobs_Success(t *testing.T) {
},
{
Job: models.Job{
ID: 2,
CompanyID: 1,
ID: "2",
CompanyID: "1",
Title: "DevOps Engineer",
Status: "open",
CreatedAt: time.Now(),
@ -219,7 +221,7 @@ func TestCreateJob_Success(t *testing.T) {
mockService := &mockJobService{
createJobFunc: func(req dto.CreateJobRequest) (*models.Job, error) {
return &models.Job{
ID: 1,
ID: "1",
CompanyID: req.CompanyID,
Title: req.Title,
Description: req.Description,
@ -233,7 +235,7 @@ func TestCreateJob_Success(t *testing.T) {
handler := newTestableJobHandler(mockService)
jobReq := dto.CreateJobRequest{
CompanyID: 1,
CompanyID: "1",
Title: "Backend Developer",
Description: "Build awesome APIs",
Status: "open",
@ -278,7 +280,7 @@ func TestCreateJob_ServiceError(t *testing.T) {
handler := newTestableJobHandler(mockService)
jobReq := dto.CreateJobRequest{
CompanyID: 1,
CompanyID: "1",
Title: "Backend Developer",
Description: "Build awesome APIs",
Status: "open",
@ -296,10 +298,10 @@ func TestCreateJob_ServiceError(t *testing.T) {
func TestGetJobByID_Success(t *testing.T) {
mockService := &mockJobService{
getJobByIDFunc: func(id int) (*models.Job, error) {
getJobByIDFunc: func(id string) (*models.Job, error) {
return &models.Job{
ID: id,
CompanyID: 1,
CompanyID: "1",
Title: "Software Engineer",
Description: "Great job opportunity",
Status: "open",
@ -312,6 +314,7 @@ func TestGetJobByID_Success(t *testing.T) {
handler := newTestableJobHandler(mockService)
req := httptest.NewRequest("GET", "/jobs/1", nil)
req.SetPathValue("id", "1")
rr := httptest.NewRecorder()
handler.GetJobByID(rr, req)
@ -326,7 +329,7 @@ func TestGetJobByID_Success(t *testing.T) {
func TestGetJobByID_NotFound(t *testing.T) {
mockService := &mockJobService{
getJobByIDFunc: func(id int) (*models.Job, error) {
getJobByIDFunc: func(id string) (*models.Job, error) {
return nil, assert.AnError
},
}
@ -334,6 +337,7 @@ func TestGetJobByID_NotFound(t *testing.T) {
handler := newTestableJobHandler(mockService)
req := httptest.NewRequest("GET", "/jobs/999", nil)
req.SetPathValue("id", "999")
rr := httptest.NewRecorder()
handler.GetJobByID(rr, req)
@ -343,7 +347,7 @@ func TestGetJobByID_NotFound(t *testing.T) {
func TestDeleteJob_Success(t *testing.T) {
mockService := &mockJobService{
deleteJobFunc: func(id int) error {
deleteJobFunc: func(id string) error {
return nil
},
}
@ -351,6 +355,7 @@ func TestDeleteJob_Success(t *testing.T) {
handler := newTestableJobHandler(mockService)
req := httptest.NewRequest("DELETE", "/jobs/1", nil)
req.SetPathValue("id", "1")
rr := httptest.NewRecorder()
handler.DeleteJob(rr, req)
@ -360,7 +365,7 @@ func TestDeleteJob_Success(t *testing.T) {
func TestDeleteJob_Error(t *testing.T) {
mockService := &mockJobService{
deleteJobFunc: func(id int) error {
deleteJobFunc: func(id string) error {
return assert.AnError
},
}
@ -368,6 +373,7 @@ func TestDeleteJob_Error(t *testing.T) {
handler := newTestableJobHandler(mockService)
req := httptest.NewRequest("DELETE", "/jobs/1", nil)
req.SetPathValue("id", "1")
rr := httptest.NewRecorder()
handler.DeleteJob(rr, req)

View file

@ -4,9 +4,9 @@ import "time"
// Application represents a job application (from user or guest)
type Application struct {
ID int `json:"id" db:"id"`
JobID int `json:"jobId" db:"job_id"`
UserID *int `json:"userId,omitempty" db:"user_id"` // NULL for guest applications
ID string `json:"id" db:"id"`
JobID string `json:"jobId" db:"job_id"`
UserID *string `json:"userId,omitempty" db:"user_id"` // NULL for guest applications
// Applicant Info (for guest applications)
Name *string `json:"name,omitempty" db:"name"`
@ -33,7 +33,7 @@ type Application struct {
type ApplicationWithDetails struct {
Application
JobTitle string `json:"jobTitle"`
CompanyID int `json:"companyId"`
CompanyID string `json:"companyId"`
CompanyName string `json:"companyName"`
ApplicantName string `json:"applicantName"` // From user or guest name
ApplicantPhone *string `json:"applicantPhone,omitempty"`

View file

@ -4,7 +4,7 @@ import "time"
// Company represents an employer organization
type Company struct {
ID int `json:"id" db:"id"`
ID string `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Slug string `json:"slug" db:"slug"`
Type string `json:"type" db:"type"`

View file

@ -4,9 +4,9 @@ import "time"
// FavoriteJob represents a saved/favorited job by a user
type FavoriteJob struct {
ID int `json:"id" db:"id"`
UserID int `json:"userId" db:"user_id"`
JobID int `json:"jobId" db:"job_id"`
ID string `json:"id" db:"id"`
UserID string `json:"userId" db:"user_id"`
JobID string `json:"jobId" db:"job_id"`
CreatedAt time.Time `json:"createdAt" db:"created_at"`
}
@ -14,7 +14,7 @@ type FavoriteJob struct {
type FavoriteJobWithDetails struct {
FavoriteJob
JobTitle string `json:"jobTitle"`
CompanyID int `json:"companyId"`
CompanyID string `json:"companyId"`
CompanyName string `json:"companyName"`
CompanyLogoURL *string `json:"companyLogoUrl,omitempty"`
Location *string `json:"location,omitempty"`

View file

@ -4,9 +4,9 @@ import "time"
// Job represents a job posting
type Job struct {
ID int `json:"id" db:"id"`
CompanyID int `json:"companyId" db:"company_id"`
CreatedBy int `json:"createdBy" db:"created_by"`
ID string `json:"id" db:"id"`
CompanyID string `json:"companyId" db:"company_id"`
CreatedBy string `json:"createdBy" db:"created_by"`
// Job Details
Title string `json:"title" db:"title"`

View file

@ -4,7 +4,7 @@ import "time"
// User represents a system user (SuperAdmin, CompanyAdmin, Recruiter, or JobSeeker)
type User struct {
ID int `json:"id" db:"id"`
ID string `json:"id" db:"id"`
Identifier string `json:"identifier" db:"identifier"`
PasswordHash string `json:"-" db:"password_hash"` // Never expose password hash in JSON
Role string `json:"role" db:"role"` // superadmin, companyAdmin, recruiter, jobSeeker
@ -28,7 +28,7 @@ type User struct {
// UserResponse is the public representation of a user (without sensitive data)
type UserResponse struct {
ID int `json:"id"`
ID string `json:"id"`
Identifier string `json:"identifier"`
Role string `json:"role"`
FullName string `json:"fullName"`

View file

@ -8,9 +8,9 @@ import (
// UserCompany represents the N:M relationship between users and companies
type UserCompany struct {
ID int `json:"id" db:"id"`
UserID int `json:"userId" db:"user_id"`
CompanyID int `json:"companyId" db:"company_id"`
ID string `json:"id" db:"id"`
UserID string `json:"userId" db:"user_id"`
CompanyID string `json:"companyId" db:"company_id"`
Role string `json:"role" db:"role"` // companyAdmin, recruiter
Permissions JSONMap `json:"permissions,omitempty" db:"permissions"`
CreatedAt time.Time `json:"createdAt" db:"created_at"`

View file

@ -69,7 +69,7 @@ func (s *AdminService) ListCompanies(ctx context.Context, verified *bool) ([]mod
return companies, nil
}
func (s *AdminService) UpdateCompanyStatus(ctx context.Context, id int, active *bool, verified *bool) (*models.Company, error) {
func (s *AdminService) UpdateCompanyStatus(ctx context.Context, id string, active *bool, verified *bool) (*models.Company, error) {
company, err := s.getCompanyByID(ctx, id)
if err != nil {
return nil, err
@ -96,7 +96,7 @@ func (s *AdminService) UpdateCompanyStatus(ctx context.Context, id int, active *
return company, nil
}
func (s *AdminService) DuplicateJob(ctx context.Context, id int) (*models.Job, error) {
func (s *AdminService) DuplicateJob(ctx context.Context, id string) (*models.Job, error) {
query := `
SELECT company_id, created_by, title, description, salary_min, salary_max, salary_type,
employment_type, work_mode, working_hours, location, region_id, city_id,
@ -446,7 +446,7 @@ func isActiveApplicationStatus(status string) bool {
}
}
func (s *AdminService) getCompanyByID(ctx context.Context, id int) (*models.Company, error) {
func (s *AdminService) getCompanyByID(ctx context.Context, id string) (*models.Company, error) {
query := `
SELECT id, name, slug, type, document, address, region_id, city_id, phone, email, website, logo_url, description, active, verified, created_at, updated_at
FROM companies WHERE id = $1

View file

@ -54,7 +54,7 @@ func (s *ApplicationService) CreateApplication(req dto.CreateApplicationRequest)
return app, nil
}
func (s *ApplicationService) GetApplications(jobID int) ([]models.Application, error) {
func (s *ApplicationService) GetApplications(jobID string) ([]models.Application, error) {
// Simple get by Job ID
query := `
SELECT id, job_id, user_id, name, phone, line_id, whatsapp, email,
@ -81,7 +81,7 @@ func (s *ApplicationService) GetApplications(jobID int) ([]models.Application, e
return apps, nil
}
func (s *ApplicationService) GetApplicationByID(id int) (*models.Application, error) {
func (s *ApplicationService) GetApplicationByID(id string) (*models.Application, error) {
var a models.Application
query := `
SELECT id, job_id, user_id, name, phone, line_id, whatsapp, email,
@ -98,7 +98,7 @@ func (s *ApplicationService) GetApplicationByID(id int) (*models.Application, er
return &a, nil
}
func (s *ApplicationService) UpdateApplicationStatus(id int, req dto.UpdateApplicationStatusRequest) (*models.Application, error) {
func (s *ApplicationService) UpdateApplicationStatus(id string, req dto.UpdateApplicationStatusRequest) (*models.Application, error) {
query := `
UPDATE applications SET status = $1, updated_at = NOW()
WHERE id = $2

View file

@ -173,7 +173,7 @@ func (s *JobService) GetJobs(filter dto.JobFilterQuery) ([]models.JobWithCompany
return jobs, total, nil
}
func (s *JobService) GetJobByID(id int) (*models.Job, error) {
func (s *JobService) GetJobByID(id string) (*models.Job, error) {
var j models.Job
query := `
SELECT id, company_id, title, description, salary_min, salary_max, salary_type,
@ -192,7 +192,7 @@ func (s *JobService) GetJobByID(id int) (*models.Job, error) {
return &j, nil
}
func (s *JobService) UpdateJob(id int, req dto.UpdateJobRequest) (*models.Job, error) {
func (s *JobService) UpdateJob(id string, req dto.UpdateJobRequest) (*models.Job, error) {
var setClauses []string
var args []interface{}
argId := 1
@ -232,7 +232,7 @@ func (s *JobService) UpdateJob(id int, req dto.UpdateJobRequest) (*models.Job, e
return s.GetJobByID(id)
}
func (s *JobService) DeleteJob(id int) error {
func (s *JobService) DeleteJob(id string) error {
_, err := s.DB.Exec("DELETE FROM jobs WHERE id = $1", id)
return err
}

View file

@ -29,21 +29,21 @@ func TestCreateJob(t *testing.T) {
{
name: "Success",
req: dto.CreateJobRequest{
CompanyID: 1,
CompanyID: "1",
Title: "Go Developer",
Status: "published",
},
mockRun: func() {
mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO jobs`)).
WithArgs(1, "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(), "published", sqlmock.AnyArg(), sqlmock.AnyArg()).
WillReturnRows(sqlmock.NewRows([]string{"id", "created_at", "updated_at"}).AddRow(100, time.Now(), time.Now()))
WithArgs("1", "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(), "published", sqlmock.AnyArg(), sqlmock.AnyArg()).
WillReturnRows(sqlmock.NewRows([]string{"id", "created_at", "updated_at"}).AddRow("100", time.Now(), time.Now()))
},
wantErr: false,
},
{
name: "DB Error",
req: dto.CreateJobRequest{
CompanyID: 1,
CompanyID: "1",
Title: "Go Developer",
},
mockRun: func() {
@ -63,7 +63,7 @@ func TestCreateJob(t *testing.T) {
return
}
if !tt.wantErr {
assert.Equal(t, 100, got.ID)
assert.Equal(t, "100", got.ID)
}
})
}
@ -99,10 +99,10 @@ func TestGetJobs(t *testing.T) {
FROM jobs j`)).
WillReturnRows(sqlmock.NewRows([]string{
"id", "company_id", "title", "description", "salary_min", "salary_max", "salary_type",
"employment_type", "location", "status", "is_featured", "created_at", "updated_at",
"employment_type", "work_mode", "location", "status", "is_featured", "created_at", "updated_at",
"company_name", "company_logo_url", "region_name", "city_name",
}).AddRow(
1, 10, "Dev", "Desc", 100, 200, "m", "ft", "Remote", "open", false, time.Now(), time.Now(),
"1", "10", "Dev", "Desc", 100, 200, "m", "ft", "Remote", "Remote", "open", false, time.Now(), time.Now(),
"Acme", "url", "Region", "City",
))