Skip to content

Commit

Permalink
#99 | Save Latest Location of User Flow (#151)
Browse files Browse the repository at this point in the history
  • Loading branch information
agitrubard authored Aug 23, 2023
1 parent 02035a6 commit e35e7bb
Show file tree
Hide file tree
Showing 17 changed files with 548 additions and 27 deletions.
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();
}

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

}
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

0 comments on commit e35e7bb

Please sign in to comment.