From f4f3811dfee7526ed323a2f87db8183ad133b1a9 Mon Sep 17 00:00:00 2001 From: Adam Gulczynski Date: Tue, 25 Jul 2023 14:17:09 +0200 Subject: [PATCH] add list jobs by company API (#66) --- api/job.go | 95 +++++++++++ api/job_test.go | 382 +++++++++++++++++++++++++++++++++++++++++++++ api/server.go | 1 + db/mock/store.go | 12 +- db/queries/job.sql | 22 +-- db/sqlc/job.sql.go | 85 +++++++--- db/sqlc/querier.go | 6 +- 7 files changed, 567 insertions(+), 36 deletions(-) diff --git a/api/job.go b/api/job.go index d8b141b..1dd5512 100644 --- a/api/job.go +++ b/api/job.go @@ -420,3 +420,98 @@ func (server *Server) listJobsByMatchingSkills(ctx *gin.Context) { ctx.JSON(http.StatusOK, jobs) } + +type listJobsByCompanyRequest struct { + ID int32 `form:"id"` + Name string `form:"name"` + NameContains string `form:"name_contains"` + Page int32 `form:"page" binding:"required,min=1"` + PageSize int32 `form:"page_size" binding:"required,min=5,max=15"` +} + +// listJobsByCompany handles listing jobs by company. +// Required parameters: +// - page (page number) +// - page_size (number of items per page) +// and either: +// - id (company id) or +// - name (company name) or +// - name_contains (part of the company name) +func (server *Server) listJobsByCompany(ctx *gin.Context) { + var request listJobsByCompanyRequest + if err := ctx.ShouldBindQuery(&request); err != nil { + ctx.JSON(http.StatusBadRequest, errorResponse(err)) + return + } + + // validate params - that only one of the three is set + numParams := 0 + if request.ID != 0 { + numParams++ + } + if request.Name != "" { + numParams++ + } + if request.NameContains != "" { + numParams++ + } + + if numParams == 0 { + err := fmt.Errorf("company id, name or name_contains is required") + ctx.JSON(http.StatusBadRequest, errorResponse(err)) + return + } else if numParams > 1 { + err := fmt.Errorf("only one of company id, name or name_contains is allowed") + ctx.JSON(http.StatusBadRequest, errorResponse(err)) + return + } + + if request.ID != 0 { + params := db.ListJobsByCompanyIDParams{ + CompanyID: request.ID, + Limit: request.PageSize, + Offset: (request.Page - 1) * request.PageSize, + } + + jobs, err := server.store.ListJobsByCompanyID(ctx, params) + if err != nil { + ctx.JSON(http.StatusInternalServerError, errorResponse(err)) + return + } + + ctx.JSON(http.StatusOK, jobs) + return + } + + if request.Name != "" { + params := db.ListJobsByCompanyExactNameParams{ + Name: request.Name, + Limit: request.PageSize, + Offset: (request.Page - 1) * request.PageSize, + } + + jobs, err := server.store.ListJobsByCompanyExactName(ctx, params) + if err != nil { + ctx.JSON(http.StatusInternalServerError, errorResponse(err)) + return + } + + ctx.JSON(http.StatusOK, jobs) + return + } + if request.NameContains != "" { + params := db.ListJobsByCompanyNameParams{ + Name: request.NameContains, + Limit: request.PageSize, + Offset: (request.Page - 1) * request.PageSize, + } + + jobs, err := server.store.ListJobsByCompanyName(ctx, params) + if err != nil { + ctx.JSON(http.StatusInternalServerError, errorResponse(err)) + return + } + + ctx.JSON(http.StatusOK, jobs) + } +} diff --git a/api/job_test.go b/api/job_test.go index 0ac7147..341958a 100644 --- a/api/job_test.go +++ b/api/job_test.go @@ -1616,6 +1616,364 @@ func TestUpdateJobAPI(t *testing.T) { } } +func TestListJobsByCompanyAPI(t *testing.T) { + _, _, company := generateRandomEmployerAndCompany(t) + var jobsByExactName []db.ListJobsByCompanyExactNameRow + var jobByName []db.ListJobsByCompanyNameRow + var jobByID []db.ListJobsByCompanyIDRow + title := utils.RandomString(5) + industry := utils.RandomString(4) + jobLocation := utils.RandomString(6) + salaryMin := utils.RandomInt(100, 150) + salaryMax := utils.RandomInt(151, 200) + + job := generateJob( + title, + industry, + jobLocation, + salaryMin, + salaryMax, + ) + + for i := 0; i < 5; i++ { + row := db.ListJobsByCompanyExactNameRow{ + ID: job.ID, + Title: job.Title, + Industry: job.Industry, + CompanyID: job.CompanyID, + Description: job.Description, + Location: job.Location, + SalaryMin: job.SalaryMin, + SalaryMax: job.SalaryMax, + Requirements: job.Requirements, + CreatedAt: job.CreatedAt, + CompanyName: company.Name, + } + jobsByExactName = append(jobsByExactName, row) + + row2 := db.ListJobsByCompanyNameRow{ + ID: job.ID, + Title: job.Title, + Industry: job.Industry, + CompanyID: job.CompanyID, + Description: job.Description, + Location: job.Location, + SalaryMin: job.SalaryMin, + SalaryMax: job.SalaryMax, + Requirements: job.Requirements, + CreatedAt: job.CreatedAt, + CompanyName: company.Name, + } + jobByName = append(jobByName, row2) + + row3 := db.ListJobsByCompanyIDRow{ + ID: job.ID, + Title: job.Title, + Industry: job.Industry, + CompanyID: job.CompanyID, + Description: job.Description, + Location: job.Location, + SalaryMin: job.SalaryMin, + SalaryMax: job.SalaryMax, + Requirements: job.Requirements, + CreatedAt: job.CreatedAt, + CompanyName: company.Name, + } + jobByID = append(jobByID, row3) + } + + type Query struct { + page int32 + pageSize int32 + id int32 + name string + nameContains string + } + + testCases := []struct { + name string + query Query + buildStubs func(store *mockdb.MockStore) + checkResponse func(recorder *httptest.ResponseRecorder) + }{ + { + name: "OK Name", + query: Query{ + page: 1, + pageSize: 10, + name: company.Name, + }, + buildStubs: func(store *mockdb.MockStore) { + store.EXPECT(). + ListJobsByCompanyID(gomock.Any(), gomock.Any()). + Times(0) + params := db.ListJobsByCompanyExactNameParams{ + Name: company.Name, + Limit: 10, + Offset: 0, + } + store.EXPECT(). + ListJobsByCompanyExactName(gomock.Any(), gomock.Eq(params)). + Times(1). + Return(jobsByExactName, nil) + store.EXPECT(). + ListJobsByCompanyName(gomock.Any(), gomock.Any()). + Times(0) + }, + checkResponse: func(recorder *httptest.ResponseRecorder) { + require.Equal(t, http.StatusOK, recorder.Code) + requireBodyMatchJobs(t, recorder.Body, jobsByExactName) + }, + }, + { + name: "OK ID", + query: Query{ + page: 1, + pageSize: 10, + id: company.ID, + }, + buildStubs: func(store *mockdb.MockStore) { + params := db.ListJobsByCompanyIDParams{ + CompanyID: company.ID, + Limit: 10, + Offset: 0, + } + store.EXPECT(). + ListJobsByCompanyID(gomock.Any(), gomock.Eq(params)). + Times(1). + Return(jobByID, nil) + store.EXPECT(). + ListJobsByCompanyExactName(gomock.Any(), gomock.Any()). + Times(0) + store.EXPECT(). + ListJobsByCompanyName(gomock.Any(), gomock.Any()). + Times(0) + }, + checkResponse: func(recorder *httptest.ResponseRecorder) { + require.Equal(t, http.StatusOK, recorder.Code) + requireBodyMatchJobs(t, recorder.Body, jobByID) + }, + }, + { + name: "OK Name Contains", + query: Query{ + page: 1, + pageSize: 10, + nameContains: company.Name[1:3], + }, + buildStubs: func(store *mockdb.MockStore) { + store.EXPECT(). + ListJobsByCompanyID(gomock.Any(), gomock.Any()). + Times(0) + store.EXPECT(). + ListJobsByCompanyExactName(gomock.Any(), gomock.Any()). + Times(0) + params := db.ListJobsByCompanyNameParams{ + Name: company.Name[1:3], + Limit: 10, + Offset: 0, + } + store.EXPECT(). + ListJobsByCompanyName(gomock.Any(), gomock.Eq(params)). + Times(1). + Return(jobByName, nil) + }, + checkResponse: func(recorder *httptest.ResponseRecorder) { + require.Equal(t, http.StatusOK, recorder.Code) + requireBodyMatchJobs(t, recorder.Body, jobByName) + }, + }, + { + name: "Invalid Page Size", + query: Query{ + page: 1, + pageSize: 50, + id: company.ID, + }, + buildStubs: func(store *mockdb.MockStore) { + store.EXPECT(). + ListJobsByCompanyID(gomock.Any(), gomock.Any()). + Times(0) + store.EXPECT(). + ListJobsByCompanyExactName(gomock.Any(), gomock.Any()). + Times(0) + store.EXPECT(). + ListJobsByCompanyName(gomock.Any(), gomock.Any()). + Times(0) + }, + checkResponse: func(recorder *httptest.ResponseRecorder) { + require.Equal(t, http.StatusBadRequest, recorder.Code) + }, + }, + { + name: "Invalid Page", + query: Query{ + page: 0, + pageSize: 10, + id: company.ID, + }, + buildStubs: func(store *mockdb.MockStore) { + store.EXPECT(). + ListJobsByCompanyID(gomock.Any(), gomock.Any()). + Times(0) + store.EXPECT(). + ListJobsByCompanyExactName(gomock.Any(), gomock.Any()). + Times(0) + store.EXPECT(). + ListJobsByCompanyName(gomock.Any(), gomock.Any()). + Times(0) + }, + checkResponse: func(recorder *httptest.ResponseRecorder) { + require.Equal(t, http.StatusBadRequest, recorder.Code) + }, + }, + { + name: "No Parameters", + query: Query{ + page: 1, + pageSize: 10, + }, + buildStubs: func(store *mockdb.MockStore) { + store.EXPECT(). + ListJobsByCompanyID(gomock.Any(), gomock.Any()). + Times(0) + store.EXPECT(). + ListJobsByCompanyExactName(gomock.Any(), gomock.Any()). + Times(0) + store.EXPECT(). + ListJobsByCompanyName(gomock.Any(), gomock.Any()). + Times(0) + }, + checkResponse: func(recorder *httptest.ResponseRecorder) { + require.Equal(t, http.StatusBadRequest, recorder.Code) + }, + }, + { + name: "To Many Parameters", + query: Query{ + page: 1, + pageSize: 10, + id: company.ID, + name: company.Name, + }, + buildStubs: func(store *mockdb.MockStore) { + store.EXPECT(). + ListJobsByCompanyID(gomock.Any(), gomock.Any()). + Times(0) + store.EXPECT(). + ListJobsByCompanyExactName(gomock.Any(), gomock.Any()). + Times(0) + store.EXPECT(). + ListJobsByCompanyName(gomock.Any(), gomock.Any()). + Times(0) + }, + checkResponse: func(recorder *httptest.ResponseRecorder) { + require.Equal(t, http.StatusBadRequest, recorder.Code) + }, + }, + { + name: "Internal Server Error ListJobsByCompanyID", + query: Query{ + page: 1, + pageSize: 10, + id: company.ID, + }, + buildStubs: func(store *mockdb.MockStore) { + store.EXPECT(). + ListJobsByCompanyID(gomock.Any(), gomock.Any()). + Times(1). + Return([]db.ListJobsByCompanyIDRow{}, sql.ErrConnDone) + store.EXPECT(). + ListJobsByCompanyExactName(gomock.Any(), gomock.Any()). + Times(0) + store.EXPECT(). + ListJobsByCompanyName(gomock.Any(), gomock.Any()). + Times(0) + }, + checkResponse: func(recorder *httptest.ResponseRecorder) { + require.Equal(t, http.StatusInternalServerError, recorder.Code) + }, + }, + { + name: "Internal Server Error ListJobsByCompanyExactName", + query: Query{ + page: 1, + pageSize: 10, + name: company.Name, + }, + buildStubs: func(store *mockdb.MockStore) { + store.EXPECT(). + ListJobsByCompanyID(gomock.Any(), gomock.Any()). + Times(0) + store.EXPECT(). + ListJobsByCompanyExactName(gomock.Any(), gomock.Any()). + Times(1). + Return([]db.ListJobsByCompanyExactNameRow{}, sql.ErrConnDone) + store.EXPECT(). + ListJobsByCompanyName(gomock.Any(), gomock.Any()). + Times(0) + }, + checkResponse: func(recorder *httptest.ResponseRecorder) { + require.Equal(t, http.StatusInternalServerError, recorder.Code) + }, + }, + { + name: "Internal Server Error ListJobsByCompanyExactName", + query: Query{ + page: 1, + pageSize: 10, + nameContains: company.Name[1:3], + }, + buildStubs: func(store *mockdb.MockStore) { + store.EXPECT(). + ListJobsByCompanyID(gomock.Any(), gomock.Any()). + Times(0) + store.EXPECT(). + ListJobsByCompanyExactName(gomock.Any(), gomock.Any()). + Times(0) + store.EXPECT(). + ListJobsByCompanyName(gomock.Any(), gomock.Any()). + Times(1). + Return([]db.ListJobsByCompanyNameRow{}, sql.ErrConnDone) + }, + checkResponse: func(recorder *httptest.ResponseRecorder) { + require.Equal(t, http.StatusInternalServerError, recorder.Code) + }, + }, + } + for i := range testCases { + tc := testCases[i] + + t.Run(tc.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + store := mockdb.NewMockStore(ctrl) + tc.buildStubs(store) + + server := newTestServer(t, store) + recorder := httptest.NewRecorder() + + url := "/jobs/company" + req, err := http.NewRequest(http.MethodGet, url, nil) + require.NoError(t, err) + + // Add query params + q := req.URL.Query() + q.Add("page", fmt.Sprintf("%d", tc.query.page)) + q.Add("page_size", fmt.Sprintf("%d", tc.query.pageSize)) + q.Add("name", tc.query.name) + q.Add("name_contains", tc.query.nameContains) + q.Add("id", fmt.Sprintf("%d", tc.query.id)) + req.URL.RawQuery = q.Encode() + + server.router.ServeHTTP(recorder, req) + + tc.checkResponse(recorder) + }) + } +} + func generateJob(title, industry, jobLocation string, salaryMin, salaryMax int32) db.Job { return db.Job{ ID: utils.RandomInt(1, 1000), @@ -1688,6 +2046,30 @@ func requireBodyMatchJobs(t *testing.T, body *bytes.Buffer, jobs interface{}) { err = json.Unmarshal(data, &gotJobRows) require.NoError(t, err) + for i := 0; i < len(j); i++ { + require.Equal(t, j[i], gotJobRows[i]) + } + case []db.ListJobsByCompanyExactNameRow: + var gotJobRows []db.ListJobsByCompanyExactNameRow + err = json.Unmarshal(data, &gotJobRows) + require.NoError(t, err) + + for i := 0; i < len(j); i++ { + require.Equal(t, j[i], gotJobRows[i]) + } + case []db.ListJobsByCompanyNameRow: + var gotJobRows []db.ListJobsByCompanyNameRow + err = json.Unmarshal(data, &gotJobRows) + require.NoError(t, err) + + for i := 0; i < len(j); i++ { + require.Equal(t, j[i], gotJobRows[i]) + } + case []db.ListJobsByCompanyIDRow: + var gotJobRows []db.ListJobsByCompanyIDRow + err = json.Unmarshal(data, &gotJobRows) + require.NoError(t, err) + for i := 0; i < len(j); i++ { require.Equal(t, j[i], gotJobRows[i]) } diff --git a/api/server.go b/api/server.go index 4d0b3ea..63e6b45 100644 --- a/api/server.go +++ b/api/server.go @@ -54,6 +54,7 @@ func (server *Server) setupRouter() { // === jobs === router.GET("/jobs/:id", server.getJob) router.GET("/jobs", server.filterAndListJobs) + router.GET("/jobs/company", server.listJobsByCompany) // ===== routes that require authentication ===== authRoutes := router.Group("/").Use(authMiddleware(server.tokenMaker)) diff --git a/db/mock/store.go b/db/mock/store.go index d51e2aa..96c8577 100644 --- a/db/mock/store.go +++ b/db/mock/store.go @@ -460,10 +460,10 @@ func (mr *MockStoreMockRecorder) ListJobSkillsByJobID(arg0, arg1 interface{}) *g } // ListJobsByCompanyExactName mocks base method. -func (m *MockStore) ListJobsByCompanyExactName(arg0 context.Context, arg1 db.ListJobsByCompanyExactNameParams) ([]db.Job, error) { +func (m *MockStore) ListJobsByCompanyExactName(arg0 context.Context, arg1 db.ListJobsByCompanyExactNameParams) ([]db.ListJobsByCompanyExactNameRow, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListJobsByCompanyExactName", arg0, arg1) - ret0, _ := ret[0].([]db.Job) + ret0, _ := ret[0].([]db.ListJobsByCompanyExactNameRow) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -475,10 +475,10 @@ func (mr *MockStoreMockRecorder) ListJobsByCompanyExactName(arg0, arg1 interface } // ListJobsByCompanyID mocks base method. -func (m *MockStore) ListJobsByCompanyID(arg0 context.Context, arg1 db.ListJobsByCompanyIDParams) ([]db.Job, error) { +func (m *MockStore) ListJobsByCompanyID(arg0 context.Context, arg1 db.ListJobsByCompanyIDParams) ([]db.ListJobsByCompanyIDRow, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListJobsByCompanyID", arg0, arg1) - ret0, _ := ret[0].([]db.Job) + ret0, _ := ret[0].([]db.ListJobsByCompanyIDRow) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -490,10 +490,10 @@ func (mr *MockStoreMockRecorder) ListJobsByCompanyID(arg0, arg1 interface{}) *go } // ListJobsByCompanyName mocks base method. -func (m *MockStore) ListJobsByCompanyName(arg0 context.Context, arg1 db.ListJobsByCompanyNameParams) ([]db.Job, error) { +func (m *MockStore) ListJobsByCompanyName(arg0 context.Context, arg1 db.ListJobsByCompanyNameParams) ([]db.ListJobsByCompanyNameRow, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListJobsByCompanyName", arg0, arg1) - ret0, _ := ret[0].([]db.Job) + ret0, _ := ret[0].([]db.ListJobsByCompanyNameRow) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/db/queries/job.sql b/db/queries/job.sql index 858b98c..722b4ac 100644 --- a/db/queries/job.sql +++ b/db/queries/job.sql @@ -47,20 +47,24 @@ WHERE industry = $1 LIMIT $2 OFFSET $3; -- name: ListJobsByCompanyID :many -SELECT * -FROM jobs -WHERE company_id = $1 +SELECT j.*, + c.name AS company_name +FROM jobs j + JOIN companies c ON j.company_id = c.id +WHERE j.company_id = $1 LIMIT $2 OFFSET $3; -- name: ListJobsByCompanyExactName :many -SELECT j.* +SELECT j.*, + c.name AS company_name FROM jobs j JOIN companies c ON j.company_id = c.id WHERE c.name = $1 LIMIT $2 OFFSET $3; -- name: ListJobsByCompanyName :many -SELECT j.* +SELECT j.*, + c.name AS company_name FROM jobs j JOIN companies c ON j.company_id = c.id WHERE c.name ILIKE '%' || @name::text || '%' @@ -79,10 +83,10 @@ SELECT j.*, FROM jobs j JOIN companies c ON j.company_id = c.id WHERE j.id IN (SELECT job_id - FROM job_skills - WHERE skill IN (SELECT skill - FROM user_skills - WHERE user_id = $1)) + FROM job_skills + WHERE skill IN (SELECT skill + FROM user_skills + WHERE user_id = $1)) LIMIT $2 OFFSET $3; -- name: UpdateJob :one diff --git a/db/sqlc/job.sql.go b/db/sqlc/job.sql.go index 8528247..3f07a45 100644 --- a/db/sqlc/job.sql.go +++ b/db/sqlc/job.sql.go @@ -154,7 +154,8 @@ func (q *Queries) GetJobDetails(ctx context.Context, id int32) (GetJobDetailsRow } const listJobsByCompanyExactName = `-- name: ListJobsByCompanyExactName :many -SELECT j.id, j.title, j.industry, j.company_id, j.description, j.location, j.salary_min, j.salary_max, j.requirements, j.created_at +SELECT j.id, j.title, j.industry, j.company_id, j.description, j.location, j.salary_min, j.salary_max, j.requirements, j.created_at, + c.name AS company_name FROM jobs j JOIN companies c ON j.company_id = c.id WHERE c.name = $1 @@ -167,15 +168,29 @@ type ListJobsByCompanyExactNameParams struct { Offset int32 `json:"offset"` } -func (q *Queries) ListJobsByCompanyExactName(ctx context.Context, arg ListJobsByCompanyExactNameParams) ([]Job, error) { +type ListJobsByCompanyExactNameRow struct { + ID int32 `json:"id"` + Title string `json:"title"` + Industry string `json:"industry"` + CompanyID int32 `json:"company_id"` + Description string `json:"description"` + Location string `json:"location"` + SalaryMin int32 `json:"salary_min"` + SalaryMax int32 `json:"salary_max"` + Requirements string `json:"requirements"` + CreatedAt time.Time `json:"created_at"` + CompanyName string `json:"company_name"` +} + +func (q *Queries) ListJobsByCompanyExactName(ctx context.Context, arg ListJobsByCompanyExactNameParams) ([]ListJobsByCompanyExactNameRow, error) { rows, err := q.db.QueryContext(ctx, listJobsByCompanyExactName, arg.Name, arg.Limit, arg.Offset) if err != nil { return nil, err } defer rows.Close() - items := []Job{} + items := []ListJobsByCompanyExactNameRow{} for rows.Next() { - var i Job + var i ListJobsByCompanyExactNameRow if err := rows.Scan( &i.ID, &i.Title, @@ -187,6 +202,7 @@ func (q *Queries) ListJobsByCompanyExactName(ctx context.Context, arg ListJobsBy &i.SalaryMax, &i.Requirements, &i.CreatedAt, + &i.CompanyName, ); err != nil { return nil, err } @@ -202,9 +218,11 @@ func (q *Queries) ListJobsByCompanyExactName(ctx context.Context, arg ListJobsBy } const listJobsByCompanyID = `-- name: ListJobsByCompanyID :many -SELECT id, title, industry, company_id, description, location, salary_min, salary_max, requirements, created_at -FROM jobs -WHERE company_id = $1 +SELECT j.id, j.title, j.industry, j.company_id, j.description, j.location, j.salary_min, j.salary_max, j.requirements, j.created_at, + c.name AS company_name +FROM jobs j + JOIN companies c ON j.company_id = c.id +WHERE j.company_id = $1 LIMIT $2 OFFSET $3 ` @@ -214,15 +232,29 @@ type ListJobsByCompanyIDParams struct { Offset int32 `json:"offset"` } -func (q *Queries) ListJobsByCompanyID(ctx context.Context, arg ListJobsByCompanyIDParams) ([]Job, error) { +type ListJobsByCompanyIDRow struct { + ID int32 `json:"id"` + Title string `json:"title"` + Industry string `json:"industry"` + CompanyID int32 `json:"company_id"` + Description string `json:"description"` + Location string `json:"location"` + SalaryMin int32 `json:"salary_min"` + SalaryMax int32 `json:"salary_max"` + Requirements string `json:"requirements"` + CreatedAt time.Time `json:"created_at"` + CompanyName string `json:"company_name"` +} + +func (q *Queries) ListJobsByCompanyID(ctx context.Context, arg ListJobsByCompanyIDParams) ([]ListJobsByCompanyIDRow, error) { rows, err := q.db.QueryContext(ctx, listJobsByCompanyID, arg.CompanyID, arg.Limit, arg.Offset) if err != nil { return nil, err } defer rows.Close() - items := []Job{} + items := []ListJobsByCompanyIDRow{} for rows.Next() { - var i Job + var i ListJobsByCompanyIDRow if err := rows.Scan( &i.ID, &i.Title, @@ -234,6 +266,7 @@ func (q *Queries) ListJobsByCompanyID(ctx context.Context, arg ListJobsByCompany &i.SalaryMax, &i.Requirements, &i.CreatedAt, + &i.CompanyName, ); err != nil { return nil, err } @@ -249,7 +282,8 @@ func (q *Queries) ListJobsByCompanyID(ctx context.Context, arg ListJobsByCompany } const listJobsByCompanyName = `-- name: ListJobsByCompanyName :many -SELECT j.id, j.title, j.industry, j.company_id, j.description, j.location, j.salary_min, j.salary_max, j.requirements, j.created_at +SELECT j.id, j.title, j.industry, j.company_id, j.description, j.location, j.salary_min, j.salary_max, j.requirements, j.created_at, + c.name AS company_name FROM jobs j JOIN companies c ON j.company_id = c.id WHERE c.name ILIKE '%' || $3::text || '%' @@ -262,15 +296,29 @@ type ListJobsByCompanyNameParams struct { Name string `json:"name"` } -func (q *Queries) ListJobsByCompanyName(ctx context.Context, arg ListJobsByCompanyNameParams) ([]Job, error) { +type ListJobsByCompanyNameRow struct { + ID int32 `json:"id"` + Title string `json:"title"` + Industry string `json:"industry"` + CompanyID int32 `json:"company_id"` + Description string `json:"description"` + Location string `json:"location"` + SalaryMin int32 `json:"salary_min"` + SalaryMax int32 `json:"salary_max"` + Requirements string `json:"requirements"` + CreatedAt time.Time `json:"created_at"` + CompanyName string `json:"company_name"` +} + +func (q *Queries) ListJobsByCompanyName(ctx context.Context, arg ListJobsByCompanyNameParams) ([]ListJobsByCompanyNameRow, error) { rows, err := q.db.QueryContext(ctx, listJobsByCompanyName, arg.Limit, arg.Offset, arg.Name) if err != nil { return nil, err } defer rows.Close() - items := []Job{} + items := []ListJobsByCompanyNameRow{} for rows.Next() { - var i Job + var i ListJobsByCompanyNameRow if err := rows.Scan( &i.ID, &i.Title, @@ -282,6 +330,7 @@ func (q *Queries) ListJobsByCompanyName(ctx context.Context, arg ListJobsByCompa &i.SalaryMax, &i.Requirements, &i.CreatedAt, + &i.CompanyName, ); err != nil { return nil, err } @@ -497,10 +546,10 @@ SELECT j.id, j.title, j.industry, j.company_id, j.description, j.location, j.sal FROM jobs j JOIN companies c ON j.company_id = c.id WHERE j.id IN (SELECT job_id - FROM job_skills - WHERE skill IN (SELECT skill - FROM user_skills - WHERE user_id = $1)) + FROM job_skills + WHERE skill IN (SELECT skill + FROM user_skills + WHERE user_id = $1)) LIMIT $2 OFFSET $3 ` diff --git a/db/sqlc/querier.go b/db/sqlc/querier.go index bd28cf6..4f9595b 100644 --- a/db/sqlc/querier.go +++ b/db/sqlc/querier.go @@ -34,9 +34,9 @@ type Querier interface { GetUserByEmail(ctx context.Context, email string) (User, error) GetUserByID(ctx context.Context, id int32) (User, error) ListJobSkillsByJobID(ctx context.Context, arg ListJobSkillsByJobIDParams) ([]ListJobSkillsByJobIDRow, error) - ListJobsByCompanyExactName(ctx context.Context, arg ListJobsByCompanyExactNameParams) ([]Job, error) - ListJobsByCompanyID(ctx context.Context, arg ListJobsByCompanyIDParams) ([]Job, error) - ListJobsByCompanyName(ctx context.Context, arg ListJobsByCompanyNameParams) ([]Job, error) + ListJobsByCompanyExactName(ctx context.Context, arg ListJobsByCompanyExactNameParams) ([]ListJobsByCompanyExactNameRow, error) + ListJobsByCompanyID(ctx context.Context, arg ListJobsByCompanyIDParams) ([]ListJobsByCompanyIDRow, error) + ListJobsByCompanyName(ctx context.Context, arg ListJobsByCompanyNameParams) ([]ListJobsByCompanyNameRow, error) ListJobsByIndustry(ctx context.Context, arg ListJobsByIndustryParams) ([]Job, error) ListJobsByLocation(ctx context.Context, arg ListJobsByLocationParams) ([]Job, error) ListJobsBySalaryRange(ctx context.Context, arg ListJobsBySalaryRangeParams) ([]Job, error)