Skip to content

Commit

Permalink
Merge pull request #94 from JimTheCat/CU-86977vg76_EPIC---API-v4---Us…
Browse files Browse the repository at this point in the history
…er-relations_Kinga-Traczyk

feature: User relations API
  • Loading branch information
JimTheCat authored Jan 5, 2025
2 parents fb721c8 + 0481bc0 commit 0af0695
Show file tree
Hide file tree
Showing 28 changed files with 872 additions and 29 deletions.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,24 @@

import lombok.NoArgsConstructor;

@NoArgsConstructor
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
public class AlertConstants {
//title
public static final String USER_WITH_LOGIN_NOT_FOUND_TITLE = "User not found";

public static final String RELATION_FOR_USERS_NOT_FOUND_TITLE = "Relation not found";
public static final String RELATION_ALREADY_EXISTS_TITLE = "Relation already exists";

public static final String RESOURCE_NOT_FOUND_TITLE = "Resource not found";
public static final String NOT_UNIQUE_OBJECT_TITLE = "Not unique object";
public static final String ILLEGAL_ARGUMENT_TITLE = "Illegal argument";

//message
public static final String USER_WITH_LOGIN_NOT_FOUND = "User with login '%s' not found";

public static final String RELATION_FOR_USERS_NOT_FOUND = "Relation %s not found for users %s and %s";
public static final String RELATION_ALREADY_EXISTS = "Relation %s already exists for users %s and %s";

public static final String RESOURCE_NOT_FOUND = "%s not found for %s = '%s'";
public static final String UNKNOWN_ERROR = "Unknown error";
public static final String BAD_CREDENTIALS = "Bad credentials";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package meowhub.backend.shared.exceptions;

public class RelationException extends RuntimeException {

public RelationException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import meowhub.backend.shared.dtos.AlertDto;
import meowhub.backend.shared.exceptions.NotUniqueObjectException;
import meowhub.backend.shared.exceptions.RelationException;
import meowhub.backend.shared.utils.AlertUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand Down Expand Up @@ -29,6 +30,15 @@ public ResponseEntity<AlertDto> handleNotFoundException(NotFoundException ex) {
return new ResponseEntity<>(AlertUtils.resourceNotFoundException(ex.getMessage()), HttpStatus.NOT_FOUND);
}

@ExceptionHandler(RelationException.class)
public ResponseEntity<AlertDto> handleNotFoundException(RelationException ex) {
if(ex.getMessage().contains("exists")) {
return new ResponseEntity<>(AlertUtils.relationAlreadyExists(ex.getMessage()), HttpStatus.CONFLICT);
} else {
return new ResponseEntity<>(AlertUtils.relationNotFoundException(ex.getMessage()), HttpStatus.NOT_FOUND);
}
}

@ExceptionHandler(Exception.class)
public ResponseEntity<AlertDto> handleUnknownException(Exception ex) {
ex.printStackTrace();
Expand Down
25 changes: 22 additions & 3 deletions backend/src/main/java/meowhub/backend/shared/utils/AlertUtils.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package meowhub.backend.shared.utils;

import lombok.NoArgsConstructor;
import meowhub.backend.shared.constants.AlertConstants;
import meowhub.backend.shared.constants.AlertLevel;
import meowhub.backend.shared.dtos.AlertDto;

import java.time.LocalDateTime;

@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
public class AlertUtils {

public static AlertDto userNotFoundException(String msg) {
Expand All @@ -18,6 +20,26 @@ public static AlertDto userNotFoundException(String msg) {
return alertDto;
}

public static AlertDto relationNotFoundException(String msg) {
AlertDto alertDto = new AlertDto();
alertDto.setTitle(AlertConstants.RELATION_FOR_USERS_NOT_FOUND_TITLE);
alertDto.setMessage(msg);
alertDto.setLevel(AlertLevel.ERROR);
alertDto.setTimestamp(LocalDateTime.now());

return alertDto;
}

public static AlertDto relationAlreadyExists(String msg) {
AlertDto alertDto = new AlertDto();
alertDto.setTitle(AlertConstants.RELATION_ALREADY_EXISTS_TITLE);
alertDto.setMessage(msg);
alertDto.setLevel(AlertLevel.ERROR);
alertDto.setTimestamp(LocalDateTime.now());

return alertDto;
}

public static AlertDto unknownException() {
AlertDto alertDto = new AlertDto();
alertDto.setTitle(AlertConstants.UNKNOWN_ERROR);
Expand Down Expand Up @@ -67,7 +89,4 @@ public static AlertDto illegalArgumentException(String msg) {

return alertDto;
}

private AlertUtils() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package meowhub.backend.user_relations.controllers;

import lombok.RequiredArgsConstructor;
import meowhub.backend.user_relations.services.UserRelationQueryService;
import meowhub.backend.user_relations.services.UserRelationService;
import meowhub.backend.users.dtos.BasicUserInfoDto;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("api/relations")
@RequiredArgsConstructor
public class UserRelationController {
private final UserRelationService service;
private final UserRelationQueryService queryService;

@GetMapping("friends")
public ResponseEntity<Page<BasicUserInfoDto>> getFriends(@AuthenticationPrincipal UserDetails userDetails, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) {
Page<BasicUserInfoDto> friends = queryService.getFriends(userDetails.getUsername(), page, size);
return ResponseEntity.ok(friends);
}

@GetMapping("{login}/friends")
public ResponseEntity<Page<BasicUserInfoDto>> getFriendsForUser(@PathVariable String login, @AuthenticationPrincipal UserDetails userDetails, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) {
Page<BasicUserInfoDto> friends = queryService.getFriendsForUser(login, userDetails.getUsername(), page, size);
return ResponseEntity.ok(friends);
}

@GetMapping("pending")
public ResponseEntity<Page<BasicUserInfoDto>> getPendingSentRequests(@AuthenticationPrincipal UserDetails userDetails, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) {
Page<BasicUserInfoDto> pending = queryService.getPendingSentRequests(userDetails.getUsername(), page, size);
return ResponseEntity.ok(pending);
}

@GetMapping("received")
public ResponseEntity<Page<BasicUserInfoDto>> getReceivedRequests(@AuthenticationPrincipal UserDetails userDetails, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) {
Page<BasicUserInfoDto> received = queryService.getReceivedRequests(userDetails.getUsername(), page, size);
return ResponseEntity.ok(received);
}

@GetMapping("rejected")
public ResponseEntity<Page<BasicUserInfoDto>> getRejectedRequests(@AuthenticationPrincipal UserDetails userDetails, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) {
Page<BasicUserInfoDto> rejected = queryService.getRejectedRequests(userDetails.getUsername(), page, size);
return ResponseEntity.ok(rejected);
}

@PostMapping("{login}/send")
public ResponseEntity<Void> sendFriendRequest(@PathVariable String login, @AuthenticationPrincipal UserDetails userDetails) {
service.sendFriendRequestTo(login, userDetails.getUsername());
return ResponseEntity.ok().build();
}

@PostMapping("{login}/reject")
public ResponseEntity<Void> rejectFriendRequest(@PathVariable String login, @AuthenticationPrincipal UserDetails userDetails) {
service.rejectFriendRequestFrom(login, userDetails.getUsername());
return ResponseEntity.ok().build();
}

@PostMapping("{acceptFrom}/accept")
public ResponseEntity<Void> acceptFriendRequest(@PathVariable String acceptFrom, @AuthenticationPrincipal UserDetails userDetails) {
service.acceptFriendRequestFrom(acceptFrom, userDetails.getUsername());
return ResponseEntity.ok().build();
}

@PostMapping("{login}/delete-friend")
public ResponseEntity<Void> deleteFriend(@PathVariable String login, @AuthenticationPrincipal UserDetails userDetails) {
service.deleteFriend(login, userDetails.getUsername());
return ResponseEntity.ok().build();
}


}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package meowhub.backend.jpa_buddy;
package meowhub.backend.user_relations.models;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package meowhub.backend.jpa_buddy;
package meowhub.backend.user_relations.models;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package meowhub.backend.user_relations.repositories;

import meowhub.backend.user_relations.models.RelationType;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface RelationTypeRepository extends JpaRepository<RelationType, String> {
Optional<RelationType> findByCode(String code);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package meowhub.backend.user_relations.repositories;

import meowhub.backend.user_relations.models.UserRelation;
import meowhub.backend.users.dtos.BasicUserInfoDto;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;


public interface UserRelationRepository extends JpaRepository<UserRelation, String> {
@Query(value = """
SELECT new meowhub.backend.users.dtos.BasicUserInfoDto (
receiver.id,
receiver.name,
receiver.surname,
receiver.login,
null
)
FROM UserRelation relation
JOIN User sender ON relation.sender.id = sender.id
JOIN RelationType relationType ON relation.relationType.id = relationType.id
JOIN User receiver ON relation.receiver.id = receiver.id
WHERE sender.login = :login
AND relationType.code = :relationTypeCode
""")
Page<BasicUserInfoDto> findRelationsFor(@Param("login") String login, @Param("relationTypeCode") String relationTypeCode, Pageable pageable);

@Query(value = """
SELECT new meowhub.backend.users.dtos.BasicUserInfoDto (
sender.id,
sender.name,
sender.surname,
sender.login,
null
)
FROM UserRelation relation
JOIN User sender ON relation.sender.id = sender.id
JOIN RelationType relationType ON relation.relationType.id = relationType.id
JOIN User receiver ON relation.receiver.id = receiver.id
WHERE receiver.login = :login
AND relationType.code = :relationTypeCode
""")
Page<BasicUserInfoDto> findRelationsWhereReceiverIs(@Param("login") String receiverLogin, @Param("relationTypeCode") String relationTypeCode, Pageable pageable);

@Query(value = """
SELECT new meowhub.backend.users.dtos.BasicUserInfoDto (
sender.id,
sender.name,
sender.surname,
sender.login,
null
)
FROM UserRelation relation
JOIN User sender ON relation.sender.id = sender.id
JOIN User receiver ON relation.receiver.id = receiver.id
JOIN RelationType relationType ON relation.relationType.id = relationType.id
WHERE receiver.login = :login
AND relationType.code = 'FRIENDS'
UNION
SELECT new meowhub.backend.users.dtos.BasicUserInfoDto (
receiver.id,
receiver.name,
receiver.surname,
receiver.login,
null
)
FROM UserRelation relation
JOIN User sender ON relation.sender.id = sender.id
JOIN User receiver ON relation.receiver.id = receiver.id
JOIN RelationType relationType ON relation.relationType.id = relationType.id
WHERE sender.login = :login
AND relationType.code = 'FRIENDS'
""")
Page<BasicUserInfoDto> findFriendsFor (@Param("login") String login, Pageable pageable);


@Modifying
@Query("""
UPDATE UserRelation relation
SET relation.relationType = (SELECT r FROM RelationType r WHERE r.code = :relationTypeCode),
relation.answerDate = CURRENT_TIMESTAMP
WHERE relation.sender.login = :updateRelationWithLogin AND relation.receiver.login = :currentlyLoggedUserLogin
AND relation.relationType.code = 'SENT_INVITATION'
""")
void updateRelationType(@Param("relationTypeCode") String relationTypeCode, @Param("updateRelationWithLogin") String updateRelationWithLogin, @Param("currentlyLoggedUserLogin") String currentlyLoggedUserLogin);


boolean existsUserRelationBySenderLoginAndReceiverLoginAndRelationTypeCode(String senderLogin, String receiverLogin, String relationTypeCode);

/***
* Deletes friend relation between two users. The relation is symmetric, so it doesn't matter who is the sender and who is the receiver.
* @param login
* @param requesterLogin
*/
@Modifying
@Query("""
DELETE
FROM UserRelation relation
WHERE (relation.sender.login = :requesterLogin
AND relation.receiver.login = :login)
OR (relation.receiver.login = :requesterLogin
AND relation.sender.login = :login)
""")
void deleteFriend(@Param("login") String login, @Param("requesterLogin") String requesterLogin);

@Query("""
SELECT
CASE WHEN EXISTS (
SELECT 1
FROM User requestedUser
WHERE requestedUser.login = :requestedLogin
AND (requestedUser.friendsPrivacy.code = 'PUBLIC'
OR (requestedUser.friendsPrivacy.code = 'FRIENDS'
AND EXISTS (
SELECT 1
FROM UserRelation relation
WHERE relation.relationType.code = 'FRIENDS'
AND (relation.sender.login = :requestedLogin AND relation.receiver.login = :requestedBy
OR
relation.sender.login = :requestedBy AND relation.receiver.login = :requestedLogin)
)
)
)
) THEN true ELSE false END
""")
boolean canViewUserPosts(@Param("requestedLogin") String requestedLogin, @Param("requestedBy") String requestedBy);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package meowhub.backend.user_relations.services;

import meowhub.backend.users.dtos.BasicUserInfoDto;
import org.springframework.data.domain.Page;

public interface UserRelationQueryService {
Page<BasicUserInfoDto> getFriends(String login, int page, int size);

Page<BasicUserInfoDto> getFriendsForUser(String login, String requestedBy, int page, int size);

Page<BasicUserInfoDto> getPendingSentRequests(String login, int page, int size);

Page<BasicUserInfoDto> getReceivedRequests(String login, int page, int size);

Page<BasicUserInfoDto> getRejectedRequests(String login, int page, int size);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package meowhub.backend.user_relations.services;

public interface UserRelationService {
void acceptFriendRequestFrom(String login, String accepterLogin);

void sendFriendRequestTo (String login, String senderLogin);

void rejectFriendRequestFrom(String login, String rejecterLogin);

void deleteFriend(String login, String requesterLogin);
}
Loading

0 comments on commit 0af0695

Please sign in to comment.