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

#95 | Implement the process of searching Assignment by location #159

Merged
merged 20 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b2bd463
Add assignment/search endpoint
ibrahimdenizz Aug 25, 2023
19a15fb
Add findByPointAndInstitutionId to AssignmentRepository
ibrahimdenizz Aug 25, 2023
dd185de
Add assignment search service
ibrahimdenizz Aug 25, 2023
e3188c1
Update search endpoint with service and update assignment response ma…
ibrahimdenizz Aug 26, 2023
3380894
Update search query with sql command that find nearest assignment
ibrahimdenizz Aug 26, 2023
bcafb2f
Add mapper for search response and refactor not exists exception
ibrahimdenizz Aug 26, 2023
4c2d8a1
Fix coordinate parameters issue.
ibrahimdenizz Aug 28, 2023
ae01d15
Add control that checks whether user is ready.
ibrahimdenizz Aug 28, 2023
d803fc7
Add assignment availability control and reserve assignment feature
ibrahimdenizz Aug 29, 2023
3d56b56
Add unit and integration test
ibrahimdenizz Aug 31, 2023
e86c632
Add system tests and update first user's support status
ibrahimdenizz Aug 31, 2023
17e0181
Fix mock service method's parameter issue
ibrahimdenizz Aug 31, 2023
bbeb172
Merge branch 'main' into feature/95/user-assignment-search
ibrahimdenizz Sep 1, 2023
f09fd1c
Fix sql function name issue in findNearestAvailableAssignment query
ibrahimdenizz Sep 3, 2023
f31cb60
Remove redundant new lines and update search service implementation's…
ibrahimdenizz Sep 3, 2023
2cd8a8c
Add fourth user has `supportStatus` `READY` for system tests
ibrahimdenizz Sep 3, 2023
f0ad047
Update parameter names of findNearestAvailableAssignment query
ibrahimdenizz Sep 3, 2023
f969be3
Update AssignmentSearchRequest fields as required
ibrahimdenizz Sep 3, 2023
48f068a
Update fourth user password
ibrahimdenizz Sep 3, 2023
cc00bd2
Add control for methods should not work in AssignmentSearchService.
ibrahimdenizz Sep 3, 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
Expand Up @@ -2,9 +2,13 @@

import com.ays.assignment.model.Assignment;
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.mapper.AssignmentToAssignmentResponseMapper;
import com.ays.assignment.model.mapper.AssignmentToAssignmentSearchResponseMapper;
import com.ays.assignment.service.AssignmentSaveService;
import com.ays.assignment.service.AssignmentSearchService;
import com.ays.assignment.service.AssignmentService;
import com.ays.common.model.dto.response.AysResponse;
import jakarta.validation.Valid;
Expand All @@ -26,10 +30,11 @@
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();

/**
* Saves a new assignment to the system.
Expand All @@ -45,7 +50,6 @@ public AysResponse<Void> saveAssignment(@RequestBody @Valid AssignmentSaveReques
return AysResponse.SUCCESS;
}


/**
* Gets a user by ID.
* Requires ADMIN authority.
Expand All @@ -62,5 +66,18 @@ public AysResponse<AssignmentResponse> getAssignmentById(@PathVariable @UUID Str
return AysResponse.successOf(assignmentResponse);
}


/**
* Retrieves nearest assignment by AssignmentSearchRequest.
* Requires USER authority.
*
* @param searchRequest The request object containing user location to search nearest assignment.
* @return A response object containing nearest assignment data.
*/
@PostMapping("/assignment/search")
@PreAuthorize("hasAnyAuthority('USER')")
public AysResponse<AssignmentSearchResponse> searchUserAssignment(@RequestBody @Valid AssignmentSearchRequest searchRequest) {
final Assignment assignment = assignmentSearchService.searchAssignment(searchRequest);
final AssignmentSearchResponse assignmentResponse = assignmentToAssignmentSearchResponseMapper.map(assignment);
return AysResponse.successOf(assignmentResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ public class AssignmentSaveRequest {
private AysPhoneNumber phoneNumber;

@NotNull
private Double latitude;
private Double longitude;

@NotNull
private Double longitude;
private Double latitude;

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

/**
* A DTO class representing the request data for searching assignment with respect to user's location.
Expand All @@ -13,9 +14,11 @@
* transfer of the data between different layers of the application.
*/
@Getter
@Setter
@Builder
public class AssignmentSearchRequest {

private Double latitude;
private Double longitude;
private Double latitude;
agitrubard marked this conversation as resolved.
Show resolved Hide resolved

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,4 @@ public class AssignmentResponse extends BaseResponse {
private Double longitude;
private Double latitude;

private AssignmentSearchResponse assignmentSearchResponse;

}
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
package com.ays.assignment.model.dto.response;

import com.ays.common.model.AysPhoneNumber;
import com.ays.assignment.model.enums.AssignmentStatus;
import com.ays.common.model.dto.response.BaseResponse;
import com.ays.institution.model.dto.response.InstitutionResponse;
import com.ays.user.model.enums.UserRole;
import com.ays.user.model.enums.UserStatus;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.experimental.SuperBuilder;

/**
* This class represents the response for a user with assignment.
* It includes information such as the user's username, first and last name, email, institution, role and status.
*/
@Data
@Getter
@EqualsAndHashCode(callSuper = true)
@SuperBuilder
public class AssignmentSearchResponse extends BaseResponse {

private String id;
private String username;
private String firstName;
private String lastName;
private String email;
private UserRole role;
private UserStatus status;
private AysPhoneNumber phoneNumber;

private InstitutionResponse institution;
private String description;
private Double longitude;
private Double latitude;
private AssignmentStatus status;

}
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ public class AssignmentEntity extends BaseEntity {
@JoinColumn(name = "USER_ID", referencedColumnName = "ID", insertable = false, updatable = false)
private UserEntity user;

public void setPoint(double latitude, double longitude) {
this.point = AysLocationUtil.generatePoint(latitude, longitude);
public void setPoint(final Double longitude, final Double latitude) {
this.point = AysLocationUtil.generatePoint(longitude, latitude);
}

public boolean isAvailable() {
Expand Down Expand Up @@ -94,9 +94,15 @@ public void updateAssignmentStatus(AssignmentStatus assignmentStatus) {
this.status = assignmentStatus;
}

public void reserve(String userId, String institutionId) {
this.status = AssignmentStatus.RESERVED;
this.userId = userId;
this.institutionId = institutionId;
}

public abstract static class AssignmentEntityBuilder<C extends AssignmentEntity, B extends AssignmentEntityBuilder<C, B>> extends BaseEntity.BaseEntityBuilder<C, B> {
public B point(final Double latitude, final Double longitude) {
this.point = AysLocationUtil.generatePoint(latitude, longitude);
public B point(final Double longitude, final Double latitude) {
this.point = AysLocationUtil.generatePoint(longitude, latitude);
return this.self();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ default AssignmentEntity mapForSaving(AssignmentSaveRequest saveRequest, String
.lastName(saveRequest.getLastName())
.lineNumber(saveRequest.getPhoneNumber().getLineNumber())
.countryCode(saveRequest.getPhoneNumber().getCountryCode())
.point(saveRequest.getLatitude(), saveRequest.getLongitude())
.point(saveRequest.getLongitude(), saveRequest.getLatitude())
.status(AssignmentStatus.AVAILABLE)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.ays.assignment.model.dto.response.AssignmentResponse;
import com.ays.common.model.mapper.BaseMapper;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

/**
Expand All @@ -16,6 +17,11 @@
@Mapper
public interface AssignmentToAssignmentResponseMapper extends BaseMapper<Assignment, AssignmentResponse> {

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

/**
* Initializes the mapper.
*
Expand Down
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.AssignmentSearchResponse;
import com.ays.common.model.mapper.BaseMapper;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

/**
* AssignmentToAssignmentSearchResponseMapper is an interface that defines the mapping between an {@link Assignment} and an {@link AssignmentSearchResponse}.
* 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 AssignmentToAssignmentSearchResponseMapper extends BaseMapper<Assignment, AssignmentSearchResponse> {

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

/**
* Initializes the mapper.
*
* @return the initialized mapper object.
*/
static AssignmentToAssignmentSearchResponseMapper initialize() {
return Mappers.getMapper(AssignmentToAssignmentSearchResponseMapper.class);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import com.ays.assignment.model.entity.AssignmentEntity;
import com.ays.assignment.model.enums.AssignmentStatus;
import org.locationtech.jts.geom.Point;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;

import java.util.Optional;

Expand All @@ -30,4 +32,20 @@ public interface AssignmentRepository extends JpaRepository<AssignmentEntity, St
*/
Optional<AssignmentEntity> findByIdAndInstitutionId(String id, String institutionId);

/**
* Retrieves nearest optional AssignmentEntity based on the provided user location point and institution ID.
*
* @param point The point of the assignment to retrieve.
* @param institutionId The institution ID of the assignment to retrieve.
* @return An Optional AssignmentEntity that nearest specified point and institution ID or an empty optional if not found.
*/
@Query("""
SELECT assignmentEntity
FROM AssignmentEntity assignmentEntity
WHERE assignmentEntity.institutionId = :institutionId AND assignmentEntity.status = 'AVAILABLE'
ORDER BY st_distance(st_geomfromtext(:#{#point.toString()}, 4326), assignmentEntity.point)
LIMIT 1"""
)
Optional<AssignmentEntity> findNearestAvailableAssignment(Point point, String institutionId);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.ays.assignment.service;

import com.ays.assignment.model.Assignment;
import com.ays.assignment.model.dto.request.AssignmentSearchRequest;


/**
* Assignment Search Service to perform assignment related search business operations.
*/
public interface AssignmentSearchService {

/**
* Search nearest assignment by AssignmentSearchRequest
*
* @param assignmentSearchRequest the AssignmentSearchRequest
* @return Assignment
*/
Assignment searchAssignment(AssignmentSearchRequest assignmentSearchRequest);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.ays.assignment.service.impl;

import com.ays.assignment.model.Assignment;
import com.ays.assignment.model.dto.request.AssignmentSearchRequest;
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.AssignmentSearchService;
import com.ays.assignment.util.exception.AysAssignmentNotExistByPointException;
import com.ays.assignment.util.exception.AysAssignmentUserNotReadyException;
import com.ays.auth.model.AysIdentity;
import com.ays.location.util.AysLocationUtil;
import com.ays.user.model.entity.UserEntity;
import com.ays.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.locationtech.jts.geom.Point;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
class AssignmentSearchServiceImpl implements AssignmentSearchService {
private final AssignmentRepository assignmentRepository;
private final UserRepository userRepository;
private final AysIdentity identity;

private final AssignmentEntityToAssignmentMapper assignmentEntityToAssignmentMapper = AssignmentEntityToAssignmentMapper.initialize();

/**
* Retrieves nearest assigment by their point
*
* @param searchRequest the AssignmentSearchRequest
* @return Assignment
*/
@Override
public Assignment searchAssignment(AssignmentSearchRequest searchRequest) {
final String userId = identity.getUserId();
final String institutionId = identity.getInstitutionId();
userRepository
.findByIdAndInstitutionId(userId, institutionId)
.filter(UserEntity::isReady)
.orElseThrow(() -> new AysAssignmentUserNotReadyException(userId, institutionId));

agitrubard marked this conversation as resolved.
Show resolved Hide resolved
final Double longitude = searchRequest.getLongitude();
final Double latitude = searchRequest.getLatitude();
final Point searchRequestPoint = AysLocationUtil.generatePoint(longitude, latitude);
AssignmentEntity assignmentEntity = assignmentRepository
.findNearestAvailableAssignment(searchRequestPoint, institutionId)
.orElseThrow(() -> new AysAssignmentNotExistByPointException(longitude, latitude));

assignmentEntity.reserve(userId, institutionId);
assignmentRepository.save(assignmentEntity);

return assignmentEntityToAssignmentMapper.map(assignmentEntity);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.ays.assignment.util.exception;

import com.ays.common.util.exception.AysNotExistException;

import java.io.Serial;

/**
* Exception to be thrown when a user assignment with given latitude and longitude does not exist.
*/
public class AysAssignmentNotExistByPointException extends AysNotExistException {

/**
* Unique serial version ID.
*/
@Serial
private static final long serialVersionUID = 6807205838979849673L;

/**
* Constructs a new AysAssignmentNotExistByPointAndInstitutionIdException with the specified latitude and longitude.
*
* @param longitude the longitude of location used for search
* @param latitude the latitude of location used for search
*/
public AysAssignmentNotExistByPointException(Double longitude, Double latitude) {
super("ASSIGNMENT NOT EXIST! longitude:" + longitude + ", latitude:" + latitude);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.ays.assignment.util.exception;

import com.ays.common.util.exception.AysAuthException;

import java.io.Serial;

/**
* Exception to be thrown when a user is not ready to take assignment.
*/
public class AysAssignmentUserNotReadyException extends AysAuthException {

/**
* Unique serial version ID.
*/
@Serial
private static final long serialVersionUID = -5396757921363054579L;

/**
* Constructs a new AysAssignmentUserNotReadyException with the specified userId.
*
* @param userId the userId of user that will take assignment.
* @param institutionId the institutionId of user that will take assignment.
*/
public AysAssignmentUserNotReadyException(String userId, String institutionId) {
super("USER NOT READY TO TAKE ASSIGNMENT! userId:" + userId + ", institutionId:" + institutionId);
}

}
Loading