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 all 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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,19 +126,19 @@ First of all, generate personal access token with this url : https://github.com/
Before running the project, you need to run the following command to start the MySQL container:

```
docker compose up -d --build mysql
docker compose up -d --build database
```

If you want to recreate the MySQL container, you can run the following command:

```
docker compose up --force-recreate -d --build mysql
docker compose up --force-recreate -d --build database
```

If you want to stop the MySQL container, you can run the following command:

```
docker compose down -v mysql
docker compose down -v database
```

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

import com.ays.assignment.model.enums.AssignmentStatus;
import com.ays.common.model.entity.BaseEntity;
import com.ays.common.util.AysLocationUtil;
import com.ays.institution.model.entity.InstitutionEntity;
import com.ays.location.util.AysLocationUtil;
import com.ays.user.model.entity.UserEntity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.ays.assignment.repository;

import com.ays.assignment.model.entity.AssignmentEntity;
import com.ays.assignment.model.enums.AssignmentStatus;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

Expand All @@ -9,5 +10,12 @@
*/
public interface AssignmentRepository extends JpaRepository<AssignmentEntity, String>, JpaSpecificationExecutor<AssignmentEntity> {

/**
* Checks whether an assignment exists for a specific user ID.
*
* @param userId The ID of the user to check the assignment for.
* @return {@code true} if an assignment exists for the specified user ID, otherwise {@code false}.
*/
boolean existsByUserIdAndStatus(String userId, AssignmentStatus status);

}
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,20 @@ 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);
}

public abstract static class UserLocationEntityBuilder<C extends UserLocationEntity, B extends UserLocationEntityBuilder<C, B>> extends BaseEntity.BaseEntityBuilder<C, B> {
public B point(final Double latitude, final Double longitude) {
this.point = AysLocationUtil.generatePoint(latitude, longitude);
return this.self();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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) {
return UserLocationEntity.builder()
.userId(userId)
.point(saveRequest.getLatitude(), saveRequest.getLongitude())
.build();
}
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,60 @@
package com.ays.location.service.impl;

import com.ays.assignment.model.enums.AssignmentStatus;
import com.ays.assignment.repository.AssignmentRepository;
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 com.ays.location.util.exception.AysUserLocationCannotBeUpdatedException;
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 AssignmentRepository assignmentRepository;

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

final boolean isAssignmentInProgress = assignmentRepository
.existsByUserIdAndStatus(identity.getUserId(), AssignmentStatus.IN_PROGRESS);
if (!isAssignmentInProgress) {
throw new AysUserLocationCannotBeUpdatedException();
}

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);
}
);
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.ays.common.util;
package com.ays.location.util;

import lombok.experimental.UtilityClass;
import org.locationtech.jts.geom.*;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.ays.location.util.exception;

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

import java.io.Serial;

/**
* This exception is thrown when a user's location cannot be updated. Reasons for not being able to update the user's
* location may include the absence of an assigned task or the assigned task not being in progress status.
* <p>
* This exception is typically derived from the {@link AysProcessException} class and represents a specific process state
* or condition.
*
* @see AysProcessException
*/
public class AysUserLocationCannotBeUpdatedException extends AysProcessException {

/**
* A special field containing version information along with the serial number.
*/
@Serial
private static final long serialVersionUID = 2733712280590701217L;

/**
* Constructs an exception for the specified identifier (id).
*
* @param id The identifier (id) that describes the reason for the unupdatable user location.
*/
public AysUserLocationCannotBeUpdatedException() {
super("USER LOCATION CANNOT BE UPDATED BECAUSE ASSIGNMENT NOT EXIST OR NOT IN PROGRESS STATUS!");
}

}
25 changes: 13 additions & 12 deletions src/main/resources/db/changelog/changes/5-ays-dml.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,23 @@
<sql>
<!-- Kadiköy, Moda (40.981210, 29.026083) -->
INSERT INTO AYS_ASSIGNMENT (ID, INSTITUTION_ID, DESCRIPTION, FIRST_NAME, LAST_NAME, COUNTRY_CODE,
LINE_NUMBER, STATUS,
POINT, CREATED_USER, CREATED_AT)
LINE_NUMBER, POINT, STATUS, CREATED_USER, CREATED_AT)
VALUES ('3b30cac9-0645-48cb-bc98-2e928df521ab', '77ece256-bf0e-4bbe-801d-173083f8bdcf',
'100L İçme Suyu İhtiyacı',
'Ahmet', 'Mehmet', '90', '1234567890', 'AVAILABLE',
ST_GeomFromText('POINT(40.981210 29.026083)', 4326),
'AYS', CURRENT_TIMESTAMP);
'100L İçme Suyu İhtiyacı', 'Ahmet', 'Mehmet', '90', '1234567890',
ST_GeomFromText('POINT(40.981210 29.026083)', 4326), 'AVAILABLE', 'AYS', CURRENT_TIMESTAMP);

INSERT INTO AYS_ASSIGNMENT (ID, INSTITUTION_ID, DESCRIPTION, FIRST_NAME, LAST_NAME, COUNTRY_CODE,
LINE_NUMBER, STATUS,
POINT, CREATED_USER, CREATED_AT)
VALUES ('a9ec051e-3c4f-4cb5-ab08-e9dcee5e1f03', '91df7ae9-d5b9-44ae-b54f-d5d55359c4a4', 'Kıyafet İhtiyacı',
'Cem', 'Orkun', '90', '1234567890', 'AVAILABLE',
ST_GeomFromText('POINT(40.981210 29.000000)', 4326),
'AYS', CURRENT_TIMESTAMP);
LINE_NUMBER, POINT, STATUS, CREATED_USER, CREATED_AT)
VALUES ('a9ec051e-3c4f-4cb5-ab08-e9dcee5e1f03', '91df7ae9-d5b9-44ae-b54f-d5d55359c4a4',
'Kıyafet İhtiyacı', 'Cem', 'Orkun', '90', '1234567890',
ST_GeomFromText('POINT(40.981210 29.000000)', 4326), 'AVAILABLE', 'AYS', CURRENT_TIMESTAMP);

INSERT INTO AYS_ASSIGNMENT (ID, USER_ID, INSTITUTION_ID, DESCRIPTION, FIRST_NAME, LAST_NAME, COUNTRY_CODE,
LINE_NUMBER, POINT, STATUS, CREATED_USER, CREATED_AT)
VALUES ('823835f4-85f0-4052-a09c-b9b6e5284afc', 'edb36891-b898-4c12-bcec-d9aaa5d45190',
'91df7ae9-d5b9-44ae-b54f-d5d55359c4a4', 'Kıyafet İhtiyacı', 'Cem', 'Orkun',
'90', '1234567890', ST_GeomFromText('POINT(40.981210 29.000000)', 4326),
'IN_PROGRESS', 'AYS', CURRENT_TIMESTAMP);
</sql>
<!-- ========================== -->
<!-- DML of AYS_ASSIGNMENT -->
Expand Down
Loading