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

Feat/user recent openings new #431

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
57c926a
transferred the frontend changes from the old recentOpening branch
jazzgrewal Oct 29, 2024
eb13223
brought the backend changes from the recentOpening branch
jazzgrewal Oct 29, 2024
793e7fc
chore: fixing entity by adding the schemas (#430)
paulushcgcj Oct 29, 2024
f22f05c
Merge branch 'main' into feat/userRecentOpeningsNew
paulushcgcj Oct 29, 2024
2a7e76d
properly configured wiremock
jazzgrewal Oct 29, 2024
ad23c21
changed from sequence to id temporarily
jazzgrewal Oct 29, 2024
47f85d6
fixed frontend tests
jazzgrewal Oct 30, 2024
94eb726
fix: removing code not required for the soft launch (#429)
paulushcgcj Oct 29, 2024
3265fc0
fix(deps): update dependency @types/node to v22 (#424)
renovate[bot] Oct 30, 2024
1458d8d
Merge branch 'main' into feat/userRecentOpeningsNew
jazzgrewal Oct 30, 2024
ac13474
fixing the recent openings endpoints and naming convention
jazzgrewal Oct 30, 2024
74f8ffc
reusingthe Openingsearch in a way so it can be used for rect openings…
jazzgrewal Oct 30, 2024
d7462cf
removing unwanted files for the recntOpenings search
jazzgrewal Oct 30, 2024
5c4358c
simplified the userRecentOpeningDto
jazzgrewal Oct 30, 2024
c55831d
simplified the UserOpeningEntity class
jazzgrewal Oct 30, 2024
d6a8575
removed version file for flyway migrations
jazzgrewal Oct 30, 2024
22f56b7
removing unwanted console logs
jazzgrewal Oct 30, 2024
3a7b286
Merge branch 'main' into feat/userRecentOpeningsNew
jazzgrewal Oct 30, 2024
bcfe38d
Merge branch 'main' into feat/userRecentOpeningsNew
jazzgrewal Oct 30, 2024
6d5c5cf
added the builder nad with from lombok
jazzgrewal Oct 30, 2024
0004f72
added test file for UserRecentOpeningService
jazzgrewal Oct 31, 2024
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 @@ -9,6 +9,7 @@ public class SilvaOracleConstants {
public static final String ORG_UNIT = "orgUnit";
public static final String CATEGORY = "category";
public static final String STATUS_LIST = "statusList";
public static final String OPENING_IDS = "openingIds";
public static final String MY_OPENINGS = "myOpenings";
public static final String SUBMITTED_TO_FRPA = "submittedToFrpa";
public static final String DISTURBANCE_DATE_START = "disturbanceDateStart";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public class OpeningSearchFiltersDto {

@Setter
private String requestUserId;
private List<String> openingIds;

/** Creates an instance of the search opening filter dto. */
public OpeningSearchFiltersDto(
Expand All @@ -58,6 +59,7 @@ public OpeningSearchFiltersDto(
this.orgUnit = Objects.isNull(orgUnit) ? null : orgUnit.toUpperCase().trim();
this.category = Objects.isNull(category) ? null : category.toUpperCase().trim();
this.statusList = new ArrayList<>();
this.openingIds = new ArrayList<>();
if (!Objects.isNull(statusList)) {
this.statusList.addAll(statusList.stream().map(s -> String.format("'%s'", s)).toList());
}
Expand All @@ -82,6 +84,28 @@ public OpeningSearchFiltersDto(
Objects.isNull(mainSearchTerm) ? null : mainSearchTerm.toUpperCase().trim();
}

// Create a constructor with only the List<String> openingIds
public OpeningSearchFiltersDto(
jazzgrewal marked this conversation as resolved.
Show resolved Hide resolved
List<String> openingIds) {
this.orgUnit = null;
this.category = null;
this.statusList = new ArrayList<>();
this.openingIds = openingIds;
this.myOpenings = null;
this.submittedToFrpa = false;
this.disturbanceDateStart = null;
this.disturbanceDateEnd = null;
this.regenDelayDateStart = null;
this.regenDelayDateEnd = null;
this.freeGrowingDateStart = null;
this.freeGrowingDateEnd = null;
this.updateDateStart = null;
this.updateDateEnd = null;
this.cuttingPermitId = null;
this.cutBlockId = null;
this.timberMark = null;
this.mainSearchTerm = null;
}
/**
* Define if a property has value.
*
Expand All @@ -93,6 +117,7 @@ public boolean hasValue(String prop) {
case SilvaOracleConstants.ORG_UNIT -> !Objects.isNull(this.orgUnit);
case SilvaOracleConstants.CATEGORY -> !Objects.isNull(this.category);
case SilvaOracleConstants.STATUS_LIST -> !this.statusList.isEmpty();
case SilvaOracleConstants.OPENING_IDS -> !this.openingIds.isEmpty();
case SilvaOracleConstants.MY_OPENINGS -> !Objects.isNull(this.myOpenings);
case SilvaOracleConstants.SUBMITTED_TO_FRPA -> !Objects.isNull(this.submittedToFrpa);
case SilvaOracleConstants.DISTURBANCE_DATE_START ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ public class OpeningSearchResponseDto {
private Boolean submittedToFrpa;
private String forestFileId;
private Long silvaReliefAppId;
private LocalDateTime lastViewDate;
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,7 @@ public PaginatedResult<OpeningSearchResponseDto> searchOpeningQuery(
int startIndex = PaginationUtil.getStartIndex(pagination.page(), pagination.perPage());
int endIndex = PaginationUtil.getEndIndex(startIndex, pagination.perPage(), result.size());

List<OpeningSearchResponseDto> resultList =
buildResultListDto(result.subList(startIndex, endIndex));
List<OpeningSearchResponseDto> resultList = buildResultListDto(result.subList(startIndex, endIndex));

paginatedResult.setData(resultList);
paginatedResult.setPerPage(resultList.size());
Expand Down Expand Up @@ -124,8 +123,7 @@ private List<OpeningSearchResponseDto> buildResultListDto(List<?> result) {
}

if (row.length > index) {
BigDecimal openingGrossAreaHa =
getValue(BigDecimal.class, row[index++], "openingGrossAreaHa");
BigDecimal openingGrossAreaHa = getValue(BigDecimal.class, row[index++], "openingGrossAreaHa");
searchOpeningDto.setOpeningGrossAreaHa(openingGrossAreaHa);
}

Expand Down Expand Up @@ -193,8 +191,7 @@ private List<OpeningSearchResponseDto> buildResultListDto(List<?> result) {
}

if (row.length > index) {
BigDecimal silvaReliefAppId =
getValue(BigDecimal.class, row[index++], "submittedToFrpa108");
BigDecimal silvaReliefAppId = getValue(BigDecimal.class, row[index++], "submittedToFrpa108");
boolean submittedApp = silvaReliefAppId.compareTo(BigDecimal.ZERO) > 0;
searchOpeningDto.setSubmittedToFrpa(submittedApp);
if (submittedApp) {
Expand Down Expand Up @@ -246,7 +243,7 @@ private Query setQueryParameters(OpeningSearchFiltersDto filtersDto, String nati
boolean itsNumeric = filtersDto.getMainSearchTerm().replaceAll("[0-9]", "").isEmpty();
if (itsNumeric) {
log.info("Setting mainSearchTerm as numeric filter value");
// Opening id or File id
// Opening id or File id
query.setParameter("openingOrFile", filtersDto.getMainSearchTerm());
} else {
log.info("Setting mainSearchTerm as non-numeric filter value");
Expand All @@ -269,7 +266,14 @@ private Query setQueryParameters(OpeningSearchFiltersDto filtersDto, String nati
if (filtersDto.hasValue(SilvaOracleConstants.STATUS_LIST)) {

log.info("Setting statusList filter values");
// No need to set value since the query already dit it. Didn't work set through named param
// No need to set value since the query already dit it. Didn't work set through
// named param
}
// similarly for openingIds
if (filtersDto.hasValue(SilvaOracleConstants.OPENING_IDS)) {
log.info("Setting openingIds filter values");
// No need to set value since the query already dit it. Didn't work set through
// named param
}
// 4. User entry id
if (filtersDto.hasValue(SilvaOracleConstants.MY_OPENINGS)) {
Expand Down Expand Up @@ -390,8 +394,17 @@ private String createNativeSqlQuery(OpeningSearchFiltersDto filtersDto) {
builder.append("WHERE 1=1 ");

/* Filters */

// List of openings from the openingIds of the filterDto object for the recent openings
if (filtersDto.hasValue(SilvaOracleConstants.OPENING_IDS)) {
String openingIds = String.join(",", filtersDto.getOpeningIds());
log.info("Filter for openingIds detected! openingIds={}", openingIds);
builder.append(String.format("AND o.OPENING_ID IN (%s) ", openingIds));
}

// 0. Main number filter [opening_id, opening_number, timber_mark, file_id]
// if it's a number, filter by openingId or fileId, otherwise filter by timber mark and opening
// if it's a number, filter by openingId or fileId, otherwise filter by timber
// mark and opening
// number
if (filtersDto.hasValue(SilvaOracleConstants.MAIN_SEARCH_TERM)) {
log.info("Filter mainSearchTerm detected! mainSearchTerm={}", filtersDto.getMainSearchTerm());
Expand Down Expand Up @@ -428,6 +441,7 @@ private String createNativeSqlQuery(OpeningSearchFiltersDto filtersDto) {
log.info("Filter statusList detected! statusList={}", statuses);
builder.append(String.format("AND o.OPENING_STATUS_CODE IN (%s) ", statuses));
}

// 4. My openings
if (filtersDto.hasValue(SilvaOracleConstants.MY_OPENINGS)) {
log.info("Filter myOpenings detected! entryUserId={}", filtersDto.getRequestUserId());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package ca.bc.gov.restapi.results.postgres.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.With;

import java.time.LocalDateTime;

@Data
@AllArgsConstructor
@With
@Builder
public class UserRecentOpeningDto {
jazzgrewal marked this conversation as resolved.
Show resolved Hide resolved
private final String userId;
private final String openingId;
private final LocalDateTime lastViewed;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package ca.bc.gov.restapi.results.postgres.endpoint;

import ca.bc.gov.restapi.results.common.pagination.PaginatedResult;
import ca.bc.gov.restapi.results.oracle.dto.OpeningSearchResponseDto;
import ca.bc.gov.restapi.results.postgres.dto.UserRecentOpeningDto;
import ca.bc.gov.restapi.results.postgres.service.UserRecentOpeningService;
import lombok.RequiredArgsConstructor;

import java.util.List;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class UserRecentOpeningEndpoint {

private final UserRecentOpeningService userRecentOpeningService;

/**
* Records the opening viewed by the user based on the provided opening ID.
*
* @param openingId The ID of the opening viewed by the user.
* @return A simple confirmation message or the HTTP code 204-No Content.
*/
@PostMapping("api/users/recent/{openingId}")
public ResponseEntity<UserRecentOpeningDto> recordUserViewedOpening(@PathVariable String openingId) {
// Store the opening and return the DTO
UserRecentOpeningDto recentOpeningDto = userRecentOpeningService.storeViewedOpening(openingId);
return ResponseEntity.ok(recentOpeningDto);
}

/**
* Retrieves a list of recent openings viewed by the user, limited by the number of results.
*
* @param limit The maximum number of results to return.
* @return A list of opening IDs viewed by the user.
*/
@GetMapping("api/user/recent-openings")
jazzgrewal marked this conversation as resolved.
Show resolved Hide resolved
public ResponseEntity<PaginatedResult<OpeningSearchResponseDto>> getUserRecentOpenings(@RequestParam(defaultValue = "10") int limit) {
// Fetch recent openings for the logged-in user with the specified limit
PaginatedResult<OpeningSearchResponseDto> recentOpenings = userRecentOpeningService.getAllRecentOpeningsForUser(limit);
return ResponseEntity.ok(recentOpenings);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package ca.bc.gov.restapi.results.postgres.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.With;

import java.time.LocalDateTime;

@Data
@NoArgsConstructor
@AllArgsConstructor
@With
@Builder
@Entity
@Table(schema = "silva", name = "user_recent_openings")
public class UserRecentOpeningEntity {
jazzgrewal marked this conversation as resolved.
Show resolved Hide resolved

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "user_id", nullable = false)
private String userId;

@Column(name = "opening_id", nullable = false)
private String openingId;

@Column(name = "last_viewed", nullable = false)
private LocalDateTime lastViewed;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package ca.bc.gov.restapi.results.postgres.repository;

import ca.bc.gov.restapi.results.postgres.entity.UserRecentOpeningEntity;
import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRecentOpeningRepository extends JpaRepository<UserRecentOpeningEntity, Long> {
UserRecentOpeningEntity findByUserIdAndOpeningId(String userId, String openingId);
// Add a method to fetch recent openings for a user with a limit and sorting by last_viewed in descending order
Page<UserRecentOpeningEntity> findByUserIdOrderByLastViewedDesc(String userId, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package ca.bc.gov.restapi.results.postgres.service;

import ca.bc.gov.restapi.results.common.pagination.PaginatedResult;
import ca.bc.gov.restapi.results.common.pagination.PaginationParameters;
import ca.bc.gov.restapi.results.common.security.LoggedUserService;
import ca.bc.gov.restapi.results.oracle.dto.OpeningSearchFiltersDto;
import ca.bc.gov.restapi.results.oracle.dto.OpeningSearchResponseDto;
import ca.bc.gov.restapi.results.oracle.service.OpeningService;
import ca.bc.gov.restapi.results.postgres.dto.UserRecentOpeningDto;
import ca.bc.gov.restapi.results.postgres.entity.UserRecentOpeningEntity;
import ca.bc.gov.restapi.results.postgres.repository.UserRecentOpeningRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class UserRecentOpeningService {

private final LoggedUserService loggedUserService;
private final UserRecentOpeningRepository userRecentOpeningRepository;
private final OpeningService openingService;

/**
* Stores the opening viewed by the user and returns the DTO.
*
* @param openingId The ID of the opening viewed by the user.
* @return A DTO with userId, openingId, and lastViewed timestamp.
*/
public UserRecentOpeningDto storeViewedOpening(String openingId) {
String userId = loggedUserService.getLoggedUserId();
LocalDateTime lastViewed = LocalDateTime.now();

// Verify that the openingId String contains numbers only and no spaces
if (!openingId.matches("^[0-9]*$")) {
throw new IllegalArgumentException("Opening ID must contain numbers only!");
}

// Check if the user has already viewed this opening
UserRecentOpeningEntity existingEntity = userRecentOpeningRepository.findByUserIdAndOpeningId(userId, openingId);

if (existingEntity != null) {
// Update the last viewed timestamp for the existing record
existingEntity.setLastViewed(lastViewed);
userRecentOpeningRepository.save(existingEntity); // Save the updated entity
} else {
// Create a new entity if this openingId is being viewed for the first time
UserRecentOpeningEntity newEntity = new UserRecentOpeningEntity(null, userId, openingId, lastViewed);
userRecentOpeningRepository.save(newEntity); // Save the new entity
}

// Return the DTO
return new UserRecentOpeningDto(userId, openingId, lastViewed);
}

/**
* Retrieves the recent openings viewed by the logged-in user, limited by the provided limit.
*
* @param limit The maximum number of recent openings to retrieve.
* @return A list of opening IDs the user has viewed, sorted by last viewed in descending order.
*/
public PaginatedResult<OpeningSearchResponseDto> getAllRecentOpeningsForUser(int limit) {
String userId = loggedUserService.getLoggedUserId();
Pageable pageable = PageRequest.of(0, limit); // PageRequest object to apply limit

// Fetch recent openings for the user
Page<UserRecentOpeningEntity> recentOpenings = userRecentOpeningRepository
.findByUserIdOrderByLastViewedDesc(userId, pageable);

// Extract opening IDs as String
Map<String,LocalDateTime> openingIds = recentOpenings.getContent().stream()
//.map(opening -> String.valueOf(opening.getOpeningId())) // Convert Integer to String
//.collect(Collectors.toList());
.collect(Collectors.toMap(UserRecentOpeningEntity::getOpeningId, UserRecentOpeningEntity::getLastViewed));
log.info("User with the userId {} has the following openindIds {}", userId, openingIds);
if (openingIds.isEmpty()) {
return new PaginatedResult<>();
}
// Call the oracle service method to fetch opening details for the given opening IDs
//convert the openingIds to a list of strings and pass it to the OpeningSearchFiltersDto constructor
OpeningSearchFiltersDto filtersDto = new OpeningSearchFiltersDto(new ArrayList<>(openingIds.keySet()));
PaginationParameters paginationParameters = new PaginationParameters(0, 10);
PaginatedResult<OpeningSearchResponseDto> pageResult = openingService.openingSearch(filtersDto, paginationParameters);
// perform the sorting and set the lastViewDate to the OpeningSearchResponseDto
pageResult.setData(
pageResult
.getData()
.stream()
.peek(result -> result.setLastViewDate(openingIds.get(result.getOpeningId().toString())))
.sorted(Comparator.comparing(OpeningSearchResponseDto::getLastViewDate).reversed())
.collect(Collectors.toList())
);
return pageResult;
}

}
Loading
Loading