diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index b7cabcd..794631f 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -16,6 +16,8 @@ repositories { } dependencies { + compileOnly("org.projectlombok:lombok") + annotationProcessor("org.projectlombok:lombok") implementation("org.springframework.boot:spring-boot-starter-jdbc") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-mail") diff --git a/backend/src/main/java/ovh/herisson/Clyde/DTO/Msg/DiscussionDTO.java b/backend/src/main/java/ovh/herisson/Clyde/DTO/Msg/DiscussionDTO.java new file mode 100644 index 0000000..cd7f3cf --- /dev/null +++ b/backend/src/main/java/ovh/herisson/Clyde/DTO/Msg/DiscussionDTO.java @@ -0,0 +1,34 @@ +package ovh.herisson.Clyde.DTO.Msg; + +/****************************************************** + * @file DiscussionDTO.java + * @author Anthony Debucquoy + * @scope Extension messagerie + * + * File to format a discussion using messageDTO + ******************************************************/ + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import lombok.AllArgsConstructor; +import lombok.Data; +import ovh.herisson.Clyde.Tables.User; +import ovh.herisson.Clyde.Tables.Msg.Discussion; +import ovh.herisson.Clyde.DTO.Msg.MessagesDTO; + +@Data +@AllArgsConstructor +public class DiscussionDTO { + private long id; + private String name; + private List members; + private List msgs; + + public static DiscussionDTO construct(Discussion d, User u){ + List msgsdto = new ArrayList<>(); + d.getMsgs().forEach(x -> msgsdto.add(MessagesDTO.construct(x, u))); + return new DiscussionDTO(d.getId(), d.getName(), d.getMembers(), msgsdto); + } +} diff --git a/backend/src/main/java/ovh/herisson/Clyde/DTO/Msg/MessagesDTO.java b/backend/src/main/java/ovh/herisson/Clyde/DTO/Msg/MessagesDTO.java new file mode 100644 index 0000000..2ce0228 --- /dev/null +++ b/backend/src/main/java/ovh/herisson/Clyde/DTO/Msg/MessagesDTO.java @@ -0,0 +1,33 @@ +package ovh.herisson.Clyde.DTO.Msg; + +/****************************************************** + * @file MessagesDTO.java + * @author Anthony Debucquoy + * @scope Extension messagerie + * + * File to Format the response adding the sender field + ******************************************************/ + +import lombok.AllArgsConstructor; +import lombok.Data; +import ovh.herisson.Clyde.Tables.User; +import ovh.herisson.Clyde.Tables.Msg.Message; +import java.util.Date; + +@Data +@AllArgsConstructor +public class MessagesDTO { + private long id; + private String content; + private User author; + private boolean sender; + private Date created; + //TODO: Attachment + + public static MessagesDTO construct(Message m, User user){ + boolean sender = false; + if(m.getAuthor().equals(user)) + sender = true; + return new MessagesDTO(m.getId(), m.getContent(), m.getAuthor(), sender, m.getCreated()); + } +} diff --git a/backend/src/main/java/ovh/herisson/Clyde/EndPoints/Msg/MessagesController.java b/backend/src/main/java/ovh/herisson/Clyde/EndPoints/Msg/MessagesController.java new file mode 100644 index 0000000..9f1db44 --- /dev/null +++ b/backend/src/main/java/ovh/herisson/Clyde/EndPoints/Msg/MessagesController.java @@ -0,0 +1,126 @@ +package ovh.herisson.Clyde.EndPoints.Msg; + +/****************************************************** + * @file MessagesController.java + * @author Anthony Debucquoy + * @scope Extension messagerie + * + * Entry point for the messages application + ******************************************************/ + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; + +import lombok.AllArgsConstructor; +import ovh.herisson.Clyde.DTO.Msg.DiscussionDTO; +import ovh.herisson.Clyde.Repositories.UserRepository; +import ovh.herisson.Clyde.Repositories.Msg.DiscussionRepository; +import ovh.herisson.Clyde.Responses.UnauthorizedResponse; +import ovh.herisson.Clyde.Services.AuthenticatorService; +import ovh.herisson.Clyde.Services.UserService; +import ovh.herisson.Clyde.Services.Msg.DiscussionService; +import ovh.herisson.Clyde.Tables.User; +import ovh.herisson.Clyde.Tables.Msg.Discussion; +import ovh.herisson.Clyde.Tables.Msg.Message; + +@RestController +@CrossOrigin(originPatterns = "*", allowCredentials = "true") +@AllArgsConstructor +public class MessagesController { + + private AuthenticatorService authServ; + private DiscussionService discServ; + private DiscussionRepository discRepo; + private UserService userServ; + + @GetMapping("/discussions") + public ResponseEntity> getDiscussions(@RequestHeader("Authorization") String token ){ + User user = authServ.getUserFromToken(token); + if(user == null){ + return new UnauthorizedResponse<>(null); + } + + Iterable mock = discServ.getOwned(authServ.getUserFromToken(token)); + + return new ResponseEntity<>(mock, HttpStatus.OK); + } + + @GetMapping("/discussion/{id}") + public ResponseEntity getDiscussion(@RequestHeader("Authorization") String token, @PathVariable long id){ + User user = authServ.getUserFromToken(token); + if(user == null || !discServ.hasDiscussion(user, id) ){ + return new UnauthorizedResponse<>(null); + } + return new ResponseEntity<>(DiscussionDTO.construct(discRepo.findById(id).orElse(null), authServ.getUserFromToken(token)), HttpStatus.OK); + } + + @PatchMapping("/discussion/{id}") + public ResponseEntity AlterDiscussion(@RequestHeader("Authorization") String token, @PathVariable long id, @RequestBody Discussion data){ + User user = authServ.getUserFromToken(token); + if(user == null){ + return new UnauthorizedResponse<>(null); + } + + Discussion disc = discRepo.findById(id).orElse(null); + disc.setName(data.getName()); + discRepo.save(disc); + return new ResponseEntity<>(disc, HttpStatus.OK); + } + + @PatchMapping("/discussion/{id}/add") + public ResponseEntity invite(@RequestHeader("Authorization") String token, @PathVariable long id, @RequestBody User data){ + User user = authServ.getUserFromToken(token); + if(user == null){ + return new UnauthorizedResponse<>(null); + } + + Discussion disc = discRepo.findById(id).orElse(null); + User invited = userServ.getUserById(data.getRegNo()); + disc.addMember(invited); + discRepo.save(disc); + return new ResponseEntity<>(disc, HttpStatus.OK); + } + + @PatchMapping("/discussion/{id}/remove") + public ResponseEntity removeMember(@RequestHeader("Authorization") String token, @PathVariable long id, @RequestBody User data){ + User user = authServ.getUserFromToken(token); + if(user == null){ + return new UnauthorizedResponse<>(null); + } + + Discussion disc = discRepo.findById(id).orElse(null); + User member = userServ.getUserById(data.getRegNo()); + disc.delMember(member); + discRepo.save(disc); + return new ResponseEntity<>(disc, HttpStatus.OK); + } + + @PostMapping("/discussion/{id}") + public ResponseEntity sendMessage(@RequestHeader("Authorization") String token, @PathVariable long id, @RequestBody Message msg){ + User user = authServ.getUserFromToken(token); + if(user == null){ + return new UnauthorizedResponse<>(null); + } + + Discussion disc = discRepo.findById(id).orElse(null); + msg.setAuthor(user); + if(disc != null) + discServ.CreateMessage(disc, msg); + return new ResponseEntity<>(disc, HttpStatus.OK); + } + + + @PostMapping("/discussion") + public ResponseEntity createDiscussion(@RequestHeader("Authorization") String token, @RequestBody Discussion data){ + return new ResponseEntity<>(discServ.create(data.getName(), authServ.getUserFromToken(token)), HttpStatus.OK); + } +} diff --git a/backend/src/main/java/ovh/herisson/Clyde/Repositories/Msg/DiscussionRepository.java b/backend/src/main/java/ovh/herisson/Clyde/Repositories/Msg/DiscussionRepository.java new file mode 100644 index 0000000..9b2675b --- /dev/null +++ b/backend/src/main/java/ovh/herisson/Clyde/Repositories/Msg/DiscussionRepository.java @@ -0,0 +1,23 @@ +package ovh.herisson.Clyde.Repositories.Msg; + +/****************************************************** + * @file DiscussionRepository.java + * @author Anthony Debucquoy + * @scope Extension messagerie + * + * Repository of Discussion allowing to fetch discussion by user + ******************************************************/ + + +import java.util.List; + +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; + +import ovh.herisson.Clyde.Tables.Msg.Discussion; + +public interface DiscussionRepository extends CrudRepository{ + + @Query("SELECT d FROM Discussion d INNER JOIN FETCH d.members dm WHERE dm.id = ?1") + List findByMembership(long userid); +} diff --git a/backend/src/main/java/ovh/herisson/Clyde/Repositories/Msg/MessageRepository.java b/backend/src/main/java/ovh/herisson/Clyde/Repositories/Msg/MessageRepository.java new file mode 100644 index 0000000..021ac79 --- /dev/null +++ b/backend/src/main/java/ovh/herisson/Clyde/Repositories/Msg/MessageRepository.java @@ -0,0 +1,14 @@ +package ovh.herisson.Clyde.Repositories.Msg; + +/****************************************************** + * @file MessageRepository.java + * @author Anthony Debucquoy + * @scope Extension messagerie + ******************************************************/ + + +import org.springframework.data.repository.CrudRepository; + +import ovh.herisson.Clyde.Tables.Msg.Message; + +public interface MessageRepository extends CrudRepository {} diff --git a/backend/src/main/java/ovh/herisson/Clyde/Services/Msg/DiscussionService.java b/backend/src/main/java/ovh/herisson/Clyde/Services/Msg/DiscussionService.java new file mode 100644 index 0000000..9a471da --- /dev/null +++ b/backend/src/main/java/ovh/herisson/Clyde/Services/Msg/DiscussionService.java @@ -0,0 +1,57 @@ +package ovh.herisson.Clyde.Services.Msg; + +import java.util.List; + +/****************************************************** + * @file DiscussionService.java + * @author Anthony Debucquoy + * @scope Extension messagerie + * + * Various function utilised by the messages application + ******************************************************/ + + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.fasterxml.jackson.databind.util.JSONPObject; + +import ovh.herisson.Clyde.Repositories.Msg.DiscussionRepository; +import ovh.herisson.Clyde.Tables.User; +import ovh.herisson.Clyde.Tables.Msg.Discussion; +import ovh.herisson.Clyde.Tables.Msg.Message; + +@Service +public class DiscussionService { + + @Autowired + private DiscussionRepository discRepo; + + public Discussion create(String name, User author){ + return discRepo.save(new Discussion(name, author)); + } + + /** + * list discussions owned by a certain user + */ + public Iterable getOwned(User author){ + return discRepo.findByMembership(author.getRegNo()); + } + + /** + * Create a message and link it to it's discussion + */ + public Discussion CreateMessage(Discussion disc, Message msg){ + disc.addMessage(msg); + return discRepo.save(disc); + } + + /** + * Check if a user is in a discussion + */ + public boolean hasDiscussion(User user, long id) { + Discussion disc = discRepo.findById(id).orElse(null); + List members = disc.getMembers(); + return members.contains(user); + } +} diff --git a/backend/src/main/java/ovh/herisson/Clyde/Tables/Msg/Discussion.java b/backend/src/main/java/ovh/herisson/Clyde/Tables/Msg/Discussion.java new file mode 100644 index 0000000..4d76d5f --- /dev/null +++ b/backend/src/main/java/ovh/herisson/Clyde/Tables/Msg/Discussion.java @@ -0,0 +1,72 @@ +package ovh.herisson.Clyde.Tables.Msg; + +/****************************************************** + * @file Discussion.java + * @author Anthony Debucquoy + * @scope Extension messagerie + * + * Discussion allow to regroupe multiple user in and message together + * for the messages application to work + ******************************************************/ + + +import java.util.List; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.OneToMany; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import ovh.herisson.Clyde.Tables.User; + +@Entity +@Getter +@Setter +@NoArgsConstructor +public class Discussion{ + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private long id; + private String name; + + @ManyToMany + @JoinTable( + name = "discussion_members", + joinColumns = @JoinColumn(name = "discussion_id"), + inverseJoinColumns = @JoinColumn(name = "user_id") + ) + private List members; + + @OneToMany(mappedBy="discussion", orphanRemoval = true, cascade = CascadeType.ALL) + private List msgs; + + public Discussion(String name){ + this.name = name; + } + + public Discussion(String name, User user){ + this.name = name; + this.members = List.of(user); + } + + public void addMessage(Message msg){ + msg.setDiscussion(this); + msgs.add(msg); + } + + public void addMember(User user) { + members.add(user); + } + + public void delMember(User user) { + members.remove(user); + } +} diff --git a/backend/src/main/java/ovh/herisson/Clyde/Tables/Msg/Message.java b/backend/src/main/java/ovh/herisson/Clyde/Tables/Msg/Message.java new file mode 100644 index 0000000..2afa6ba --- /dev/null +++ b/backend/src/main/java/ovh/herisson/Clyde/Tables/Msg/Message.java @@ -0,0 +1,66 @@ +package ovh.herisson.Clyde.Tables.Msg; + +/****************************************************** + * @file Message.java + * @author Anthony Debucquoy + * @scope Extension messagerie + * + * Represent a message sent to a discussion + ******************************************************/ + + +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import java.util.Date; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import jakarta.persistence.PrePersist; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import ovh.herisson.Clyde.Tables.User; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Entity +public class Message { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private long id; + private String content; + + @CreationTimestamp + @Column(nullable = false) + private Date created; + + @ManyToOne + private User author; + + public User getAuthor() { + return author; + } + + @OneToOne + private Message response; + + @ManyToOne(optional = false) + @JsonIgnore + private Discussion discussion; + +} diff --git a/backend/src/main/java/ovh/herisson/Clyde/Tables/User.java b/backend/src/main/java/ovh/herisson/Clyde/Tables/User.java index de958df..2badd32 100644 --- a/backend/src/main/java/ovh/herisson/Clyde/Tables/User.java +++ b/backend/src/main/java/ovh/herisson/Clyde/Tables/User.java @@ -1,7 +1,11 @@ package ovh.herisson.Clyde.Tables; import jakarta.persistence.*; +import ovh.herisson.Clyde.Tables.Msg.Discussion; +import ovh.herisson.Clyde.Tables.Msg.Message; + import java.util.Date; +import java.util.List; @Entity @@ -20,6 +24,15 @@ public class User { private String profilePictureUrl; private ovh.herisson.Clyde.Tables.Role role; private String password; + + ////// Extension Messagerie ///// + @OneToMany(mappedBy = "author", cascade = CascadeType.ALL) + private List msgs; + ///////////////////////////////// + + @ManyToMany( mappedBy = "members" ) + private List discussions; + public User(String lastName, String firstName, String email, String address, String country, Date birthDate, String profilePictureUrl, Role role, String password) { diff --git a/frontend/src/Apps/Msg.vue b/frontend/src/Apps/Msg.vue new file mode 100644 index 0000000..709f8c4 --- /dev/null +++ b/frontend/src/Apps/Msg.vue @@ -0,0 +1,213 @@ + + + + + + + diff --git a/frontend/src/rest/apps.js b/frontend/src/rest/apps.js index 99cbc10..b6568e8 100644 --- a/frontend/src/rest/apps.js +++ b/frontend/src/rest/apps.js @@ -9,6 +9,7 @@ import Profil from "@/Apps/Profil.vue" import Courses from "@/Apps/ManageCourses.vue" import Users from "@/Apps/UsersList.vue" import Students from "@/Apps/StudentsList.vue" +import Msg from "@/Apps/Msg.vue" const apps = { '/login': LoginPage, @@ -17,6 +18,7 @@ const apps = { '/manage-courses' : Courses, '/users-list' : Users, '/students-list' : Students, + '/msg' : Msg, } const appsList = { diff --git a/frontend/src/rest/msg.js b/frontend/src/rest/msg.js new file mode 100644 index 0000000..fb832dc --- /dev/null +++ b/frontend/src/rest/msg.js @@ -0,0 +1,60 @@ +/******************************************************* + * File: msg.js + * Author: Anthony Debucquoy + * Scope: Extension messagerie + * Description: Messages frontend api consumer + *******************************************************/ + +import { restGet, restPost, restPatch } from './restConsumer.js' +import { ref } from 'vue' + +/** + * - id + * - name + * - members + */ +export const discussionsList = ref(); +export const currentDiscussion = ref([]); +let timerSet = false + + +export async function createDiscussion(name){ + let disc = await restPost("/discussion", {name: name}); + discussionsList.value.push(disc); +} + + +export async function invite(id, regNo){ + restPatch("/discussion/"+ id+ "/add", {regNo: parseInt(regNo)}).then(() => fetchDiscussion(id)) +} + +export async function removeMember(id, regNo){ + restPatch("/discussion/"+ id+ "/remove", {regNo: parseInt(regNo)}).then(() => fetchDiscussion(id)) +} + +export async function sendMessage(id, content, responseId){ + let data = { + content: content, + response: responseId, + } + restPost("/discussion/" + id, data).then(() => fetchDiscussion(id)); +} + +export async function updateDiscussionName(id, name){ + restPatch("/discussion/" + id, {name: name}).then(() => fetchDiscussions()); +} + + +async function fetchDiscussions(){ + discussionsList.value = await restGet("/discussions"); +} + +export async function fetchDiscussion(id){ + currentDiscussion.value = await restGet("/discussion/" + id); + if(!timerSet){ + timerSet = true; + setTimeout(() => {timerSet = false;fetchDiscussion(currentDiscussion.value.id)} , 5000); + } +} + +await fetchDiscussions();