Skip to content

Commit

Permalink
#93 | Implementation of 'Assignment Listing' API Endpoint (#160)
Browse files Browse the repository at this point in the history
Co-authored-by: Agit Rubar Demir <[email protected]>
  • Loading branch information
AhmetAksunger and agitrubard authored Sep 8, 2023
1 parent af444a2 commit 5aecf16
Show file tree
Hide file tree
Showing 15 changed files with 536 additions and 42 deletions.
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,64 @@
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.apache.commons.collections4.CollectionUtils;
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
@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
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 +75,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) {
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) {
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 (!CollectionUtils.isEmpty(this.filter.getStatuses())) {
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 {
private Double longitude;
private Double latitude;
}

@Getter
@Setter
public static class User {
private String id;
private String firstName;
private String lastName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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")
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,6 +1,7 @@
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;
Expand All @@ -11,10 +12,14 @@
import com.ays.location.model.entity.UserLocationEntity;
import com.ays.location.model.mapper.UserLocationEntityToUserLocationMapper;
import com.ays.location.repository.UserLocationRepository;
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.Optional;
import java.util.List;

@Service
@RequiredArgsConstructor
Expand Down Expand Up @@ -57,4 +62,32 @@ public Assignment getAssignmentById(String id) {
return assignment;
}

/**
* 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) {

String institutionId = identity.getInstitutionId();

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

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

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

return AysPage.of(
listRequest.getFilter(),
assignmentEntities,
assignments
);
}


}
2 changes: 1 addition & 1 deletion src/main/java/com/ays/common/model/AysSpecification.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class AysSpecification {
* A builder class for creating specifications in the context of AysSpecification.
* Use this builder to construct predicates based on a provided filter.
*/
@SuppressWarnings("unused")
@SuppressWarnings("This method is unused by the application directly but Spring is using it in the background.")
public static class AysSpecificationBuilder<C> {

/**
Expand Down
Loading

0 comments on commit 5aecf16

Please sign in to comment.