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

#93 | Implementation of 'Assignment Listing' API Endpoint #160

Merged
merged 48 commits into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
98646ef
Update SuppressWarnings description
AhmetAksunger Aug 28, 2023
625cc4b
Update AssignmentListRequest
AhmetAksunger Aug 28, 2023
14d0888
Removed Assignment Search Response from the AssignmentResponse
AhmetAksunger Aug 28, 2023
c9b1567
Define AssignmentsResponse
AhmetAksunger Aug 28, 2023
dbfcbb4
Define getAssignments in AssignmentController
AhmetAksunger Aug 28, 2023
6f28d9a
Add JavaDoc for getAssignments method
AhmetAksunger Aug 28, 2023
f9cbacf
Define getAssignments in AssignmentService
AhmetAksunger Aug 28, 2023
00fe7f6
Implement getAssignments in AssignmentServiceImpl
AhmetAksunger Aug 28, 2023
3813c83
Replacing statement lambda with expression lambda in AssignmentServic…
AhmetAksunger Aug 28, 2023
13120cd
JavaDoc for the getAssignments method in AssignmentServiceImpl
AhmetAksunger Aug 28, 2023
db93be9
Create AssignmentToAssignmentsResponseMapper
AhmetAksunger Aug 28, 2023
08b4f64
Update initialize method in AssignmentToAssignmentsResponseMapper
AhmetAksunger Aug 28, 2023
bae463c
Add lombok constructors to AssignmentListRequest & Filter
AhmetAksunger Aug 29, 2023
6e8054b
Update AssignmentToAssignmentsResponseMapper
AhmetAksunger Aug 29, 2023
cbe3c0c
Add Getter & Setter for the inner classes in AssignmentsResponse
AhmetAksunger Aug 29, 2023
7871f43
Update getAssignments in AssignmentController
AhmetAksunger Aug 29, 2023
21b1836
Add phoneNumber filter to AssignmentListRequest
AhmetAksunger Aug 29, 2023
cfa373a
Implement the phoneNumber filter in AssignmentServiceImpl
AhmetAksunger Aug 29, 2023
e26493b
Update JavaDoc for getAssignments
AhmetAksunger Aug 29, 2023
62efd39
Add if status empty check
AhmetAksunger Aug 29, 2023
ac64228
Fixed typo
AhmetAksunger Aug 29, 2023
50adc5b
Create AssignmentListRequestBuilder
AhmetAksunger Aug 29, 2023
4260cae
Update AssignmentListRequestBuilder
AhmetAksunger Aug 29, 2023
88aebbc
Handled givenAssignmentListRequest_whenAssignmentStatusIsAvailable_th…
AhmetAksunger Aug 29, 2023
dfe780d
Handled givenAssignmentListRequest_whenPhoneNumberIsAvailable_thenRet…
AhmetAksunger Aug 29, 2023
f513901
Add isEmpty check for the fields countryCode & lineNumber in Assignme…
AhmetAksunger Aug 30, 2023
9360480
Handle givenValidAssignmentListRequest_whenAssignmentsFound_thenRetur…
AhmetAksunger Aug 30, 2023
62d3a53
Handled givenValidAssignmentListRequest_whenUserUnauthorizedForListin…
AhmetAksunger Aug 31, 2023
ca3a547
Changed GET to POST in the method "getAssignments"
AhmetAksunger Aug 31, 2023
9a4fdc7
Resolved the latitude & longitude mistake
AhmetAksunger Aug 31, 2023
e9c0e29
Add End-To-End tests
AhmetAksunger Sep 1, 2023
a97a0ce
AssignmentSystemTest Update
AhmetAksunger Sep 2, 2023
b5d18d5
Fixed AssignmentToAssignmentResponseMapper
AhmetAksunger Sep 2, 2023
d3052b0
Merge remote-tracking branch 'origin/main' into feature/93/get-assign…
agitrubard Sep 5, 2023
354b5b2
Replace AysPhoneNumber with an inner class "PhoneNumber" & validate
AhmetAksunger Sep 5, 2023
67fdbe1
Merge remote-tracking branch 'origin/feature/93/get-assignments' into…
AhmetAksunger Sep 5, 2023
d48210c
Add AssignmentStatus to AssignmentsResponse
AhmetAksunger Sep 5, 2023
bc0b66f
Fixed AysPhoneNumber bug in AssignmentListRequestBuilder
AhmetAksunger Sep 5, 2023
0bc891e
Optimize status null & empty check
AhmetAksunger Sep 6, 2023
a5f6040
Remove unnecessary status mapping in AssignmentToAssignmentsResponseM…
AhmetAksunger Sep 6, 2023
d28950b
Replace Collections.singletonList with List.of
AhmetAksunger Sep 6, 2023
af7d418
Reformat AssignmentServiceImplTest
AhmetAksunger Sep 6, 2023
d738ba7
Change AssignmentListRequestBuilder and its implementations
AhmetAksunger Sep 6, 2023
ddd19ea
[FORGOT TO COMMIT] Change AssignmentListRequestBuilder
AhmetAksunger Sep 6, 2023
b8704b3
Remove unnecessary isEmpty checks
AhmetAksunger Sep 6, 2023
0a00e10
Verify identity.getInstitutionId
AhmetAksunger Sep 6, 2023
46772a8
Removed EqualsAndHashCode (was causing tests to fail)
AhmetAksunger Sep 6, 2023
64d9b7b
Merge remote-tracking branch 'origin/main' into feature/93/get-assign…
AhmetAksunger Sep 8, 2023
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
@@ -1,15 +1,20 @@
package com.ays.assignment.controller;

import com.ays.assignment.model.Assignment;
import com.ays.assignment.model.dto.request.AssignmentListRequest;
import com.ays.assignment.model.dto.request.AssignmentSaveRequest;
import com.ays.assignment.model.dto.request.AssignmentSearchRequest;
import com.ays.assignment.model.dto.response.AssignmentResponse;
import com.ays.assignment.model.dto.response.AssignmentSearchResponse;
import com.ays.assignment.model.dto.response.AssignmentsResponse;
import com.ays.assignment.model.mapper.AssignmentToAssignmentResponseMapper;
import com.ays.assignment.model.mapper.AssignmentToAssignmentSearchResponseMapper;
import com.ays.assignment.model.mapper.AssignmentToAssignmentsResponseMapper;
import com.ays.assignment.service.AssignmentSaveService;
import com.ays.assignment.service.AssignmentSearchService;
import com.ays.assignment.service.AssignmentService;
import com.ays.common.model.AysPage;
import com.ays.common.model.dto.response.AysPageResponse;
import com.ays.common.model.dto.response.AysResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
Expand All @@ -30,24 +35,34 @@
class AssignmentController {

private final AssignmentSaveService assignmentSaveService;

private final AssignmentSearchService assignmentSearchService;

private final AssignmentService assignmentService;

private static final AssignmentToAssignmentResponseMapper assignmentToAssignmentResponseMapper = AssignmentToAssignmentResponseMapper.initialize();
private static final AssignmentToAssignmentSearchResponseMapper assignmentToAssignmentSearchResponseMapper = AssignmentToAssignmentSearchResponseMapper.initialize();
private static final AssignmentToAssignmentsResponseMapper assignmentToAssignmentsResponseMapper = AssignmentToAssignmentsResponseMapper.initialize();

/**
* Saves a new assignment to the system.
* Requires ADMIN authority.
* Gets an Assignments list based on the specified filters in the {@link AssignmentListRequest}
* Requires ADMIN authority
*
* @param saveRequest The request object containing the assignment data to be saved.
* @return A response object containing the saved assignment data.
* @param listRequest The assignment request that contains the status filter
* @return A response object that contains the retrieved assignments' data
* @see AssignmentListRequest
*/
@PostMapping("/assignment")
@PostMapping("/assignments")
@PreAuthorize("hasAnyAuthority('ADMIN')")
public AysResponse<Void> saveAssignment(@RequestBody @Valid AssignmentSaveRequest saveRequest) {
assignmentSaveService.saveAssignment(saveRequest);
return AysResponse.SUCCESS;
public AysResponse<AysPageResponse<AssignmentsResponse>> getAssignments(@RequestBody @Valid AssignmentListRequest listRequest) {
final AysPage<Assignment> pageOfAssignments = assignmentService.getAssignments(listRequest);
final AysPageResponse<AssignmentsResponse> pageOfAssignmentsResponse = AysPageResponse
.<AssignmentsResponse>builder()
.of(pageOfAssignments)
.content(assignmentToAssignmentsResponseMapper.map(pageOfAssignments.getContent()))
.filteredBy(listRequest.getFilter())
.build();
return AysResponse.successOf(pageOfAssignmentsResponse);
}

/**
Expand All @@ -66,6 +81,20 @@ public AysResponse<AssignmentResponse> getAssignmentById(@PathVariable @UUID Str
return AysResponse.successOf(assignmentResponse);
}

/**
* Saves a new assignment to the system.
* Requires ADMIN authority.
*
* @param saveRequest The request object containing the assignment data to be saved.
* @return A response object containing the saved assignment data.
*/
@PostMapping("/assignment")
@PreAuthorize("hasAnyAuthority('ADMIN')")
public AysResponse<Void> saveAssignment(@RequestBody @Valid AssignmentSaveRequest saveRequest) {
assignmentSaveService.saveAssignment(saveRequest);
return AysResponse.SUCCESS;
}

/**
* Retrieves nearest assignment by AssignmentSearchRequest.
* Requires USER authority.
Expand All @@ -80,4 +109,5 @@ public AysResponse<AssignmentSearchResponse> searchUserAssignment(@RequestBody @
final AssignmentSearchResponse assignmentResponse = assignmentToAssignmentSearchResponseMapper.map(assignment);
return AysResponse.successOf(assignmentResponse);
}

}
Original file line number Diff line number Diff line change
@@ -1,47 +1,63 @@
package com.ays.assignment.model.dto.request;

import com.ays.assignment.model.enums.AssignmentStatus;
import com.ays.common.model.AysFiltering;
import com.ays.common.model.dto.request.AysFilteringRequest;
import com.ays.common.model.dto.request.AysPagingRequest;
import com.ays.common.util.validation.EnumValidation;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.validation.Valid;
import jakarta.validation.constraints.AssertTrue;
import lombok.Builder;
import lombok.Data;
import jakarta.validation.constraints.Digits;
import lombok.*;
import org.springframework.data.jpa.domain.Specification;

import java.util.EnumSet;
import java.util.List;
import java.util.Set;

/**
* Represents a request object for fetching a list of user assignment with pagination,sorting and filtering options
* This class extends the {@link AysPagingRequest} class and adds additional validation rules for sorting.
*/
@Data
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
agitrubard marked this conversation as resolved.
Show resolved Hide resolved
@Builder
public class AssignmentListRequest extends AysPagingRequest implements AysFilteringRequest {

private AssignmentStatus status;
@Valid
private Filter filter;

/**
* Checks if the assignment status is valid.
*
* @return true if the assignment status is valid or null, false otherwise.
* Represents a filtering configuration for assignments based on the class fields.
*/
@AssertTrue(message = "IS ASSIGNMENT STATUS NOT ACCEPTED")
private boolean isStatusAccepted() {
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public static class Filter implements AysFiltering {

if (this.status == null) {
return true;
}

EnumSet<AssignmentStatus> acceptedAssignmentStatuses = EnumSet.of(AssignmentStatus.AVAILABLE,
AssignmentStatus.RESERVED,
AssignmentStatus.ASSIGNED,
AssignmentStatus.IN_PROGRESS,
AssignmentStatus.DONE
);
return EnumValidation.anyOf(this.status, acceptedAssignmentStatuses);
/**
* List of assignment statuses used for filtering.
*/
private List<AssignmentStatus> statuses;

@Valid
private PhoneNumber phoneNumber;
}

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
agitrubard marked this conversation as resolved.
Show resolved Hide resolved
public static class PhoneNumber {

@Digits(integer = 7, fraction = 0, message = "MUST BE 7-DIGIT NUMBER")
private String countryCode;

@Digits(integer = 13, fraction = 0, message = "MUST BE 13-DIGIT NUMBER")
private String lineNumber;
}

/**
Expand All @@ -58,21 +74,49 @@ public boolean isSortPropertyAccepted() {
return this.isPropertyAccepted(acceptedFilterFields);
}


/**
* Converts the request into a JPA Specification that filters assignments based on the specified status,
* if it is provided.
* Converts the request into a JPA Specification that filters assignments based on the specified
* statuses and phoneNumber, if they are provided.
*
* @param clazz the class type of the specification.
* @return the generated JPA Specification based on the request filters.
*/
@Override
public <C> Specification<C> toSpecification(Class<C> clazz) {

if (this.filter == null) {
return Specification.allOf();
}

Specification<C> specification = Specification.where(null);
if (status != null) {
specification = specification.and((root, query, builder) ->
builder.equal(root.get("status"), status));

if (this.filter.phoneNumber != null) {

if (this.filter.phoneNumber.getLineNumber() != null && !this.filter.phoneNumber.getLineNumber().isEmpty()) {
agitrubard marked this conversation as resolved.
Show resolved Hide resolved
Specification<C> lineNumberSpecification = (root, query, criteriaBuilder) ->
criteriaBuilder.equal(root.get("lineNumber"), this.filter.phoneNumber.getLineNumber());

specification = specification.and(lineNumberSpecification);
}

if (this.filter.phoneNumber.getCountryCode() != null && !this.filter.phoneNumber.getCountryCode().isEmpty()) {
agitrubard marked this conversation as resolved.
Show resolved Hide resolved
Specification<C> countryCodeSpecification = (root, query, criteriaBuilder)
-> criteriaBuilder.equal(root.get("countryCode"), this.filter.phoneNumber.getCountryCode());

specification = specification.and(countryCodeSpecification);
}
}
// Add more filter conditions if needed

if (this.filter.statuses != null && !this.filter.statuses.isEmpty()) {
agitrubard marked this conversation as resolved.
Show resolved Hide resolved
Specification<C> statusSpecification = this.filter.statuses.stream().map(status ->
(Specification<C>) (root, query, criteriaBuilder) ->
criteriaBuilder.equal(root.get("status"), status))
.reduce(Specification::or).orElse(null);

specification = specification.and(statusSpecification);
}

return specification;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.ays.assignment.model.dto.response;

import com.ays.assignment.model.enums.AssignmentStatus;
import com.ays.common.model.dto.response.BaseResponse;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;

@Data
@EqualsAndHashCode(callSuper = true)
@SuperBuilder
public class AssignmentsResponse extends BaseResponse {

private String id;
private AssignmentStatus status;
private String description;
private String firstName;
private String lastName;
private Location location;
private User user;

@Getter
@Setter
public static class Location {
agitrubard marked this conversation as resolved.
Show resolved Hide resolved
private Double longitude;
private Double latitude;
}

@Getter
@Setter
public static class User {
agitrubard marked this conversation as resolved.
Show resolved Hide resolved
private String id;
private String firstName;
private String lastName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
public interface AssignmentToAssignmentResponseMapper extends BaseMapper<Assignment, AssignmentResponse> {

@Override
@Mapping(target = "longitude", source = "source.point.x")
@Mapping(target = "latitude", source = "source.point.y")
@Mapping(target = "longitude", source = "point.x")
@Mapping(target = "latitude", source = "point.y")
AssignmentResponse map(Assignment source);

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.ays.assignment.model.mapper;

import com.ays.assignment.model.Assignment;
import com.ays.assignment.model.dto.response.AssignmentsResponse;
import com.ays.common.model.mapper.BaseMapper;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

/**
* AssignmentEntityToAssignmentMapper is an interface that defines the mapping between an {@link Assignment} and an {@link AssignmentsResponse}.
* This interface uses the MapStruct annotation @Mapper to generate an implementation of this interface at compile-time.
* <p>The class provides a static method {@code initialize()} that returns an instance of the generated mapper implementation.
* <p>The interface extends the MapStruct interface {@link BaseMapper}, which defines basic mapping methods.
* The interface adds no additional mapping methods, but simply defines the types to be used in the mapping process.
*/

@Mapper
public interface AssignmentToAssignmentsResponseMapper extends BaseMapper<Assignment, AssignmentsResponse> {

@Override
@Mapping(target = "location.longitude", source = "point.x")
@Mapping(target = "location.latitude", source = "point.y")
@Mapping(target = "status", source = "status")
agitrubard marked this conversation as resolved.
Show resolved Hide resolved
AssignmentsResponse map(Assignment source);

/**
* Initializes the mapper.
*
* @return the initialized mapper object.
*/
static AssignmentToAssignmentsResponseMapper initialize() {
return Mappers.getMapper(AssignmentToAssignmentsResponseMapper.class);
}
}
10 changes: 10 additions & 0 deletions src/main/java/com/ays/assignment/service/AssignmentService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@


import com.ays.assignment.model.Assignment;
import com.ays.assignment.model.dto.request.AssignmentListRequest;
import com.ays.common.model.AysPage;

/**
* Assignment Save Service to perform assignment related business operations.
Expand All @@ -15,4 +17,12 @@ public interface AssignmentService {
* @return Assignment
*/
Assignment getAssignmentById(String id);

/**
* Get Assignments based on the specified filters in the {@link AssignmentListRequest}
*
* @param listRequest The request dto object
* @return Assignments list
*/
AysPage<Assignment> getAssignments(AssignmentListRequest listRequest);
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
package com.ays.assignment.service.impl;

import com.ays.assignment.model.Assignment;
import com.ays.assignment.model.dto.request.AssignmentListRequest;
import com.ays.assignment.model.entity.AssignmentEntity;
import com.ays.assignment.model.mapper.AssignmentEntityToAssignmentMapper;
import com.ays.assignment.repository.AssignmentRepository;
import com.ays.assignment.service.AssignmentService;
import com.ays.assignment.util.exception.AysAssignmentNotExistByIdException;
import com.ays.auth.model.AysIdentity;
import com.ays.common.model.AysPage;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RequiredArgsConstructor
class AssignmentServiceImpl implements AssignmentService {
Expand All @@ -35,4 +41,29 @@ public Assignment getAssignmentById(String id) {

return assignmentEntityToAssignmentMapper.map(assignmentEntity);
}

/**
* Retrieves a paginated list of assignments based on the specified filters and the institution's identity.
*
* @param listRequest The request containing filters and pagination parameters.
* @return An {@link AysPage} object containing the filtered assignments.
*/
@Override
public AysPage<Assignment> getAssignments(AssignmentListRequest listRequest) {

Specification<AssignmentEntity> byStatusAndPhoneNumber = listRequest.toSpecification(AssignmentEntity.class);
Specification<AssignmentEntity> byInstitutionId = (root, query, criteriaBuilder) ->
criteriaBuilder.equal(root.get("institutionId"),identity.getInstitutionId());

Page<AssignmentEntity> assignmentEntities = assignmentRepository
.findAll(byStatusAndPhoneNumber.and(byInstitutionId),listRequest.toPageable());

List<Assignment> assignments = assignmentEntityToAssignmentMapper.map(assignmentEntities.getContent());

return AysPage.of(
listRequest.getFilter(),
assignmentEntities,
assignments
);
}
}
agitrubard marked this conversation as resolved.
Show resolved Hide resolved
Loading