Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: job offer application #67

Merged
merged 3 commits into from
Feb 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
Expand All @@ -15,8 +16,16 @@ import java.security.Principal
@RestController
@RequestMapping("/api/v1/jobOffers")
class JobOffersController(private val jobOffersService: JobOfferService) {

@GetMapping
open fun getJobOffers(request: ServerHttpRequest, principal: Principal): Flux<JobOffer> {
open fun getJobOffers(
request: ServerHttpRequest,
principal: Principal,
@RequestParam(required = false, defaultValue = "false") onlyApplied: Boolean
): Flux<JobOffer> {
if (onlyApplied) {
return Flux.fromIterable(jobOffersService.findAllApplied(request.id, principal.name))
}
return Flux.fromIterable(jobOffersService.findAll(request.id, principal.name))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ import java.time.LocalDate
class JobOfferService(
private val natsService: NatsService,
@Value("\${app.services.jobOffers.subjects.findAllForUser}") private val findAllSubject: String,
@Value("\${app.services.jobOffers.subjects.findAllAppliedForUser}") private val findAllAppliedSubject: String,
@Value("\${app.services.jobOffers.subjects.findOneForUser}") private val findOneSubject: String,
@Value("\${app.services.jobOffers.subjects.findMultiple}") private val findMultipleSubject: String,
@Value("\${app.services.jobOffers.subjects.applyTo}") private val applyToSubject: String,
@Value("\${app.services.recommendation.subjects.findAll}") private val findAll: String

) {
fun findAll(requestId: String, userId: String): List<JobOffer> {
// Request job offers from the job service
// Request job offers from the recommendation
val request = RequestResponseFactory.newRequest(requestId)
.setGetRecommendationRequest(
Recommendations.GetRecommendationRequest.newBuilder()
Expand All @@ -43,21 +44,62 @@ class JobOfferService(
recommendation.id // Assuming Recommendation has an 'id' field representing its ID
}

if (recommendationIds.size === 0) {
val requestJobOffers = RequestResponseFactory.newRequest(requestId)
.setGetUserJobOffersRequest(
Jobs.GetUserJobOffersRequest.newBuilder()
.setUserId(userId)
).build()

val responseJobOffers = natsService.requestWithReply(findAllSubject, requestJobOffers)

// Handle the response
if (!responseJobOffers.hasGetUserJobOffersResponse()) {
throw Exception("Invalid response")
}

val getUserJobOffersResponse = responseJobOffers.getUserJobOffersResponse
return getUserJobOffersResponse.jobOffersList.map { jobOffer ->
convertJobOfferFromProto(jobOffer)
}
} else {
val requestJobOffers = RequestResponseFactory.newRequest(requestId)
.setGetMultipleJobOffersRequest(
Jobs.GetMultipleJobOffersRequest.newBuilder()
.addAllIds(recommendationIds)
).build()

val responseJobOffers = natsService.requestWithReply(findMultipleSubject, requestJobOffers)

// Handle the response
if (!responseJobOffers.hasGetMultipleJobOffersResponse()) {
throw Exception("Invalid response")
}

val getUserJobOffersResponse = responseJobOffers.getMultipleJobOffersResponse
return getUserJobOffersResponse.jobOffersList.map { jobOffer ->
convertJobOfferFromProto(jobOffer)
}
}
}

fun findAllApplied(requestId: String, userId: String): List<JobOffer> {
// Request job offers from the job service
val requestJobOffers = RequestResponseFactory.newRequest(requestId)
.setGetMultipleJobOffersRequest(
Jobs.GetMultipleJobOffersRequest.newBuilder()
.addAllIds(recommendationIds)
.setGetUserJobOffersAppliedRequest(
Jobs.GetUserJobOffersAppliedRequest.newBuilder()
.setUserId(userId)
).build()

val responseJobOffers = natsService.requestWithReply(findMultipleSubject, requestJobOffers)
val responseJobOffers = natsService.requestWithReply(findAllAppliedSubject, requestJobOffers)

// Handle the response
if (!responseJobOffers.hasGetMultipleJobOffersResponse()) {
if (!responseJobOffers.hasGetUserJobOffersAppliedResponse()) {
throw Exception("Invalid response")
}

val getUserJobOffersResponse = responseJobOffers.getMultipleJobOffersResponse
return getUserJobOffersResponse.jobOffersList.map { jobOffer ->
val getUserJobOffersAppliedResponse = responseJobOffers.getUserJobOffersAppliedResponse
return getUserJobOffersAppliedResponse.jobOffersList.map { jobOffer ->
convertJobOfferFromProto(jobOffer)
}
}
Expand Down
1 change: 1 addition & 0 deletions backend/api_gateway/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ app:
jobOffers:
subjects:
findAllForUser: jobs.findAllJobOffersForUser
findAllAppliedForUser: jobs.findAllJobOffersAppliedForUser
findMultiple: jobs.findMultipleJobOffers
findOneForUser: jobs.findOneJobOfferForUser
applyTo: jobs.applyToJobOffer
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.linkedout.jobs.function.jobOffers

import com.linkedout.common.utils.RequestResponseFactory
import com.linkedout.common.utils.handleRequestError
import com.linkedout.jobs.converter.jobOffers.JobOfferWithJobAndCompanyToProto
import com.linkedout.jobs.service.JobOfferService
import com.linkedout.proto.RequestOuterClass.Request
import com.linkedout.proto.ResponseOuterClass.Response
import com.linkedout.proto.services.Jobs
import org.springframework.stereotype.Component
import java.util.UUID
import java.util.function.Function

@Component
class GetJobOffersAppliedForUser(private val jobOfferService: JobOfferService) : Function<Request, Response> {
override fun apply(t: Request): Response = handleRequestError {
// Extract the request
val request = t.getUserJobOffersAppliedRequest
val userId = UUID.fromString(request.userId)

// Get all the job offers from the database
val reactiveResponse = jobOfferService.findAllAppliedForUser(userId)
.map { jobOffer ->
JobOfferWithJobAndCompanyToProto().convert(jobOffer)
.setStatusValue(jobOffer.jobApplicationStatus)
}
.filter { it != null }
.reduce(Jobs.GetUserJobOffersAppliedResponse.newBuilder()) { builder, jobOffer ->
builder.addJobOffers(jobOffer)
builder
}
.map { builder ->
builder.build()
}

// Block until the response is ready
val response = reactiveResponse.block()
?: return RequestResponseFactory.newSuccessfulResponse()
.setGetUserJobOffersAppliedResponse(Jobs.GetUserJobOffersAppliedResponse.getDefaultInstance())
.build()

return RequestResponseFactory.newSuccessfulResponse()
.setGetUserJobOffersAppliedResponse(response)
.build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,30 @@ interface JobOfferRepository : ReactiveCrudRepository<JobOffer, UUID> {
)
fun findAllForUserWithJobAndCompany(userId: UUID): Flux<JobOfferWithJobAndCompanyAndApplicationStatus>

@Query(
"""
SELECT jo.id as jobOfferId,
jo.job as jobId,
jo.title as jobOfferTitle,
jo.description as jobOfferDescription,
jo.geographicArea as jobOfferGeographicArea,
jo.startdate as jobOfferStartDate,
jo.enddate as jobOfferEndDate,
jo.company as companyId,
c.name as companyName,
jo.salary as jobOfferSalary,
jc.title as jobCategoryTitle,
j.title as jobTitle,
COALESCE(ja.status, -1) as jobApplicationStatus
FROM jobOffer jo
JOIN company c ON jo.company = c.id
JOIN job j ON jo.job = j.id
JOIN jobcategory jc ON j.category = jc.id
RIGHT JOIN jobapplication ja ON jo.id = ja.offerid AND ja.userid = :userId
"""
)
fun findAllAppliedForUserWithJobAndCompany(userId: UUID): Flux<JobOfferWithJobAndCompanyAndApplicationStatus>

@Query(
"""
SELECT jo.id as jobOfferId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ class JobOfferService(
return jobOffer.findAllForUserWithJobAndCompany(userId)
}

fun findAllAppliedForUser(userId: UUID): Flux<JobOfferWithJobAndCompanyAndApplicationStatus> {
return jobOffer.findAllAppliedForUserWithJobAndCompany(userId)
}

fun findOneForUser(userId: UUID, jobOfferId: UUID): Mono<JobOfferWithJobAndCompanyAndApplicationStatus> {
return jobOffer.findOneForUserWithJobAndCompany(userId, jobOfferId)
}
Expand Down
12 changes: 11 additions & 1 deletion backend/jobs/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
spring.cloud.function.definition=getJobs;getJob;getMultipleJobs;getJobCategories;getJobCategory;getMultipleJobCategories;getCompanies;getCompany;getMultipleCompanies;ensureCompanyExists;getJobOffersForUser;getJobOfferForUser;applyToJobOffer;getMultipleJobOffers
spring.cloud.function.definition=getJobs;getJob;getMultipleJobs;getJobCategories;getJobCategory;getMultipleJobCategories;getCompanies;getCompany;getMultipleCompanies;ensureCompanyExists;getJobOffersForUser;getJobOfferForUser;applyToJobOffer;getMultipleJobOffers;getJobOffersAppliedForUser

spring.cloud.stream.bindings.getJobs-in-0.destination=jobs.findAll
spring.cloud.stream.bindings.getJobs-in-0.group=jobsSvc
Expand Down Expand Up @@ -125,4 +125,14 @@ spring.cloud.stream.bindings.getMultipleJobOffers-out-0.destination=
spring.cloud.stream.bindings.getMultipleJobOffers-out-0.group=jobsSvc
spring.cloud.stream.bindings.getMultipleJobOffers-out-0.binder=nats
spring.cloud.stream.bindings.getMultipleJobOffers-out-0.content-type=application/vnd.linkedout.proto-response

spring.cloud.stream.bindings.getJobOffersAppliedForUser-in-0.destination=jobs.findAllJobOffersAppliedForUser
spring.cloud.stream.bindings.getJobOffersAppliedForUser-in-0.group=jobsSvc
spring.cloud.stream.bindings.getJobOffersAppliedForUser-in-0.binder=nats
spring.cloud.stream.bindings.getJobOffersAppliedForUser-in-0.content-type=application/vnd.linkedout.proto-request
spring.cloud.stream.bindings.getJobOffersAppliedForUser-out-0.destination=
spring.cloud.stream.bindings.getJobOffersAppliedForUser-out-0.group=jobsSvc
spring.cloud.stream.bindings.getJobOffersAppliedForUser-out-0.binder=nats
spring.cloud.stream.bindings.getJobOffersAppliedForUser-out-0.content-type=application/vnd.linkedout.proto-response

nats.spring.server=nats://localhost:4222
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

package com.linkedout.profile.function.profile

import com.linkedout.common.utils.RequestResponseFactory
Expand Down
1 change: 1 addition & 0 deletions backend/protobuf/src/main/proto/request.proto
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,6 @@ message Request {
services.CreateRecommendationProfileRequest create_recommendation_profile_request = 58;
services.CreateRecommendationExperienceRequest create_recommendation_experience_request = 59;
services.GetMultipleJobOffersRequest get_multiple_job_offers_request = 60;
services.GetUserJobOffersAppliedRequest get_user_job_offers_applied_request = 61;
}
}
1 change: 1 addition & 0 deletions backend/protobuf/src/main/proto/response.proto
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,6 @@ message Response {
services.CreateRecommendationProfileResponse create_recommendation_profile_response = 57;
services.CreateRecommendationExperienceResponse create_recommendation_experience_response = 58;
services.GetMultipleJobOffersResponse get_multiple_job_offers_response = 59;
services.GetUserJobOffersAppliedResponse get_user_job_offers_applied_response = 60;
}
}
9 changes: 9 additions & 0 deletions backend/protobuf/src/main/proto/services/jobs.proto
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ message GetUserJobOffersResponse {
repeated models.JobOffer jobOffers = 1;
}

// Get job offers that a user applied to
message GetUserJobOffersAppliedRequest {
string user_id = 1;
}

message GetUserJobOffersAppliedResponse {
repeated models.JobOffer jobOffers = 1;
}

// Get a job offer for a user
message GetUserJobOfferRequest {
string user_id = 1;
Expand Down
3 changes: 3 additions & 0 deletions front/assets/translations/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
"apply": "Apply",
"applyTitle": "Apply confirmation",
"applyConfirm": "Do you want to apply to this job?"
},
"applied": {
"jobOfferAppliedList": "Job offer applied list"
}
},
"messaging": {
Expand Down
3 changes: 3 additions & 0 deletions front/assets/translations/fr_FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
"apply": "Postuler",
"applyTitle": "Confirmation de candidature",
"applyConfirm": "Voulez-vous candidater à cette offre ?"
},
"applied": {
"jobOfferAppliedList": "Liste des offres d'emploi candidatés"
}
},
"messaging": {
Expand Down
5 changes: 3 additions & 2 deletions front/src/pages/MainTabNav.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { createMaterialBottomTabNavigator } from 'react-native-paper/react-navigation';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';

import InternalPagesNav from '@/pages/internal/InternalPagesNav';
import JobOffersNav from '@/pages/jobOffer/JobOfferNav';
import MessagingNav from '@/pages/messaging/MessagingNav';
import ProfileNav from '@/pages/profile/ProfileNav';
import i18n from '@/utils/i18n';

import JobOffersApplicationNav from './jobOffer/JobOfferApplicationNav';

/**
* The parameter list for the MainTab navigator.
*/
Expand Down Expand Up @@ -43,7 +44,7 @@ const MainTabNav = () => {
/>
<MainTab.Screen
name='Applications'
component={InternalPagesNav}
component={JobOffersApplicationNav}
options={{
tabBarLabel: i18n.t('applications.applications'),
tabBarIcon: ({ color }) => (
Expand Down
46 changes: 46 additions & 0 deletions front/src/pages/jobOffer/JobOfferApplicationNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { createNativeStackNavigator } from '@react-navigation/native-stack';

import PaperNavigationBar from '@/components/utils/PaperNavigationBar';
import i18n from '@/utils/i18n';

import JobOfferPage, { JobOfferPageParams } from './JobOfferPage';
import JobOfferApplicationPage from './jobOfferApplicationPage';

/**
* The parameter list for the JobOffersNav navigator.
*/
export type JobOfferApplicationStackParamList = {
JobOffer: JobOfferPageParams;
JobOfferApplicationList: undefined;
};

const JobOfferApplicationStack =
createNativeStackNavigator<JobOfferApplicationStackParamList>();

/**
* The stack navigator for the job offers pages.
* @constructor
*/
const JobOffersApplicationNav = () => {
return (
<JobOfferApplicationStack.Navigator
initialRouteName='JobOfferApplicationList'
screenOptions={{ header: (props) => <PaperNavigationBar {...props} /> }}
>
<JobOfferApplicationStack.Screen
name='JobOfferApplicationList'
component={JobOfferApplicationPage}
options={{
headerTitle: `${i18n.t('jobOffer.applied.jobOfferAppliedList')}`,
}}
/>
<JobOfferApplicationStack.Screen
name='JobOffer'
component={JobOfferPage}
options={{ headerTitle: `${i18n.t('jobOffer.info.jobOffer')}` }}
/>
</JobOfferApplicationStack.Navigator>
);
};

export default JobOffersApplicationNav;
1 change: 1 addition & 0 deletions front/src/pages/jobOffer/JobOfferNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import JobOfferPage, { JobOfferPageParams } from './JobOfferPage';
export type JobOfferStackParamList = {
JobOfferList: undefined;
JobOffer: JobOfferPageParams;
JobOfferApplication: undefined;
};

const JobOfferStack = createNativeStackNavigator<JobOfferStackParamList>();
Expand Down
Loading
Loading