Skip to content

Commit

Permalink
Merge pull request #5857 from ita-social-projects/saved-events
Browse files Browse the repository at this point in the history
[Green City] - Enabling the ability to add events to favorites and retrieve all favorite events. #5856
  • Loading branch information
ABbondar committed May 30, 2023
2 parents 36fb922 + 22c7bf5 commit 2dd3afa
Show file tree
Hide file tree
Showing 12 changed files with 370 additions and 146 deletions.
2 changes: 2 additions & 0 deletions core/src/main/java/greencity/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ protected void configure(HttpSecurity http) throws Exception {
"/events/comments/{eventId}",
"/events/comments/like",
EVENTS + "/addAttender/{eventId}",
EVENTS + "/addToFavorites/{eventId}",
EVENTS + "/create",
EVENTS + "/rateEvent/{eventId}/{rate}",
CUSTOM_SHOPPING_LIST_ITEMS,
Expand Down Expand Up @@ -295,6 +296,7 @@ protected void configure(HttpSecurity http) throws Exception {
USER_SHOPPING_LIST,
EVENTS + "/delete/{eventId}",
EVENTS + "/removeAttender/{eventId}",
EVENTS + "/removeFromFavorites/{eventId}",
"/user/{userId}/userFriend/{friendId}",
"/habit/assign/delete/{habitAssignId}")
.hasAnyRole(USER, ADMIN, MODERATOR, UBS_EMPLOYEE)
Expand Down
45 changes: 44 additions & 1 deletion core/src/main/java/greencity/controller/EventsController.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.DeleteMapping;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import springfox.documentation.annotations.ApiIgnore;

Expand Down Expand Up @@ -230,6 +237,42 @@ public ResponseEntity<Object> removeAttender(@PathVariable Long eventId, @ApiIgn
return ResponseEntity.status(HttpStatus.OK).build();
}

/**
* Method for adding an event to favorites by event id.
*
* @author Anton Bondar.
*/
@ApiOperation(value = "Add an event to favorites")
@ApiResponses(value = {
@ApiResponse(code = 200, message = HttpStatuses.OK),
@ApiResponse(code = 400, message = HttpStatuses.BAD_REQUEST),
@ApiResponse(code = 401, message = HttpStatuses.UNAUTHORIZED),
@ApiResponse(code = 404, message = HttpStatuses.NOT_FOUND)
})
@PostMapping("/addToFavorites/{eventId}")
public ResponseEntity<Object> addToFavorites(@PathVariable Long eventId, @ApiIgnore Principal principal) {
eventService.addToFavorites(eventId, principal.getName());
return ResponseEntity.status(HttpStatus.OK).build();
}

/**
* Method for removing an event from favorites by event id.
*
* @author Anton Bondar.
*/
@ApiOperation(value = "Remove an event from favorites")
@ApiResponses(value = {
@ApiResponse(code = 200, message = HttpStatuses.OK),
@ApiResponse(code = 400, message = HttpStatuses.BAD_REQUEST),
@ApiResponse(code = 401, message = HttpStatuses.UNAUTHORIZED),
@ApiResponse(code = 404, message = HttpStatuses.NOT_FOUND)
})
@DeleteMapping("/removeFromFavorites/{eventId}")
public ResponseEntity<Object> removeFromFavorites(@PathVariable Long eventId, @ApiIgnore Principal principal) {
eventService.removeFromFavorites(eventId, principal.getName());
return ResponseEntity.status(HttpStatus.OK).build();
}

/**
* Method for rating event by user.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,10 @@ class EventsControllerTest {

@Mock
private UserService userService;

@Mock
private ModelMapper modelMapper;

private final Principal principal = getPrincipal();

@BeforeEach
Expand All @@ -97,6 +99,32 @@ void getEventsCreatedByUser() {

}

@Test
@SneakyThrows
void getAllEventsTest() {
int pageNumber = 0;
int pageSize = 20;
Pageable pageable = PageRequest.of(pageNumber, pageSize);

PageableAdvancedDto<EventDto> pageableAdvancedDto = getPageableAdvancedDtoEventDto();

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
ObjectWriter ow = objectMapper.writer();
String expectedJson = ow.writeValueAsString(pageableAdvancedDto);

when(eventService.getAll(pageable, principal)).thenReturn(pageableAdvancedDto);

mockMvc.perform(get(EVENTS_CONTROLLER_LINK)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.principal(principal))
.andExpect(status().isOk())
.andExpect(content().json(expectedJson));

verify(eventService).getAll(pageable, principal);
}

@Test
@SneakyThrows
void getUserEventsTest() {
Expand Down Expand Up @@ -182,6 +210,26 @@ void addAttenderBadRequestTest() {
.hasCause(new BadRequestException("ErrorMessage"));
}

@Test
@SneakyThrows
void addToFavoritesTest() {
Long eventId = 1L;
mockMvc.perform(post(EVENTS_CONTROLLER_LINK + "/addToFavorites/{eventId}", eventId)
.principal(principal))
.andExpect(status().isOk());
verify(eventService).addToFavorites(eventId, principal.getName());
}

@Test
@SneakyThrows
void removeFromFavoritesTest() {
Long eventId = 1L;
mockMvc.perform(delete(EVENTS_CONTROLLER_LINK + "/removeFromFavorites/{eventId}", eventId)
.principal(principal))
.andExpect(status().isOk());
verify(eventService).removeFromFavorites(eventId, principal.getName());
}

@Test
@SneakyThrows
void saveTest() {
Expand Down Expand Up @@ -613,5 +661,4 @@ private EventDto getEventDto() {
objectMapper.findAndRegisterModules();
return objectMapper.readValue(json, EventDto.class);
}

}
27 changes: 25 additions & 2 deletions dao/src/main/java/greencity/entity/event/Event.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,25 @@

import greencity.entity.Tag;
import greencity.entity.User;
import lombok.*;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;

import javax.persistence.*;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashSet;
Expand Down Expand Up @@ -47,6 +63,13 @@ public class Event {
inverseJoinColumns = @JoinColumn(name = "user_id"))
private Set<User> attenders = new HashSet<>();

@ManyToMany
@JoinTable(
name = "events_followers",
joinColumns = @JoinColumn(name = "event_id"),
inverseJoinColumns = @JoinColumn(name = "user_id"))
private Set<User> followers;

@NonNull
@OneToMany(mappedBy = "event", cascade = CascadeType.ALL)
private List<EventDateLocation> dates = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
<include file="/db/changelog/logs/ch-add-table-employee-positions-mapping-Bondar.xml"/>
<include file="db/changelog/logs/ch-change-table-events-dates-locations-dropNotNullConstraint-Seti.xml"/>
<include file="db/changelog/logs/ch-update-employee_authorities-table-name-value-Bondar.xml"/>
<include file="db/changelog/logs/ch-add-table-events-followers-Bondar.xml"/>
<include file="db/changelog/logs/ch-add-formatted_address-to-event_dates_locations-Lenets.xml"/>
</databaseChangeLog>

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">

<changeSet id="Bondar-3" author="Anton Bondar">
<createTable tableName="events_followers">
<column name="event_id" type="BIGINT" >
<constraints nullable="false"/>
</column>
<column name="user_id" type="BIGINT">
<constraints nullable="false"/>
</column>
</createTable>

<addPrimaryKey tableName="events_followers"
columnNames="event_id, user_id"/>
<addForeignKeyConstraint baseTableName="events_followers"
baseColumnNames="event_id"
constraintName="fk_events_followers_event_id"
referencedTableName="events"
referencedColumnNames="id"/>
<addForeignKeyConstraint baseTableName="events_followers"
baseColumnNames="user_id"
constraintName="fk_events_followers_user_id"
referencedTableName="users"
referencedColumnNames="id"/>
</changeSet>
</databaseChangeLog>
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ public final class ErrorMessage {
public static final String HAVE_ALREADY_RATED = "You have already rated this event";
public static final String EVENT_IS_NOT_FINISHED = "Event is not finished yet";
public static final String EVENT_NOT_FOUND_BY_ID = "Event doesn't exist by this id: ";
public static final String USER_HAS_ALREADY_ADDED_EVENT_TO_FAVORITES =
"User has already added this event to favorites.";
public static final String EVENT_IS_NOT_IN_FAVORITES = "This event is not in favorites.";
public static final String EVENT_COMMENT_NOT_FOUND_BY_ID = "Event comment doesn't exist by this id: ";
public static final String EVENT_IS_FINISHED = "Finished event cannot be modified";

Expand Down
2 changes: 2 additions & 0 deletions service-api/src/main/java/greencity/dto/event/EventDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ public class EventDto {

private Boolean isSubscribed;

private Boolean isFavorite;

/**
* Return String of event tags in English.
*
Expand Down
20 changes: 20 additions & 0 deletions service-api/src/main/java/greencity/service/EventService.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,26 @@ public interface EventService {
*/
void removeAttender(Long eventId, String email);

/**
* Method for adding an event to favorites by event id.
*
* @param eventId - event id.
* @param email - user email.
*
* @author Anton Bondar.
*/
void addToFavorites(Long eventId, String email);

/**
* Method for removing an event from favorites by event id.
*
* @param eventId - event id.
* @param email - user email.
*
* @author Anton Bondar.
*/
void removeFromFavorites(Long eventId, String email);

/**
* Return Events searched by some query.
*
Expand Down
60 changes: 58 additions & 2 deletions service/src/main/java/greencity/service/EventServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@
import greencity.constant.AppConstant;
import greencity.constant.ErrorMessage;
import greencity.dto.PageableAdvancedDto;
import greencity.dto.event.*;
import greencity.dto.event.AddEventDtoRequest;
import greencity.dto.event.AddressDto;
import greencity.dto.event.EventAttenderDto;
import greencity.dto.event.EventDateLocationDto;
import greencity.dto.event.EventDto;
import greencity.dto.event.EventVO;
import greencity.dto.event.UpdateEventDto;
import greencity.dto.geocoding.AddressLatLngResponse;
import greencity.dto.tag.TagVO;
import greencity.entity.Tag;
Expand Down Expand Up @@ -33,7 +39,12 @@
import java.security.Principal;
import java.time.LocalDate;
import java.time.ZonedDateTime;
import java.util.*;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

@Service
Expand Down Expand Up @@ -119,6 +130,7 @@ public PageableAdvancedDto<EventDto> getAll(Pageable page, Principal principal)
if (principal != null) {
User user = modelMapper.map(restClient.findByEmail(principal.getName()), User.class);
setSubscribes(events, eventDtos, user);
setFollowers(events, eventDtos, user);
}
return eventDtos;
}
Expand Down Expand Up @@ -159,6 +171,15 @@ private void setSubscribes(Page<Event> events, PageableAdvancedDto<EventDto> eve
eventDtos.getPage().forEach(eventDto -> eventDto.setIsSubscribed(eventIds.contains(eventDto.getId())));
}

private void setFollowers(Page<Event> events, PageableAdvancedDto<EventDto> eventDtos, User user) {
List<Long> eventIds = events.stream()
.filter(event -> event.getFollowers().stream().map(User::getId).collect(Collectors.toList())
.contains(user.getId()))
.map(Event::getId)
.collect(Collectors.toList());
eventDtos.getPage().forEach(eventDto -> eventDto.setIsFavorite(eventIds.contains(eventDto.getId())));
}

private PageableAdvancedDto<EventDto> buildPageableAdvancedDto(Page<Event> eventsPage) {
List<EventDto> eventDtos = eventsPage.stream()
.map(event -> modelMapper.map(event, EventDto.class))
Expand Down Expand Up @@ -209,6 +230,41 @@ public void removeAttender(Long eventId, String email) {
eventRepo.save(event);
}

@Override
public void addToFavorites(Long eventId, String email) {
Event event = eventRepo.findById(eventId)
.orElseThrow(() -> new NotFoundException(ErrorMessage.EVENT_NOT_FOUND_BY_ID + eventId));

User currentUser = userRepo.findByEmail(email)
.orElseThrow(() -> new NotFoundException(ErrorMessage.USER_NOT_FOUND_BY_EMAIL + email));

if (event.getFollowers().contains(currentUser)) {
throw new BadRequestException(ErrorMessage.USER_HAS_ALREADY_ADDED_EVENT_TO_FAVORITES);
}

event.getFollowers().add(currentUser);
eventRepo.save(event);
}

@Override
public void removeFromFavorites(Long eventId, String email) {
Event event = eventRepo.findById(eventId)
.orElseThrow(() -> new NotFoundException(ErrorMessage.EVENT_NOT_FOUND_BY_ID + eventId));

User currentUser = userRepo.findByEmail(email)
.orElseThrow(() -> new NotFoundException(ErrorMessage.USER_NOT_FOUND_BY_EMAIL + email));

if (!event.getFollowers().contains(currentUser)) {
throw new BadRequestException(ErrorMessage.EVENT_IS_NOT_IN_FAVORITES);
}

event.setFollowers(event.getAttenders()
.stream()
.filter(user -> !user.getId().equals(currentUser.getId()))
.collect(Collectors.toSet()));
eventRepo.save(event);
}

@Override
public PageableAdvancedDto<EventDto> searchEventsBy(Pageable paging, String query) {
Page<Event> page = eventRepo.searchEventsBy(paging, query);
Expand Down
Loading

0 comments on commit 2dd3afa

Please sign in to comment.