diff --git a/BE/src/main/java/org/example/controllers/InteractionController.java b/BE/src/main/java/org/example/controllers/InteractionController.java new file mode 100644 index 0000000..238359f --- /dev/null +++ b/BE/src/main/java/org/example/controllers/InteractionController.java @@ -0,0 +1,39 @@ +package org.example.controllers; + +import org.example.objects.CommentDTO; +import org.example.objects.InteractionsDTO; +import org.example.services.InteractService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/interactions") +public class InteractionController { + private final InteractService interactionService; + + @Autowired + public InteractionController(InteractService interactionService) { + this.interactionService = interactionService; + } + + @PostMapping("/like") + public ResponseEntity likePost(@RequestBody InteractionsDTO interactionsDTO) { + interactionService.toggleLike(interactionsDTO.getUsername(), interactionsDTO.getPostId()); + return new ResponseEntity<>(HttpStatus.OK); + } + + @PostMapping("/comment") + public ResponseEntity commentPost(@RequestBody CommentDTO interactionsDTO) { + interactionService.commentPost(interactionsDTO.getUsername(), interactionsDTO.getPostId(), interactionsDTO.getContent()); + return new ResponseEntity<>(HttpStatus.OK); + } + + @PostMapping("/share") + public ResponseEntity sharePost(@RequestBody InteractionsDTO interactionsDTO) { + interactionService.sharePost(interactionsDTO.getUsername(), interactionsDTO.getPostId()); + return new ResponseEntity<>(HttpStatus.OK); + } +} + diff --git a/BE/src/main/java/org/example/controllers/PostController.java b/BE/src/main/java/org/example/controllers/PostController.java index 463b7e1..ac85610 100644 --- a/BE/src/main/java/org/example/controllers/PostController.java +++ b/BE/src/main/java/org/example/controllers/PostController.java @@ -8,6 +8,7 @@ import org.example.models.User; import org.example.objects.PostDTO; import org.example.repositories.PostRepository; import org.example.services.PostService; +import org.example.services.RecommendationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -23,11 +24,13 @@ public class PostController { private final PostService postService; private final PostRepository postRepository; + private final RecommendationService recommendationService; @Autowired - public PostController(PostService postService, PostRepository postRepository) { + public PostController(PostService postService, PostRepository postRepository, RecommendationService recommendationService) { this.postService = postService; this.postRepository = postRepository; + this.recommendationService = recommendationService; } @GetMapping("/getAllPosts") @@ -44,18 +47,6 @@ public class PostController { public Post updatePost(@PathVariable Long id,@RequestBody PostDTO postDTO) throws IOException { return postService.editPost(id, postDTO.getTitle(), postDTO.getContent(), postDTO.getImageFile(), postDTO.getVideoFile()); } - @PostMapping("/like") - public ResponseEntity handleLikeAction(@RequestBody LikeRequest request){ - try{ - System.out.println(request.getUsername()+" "+request.getPostId()); - postService.toggleLike(request.getUsername(),request.getPostId()); - return ResponseEntity.ok("Success"); - } - catch(Exception e){ - - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error:"+e.getMessage()); - } - } @PostMapping public Post createPost( @@ -67,4 +58,9 @@ public class PostController { return postService.createPost(title, content, imageFile, videoFile); } + @GetMapping("/recommend/{username}") + public List getRecommendationPostByUsername(@PathVariable String username){ + List recommend= recommendationService.getRecommendationPosts(username); + return recommend; + } } diff --git a/BE/src/main/java/org/example/models/Category.java b/BE/src/main/java/org/example/models/Category.java new file mode 100644 index 0000000..5a6bbf9 --- /dev/null +++ b/BE/src/main/java/org/example/models/Category.java @@ -0,0 +1,43 @@ +package org.example.models; + +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Relationship; + +import java.util.List; + +@Node +public class Category { + @Id + @GeneratedValue + private Long id; + private String name; + + @Relationship(type = "HAS_POST") + private List posts; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getPosts() { + return posts; + } + + public void setPosts(List posts) { + this.posts = posts; + } +} diff --git a/BE/src/main/java/org/example/models/Post.java b/BE/src/main/java/org/example/models/Post.java index 9270ad2..92904dc 100644 --- a/BE/src/main/java/org/example/models/Post.java +++ b/BE/src/main/java/org/example/models/Post.java @@ -2,6 +2,8 @@ package org.example.models; import lombok.Getter; +import org.example.realtionship.CommentRelationship; +import org.example.realtionship.ShareRelationship; import org.example.repositories.PostRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.neo4j.core.schema.GeneratedValue; @@ -24,6 +26,57 @@ public class Post { private String content; private String author; private String imgUrl; + private int interactions; + @Relationship(type = "HAS_LIKE", direction = Relationship.Direction.INCOMING) + private List likedByUsers; + + @Relationship(type = "HAS_COMMENT", direction = Relationship.Direction.INCOMING) + private List comments; + + @Relationship(type = "HAS_SHARE", direction = Relationship.Direction.INCOMING) + private List sharedByUsers; + @Relationship(type = "HAS_POST", direction = Relationship.Direction.INCOMING) + private Category category; + + + public List getLikedByUsers() { + return likedByUsers; + } + + public void setLikedByUsers(List likedByUsers) { + this.likedByUsers = likedByUsers; + } + + public void setComments(List comments) { + this.comments = comments; + } + + public List getSharedByUsers() { + return sharedByUsers; + } + + public void setSharedByUsers(List sharedByUsers) { + this.sharedByUsers = sharedByUsers; + } + + + public int getInteractions() { + return interactions; + } + + public void setInteractions(int interactions) { + this.interactions = interactions; + } + + public Category getCategory() { + return category; + } + + public void setCategory(Category category) { + this.category = category; + } + + public String getImgUrl() { return imgUrl; diff --git a/BE/src/main/java/org/example/models/User.java b/BE/src/main/java/org/example/models/User.java index 4ea97f4..d5c5e34 100644 --- a/BE/src/main/java/org/example/models/User.java +++ b/BE/src/main/java/org/example/models/User.java @@ -3,12 +3,14 @@ package org.example.models; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Relationship; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Arrays; import java.util.Collection; +import java.util.List; @Node public class User implements UserDetails { @@ -20,6 +22,22 @@ public class User implements UserDetails { private String password; private String roles; + @Relationship(type = "FRIEND") + private List friends; + + public void setId(Long id) { + this.id = id; + } + + + public List getFriends() { + return friends; + } + + public void setFriends(List friends) { + this.friends = friends; + } + public User() { } diff --git a/BE/src/main/java/org/example/objects/CommentDTO.java b/BE/src/main/java/org/example/objects/CommentDTO.java new file mode 100644 index 0000000..475356a --- /dev/null +++ b/BE/src/main/java/org/example/objects/CommentDTO.java @@ -0,0 +1,31 @@ +package org.example.objects; + +public class CommentDTO { + private String username; + private Long postId; + private String content; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public Long getPostId() { + return postId; + } + + public void setPostId(Long postId) { + this.postId = postId; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} diff --git a/BE/src/main/java/org/example/objects/InteractionsDTO.java b/BE/src/main/java/org/example/objects/InteractionsDTO.java new file mode 100644 index 0000000..aa44567 --- /dev/null +++ b/BE/src/main/java/org/example/objects/InteractionsDTO.java @@ -0,0 +1,22 @@ +package org.example.objects; + +public class InteractionsDTO { + private String username; + private Long postId; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public Long getPostId() { + return postId; + } + + public void setPostId(Long postId) { + this.postId = postId; + } +} diff --git a/BE/src/main/java/org/example/queryresults/CourseEnrolmentQueryResult.java b/BE/src/main/java/org/example/queryresults/CourseEnrolmentQueryResult.java index 45256e9..8c0deea 100644 --- a/BE/src/main/java/org/example/queryresults/CourseEnrolmentQueryResult.java +++ b/BE/src/main/java/org/example/queryresults/CourseEnrolmentQueryResult.java @@ -3,7 +3,7 @@ package org.example.queryresults; import org.example.models.User; -public class CourseEnrolmentQueryResult { +public class CourseEnrolmentQueryResult { private User user; public CourseEnrolmentQueryResult() { diff --git a/BE/src/main/java/org/example/realtionship/CommentRelationship.java b/BE/src/main/java/org/example/realtionship/CommentRelationship.java new file mode 100644 index 0000000..7e186aa --- /dev/null +++ b/BE/src/main/java/org/example/realtionship/CommentRelationship.java @@ -0,0 +1,61 @@ +package org.example.realtionship; + +import org.example.models.User; +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.RelationshipProperties; +import org.springframework.data.neo4j.core.schema.TargetNode; +import org.springframework.web.bind.annotation.GetMapping; + +import java.time.LocalDateTime; + +@RelationshipProperties +public class CommentRelationship { + @Id + @GeneratedValue + private Long id; + private String content; + private LocalDateTime timestamp; + + @TargetNode private User user; + + public CommentRelationship(String content, LocalDateTime timestamp, User user) { + this.content = content; + this.timestamp = timestamp; + this.user = user; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public LocalDateTime getTimestamp() { + return timestamp; + } + + public void setTimestamp(LocalDateTime timestamp) { + this.timestamp = timestamp; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + // Getters and Setters +} diff --git a/BE/src/main/java/org/example/realtionship/ShareRelationship.java b/BE/src/main/java/org/example/realtionship/ShareRelationship.java new file mode 100644 index 0000000..32518ec --- /dev/null +++ b/BE/src/main/java/org/example/realtionship/ShareRelationship.java @@ -0,0 +1,51 @@ +package org.example.realtionship; + +import org.example.models.User; +import org.springframework.data.annotation.Id; +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.RelationshipProperties; +import org.springframework.data.neo4j.core.schema.TargetNode; + +import java.time.LocalDateTime; + +@RelationshipProperties +public class ShareRelationship { + @Id + @GeneratedValue + private Long id; + private LocalDateTime timestamp; + + @TargetNode + private User user; + + public ShareRelationship(LocalDateTime timestamp, User user) { + this.timestamp = timestamp; + this.user = user; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public LocalDateTime getTimestamp() { + return timestamp; + } + + public void setTimestamp(LocalDateTime timestamp) { + this.timestamp = timestamp; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + // Getters and Setters +} \ No newline at end of file diff --git a/BE/src/main/java/org/example/repositories/CategoryRepository.java b/BE/src/main/java/org/example/repositories/CategoryRepository.java new file mode 100644 index 0000000..7cd7f0a --- /dev/null +++ b/BE/src/main/java/org/example/repositories/CategoryRepository.java @@ -0,0 +1,16 @@ +package org.example.repositories; + +import org.example.models.Category; +import org.example.models.Post; +import org.springframework.data.neo4j.repository.Neo4jRepository; +import org.springframework.data.neo4j.repository.query.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface CategoryRepository extends Neo4jRepository { + @Query("MATCH (c:Category)-[:HAS_POST]->(p:Post) WHERE c.name IN $categoryNames RETURN p") + List findPostsByCategoryNames(@Param("categoryNames") List categoryNames); +} diff --git a/BE/src/main/java/org/example/repositories/PostRepository.java b/BE/src/main/java/org/example/repositories/PostRepository.java index 367d51f..fc8044a 100644 --- a/BE/src/main/java/org/example/repositories/PostRepository.java +++ b/BE/src/main/java/org/example/repositories/PostRepository.java @@ -48,11 +48,39 @@ public interface PostRepository extends Neo4jRepository { ")\n" + "RETURN p") Post updatePost(@Param("postId") Long postId, @Param("title") String title, @Param("content") String content, @Param("imageFile")MultipartFile imageFile, @Param("videoFile") MultipartFile videoFile); - @Query("MATCH (u:User {username: $username}), (p:Post) WHERE ID(p)=$postId CREATE (u)-[:LIKES]->(p)") - void createLike(@Param("username") String username,@Param("postId") Long postId); - @Query("MATCH (u:User {username: $username})-[r:LIKES]->(p:Post ) WHERE ID(p)=$postId DELETE r") - void deleteLike(@Param("username") String username, @Param("postId") Long postId); + @Query("MATCH (u:User), (p:Post) WHERE u.username = $username AND ID(p) = $postId " + + "MERGE (u)-[:LIKED {timestamp: datetime()}]->(p) " + + "RETURN p") + Post likePost(@Param("username") String username, @Param("postId") Long postId); + + @Query("MATCH (u:User)-[l:LIKES]->(p:Post) " + + "WHERE u.username=$username AND ID(p) = $postId " + + "DELETE l " + + "RETURN CASE WHEN COUNT(l) > 0 THEN true ELSE false END AS removed") + boolean toggleLike(@Param("username") String username, @Param("postId") Long postId); + + + @Query("MATCH (u:User), (p:Post) WHERE u.username=$username AND ID(p) = $postId " + + "MERGE (u)-[:COMMENTED {content: $content, timestamp: datetime()}]->(p) " + + "RETURN p") + Post commentPost(@Param("username") String username, @Param("postId") Long postId, @Param("content") String content); + @Query("MATCH (u:User), (p:Post) WHERE u.username=$username AND ID(p) = $postId " + + "MERGE (u)-[:SHARED {timestamp: datetime()}]->(p) " + + "RETURN p") + Post sharePost(@Param("username") String username, @Param("postId") Long postId); + + @Query("MATCH (p:Post)\n" + + "OPTIONAL MATCH (p)-[:HAS_LIKE]->(l)\n" + + "OPTIONAL MATCH (p)-[:HAS_COMMENT]->(c)\n" + + "OPTIONAL MATCH (p)-[:HAS_SHARE]->(s)\n" + + "OPTIONAL MATCH (p)-[:HAS_IMAGE]->(image:Image)\n" + + "OPTIONAL MATCH (p)-[:HAS_VIDEO]->(video:Video)\n" + + "WITH p, COUNT(l) AS likeCount, COUNT(c) AS commentCount, COUNT(s) AS shareCount, COLLECT(DISTINCT image) AS images, COLLECT(DISTINCT video) AS videos\n" + + "RETURN p, images, videos\n" + + "ORDER BY (likeCount + commentCount + shareCount) DESC\n" + + "LIMIT 10") + List findTop10ByInteractions(); @Query("MATCH (user:User {username: $username}),(post:Post )"+"where ID(post)=$postId"+ " CREATE (user)-[:createPost ]->(post)"+ "RETURN user, post") diff --git a/BE/src/main/java/org/example/repositories/UserRepository.java b/BE/src/main/java/org/example/repositories/UserRepository.java index 0b29d02..a2ccac4 100644 --- a/BE/src/main/java/org/example/repositories/UserRepository.java +++ b/BE/src/main/java/org/example/repositories/UserRepository.java @@ -1,25 +1,28 @@ package org.example.repositories; +import org.example.models.Category; +import org.example.models.Post; import org.example.models.User; import org.example.queryresults.PostQueryResult; import org.springframework.data.neo4j.repository.Neo4jRepository; import org.springframework.data.neo4j.repository.query.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; +@Repository public interface UserRepository extends Neo4jRepository { Optional findUserByUsername(String username); - User findUserBy(String username); boolean existsByUsername(String username); - @Query("MATCH (user:User), (course:Course) WHERE user.username = $username AND course.identifier = $identifier " + - "RETURN EXISTS((user)-[:ENROLLED_IN]->(course))") - Boolean findEnrolmentStatus(String username, String identifier); + @Query("MATCH (u:User)-[:FRIEND]->(f:User)-[:LIKED|COMMENTED|SHARED]->(p:Post) WHERE u.username=$username RETURN p") + List findFriendPosts(@Param("username") String username); -// @Query("MATCH (user:User), (course:Course) WHERE user.username = $username AND course.identifier = $identifier " + -// "CREATE (user)-[:ENROLLED_IN]->(course) RETURN user, course") -// CourseEnrolmentQueryResult createEnrolmentRelationship(String username, String identifier); + @Query("MATCH (u:User)-[:LIKED|COMMENTED|SHARED]->(p:Post)-[:HAS_POST]->(c:Category) WHERE u.username=$username RETURN DISTINCT c") + List findInteractedCategories(@Param("username") String username); } diff --git a/BE/src/main/java/org/example/services/InteractService.java b/BE/src/main/java/org/example/services/InteractService.java new file mode 100644 index 0000000..56c3b49 --- /dev/null +++ b/BE/src/main/java/org/example/services/InteractService.java @@ -0,0 +1,43 @@ +package org.example.services; + +import org.example.models.*; +import org.example.realtionship.CommentRelationship; +import org.example.realtionship.ShareRelationship; +import org.example.repositories.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@Service +public class InteractService { + private final PostRepository postRepository; + private final UserRepository userRepository; + + @Autowired + public InteractService(PostRepository postRepository, UserRepository userRepository) { + this.postRepository = postRepository; + this.userRepository = userRepository; + } + + @Transactional + public void toggleLike(String username , Long postId) { + boolean removed = postRepository.toggleLike(username, postId); + if (!removed) { + postRepository.likePost(username, postId); + System.out.println("Liked"); + } + } + + @Transactional + public void commentPost(String username, Long postId, String content) { + postRepository.commentPost(username, postId, content); + } + + @Transactional + public void sharePost(String username, Long postId) { + postRepository.sharePost(username, postId); + } +} \ No newline at end of file diff --git a/BE/src/main/java/org/example/services/PostService.java b/BE/src/main/java/org/example/services/PostService.java index d0eb34b..1786254 100644 --- a/BE/src/main/java/org/example/services/PostService.java +++ b/BE/src/main/java/org/example/services/PostService.java @@ -3,10 +3,7 @@ package org.example.services; //import org.example.models.Author; import jakarta.transaction.Transactional; import org.example.controllers.UserController; -import org.example.models.Image; -import org.example.models.Post; -import org.example.models.User; -import org.example.models.Video; +import org.example.models.*; import org.example.queryresults.PostQueryResult; import org.example.repositories.PostRepository; import org.example.repositories.UserRepository; @@ -23,6 +20,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.Principal; +import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.Optional; @@ -64,27 +62,13 @@ public class PostService { return getAllPost; } @Transactional - public void toggleLike(String username,Long postId){ - System.out.println("Service:"+username+postId); - boolean alreadyLiked = neo4jTemplate.count("MATCH (u:User {username: $username})-[r:LIKES]->(p:Post) WHERE ID(p)=$postId RETURN count(r) ", - Map.of("username", username, "postId", postId)) > 0; - - if (alreadyLiked) { - postRepository.deleteLike(username,postId); - - } else { - System.out.println("Like de"); - postRepository.createLike(username,postId); - } - } - @Transactional public Post createPost(String title, String content, MultipartFile imageFile, MultipartFile videoFile) throws IOException { Post post = new Post(); post.setTitle(title); post.setContent(content); if (imageFile != null) { - Image image = saveImageFile(imageFile, "C:\\Users\\P R O B O O K\\Documents\\Phuc\\Facebook\\FE/FE/public/picture"); + Image image = saveImageFile(imageFile, "C:\\Users\\phucl\\OneDrive\\Desktop\\JAVA_INTER\\Facebook\\FE\\FE\\public\\picture"); // post.getImages().add(image); post.setImages(image); } @@ -116,7 +100,7 @@ public class PostService { Post post= postRepository.updatePost(postId,title,content,imageFile,videoFile); // Update the image file if provided if (imageFile != null) { - Image newImage = saveImageFile(imageFile, "C:\\Users\\P R O B O O K\\Documents\\Phuc\\Facebook\\FE/FE/public/picture"); + Image newImage = saveImageFile(imageFile, "C:\\Users\\phucl\\OneDrive\\Desktop\\JAVA_INTER\\Facebook\\FE\\FE\\public\\picture"); post.setImages(newImage); } diff --git a/BE/src/main/java/org/example/services/RecommendationService.java b/BE/src/main/java/org/example/services/RecommendationService.java new file mode 100644 index 0000000..d20829a --- /dev/null +++ b/BE/src/main/java/org/example/services/RecommendationService.java @@ -0,0 +1,39 @@ +package org.example.services; + + +import org.example.models.Category; +import org.example.models.Post; +import org.example.repositories.CategoryRepository; +import org.example.repositories.PostRepository; +import org.example.repositories.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class RecommendationService { + private final PostRepository postRepository; + private final UserRepository userRepository; + private final CategoryRepository categoryRepository; + + @Autowired + public RecommendationService(PostRepository postRepository, UserRepository userRepository, CategoryRepository categoryRepository) { + this.postRepository = postRepository; + this.userRepository = userRepository; + this.categoryRepository = categoryRepository; + } + + public List getRecommendationPosts(String username) { + List recommendationPosts = new ArrayList<>(); + recommendationPosts.addAll(postRepository.findTop10ByInteractions()); + List categories = userRepository.findInteractedCategories(username); + List categoriesNames = categories.stream().map(Category::getName).collect(Collectors.toList()); + recommendationPosts.addAll(categoryRepository.findPostsByCategoryNames(categoriesNames)); + recommendationPosts.addAll(userRepository.findFriendPosts(username)); + return recommendationPosts.stream().distinct().collect(Collectors.toList()); + } + +} diff --git a/BE/src/main/resources/application.properties b/BE/src/main/resources/application.properties index 93c077c..f808f6d 100644 --- a/BE/src/main/resources/application.properties +++ b/BE/src/main/resources/application.properties @@ -1,4 +1,3 @@ spring.neo4j.uri=neo4j://localhost:7687 spring.neo4j.authentication.username=neo4j -spring.neo4j.authentication.password=12345678 -file_upload = C:/Users/P R O B O O K/Documents/Facebook/BE/images \ No newline at end of file +spring.neo4j.authentication.password=12345678 \ No newline at end of file