From 2b01b33590f5f95e04898b39690d2bae3333024e Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Mon, 28 Oct 2024 22:26:01 +0300 Subject: [PATCH 01/29] feat: Implement Job model for job postings This commit introduces the Job model representing job postings in the application. It includes fields for title, description, budget, job type, status, and creation timestamp. --- .../com/activecourses/upwork/model/Job.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/main/java/com/activecourses/upwork/model/Job.java diff --git a/src/main/java/com/activecourses/upwork/model/Job.java b/src/main/java/com/activecourses/upwork/model/Job.java new file mode 100644 index 0000000..d0b9ae7 --- /dev/null +++ b/src/main/java/com/activecourses/upwork/model/Job.java @@ -0,0 +1,61 @@ +package com.activecourses.upwork.model; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import org.springframework.data.annotation.CreatedDate; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "jobs") +public class Job { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @ManyToOne + @JoinColumn(name = "client_id") + private User client; + + @Column(nullable = false) + private String title; + + @Column(nullable = false, columnDefinition = "TEXT") + private String description; + + @Column(nullable = false) + private BigDecimal budget; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private JobType jobType; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private JobStatus jobStatus; + + @CreatedDate + @Column(nullable = false, updatable = false) + private LocalDateTime createdAt; +} From 497853067a7a2b881491ac41e6dc3f3d00786a66 Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Mon, 28 Oct 2024 22:26:16 +0300 Subject: [PATCH 02/29] feat: Define JobStatus enum for job posting lifecycle This commit defines the JobStatus enum with possible values for the status of a job posting: - OPEN: Job is open for applications. - CLOSED: Job is no longer accepting applications. - IN_PROGRESS: Job has been awarded and work is ongoing. --- .../java/com/activecourses/upwork/model/JobStatus.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/com/activecourses/upwork/model/JobStatus.java diff --git a/src/main/java/com/activecourses/upwork/model/JobStatus.java b/src/main/java/com/activecourses/upwork/model/JobStatus.java new file mode 100644 index 0000000..222200a --- /dev/null +++ b/src/main/java/com/activecourses/upwork/model/JobStatus.java @@ -0,0 +1,7 @@ +package com.activecourses.upwork.model; + +public enum JobStatus { + OPEN, + CLOSED, + IN_PROGRESS +} \ No newline at end of file From 941ccbfad01b939a72d855db71c9c996935446bb Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Mon, 28 Oct 2024 22:26:27 +0300 Subject: [PATCH 03/29] feat: Define JobType enum for job posting types This commit defines the JobType enum with possible types for a job posting: - HOURLY: Job is billed by the hour. - FIXED: Job has a fixed price. --- src/main/java/com/activecourses/upwork/model/JobType.java | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/main/java/com/activecourses/upwork/model/JobType.java diff --git a/src/main/java/com/activecourses/upwork/model/JobType.java b/src/main/java/com/activecourses/upwork/model/JobType.java new file mode 100644 index 0000000..faede4f --- /dev/null +++ b/src/main/java/com/activecourses/upwork/model/JobType.java @@ -0,0 +1,6 @@ +package com.activecourses.upwork.model; + +public enum JobType { + HOURLY, + FIXED +} \ No newline at end of file From e3afcc3700a8f9573ee4355bd9e06bfd617f0902 Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Tue, 29 Oct 2024 00:20:14 +0300 Subject: [PATCH 04/29] Refactored Job entity variable names for improved readability. --- .../com/activecourses/upwork/model/Job.java | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/activecourses/upwork/model/Job.java b/src/main/java/com/activecourses/upwork/model/Job.java index d0b9ae7..2fa80b5 100644 --- a/src/main/java/com/activecourses/upwork/model/Job.java +++ b/src/main/java/com/activecourses/upwork/model/Job.java @@ -2,19 +2,8 @@ import java.math.BigDecimal; import java.time.LocalDateTime; +import jakarta.persistence.*; -import org.springframework.data.annotation.CreatedDate; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -32,13 +21,13 @@ public class Job { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; + private Integer jobId; @ManyToOne @JoinColumn(name = "client_id") private User client; - @Column(nullable = false) + @Column(nullable = false, length = 255) private String title; @Column(nullable = false, columnDefinition = "TEXT") @@ -53,9 +42,9 @@ public class Job { @Enumerated(EnumType.STRING) @Column(nullable = false) - private JobStatus jobStatus; + private JobStatus status; - @CreatedDate + @Builder.Default @Column(nullable = false, updatable = false) - private LocalDateTime createdAt; -} + private LocalDateTime createdAt = LocalDateTime.now(); +} \ No newline at end of file From 864a2f73225611e1c92941acd71997639a1eecc4 Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Tue, 29 Oct 2024 00:23:07 +0300 Subject: [PATCH 05/29] Created JobRepository to interact with the 'jobs' table in the database. --- .../upwork/repository/job/JobRepository.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/com/activecourses/upwork/repository/job/JobRepository.java diff --git a/src/main/java/com/activecourses/upwork/repository/job/JobRepository.java b/src/main/java/com/activecourses/upwork/repository/job/JobRepository.java new file mode 100644 index 0000000..79355ee --- /dev/null +++ b/src/main/java/com/activecourses/upwork/repository/job/JobRepository.java @@ -0,0 +1,10 @@ +package com.activecourses.upwork.repository.job; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import com.activecourses.upwork.model.Job; + +@Repository +public interface JobRepository extends JpaRepository { + +} \ No newline at end of file From 552df0a7ea466502058f9e683deda662b15a7286 Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Tue, 29 Oct 2024 00:24:32 +0300 Subject: [PATCH 06/29] feat: Create JobDTO class for job data transfer - Added JobDTO class to encapsulate job data for transfer between layers. - Contains fields for job attributes including id, title, description, budget, job type, status, and creation timestamp. - Utilizes Lombok's @Data annotation for automatic getter/setter generation. --- .../com/activecourses/upwork/dto/JobDTO.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/java/com/activecourses/upwork/dto/JobDTO.java diff --git a/src/main/java/com/activecourses/upwork/dto/JobDTO.java b/src/main/java/com/activecourses/upwork/dto/JobDTO.java new file mode 100644 index 0000000..83d99e3 --- /dev/null +++ b/src/main/java/com/activecourses/upwork/dto/JobDTO.java @@ -0,0 +1,20 @@ +package com.activecourses.upwork.dto; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import com.activecourses.upwork.model.JobStatus; +import com.activecourses.upwork.model.JobType; + +import lombok.Data; + +@Data +public class JobDTO { + private Integer id; + private String title; + private String description; + private BigDecimal budget; + private JobType jobType; + private JobStatus status; + private LocalDateTime createdAt; +} \ No newline at end of file From 11a43e9a4433b76e72cfd74ce1a865f9ab57ba87 Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Tue, 29 Oct 2024 00:27:28 +0300 Subject: [PATCH 07/29] feat: Define JobService interface for job management - Created JobService interface to outline methods for job-related operations. - Added createJob method to facilitate the creation of a new Job using JobDTO. --- .../com/activecourses/upwork/service/job/JobService.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/com/activecourses/upwork/service/job/JobService.java diff --git a/src/main/java/com/activecourses/upwork/service/job/JobService.java b/src/main/java/com/activecourses/upwork/service/job/JobService.java new file mode 100644 index 0000000..6276349 --- /dev/null +++ b/src/main/java/com/activecourses/upwork/service/job/JobService.java @@ -0,0 +1,8 @@ +package com.activecourses.upwork.service.job; + +import com.activecourses.upwork.dto.JobDTO; +import com.activecourses.upwork.model.Job; + +public interface JobService { + Job createJob(JobDTO jobDTO); +} From 42b3f4b3611772fb3da2190a14e973b34204ba1a Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Tue, 29 Oct 2024 00:30:04 +0300 Subject: [PATCH 08/29] feat: Implement JobMapper for Job and JobDTO conversion - Created JobMapper class to handle conversion between Job and JobDTO objects. - Implemented mapTo method to convert Job to JobDTO. - Implemented mapFrom method to convert JobDTO to Job. - Ensured that all relevant fields are mapped correctly for seamless data transfer. --- .../upwork/mapper/JobMapper.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/main/java/com/activecourses/upwork/mapper/JobMapper.java diff --git a/src/main/java/com/activecourses/upwork/mapper/JobMapper.java b/src/main/java/com/activecourses/upwork/mapper/JobMapper.java new file mode 100644 index 0000000..0099336 --- /dev/null +++ b/src/main/java/com/activecourses/upwork/mapper/JobMapper.java @@ -0,0 +1,33 @@ +package com.activecourses.upwork.mapper; + +import com.activecourses.upwork.dto.JobDTO; +import com.activecourses.upwork.model.Job; + +public class JobMapper implements Mapper { + + @Override + public JobDTO mapTo(Job job) { + JobDTO jobDTO = new JobDTO(); + jobDTO.setId(job.getJobId()); + jobDTO.setTitle(job.getTitle()); + jobDTO.setDescription(job.getDescription()); + jobDTO.setBudget(job.getBudget()); + jobDTO.setJobType(job.getJobType()); + jobDTO.setStatus(job.getStatus()); + jobDTO.setCreatedAt(job.getCreatedAt()); + return jobDTO; + } + + @Override + public Job mapFrom(JobDTO jobDTO) { + Job job = new Job(); + job.setJobId(jobDTO.getId()); + job.setTitle(jobDTO.getTitle()); + job.setDescription(jobDTO.getDescription()); + job.setBudget(jobDTO.getBudget()); + job.setJobType(jobDTO.getJobType()); + job.setStatus(jobDTO.getStatus()); + job.setCreatedAt(jobDTO.getCreatedAt()); + return job; + } +} From 2f7aa7f9fe14496f3aaae6e5ec1080623dd1759a Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Tue, 29 Oct 2024 00:33:35 +0300 Subject: [PATCH 09/29] feat: Implement getCurrentUserId method for authentication - Added getCurrentUserId method to retrieve the ID of the currently authenticated user. - Utilized SecurityContextHolder to access authentication details. - Checked for non-null authentication and user authentication status before returning the user ID. - Returns null if no authenticated user is found, ensuring safe handling of authentication context. --- .../upwork/service/authentication/AuthService.java | 2 ++ .../service/authentication/AuthServiceImpl.java | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/main/java/com/activecourses/upwork/service/authentication/AuthService.java b/src/main/java/com/activecourses/upwork/service/authentication/AuthService.java index a26f689..b73eb55 100644 --- a/src/main/java/com/activecourses/upwork/service/authentication/AuthService.java +++ b/src/main/java/com/activecourses/upwork/service/authentication/AuthService.java @@ -23,4 +23,6 @@ public interface AuthService { boolean deactivateUser(int userId); boolean reactivateUser(int userId); + + Integer getCurrentUserId(); } diff --git a/src/main/java/com/activecourses/upwork/service/authentication/AuthServiceImpl.java b/src/main/java/com/activecourses/upwork/service/authentication/AuthServiceImpl.java index 45207a0..1ada5d2 100644 --- a/src/main/java/com/activecourses/upwork/service/authentication/AuthServiceImpl.java +++ b/src/main/java/com/activecourses/upwork/service/authentication/AuthServiceImpl.java @@ -195,6 +195,17 @@ public boolean reactivateUser(int userId) { return true; } + @Override + public Integer getCurrentUserId() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null && authentication.isAuthenticated()) { + UserDetails userDetails = (UserDetails) authentication.getPrincipal(); + User user = (User) userDetails; + return user.getId(); + } + return null; + } + static User unwrapUser(Optional entity) { if (entity.isPresent()) return entity.get(); else throw new UnsupportedOperationException("Unimplemented method 'unwrapUser'"); From 5b88751921492fa907741ee3424fc8beaba67485 Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Tue, 29 Oct 2024 00:35:33 +0300 Subject: [PATCH 10/29] feat: Implement job creation in JobServiceImpl - Integrated JobRepository and UserRepository into JobServiceImpl to manage job creation. - Implemented createJob method to map JobDTO to Job entity and associate it with the current authenticated user. - Utilized AuthService to retrieve the current user's ID and fetch the corresponding User. - Included error handling to throw an exception if the user is not found. - Saved the newly created job entity in the database. --- .../upwork/service/job/JobServiceImpl.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/main/java/com/activecourses/upwork/service/job/JobServiceImpl.java diff --git a/src/main/java/com/activecourses/upwork/service/job/JobServiceImpl.java b/src/main/java/com/activecourses/upwork/service/job/JobServiceImpl.java new file mode 100644 index 0000000..79ab696 --- /dev/null +++ b/src/main/java/com/activecourses/upwork/service/job/JobServiceImpl.java @@ -0,0 +1,34 @@ +package com.activecourses.upwork.service.job; + +import org.springframework.stereotype.Service; + +import com.activecourses.upwork.dto.JobDTO; +import com.activecourses.upwork.mapper.JobMapper; +import com.activecourses.upwork.model.Job; +import com.activecourses.upwork.model.User; +import com.activecourses.upwork.repository.job.JobRepository; +import com.activecourses.upwork.repository.user.UserRepository; +import com.activecourses.upwork.service.authentication.AuthService; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class JobServiceImpl implements JobService { + + private final JobRepository jobRepository; + private final UserRepository userRepository; + private final AuthService authService; + private JobMapper jobMapper; + + @Override + public Job createJob(JobDTO jobDTO) { + int clientId = authService.getCurrentUserId(); + User client = userRepository.findById(clientId).orElseThrow(() -> new RuntimeException("User not found")); + + Job job = jobMapper.mapFrom(jobDTO); + job.setClient(client); + + return jobRepository.save(job); + } +} From ddb3a87eeb546cf3e345db90128aa6ba8e8d1bf0 Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Tue, 29 Oct 2024 02:23:25 +0300 Subject: [PATCH 11/29] feat: Implement JobController for managing job-related operations - Added createJob endpoint to handle job creation requests. - Integrated JobService for business logic. - Included necessary validations for user authentication and job data. - Updated exception handling for better error responses. --- .../upwork/controller/job/JobController.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/main/java/com/activecourses/upwork/controller/job/JobController.java diff --git a/src/main/java/com/activecourses/upwork/controller/job/JobController.java b/src/main/java/com/activecourses/upwork/controller/job/JobController.java new file mode 100644 index 0000000..ff12bf0 --- /dev/null +++ b/src/main/java/com/activecourses/upwork/controller/job/JobController.java @@ -0,0 +1,51 @@ +package com.activecourses.upwork.controller.job; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import com.activecourses.upwork.dto.JobDTO; +import com.activecourses.upwork.dto.ResponseDto; +import com.activecourses.upwork.mapper.JobMapper; +import com.activecourses.upwork.model.Job; +import com.activecourses.upwork.service.job.JobService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; + +@Tag(name = "Job", description = "Job Management API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/jobs/") +public class JobController { + private final JobService jobService; + private final JobMapper jobMapper; + + @Operation( + summary = "Create a new job", + description = "Creates a new job posting", + security = @SecurityRequirement(name = "bearerAuth") + ) + @PreAuthorize("hasRole('ADMIN') or hasRole('CLIENT') or hasRole('FREELANCER')") + @PostMapping("/post") + public ResponseEntity createJob(@RequestBody JobDTO jobDTO) { + Job createdJob = jobService.createJob(jobDTO); + return buildResponseEntity(HttpStatus.CREATED, true, jobMapper.mapTo(createdJob), null); + } + + private ResponseEntity buildResponseEntity(HttpStatus status, boolean success, Object data, Object error) { + return ResponseEntity + .status(status) + .body(ResponseDto + .builder() + .status(status) + .success(success) + .data(data) + .error(error) + .build() + ); + } +} From 1b50c126677e943bec699494e7e5933bb93d8d0d Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Tue, 29 Oct 2024 02:24:18 +0300 Subject: [PATCH 12/29] fix: Rename 'id' to 'jobId' in JobDTO class - Updated field name for clarity and consistency. - Ensured that the 'jobId' field accurately represents the job identifier. --- src/main/java/com/activecourses/upwork/dto/JobDTO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/activecourses/upwork/dto/JobDTO.java b/src/main/java/com/activecourses/upwork/dto/JobDTO.java index 83d99e3..25431e1 100644 --- a/src/main/java/com/activecourses/upwork/dto/JobDTO.java +++ b/src/main/java/com/activecourses/upwork/dto/JobDTO.java @@ -10,7 +10,7 @@ @Data public class JobDTO { - private Integer id; + private Integer jobId; private String title; private String description; private BigDecimal budget; From 796b94c4c116a07634ea200a14c038169acea5be Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Tue, 29 Oct 2024 02:25:11 +0300 Subject: [PATCH 13/29] fix: Update field names in JobMapper for consistency - Renamed 'id' to 'jobId' to accurately represent the job identifier. - Changed 'jobStatus' to 'status' for clarity and to match the JobDTO class. --- .../java/com/activecourses/upwork/mapper/JobMapper.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/activecourses/upwork/mapper/JobMapper.java b/src/main/java/com/activecourses/upwork/mapper/JobMapper.java index 0099336..5b2f3b3 100644 --- a/src/main/java/com/activecourses/upwork/mapper/JobMapper.java +++ b/src/main/java/com/activecourses/upwork/mapper/JobMapper.java @@ -1,14 +1,17 @@ package com.activecourses.upwork.mapper; +import org.springframework.stereotype.Component; + import com.activecourses.upwork.dto.JobDTO; import com.activecourses.upwork.model.Job; +@Component public class JobMapper implements Mapper { @Override public JobDTO mapTo(Job job) { JobDTO jobDTO = new JobDTO(); - jobDTO.setId(job.getJobId()); + jobDTO.setJobId(job.getJobId()); jobDTO.setTitle(job.getTitle()); jobDTO.setDescription(job.getDescription()); jobDTO.setBudget(job.getBudget()); @@ -21,7 +24,7 @@ public JobDTO mapTo(Job job) { @Override public Job mapFrom(JobDTO jobDTO) { Job job = new Job(); - job.setJobId(jobDTO.getId()); + job.setJobId(jobDTO.getJobId()); job.setTitle(jobDTO.getTitle()); job.setDescription(jobDTO.getDescription()); job.setBudget(jobDTO.getBudget()); From 9ca2e416d3ec4c95d4a2bedffa32e0945a2a4449 Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Tue, 29 Oct 2024 02:26:44 +0300 Subject: [PATCH 14/29] Refactor JobStatus enum: Change enum constants to capitalize only the first letter - Renamed enum constants to follow Java naming conventions: `In_Progress` to `InProgress` - Ensured consistency in naming style across the application --- .../java/com/activecourses/upwork/model/JobStatus.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/activecourses/upwork/model/JobStatus.java b/src/main/java/com/activecourses/upwork/model/JobStatus.java index 222200a..3926c49 100644 --- a/src/main/java/com/activecourses/upwork/model/JobStatus.java +++ b/src/main/java/com/activecourses/upwork/model/JobStatus.java @@ -1,7 +1,8 @@ package com.activecourses.upwork.model; public enum JobStatus { - OPEN, - CLOSED, - IN_PROGRESS + Open, + Closed, + InProgress; + } \ No newline at end of file From e9d30572c49e2031cf59cff33bc3f70987cdfeee Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Tue, 29 Oct 2024 02:27:08 +0300 Subject: [PATCH 15/29] Refactor JobType enum: Change enum constants to capitalize only the first letter - Updated enum constants to follow Java naming conventions --- src/main/java/com/activecourses/upwork/model/JobType.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/activecourses/upwork/model/JobType.java b/src/main/java/com/activecourses/upwork/model/JobType.java index faede4f..3866606 100644 --- a/src/main/java/com/activecourses/upwork/model/JobType.java +++ b/src/main/java/com/activecourses/upwork/model/JobType.java @@ -1,6 +1,7 @@ package com.activecourses.upwork.model; public enum JobType { - HOURLY, - FIXED + Hourly, + Fixed; + } \ No newline at end of file From 7b1f46ac820813a41c77c5a25665cd2c9d519e9e Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Tue, 29 Oct 2024 02:27:38 +0300 Subject: [PATCH 16/29] Enhance JobServiceImpl: Add exception handling for user authentication and retrieval - Throw IllegalStateException if the user is not authenticated - Throw IllegalArgumentException if the user is not found in the database --- .../upwork/service/job/JobServiceImpl.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/activecourses/upwork/service/job/JobServiceImpl.java b/src/main/java/com/activecourses/upwork/service/job/JobServiceImpl.java index 79ab696..ba2ada7 100644 --- a/src/main/java/com/activecourses/upwork/service/job/JobServiceImpl.java +++ b/src/main/java/com/activecourses/upwork/service/job/JobServiceImpl.java @@ -19,16 +19,22 @@ public class JobServiceImpl implements JobService { private final JobRepository jobRepository; private final UserRepository userRepository; private final AuthService authService; - private JobMapper jobMapper; + private final JobMapper jobMapper; @Override public Job createJob(JobDTO jobDTO) { - int clientId = authService.getCurrentUserId(); - User client = userRepository.findById(clientId).orElseThrow(() -> new RuntimeException("User not found")); + Integer clientId = authService.getCurrentUserId(); + if (clientId == null) { + throw new IllegalStateException("User is not authenticated"); + } + + User client = userRepository.findById(clientId) + .orElseThrow(() -> new IllegalArgumentException("User not found with ID: " + clientId)); Job job = jobMapper.mapFrom(jobDTO); job.setClient(client); return jobRepository.save(job); } + } From 9c51b8cdf4345d34437ffd6e46861a51636c0c90 Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Tue, 29 Oct 2024 02:28:28 +0300 Subject: [PATCH 17/29] Add unit tests for JobServiceImpl - Implemented tests for createJob method, covering success and error scenarios. - Verified user authentication and user existence checks. - Used Mockito to mock dependencies and assert expected behavior. --- .../upwork/service/JobServiceImplTest.java | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 src/test/java/com/activecourses/upwork/service/JobServiceImplTest.java diff --git a/src/test/java/com/activecourses/upwork/service/JobServiceImplTest.java b/src/test/java/com/activecourses/upwork/service/JobServiceImplTest.java new file mode 100644 index 0000000..d427941 --- /dev/null +++ b/src/test/java/com/activecourses/upwork/service/JobServiceImplTest.java @@ -0,0 +1,115 @@ +package com.activecourses.upwork.service; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.activecourses.upwork.dto.JobDTO; +import com.activecourses.upwork.mapper.JobMapper; +import com.activecourses.upwork.model.Job; +import com.activecourses.upwork.model.JobStatus; +import com.activecourses.upwork.model.JobType; +import com.activecourses.upwork.model.User; +import com.activecourses.upwork.repository.job.JobRepository; +import com.activecourses.upwork.repository.user.UserRepository; +import com.activecourses.upwork.service.authentication.AuthService; +import com.activecourses.upwork.service.job.JobServiceImpl; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.math.BigDecimal; +import java.util.Optional; + +@ExtendWith(MockitoExtension.class) +class JobServiceImplTest { + + @Mock + private JobRepository jobRepository; + + @Mock + private UserRepository userRepository; + + @Mock + private AuthService authService; + + @Mock + private JobMapper jobMapper; + + @InjectMocks + private JobServiceImpl jobService; + + private JobDTO jobDTO; + private User client; + private Job job; + + @BeforeEach + void setUp() { + // Initialize test data + jobDTO = new JobDTO(); + jobDTO.setTitle("Java Developer"); + jobDTO.setDescription("We are looking for a skilled Java developer to join our team."); + jobDTO.setBudget(BigDecimal.valueOf(1000.00)); + jobDTO.setJobType(JobType.Hourly); + jobDTO.setStatus(JobStatus.Open); + + client = new User(); + client.setId(1); // Set a mock user ID + + job = new Job(); + job.setTitle(jobDTO.getTitle()); + job.setDescription(jobDTO.getDescription()); + job.setBudget(jobDTO.getBudget()); + job.setJobType(JobType.Hourly); // Assuming JobType is an enum + job.setStatus(JobStatus.Open); // Assuming JobStatus is an enum + } + + @Test + void createJob_Success() { + // Arrange + when(authService.getCurrentUserId()).thenReturn(client.getId()); + when(userRepository.findById(client.getId())).thenReturn(Optional.of(client)); + when(jobMapper.mapFrom(jobDTO)).thenReturn(job); + when(jobRepository.save(any(Job.class))).thenReturn(job); + + // Act + Job createdJob = jobService.createJob(jobDTO); + + // Assert + assertNotNull(createdJob); + assertEquals(jobDTO.getTitle(), createdJob.getTitle()); + assertEquals(jobDTO.getDescription(), createdJob.getDescription()); + assertEquals(jobDTO.getBudget(), createdJob.getBudget()); + assertEquals(jobDTO.getJobType(), createdJob.getJobType()); + assertEquals(jobDTO.getStatus(), createdJob.getStatus()); + verify(jobRepository).save(any(Job.class)); // Verify save was called + } + + @Test + void createJob_UserNotAuthenticated() { + // Arrange + when(authService.getCurrentUserId()).thenReturn(null); + + // Act & Assert + IllegalStateException exception = assertThrows(IllegalStateException.class, () -> { + jobService.createJob(jobDTO); + }); + assertEquals("User is not authenticated", exception.getMessage()); + } + + @Test + void createJob_UserNotFound() { + // Arrange + when(authService.getCurrentUserId()).thenReturn(client.getId()); + when(userRepository.findById(client.getId())).thenReturn(Optional.empty()); + + // Act & Assert + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + jobService.createJob(jobDTO); + }); + assertEquals("User not found with ID: " + client.getId(), exception.getMessage()); + } +} From 76cb950d4264821d9dd7112841eba39ec63b6f51 Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Tue, 29 Oct 2024 02:39:42 +0300 Subject: [PATCH 18/29] Fix: Handle missing roles and users in assignRolesToUser - Previously, the code assumed roles in the request body were valid. This commit updates the logic to: - Extract role names from the "roles" key in the request body. - Throw a `RuntimeException` if the user or any requested role is not found. --- .../upwork/service/role/RoleServiceImpl.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/activecourses/upwork/service/role/RoleServiceImpl.java b/src/main/java/com/activecourses/upwork/service/role/RoleServiceImpl.java index 5951727..cf6ca6e 100644 --- a/src/main/java/com/activecourses/upwork/service/role/RoleServiceImpl.java +++ b/src/main/java/com/activecourses/upwork/service/role/RoleServiceImpl.java @@ -58,10 +58,17 @@ public boolean assignRolesToUser(int userId, Map roles) { logger.info("Assigning roles to user with id: {}", userId); Optional user = userRepository.findById(userId); User unwrappedUser = user.orElseThrow(() -> new RuntimeException("User not found: " + userId)); - List roleList = roles.keySet().stream() + + // Extract role names from the request body + List roleNames = (List) roles.get("roles"); + + // Fetch roles from the database + List roleList = roleNames.stream() .map(roleName -> roleRepository.findByName(roleName) .orElseThrow(() -> new RuntimeException("Role not found: " + roleName))) .collect(Collectors.toList()); + + // Assign roles to the user unwrappedUser.setRoles(roleList); userRepository.save(unwrappedUser); return true; From 09dcf63d09e4420274bb108f8877ea5fb8f85170 Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Tue, 29 Oct 2024 02:46:55 +0300 Subject: [PATCH 19/29] Refactor unit test for role assignment to match updated method signature - Modified the `assignRolesToUser` test to work with the new implementation that accepts a list of role names. - Updated the test to populate the roles map with a list of roles, ensuring compatibility with the revised method. - Verified that roles are correctly assigned to the user and that the user repository is called as expected. --- .../activecourses/upwork/service/RoleServiceImplTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/activecourses/upwork/service/RoleServiceImplTest.java b/src/test/java/com/activecourses/upwork/service/RoleServiceImplTest.java index 560e0da..a8c1af1 100644 --- a/src/test/java/com/activecourses/upwork/service/RoleServiceImplTest.java +++ b/src/test/java/com/activecourses/upwork/service/RoleServiceImplTest.java @@ -15,6 +15,7 @@ import java.util.Map; import java.util.Optional; import java.util.HashMap; +import java.util.Arrays; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; @@ -108,8 +109,7 @@ void getAllRoles() { void assignRolesToUser() { int userId = 1; Map roles = new HashMap<>(); - roles.put("ROLE_ADMIN", null); - roles.put("ROLE_USER", null); + roles.put("roles", Arrays.asList("ROLE_ADMIN", "ROLE_USER")); // Updated to use a list of role names User user = new User(); user.setId(userId); @@ -131,4 +131,5 @@ void assignRolesToUser() { assertTrue(user.getRoles().contains(adminRole)); assertTrue(user.getRoles().contains(userRole)); } + } From 4d6a1453c935af560902c846176dbdd284eee0e5 Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Sat, 2 Nov 2024 05:51:40 +0200 Subject: [PATCH 20/29] Remove 'status' field from JobDTO as it's now set to 'Open' by default. --- src/main/java/com/activecourses/upwork/dto/JobDTO.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/com/activecourses/upwork/dto/JobDTO.java b/src/main/java/com/activecourses/upwork/dto/JobDTO.java index 25431e1..f58b488 100644 --- a/src/main/java/com/activecourses/upwork/dto/JobDTO.java +++ b/src/main/java/com/activecourses/upwork/dto/JobDTO.java @@ -10,11 +10,8 @@ @Data public class JobDTO { - private Integer jobId; private String title; private String description; private BigDecimal budget; private JobType jobType; - private JobStatus status; - private LocalDateTime createdAt; } \ No newline at end of file From 016c6672efc602fedfdfe2ab09baf54443eb6bcd Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Sat, 2 Nov 2024 05:52:10 +0200 Subject: [PATCH 21/29] Set default 'status' to 'Open' in JobMapper. --- .../java/com/activecourses/upwork/mapper/JobMapper.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/activecourses/upwork/mapper/JobMapper.java b/src/main/java/com/activecourses/upwork/mapper/JobMapper.java index 5b2f3b3..62f9d48 100644 --- a/src/main/java/com/activecourses/upwork/mapper/JobMapper.java +++ b/src/main/java/com/activecourses/upwork/mapper/JobMapper.java @@ -4,6 +4,7 @@ import com.activecourses.upwork.dto.JobDTO; import com.activecourses.upwork.model.Job; +import com.activecourses.upwork.model.JobStatus; @Component public class JobMapper implements Mapper { @@ -11,26 +12,21 @@ public class JobMapper implements Mapper { @Override public JobDTO mapTo(Job job) { JobDTO jobDTO = new JobDTO(); - jobDTO.setJobId(job.getJobId()); jobDTO.setTitle(job.getTitle()); jobDTO.setDescription(job.getDescription()); jobDTO.setBudget(job.getBudget()); jobDTO.setJobType(job.getJobType()); - jobDTO.setStatus(job.getStatus()); - jobDTO.setCreatedAt(job.getCreatedAt()); return jobDTO; } @Override public Job mapFrom(JobDTO jobDTO) { Job job = new Job(); - job.setJobId(jobDTO.getJobId()); job.setTitle(jobDTO.getTitle()); job.setDescription(jobDTO.getDescription()); job.setBudget(jobDTO.getBudget()); job.setJobType(jobDTO.getJobType()); - job.setStatus(jobDTO.getStatus()); - job.setCreatedAt(jobDTO.getCreatedAt()); + job.setStatus(JobStatus.Open); // Default status return job; } } From cb9427e363b011d067d27807af02897422513243 Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Sat, 2 Nov 2024 05:53:26 +0200 Subject: [PATCH 22/29] fix: Renamed migration file V5_Create_user_profile_to_all_inserted_users.sql to V5__Create_user_profile_to_all_inserted_users.sql to fix a typo in the filename. --- ...sers.sql => V5__Create_user_profile_to_all_inserted_users.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/resources/db/migration/{V5_Create_user_profile_to_all_inserted_users.sql => V5__Create_user_profile_to_all_inserted_users.sql} (100%) diff --git a/src/main/resources/db/migration/V5_Create_user_profile_to_all_inserted_users.sql b/src/main/resources/db/migration/V5__Create_user_profile_to_all_inserted_users.sql similarity index 100% rename from src/main/resources/db/migration/V5_Create_user_profile_to_all_inserted_users.sql rename to src/main/resources/db/migration/V5__Create_user_profile_to_all_inserted_users.sql From 18418a40b968cea4b76d65c2b375952a5d9ff351 Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Sat, 2 Nov 2024 06:12:36 +0200 Subject: [PATCH 23/29] Updated the JobServiceImplTest based on the new changes in JobDTO that reflectd the JobService. --- .../com/activecourses/upwork/service/JobServiceImplTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/java/com/activecourses/upwork/service/JobServiceImplTest.java b/src/test/java/com/activecourses/upwork/service/JobServiceImplTest.java index d427941..fb6ce9a 100644 --- a/src/test/java/com/activecourses/upwork/service/JobServiceImplTest.java +++ b/src/test/java/com/activecourses/upwork/service/JobServiceImplTest.java @@ -54,7 +54,6 @@ void setUp() { jobDTO.setDescription("We are looking for a skilled Java developer to join our team."); jobDTO.setBudget(BigDecimal.valueOf(1000.00)); jobDTO.setJobType(JobType.Hourly); - jobDTO.setStatus(JobStatus.Open); client = new User(); client.setId(1); // Set a mock user ID @@ -84,7 +83,6 @@ void createJob_Success() { assertEquals(jobDTO.getDescription(), createdJob.getDescription()); assertEquals(jobDTO.getBudget(), createdJob.getBudget()); assertEquals(jobDTO.getJobType(), createdJob.getJobType()); - assertEquals(jobDTO.getStatus(), createdJob.getStatus()); verify(jobRepository).save(any(Job.class)); // Verify save was called } From 0dff100bfa29acfd5d9679c66e72d18e2163644e Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Sat, 2 Nov 2024 06:12:59 +0200 Subject: [PATCH 24/29] Fix Checkstyle violations in JobController - Refactor method signature and chained method calls in buildResponseEntity to adhere to 120-character line limit. - Improve code readability and maintain consistency with Checkstyle formatting rules. --- .../upwork/controller/job/JobController.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/activecourses/upwork/controller/job/JobController.java b/src/main/java/com/activecourses/upwork/controller/job/JobController.java index ff12bf0..8a6d4c7 100644 --- a/src/main/java/com/activecourses/upwork/controller/job/JobController.java +++ b/src/main/java/com/activecourses/upwork/controller/job/JobController.java @@ -25,9 +25,9 @@ public class JobController { private final JobMapper jobMapper; @Operation( - summary = "Create a new job", - description = "Creates a new job posting", - security = @SecurityRequirement(name = "bearerAuth") + summary = "Create a new job", + description = "Creates a new job posting", + security = @SecurityRequirement(name = "bearerAuth") ) @PreAuthorize("hasRole('ADMIN') or hasRole('CLIENT') or hasRole('FREELANCER')") @PostMapping("/post") @@ -36,7 +36,8 @@ public ResponseEntity createJob(@RequestBody JobDTO jobDTO) { return buildResponseEntity(HttpStatus.CREATED, true, jobMapper.mapTo(createdJob), null); } - private ResponseEntity buildResponseEntity(HttpStatus status, boolean success, Object data, Object error) { + private ResponseEntity buildResponseEntity( + HttpStatus status, boolean success, Object data, Object error) { return ResponseEntity .status(status) .body(ResponseDto From 509e0afb18dff21eabf21ced667ffd3a47a41f93 Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Sat, 23 Nov 2024 17:11:29 +0200 Subject: [PATCH 25/29] feat: add Skill entity and configure relationships with Job and UserProfile - Implemented the Skill entity to map the `skills` table in the database. - Established a @ManyToMany relationship between Skill and Job via the `job_skills` table. - Configured a @ManyToMany relationship between Skill and UserProfile via the `user_skills` table. - Added fields for `id`, `name`, and relevant relationships in the Skill entity. This change enables the proper management of skills associated with jobs and user profiles. --- .../com/activecourses/upwork/model/Job.java | 10 ++++++ .../com/activecourses/upwork/model/Skill.java | 33 +++++++++++++++++++ .../upwork/model/UserProfile.java | 8 +++++ 3 files changed, 51 insertions(+) create mode 100644 src/main/java/com/activecourses/upwork/model/Skill.java diff --git a/src/main/java/com/activecourses/upwork/model/Job.java b/src/main/java/com/activecourses/upwork/model/Job.java index 2fa80b5..fec4f6b 100644 --- a/src/main/java/com/activecourses/upwork/model/Job.java +++ b/src/main/java/com/activecourses/upwork/model/Job.java @@ -2,6 +2,8 @@ import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.Set; + import jakarta.persistence.*; import lombok.AllArgsConstructor; @@ -47,4 +49,12 @@ public class Job { @Builder.Default @Column(nullable = false, updatable = false) private LocalDateTime createdAt = LocalDateTime.now(); + + @ManyToMany + @JoinTable( + name = "job_skills", + joinColumns = @JoinColumn(name = "job_id"), + inverseJoinColumns = @JoinColumn(name = "skill_id") + ) + private Set skills; } \ No newline at end of file diff --git a/src/main/java/com/activecourses/upwork/model/Skill.java b/src/main/java/com/activecourses/upwork/model/Skill.java new file mode 100644 index 0000000..db36edc --- /dev/null +++ b/src/main/java/com/activecourses/upwork/model/Skill.java @@ -0,0 +1,33 @@ +package com.activecourses.upwork.model; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.Set; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "skills") +public class Skill { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "skill_id") + private Long id; + + @Column(name = "skill_name", nullable = false, unique = true, length = 50) + private String name; + + @ManyToMany(mappedBy = "skills") + private Set jobs; + + @ManyToMany(mappedBy = "skills") + private Set userProfiles; +} diff --git a/src/main/java/com/activecourses/upwork/model/UserProfile.java b/src/main/java/com/activecourses/upwork/model/UserProfile.java index 7b8151c..7bb7dcd 100644 --- a/src/main/java/com/activecourses/upwork/model/UserProfile.java +++ b/src/main/java/com/activecourses/upwork/model/UserProfile.java @@ -8,6 +8,7 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener; import java.math.BigDecimal; +import java.util.Set; @Data @NoArgsConstructor @@ -35,4 +36,11 @@ public class UserProfile { @JoinColumn(name = "user_id", unique = true) private User user; + @ManyToMany + @JoinTable( + name = "user_skills", + joinColumns = @JoinColumn(name = "user_id"), + inverseJoinColumns = @JoinColumn(name = "skill_id") + ) + private Set skills; } From dceed54bb3be7f950082fc46c7fd568011182cd4 Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Sat, 23 Nov 2024 17:27:59 +0200 Subject: [PATCH 26/29] Commit Message: feat: implement job posting functionality with skill association --- .../java/com/activecourses/upwork/dto/JobDTO.java | 4 ++-- .../java/com/activecourses/upwork/model/Skill.java | 2 +- .../upwork/repository/skill/SkillRepository.java | 11 +++++++++++ .../upwork/service/job/JobServiceImpl.java | 14 ++++++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/activecourses/upwork/repository/skill/SkillRepository.java diff --git a/src/main/java/com/activecourses/upwork/dto/JobDTO.java b/src/main/java/com/activecourses/upwork/dto/JobDTO.java index f58b488..a40faca 100644 --- a/src/main/java/com/activecourses/upwork/dto/JobDTO.java +++ b/src/main/java/com/activecourses/upwork/dto/JobDTO.java @@ -1,9 +1,8 @@ package com.activecourses.upwork.dto; import java.math.BigDecimal; -import java.time.LocalDateTime; +import java.util.Set; -import com.activecourses.upwork.model.JobStatus; import com.activecourses.upwork.model.JobType; import lombok.Data; @@ -14,4 +13,5 @@ public class JobDTO { private String description; private BigDecimal budget; private JobType jobType; + private Set skillIds; } \ No newline at end of file diff --git a/src/main/java/com/activecourses/upwork/model/Skill.java b/src/main/java/com/activecourses/upwork/model/Skill.java index db36edc..07b9699 100644 --- a/src/main/java/com/activecourses/upwork/model/Skill.java +++ b/src/main/java/com/activecourses/upwork/model/Skill.java @@ -20,7 +20,7 @@ public class Skill { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "skill_id") - private Long id; + private Integer id; @Column(name = "skill_name", nullable = false, unique = true, length = 50) private String name; diff --git a/src/main/java/com/activecourses/upwork/repository/skill/SkillRepository.java b/src/main/java/com/activecourses/upwork/repository/skill/SkillRepository.java new file mode 100644 index 0000000..8075f7b --- /dev/null +++ b/src/main/java/com/activecourses/upwork/repository/skill/SkillRepository.java @@ -0,0 +1,11 @@ +package com.activecourses.upwork.repository.skill; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import com.activecourses.upwork.model.Skill; + +@Repository +public interface SkillRepository extends JpaRepository { + +} diff --git a/src/main/java/com/activecourses/upwork/service/job/JobServiceImpl.java b/src/main/java/com/activecourses/upwork/service/job/JobServiceImpl.java index ba2ada7..ab4a341 100644 --- a/src/main/java/com/activecourses/upwork/service/job/JobServiceImpl.java +++ b/src/main/java/com/activecourses/upwork/service/job/JobServiceImpl.java @@ -1,12 +1,17 @@ package com.activecourses.upwork.service.job; +import java.util.HashSet; +import java.util.Set; + import org.springframework.stereotype.Service; import com.activecourses.upwork.dto.JobDTO; import com.activecourses.upwork.mapper.JobMapper; import com.activecourses.upwork.model.Job; +import com.activecourses.upwork.model.Skill; import com.activecourses.upwork.model.User; import com.activecourses.upwork.repository.job.JobRepository; +import com.activecourses.upwork.repository.skill.SkillRepository; import com.activecourses.upwork.repository.user.UserRepository; import com.activecourses.upwork.service.authentication.AuthService; @@ -20,6 +25,7 @@ public class JobServiceImpl implements JobService { private final UserRepository userRepository; private final AuthService authService; private final JobMapper jobMapper; + private final SkillRepository skillRepository; @Override public Job createJob(JobDTO jobDTO) { @@ -32,6 +38,14 @@ public Job createJob(JobDTO jobDTO) { .orElseThrow(() -> new IllegalArgumentException("User not found with ID: " + clientId)); Job job = jobMapper.mapFrom(jobDTO); + + Set skills = new HashSet<>(); + for (Integer skillId : jobDTO.getSkillIds()) { + skills.add(skillRepository.findById(skillId) + .orElseThrow(() -> new IllegalArgumentException("Skill not found: " + skillId))); + } + job.setSkills(skills); + job.setClient(client); return jobRepository.save(job); From baf771afd9a9cd430526cf18d9d6f1040aef1131 Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Sat, 23 Nov 2024 17:29:31 +0200 Subject: [PATCH 27/29] chore: add Flyway migration script to insert test skills - Created a Flyway migration script `V6__Add_test_skills.sql`. - Inserted four sample skills: 'Java', 'Spring Boot', 'Angular', and 'PostgreSQL'. - These skills will be used for testing job posting functionality. --- src/main/resources/db/migration/V6__Add_test_skills.sql | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/resources/db/migration/V6__Add_test_skills.sql diff --git a/src/main/resources/db/migration/V6__Add_test_skills.sql b/src/main/resources/db/migration/V6__Add_test_skills.sql new file mode 100644 index 0000000..a42096a --- /dev/null +++ b/src/main/resources/db/migration/V6__Add_test_skills.sql @@ -0,0 +1,8 @@ +-- V6__Add_test_skills.sql + +-- Insert sample skills for testing purposes +INSERT INTO skills (skill_name) VALUES +('Java'), +('Spring Boot'), +('Angular'), +('PostgreSQL'); From a481a35e788122983c39ea804a0e8be9fe9eb470 Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Sun, 24 Nov 2024 00:47:16 +0200 Subject: [PATCH 28/29] test: update JobServiceImplTest to support skill association - Mock SkillRepository to simulate fetching skills by ID. - Add test cases for handling skill association in createJob: - Verify successful creation with valid skills. - Ensure appropriate exceptions are thrown for missing skills. - Update existing tests to account for new skill-related logic. - Validate that the Job object includes the correct number of associated skills. --- .../upwork/service/JobServiceImplTest.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/test/java/com/activecourses/upwork/service/JobServiceImplTest.java b/src/test/java/com/activecourses/upwork/service/JobServiceImplTest.java index fb6ce9a..6fc8c38 100644 --- a/src/test/java/com/activecourses/upwork/service/JobServiceImplTest.java +++ b/src/test/java/com/activecourses/upwork/service/JobServiceImplTest.java @@ -1,6 +1,8 @@ package com.activecourses.upwork.service; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.*; import com.activecourses.upwork.dto.JobDTO; @@ -8,8 +10,10 @@ import com.activecourses.upwork.model.Job; import com.activecourses.upwork.model.JobStatus; import com.activecourses.upwork.model.JobType; +import com.activecourses.upwork.model.Skill; import com.activecourses.upwork.model.User; import com.activecourses.upwork.repository.job.JobRepository; +import com.activecourses.upwork.repository.skill.SkillRepository; import com.activecourses.upwork.repository.user.UserRepository; import com.activecourses.upwork.service.authentication.AuthService; import com.activecourses.upwork.service.job.JobServiceImpl; @@ -22,7 +26,9 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.math.BigDecimal; +import java.util.HashSet; import java.util.Optional; +import java.util.Set; @ExtendWith(MockitoExtension.class) class JobServiceImplTest { @@ -39,12 +45,16 @@ class JobServiceImplTest { @Mock private JobMapper jobMapper; + @Mock + private SkillRepository skillRepository; + @InjectMocks private JobServiceImpl jobService; private JobDTO jobDTO; private User client; private Job job; + private Set skillIds; @BeforeEach void setUp() { @@ -55,6 +65,10 @@ void setUp() { jobDTO.setBudget(BigDecimal.valueOf(1000.00)); jobDTO.setJobType(JobType.Hourly); + // Simulating skills + skillIds = Set.of(1, 2); // Example skill IDs + jobDTO.setSkillIds(skillIds); + client = new User(); client.setId(1); // Set a mock user ID @@ -72,6 +86,19 @@ void createJob_Success() { when(authService.getCurrentUserId()).thenReturn(client.getId()); when(userRepository.findById(client.getId())).thenReturn(Optional.of(client)); when(jobMapper.mapFrom(jobDTO)).thenReturn(job); + + // Mock skill fetching + Skill skill1 = new Skill(); + skill1.setId(1); + skill1.setName("Java"); + + Skill skill2 = new Skill(); + skill2.setId(2); + skill2.setName("Spring Boot"); + when(skillRepository.findById(1)).thenReturn(Optional.of(skill1)); + when(skillRepository.findById(2)).thenReturn(Optional.of(skill2)); + + // Saving the job when(jobRepository.save(any(Job.class))).thenReturn(job); // Act @@ -83,6 +110,7 @@ void createJob_Success() { assertEquals(jobDTO.getDescription(), createdJob.getDescription()); assertEquals(jobDTO.getBudget(), createdJob.getBudget()); assertEquals(jobDTO.getJobType(), createdJob.getJobType()); + assertEquals(2, createdJob.getSkills().size()); verify(jobRepository).save(any(Job.class)); // Verify save was called } @@ -110,4 +138,20 @@ void createJob_UserNotFound() { }); assertEquals("User not found with ID: " + client.getId(), exception.getMessage()); } + + @Test + void createJob_SkillNotFound() { + // Arrange + when(authService.getCurrentUserId()).thenReturn(client.getId()); + when(userRepository.findById(client.getId())).thenReturn(Optional.of(client)); + when(skillRepository.findById(anyInt())).thenReturn(Optional.empty()); + + // Act & Assert + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + jobService.createJob(jobDTO); + }); + + assertEquals("Skill not found: " + jobDTO.getSkillIds().iterator().next(), exception.getMessage()); + verify(skillRepository).findById(anyInt()); + } } From 0b6ea960dccccd3d8afe776fd4de6cc84aaf6dcc Mon Sep 17 00:00:00 2001 From: AhmedFatthy1040 Date: Sun, 24 Nov 2024 00:48:26 +0200 Subject: [PATCH 29/29] fix: correct indentation for error handling in JobServiceImpl - Adjusted the indentation level for the `.orElseThrow()` line to improve code readability. - Ensures consistent formatting and alignment with the project's coding standards. --- .../com/activecourses/upwork/service/job/JobServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/activecourses/upwork/service/job/JobServiceImpl.java b/src/main/java/com/activecourses/upwork/service/job/JobServiceImpl.java index ab4a341..e068083 100644 --- a/src/main/java/com/activecourses/upwork/service/job/JobServiceImpl.java +++ b/src/main/java/com/activecourses/upwork/service/job/JobServiceImpl.java @@ -42,7 +42,7 @@ public Job createJob(JobDTO jobDTO) { Set skills = new HashSet<>(); for (Integer skillId : jobDTO.getSkillIds()) { skills.add(skillRepository.findById(skillId) - .orElseThrow(() -> new IllegalArgumentException("Skill not found: " + skillId))); + .orElseThrow(() -> new IllegalArgumentException("Skill not found: " + skillId))); } job.setSkills(skills);