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

#99 | Save Latest Location of User Flow #151

Merged
merged 22 commits into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
544a6c4
`AysLocationUtil` Class Has Been Created for Logical Methods of Location
agitrubard Aug 19, 2023
dfd4d8b
`UserLocationEntity.setPoint()` Has Been Updated with `AysLocationUti…
agitrubard Aug 19, 2023
7cc0d41
`UserLocationSaveRequest` Has Been Created and Validation Annotations…
agitrubard Aug 19, 2023
1bba732
Mapper Class Has Been Created for Converting `UserLocationSaveRequest…
agitrubard Aug 19, 2023
ccf82ee
`findByUserId()` Method Has Been Added to `UserLocationRepository` Class
agitrubard Aug 19, 2023
608a85c
Save Latest Location of User Flow Has Been Created in Service Layer
agitrubard Aug 19, 2023
91a49cb
Save Latest Location of User Endpoint Has Been Created in Controller …
agitrubard Aug 19, 2023
ea23fda
`UserLocationSaveRequestBuilder` Class Has Been Created for Generatin…
agitrubard Aug 19, 2023
2db3d81
Save Latest Location of User Flow Has Been Covered with Controller In…
agitrubard Aug 19, 2023
0fd843a
Save Latest Location of User Flow Has Been Covered with Unit Tests
agitrubard Aug 19, 2023
67a22a6
Save Latest Location of User Flow Has Been Covered with System Tests
agitrubard Aug 19, 2023
3a56159
`point()` Method Has Been Override on Default Lombok Builder Pattern …
agitrubard Aug 20, 2023
b71ff17
Merge remote-tracking branch 'origin/main' into feature/99/user-locat…
agitrubard Aug 22, 2023
c8236cb
Terminal Commands Have Been Updated for Running Database on Docker
agitrubard Aug 22, 2023
975e742
Duplicated `AysLocationUtil` Has Been Removed
agitrubard Aug 22, 2023
404e922
`AysUserLocationCannotBeUpdatedException` Class Has Been Created
agitrubard Aug 22, 2023
30ebf75
`isAssignmentInProgress` Check Has Been Added to User Location Save Flow
agitrubard Aug 22, 2023
f129b8d
Mock Assignment DMLs Have Been Fixed and New Mock Assignment DML Has …
agitrubard Aug 22, 2023
bb76d1b
`givenValidUserLocationSaveRequest_whenSavedOrUpdatedLocation_thenRet…
agitrubard Aug 22, 2023
3e62370
`isAssignmentInProgress` Check Has Been Added to `givenValidUserLocat…
agitrubard Aug 22, 2023
3e269e9
`isAssignmentInProgress` Check Has Been Added to `givenValidUserLocat…
agitrubard Aug 22, 2023
74d1fff
`givenValidUserLocationSaveRequest_whenAssignmentNotFoundOrNotInProgr…
agitrubard Aug 22, 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
@@ -0,0 +1,38 @@
package com.ays.location.controller;

import com.ays.common.model.dto.response.AysResponse;
import com.ays.location.model.dto.request.UserLocationSaveRequest;
import com.ays.location.service.UserLocationService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* Controller class responsible for handling user location-related API endpoints.
* Provides endpoints for saving user location information.
*/
@RestController
@RequestMapping("/api/v1")
@RequiredArgsConstructor
class UserLocationController {

private final UserLocationService userLocationService;

/**
* Saves the user's location based on the provided UserLocationSaveRequest.
*
* @param saveRequest The request containing the user's location information to be saved.
* @return A response indicating the success of the operation.
*/
@PostMapping("/user/location")
@PreAuthorize("hasAnyAuthority('USER')")
public AysResponse<Void> saveUserLocation(@RequestBody @Valid final UserLocationSaveRequest saveRequest) {
userLocationService.saveUserLocation(saveRequest);
return AysResponse.SUCCESS;
}

}
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
package com.ays.location.model.dto.request;

import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;

/**
* A DTO class representing the request data for updating user location.
* A DTO class representing the request data for saving/updating user location.
* <p>
* This class provides getters and setters for the latitude, and longitude fields.
* It also includes a builder pattern implementation for constructing instances of this class with optional parameters.
* <p>
* The purpose of this class is to encapsulate the request data related to updating user location, allowing for easy
* transfer of the data between different layers of the application.
*/
@Data
@Getter
@Setter
@Builder
public class UserLocationRequest {
public class UserLocationSaveRequest {

@NotNull
private Double latitude;

@NotNull
private Double longitude;

}
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package com.ays.location.model.entity;

import com.ays.common.model.entity.BaseEntity;
import com.ays.location.util.AysLocationUtil;
import com.ays.user.model.entity.UserEntity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;

/**
Expand All @@ -35,14 +34,13 @@ public class UserLocationEntity extends BaseEntity {
@Column(name = "POINT", columnDefinition = "ST_GeomFromText(Point, 4326)")
private Point point;


@OneToOne
@JoinColumn(name = "USER_ID", referencedColumnName = "ID", insertable = false, updatable = false)
private UserEntity user;

public void setPoint(double latitude, double longitude) {
Coordinate coordinate = new Coordinate(latitude, longitude);
GeometryFactory geometryFactory = new GeometryFactory();
this.point = geometryFactory.createPoint(coordinate);
public void setPoint(final Double latitude, final Double longitude) {
this.point = AysLocationUtil.generatePoint(latitude, longitude);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.ays.location.model.mapper;

import com.ays.common.model.mapper.BaseMapper;
import com.ays.location.model.dto.request.UserLocationSaveRequest;
import com.ays.location.model.entity.UserLocationEntity;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

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

/**
* Maps an {@link UserLocationSaveRequest} object to an {@link UserLocationEntity} object for saving in the database.
*
* @param saveRequest the {@link UserLocationSaveRequest} object to be mapped.
* @param userId the {@link String} object.
* @return the mapped {@link UserLocationEntity} object.
*/
default UserLocationEntity mapForSaving(UserLocationSaveRequest saveRequest, String userId) {
final UserLocationEntity userLocationEntity = UserLocationEntity.builder()
.userId(userId)
.build();
userLocationEntity.setPoint(saveRequest.getLatitude(), saveRequest.getLongitude());
return userLocationEntity;
}
agitrubard marked this conversation as resolved.
Show resolved Hide resolved

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,19 @@
import com.ays.location.model.entity.UserLocationEntity;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

/**
* Repository interface for performing CRUD operations on UserLocationEntity objects.
*/
public interface UserLocationRepository extends JpaRepository<UserLocationEntity, Long> {

/**
* Retrieves the user's location entity based on the provided user ID.
*
* @param userId The unique identifier of the user.
* @return An Optional containing the user's location entity if found, or an empty Optional if not found.
*/
Optional<UserLocationEntity> findByUserId(String userId);
agitrubard marked this conversation as resolved.
Show resolved Hide resolved

}
18 changes: 18 additions & 0 deletions src/main/java/com/ays/location/service/UserLocationService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.ays.location.service;

import com.ays.location.model.dto.request.UserLocationSaveRequest;

/**
* The UserLocationService interface provides methods to manage and store user location data.
* Implementing classes should define the behavior to save user location information.
*/
public interface UserLocationService {

/**
* Saves the user's location based on the provided UserLocationSaveRequest.
*
* @param saveRequest The request containing the user's location information to be saved.
*/
void saveUserLocation(UserLocationSaveRequest saveRequest);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.ays.location.service.impl;

import com.ays.auth.model.AysIdentity;
import com.ays.location.model.dto.request.UserLocationSaveRequest;
import com.ays.location.model.entity.UserLocationEntity;
import com.ays.location.model.mapper.UserLocationSaveRequestToUserLocationEntityMapper;
import com.ays.location.repository.UserLocationRepository;
import com.ays.location.service.UserLocationService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

/**
* Implementation of the UserLocationService interface that manages and stores user location data.
* This service utilizes a repository to interact with the database for saving user location information.
*/
@Service
@RequiredArgsConstructor
class UserLocationServiceImpl implements UserLocationService {

private final UserLocationRepository userLocationRepository;

private final AysIdentity identity;


private final UserLocationSaveRequestToUserLocationEntityMapper userLocationSaveRequestToUserLocationEntityMapper = UserLocationSaveRequestToUserLocationEntityMapper.initialize();

/**
* Saves the user's location based on the provided UserLocationSaveRequest.
* If the user's location already exists in the database, updates the location; otherwise, creates a new entry.
*
* @param saveRequest The request containing the user's location information to be saved.
*/
@Override
public void saveUserLocation(final UserLocationSaveRequest saveRequest) {
userLocationRepository.findByUserId(identity.getUserId())
.ifPresentOrElse(
userLocationEntityFromDatabase -> {
userLocationEntityFromDatabase.setPoint(saveRequest.getLatitude(), saveRequest.getLongitude());
userLocationRepository.save(userLocationEntityFromDatabase);
},
() -> {
final UserLocationEntity userLocationEntity = userLocationSaveRequestToUserLocationEntityMapper
.mapForSaving(saveRequest, identity.getUserId());
userLocationRepository.save(userLocationEntity);
}
);
}

}
28 changes: 28 additions & 0 deletions src/main/java/com/ays/location/util/AysLocationUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.ays.location.util;

import lombok.experimental.UtilityClass;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.geom.impl.CoordinateArraySequence;

/**
* Utility class for handling location-related operations.
* Provides methods to generate geometric points based on latitude and longitude coordinates.
*/
@UtilityClass
public class AysLocationUtil {

/**
* Generates a Point object representing a location based on the provided latitude and longitude coordinates.
*
* @param latitude The latitude coordinate of the location.
* @param longitude The longitude coordinate of the location.
* @return A Point object representing the location.
*/
public static Point generatePoint(final Double latitude, final Double longitude) {
final Coordinate[] coordinates = new Coordinate[]{new Coordinate(latitude, longitude)};
final CoordinateSequence coordinateSequence = new CoordinateArraySequence(coordinates);
final PrecisionModel precisionModel = new PrecisionModel(PrecisionModel.FLOATING);
return new GeometryFactory(precisionModel, 4326).createPoint(coordinateSequence);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.ays.location.controller;

import com.ays.AbstractRestControllerTest;
import com.ays.common.model.dto.response.AysResponse;
import com.ays.common.model.dto.response.AysResponseBuilder;
import com.ays.common.util.exception.model.AysError;
import com.ays.location.model.dto.request.UserLocationSaveRequest;
import com.ays.location.model.dto.request.UserLocationSaveRequestBuilder;
import com.ays.location.service.UserLocationService;
import com.ays.util.AysMockMvcRequestBuilders;
import com.ays.util.AysMockResultMatchersBuilders;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;

class UserLocationControllerTest extends AbstractRestControllerTest {

@MockBean
private UserLocationService userLocationService;


private static final String BASE_PATH = "/api/v1/user/location";

@Test
void givenValidUserLocationSaveRequest_whenSavedOrUpdatedLocation_thenReturnSuccess() throws Exception {
// Given
UserLocationSaveRequest mockUserLocationSaveRequest = new UserLocationSaveRequestBuilder()
.withValidFields()
.build();

// When
Mockito.doNothing().when(userLocationService).saveUserLocation(Mockito.any(UserLocationSaveRequest.class));

// Then
AysResponse<Void> mockAysResponse = AysResponse.SUCCESS;
mockMvc.perform(AysMockMvcRequestBuilders
.post(BASE_PATH, mockUserToken.getAccessToken(), mockUserLocationSaveRequest))
.andDo(MockMvcResultHandlers.print())
.andExpect(AysMockResultMatchersBuilders.status().isOk())
.andExpect(AysMockResultMatchersBuilders.time()
.isNotEmpty())
.andExpect(AysMockResultMatchersBuilders.httpStatus()
.value(mockAysResponse.getHttpStatus().getReasonPhrase()))
.andExpect(AysMockResultMatchersBuilders.isSuccess()
.value(mockAysResponse.getIsSuccess()))
.andExpect(AysMockResultMatchersBuilders.response()
.doesNotExist());

Mockito.verify(userLocationService, Mockito.times(1))
.saveUserLocation(Mockito.any(UserLocationSaveRequest.class));
}

@Test
void givenValidUserSupportStatusUpdateRequest_whenAdminRole_thenReturnAccessDeniedException() throws Exception {
// Given
UserLocationSaveRequest mockUserLocationSaveRequest = new UserLocationSaveRequestBuilder()
.withValidFields()
.build();

// When
Mockito.doNothing().when(userLocationService).saveUserLocation(mockUserLocationSaveRequest);

// Then
MockHttpServletRequestBuilder mockHttpServletRequestBuilder = AysMockMvcRequestBuilders
.post(BASE_PATH, mockAdminUserToken.getAccessToken(), mockUserLocationSaveRequest);

AysResponse<AysError> mockResponse = AysResponseBuilder.FORBIDDEN;
mockMvc.perform(mockHttpServletRequestBuilder)
.andDo(MockMvcResultHandlers.print())
.andExpect(AysMockResultMatchersBuilders.status().isForbidden())
.andExpect(AysMockResultMatchersBuilders.time()
.isNotEmpty())
.andExpect(AysMockResultMatchersBuilders.httpStatus()
.value(mockResponse.getHttpStatus().name()))
.andExpect(AysMockResultMatchersBuilders.isSuccess()
.value(mockResponse.getIsSuccess()))
.andExpect(AysMockResultMatchersBuilders.response()
.doesNotExist());
}

}
Loading