Compare commits
30 Commits
tonitch/ma
...
7b0d76dae8
Author | SHA1 | Date | |
---|---|---|---|
7b0d76dae8
|
|||
61e269eb27
|
|||
76c3b76153 | |||
5f483216b9
|
|||
5a4d066c45
|
|||
f9bcff6d4f
|
|||
5bb7606721
|
|||
b049c46571
|
|||
7bd745fd5e
|
|||
a96609d2ef
|
|||
cb750b8505
|
|||
7ca5c34afe
|
|||
2b9bdf8dac
|
|||
ccb954e348
|
|||
ce56e37a33
|
|||
1c61a356a4
|
|||
2bdffe6ab4
|
|||
729d1ad504
|
|||
1522d74ed3
|
|||
b4499e04c7
|
|||
914f6bdf36
|
|||
66e7fa24a1
|
|||
7b9f021c24
|
|||
2dfa0a0ee0
|
|||
9e0db361b8 | |||
7a13d412f1 | |||
9de4b06e75 | |||
123fa97611 | |||
1fad792be7 | |||
acd1262955
|
@ -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")
|
||||
|
@ -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<User> members;
|
||||
private List<MessagesDTO> msgs;
|
||||
|
||||
public static DiscussionDTO construct(Discussion d, User u){
|
||||
List<MessagesDTO> msgsdto = new ArrayList<>();
|
||||
d.getMsgs().forEach(x -> msgsdto.add(MessagesDTO.construct(x, u)));
|
||||
return new DiscussionDTO(d.getId(), d.getName(), d.getMembers(), msgsdto);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package ovh.herisson.Clyde.EndPoints.Msg;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
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.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 jakarta.websocket.server.PathParam;
|
||||
import lombok.AllArgsConstructor;
|
||||
import ovh.herisson.Clyde.Repositories.CourseRepository;
|
||||
import ovh.herisson.Clyde.Repositories.Msg.ForumRepository;
|
||||
import ovh.herisson.Clyde.Repositories.Msg.TopicRepository;
|
||||
import ovh.herisson.Clyde.Responses.UnauthorizedResponse;
|
||||
import ovh.herisson.Clyde.Services.AuthenticatorService;
|
||||
import ovh.herisson.Clyde.Services.CourseService;
|
||||
import ovh.herisson.Clyde.Services.Msg.ForumService;
|
||||
import ovh.herisson.Clyde.Tables.Course;
|
||||
import ovh.herisson.Clyde.Tables.Role;
|
||||
import ovh.herisson.Clyde.Tables.Token;
|
||||
import ovh.herisson.Clyde.Tables.User;
|
||||
import ovh.herisson.Clyde.Tables.Msg.Forum;
|
||||
import ovh.herisson.Clyde.Tables.Msg.Topic;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin(originPatterns = "*", allowCredentials = "true")
|
||||
@AllArgsConstructor
|
||||
public class ForumController {
|
||||
|
||||
private CourseRepository courseRepo;
|
||||
private CourseService courseServ;
|
||||
private AuthenticatorService authServ;
|
||||
private ForumService forumServ;
|
||||
private ForumRepository forumRepo;
|
||||
private TopicRepository topicRepo;
|
||||
|
||||
//// Endpoints to get and create new forums
|
||||
|
||||
@GetMapping("/forums/{id}")
|
||||
public ResponseEntity<List<Forum>> getForumFromCourseId(@RequestHeader("Authorization") String token, @PathVariable long id){
|
||||
User u = authServ.getUserFromToken(token);
|
||||
if(u == null){
|
||||
return new UnauthorizedResponse<>(null);
|
||||
}
|
||||
return new ResponseEntity<>(courseRepo.findById(id).getForums(), HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping("/forums/{id}")
|
||||
public ResponseEntity<Forum> createForumOfCourse(@RequestHeader("Authorization") String token, @PathVariable long id, @RequestBody Forum data){
|
||||
User u = authServ.getUserFromToken(token);
|
||||
Course c = courseRepo.findById(id);
|
||||
if(!(c.getOwner().equals(u) || u.getRole() == Role.Admin)){
|
||||
return new UnauthorizedResponse<>(null);
|
||||
}
|
||||
forumServ.createForum(c, data);
|
||||
return new ResponseEntity<>(HttpStatus.ACCEPTED);
|
||||
}
|
||||
|
||||
//// Endpoints to get and create forum's topic
|
||||
|
||||
@GetMapping("/forum/{id}")
|
||||
public ResponseEntity<List<Topic>> getTopicsFromForumId(@RequestHeader("Authorization") String token, @PathVariable long id){
|
||||
User u = authServ.getUserFromToken(token);
|
||||
if(u != null){
|
||||
return new UnauthorizedResponse<>(null);
|
||||
}
|
||||
return new ResponseEntity<>(forumRepo.findById(id).orElse(null).getTopics(), HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping("/forum/{id}")
|
||||
public ResponseEntity<Topic> postTopicToForum(@RequestHeader("Authorization") String token, @PathVariable long id, @RequestBody Topic data){
|
||||
User u = authServ.getUserFromToken(token);
|
||||
Forum f = forumRepo.findById(id).orElse(null);
|
||||
if(!f.getWriters().contains(u)){
|
||||
return new UnauthorizedResponse<>(null);
|
||||
}
|
||||
forumServ.createTopic(f, data);
|
||||
return new ResponseEntity<>(HttpStatus.ACCEPTED);
|
||||
}
|
||||
|
||||
//// Endpoints related to topics and messages
|
||||
|
||||
@GetMapping("/forum/post/{id}")
|
||||
public ResponseEntity<Topic> getPost(@RequestHeader("Authorization") String token, @PathVariable long id){
|
||||
User u = authServ.getUserFromToken(token);
|
||||
if(u != null){
|
||||
return new UnauthorizedResponse<>(null);
|
||||
}
|
||||
Topic t = topicRepo.findById(id).orElse(null);
|
||||
return new ResponseEntity<>(t, HttpStatus.OK);
|
||||
}
|
||||
|
||||
|
||||
// TODO: <tonitch> Create a new post/topic and response to a topic
|
||||
}
|
@ -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<Iterable<Discussion>> getDiscussions(@RequestHeader("Authorization") String token ){
|
||||
User user = authServ.getUserFromToken(token);
|
||||
if(user == null){
|
||||
return new UnauthorizedResponse<>(null);
|
||||
}
|
||||
|
||||
Iterable<Discussion> mock = discServ.getOwned(authServ.getUserFromToken(token));
|
||||
|
||||
return new ResponseEntity<>(mock, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@GetMapping("/discussion/{id}")
|
||||
public ResponseEntity<DiscussionDTO> 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<Discussion> 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<Discussion> 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<Discussion> 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<Discussion> 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<Discussion> createDiscussion(@RequestHeader("Authorization") String token, @RequestBody Discussion data){
|
||||
return new ResponseEntity<>(discServ.create(data.getName(), authServ.getUserFromToken(token)), HttpStatus.OK);
|
||||
}
|
||||
}
|
@ -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<Discussion, Long>{
|
||||
|
||||
@Query("SELECT d FROM Discussion d INNER JOIN FETCH d.members dm WHERE dm.id = ?1")
|
||||
List<Discussion> findByMembership(long userid);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package ovh.herisson.Clyde.Repositories.Msg;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
import ovh.herisson.Clyde.Tables.Msg.Forum;
|
||||
|
||||
public interface ForumRepository extends CrudRepository<Forum, Long> {
|
||||
|
||||
}
|
@ -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<Message, Long> {}
|
@ -0,0 +1,10 @@
|
||||
package ovh.herisson.Clyde.Repositories.Msg;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
import ovh.herisson.Clyde.Tables.Msg.Topic;
|
||||
|
||||
public interface TopicRepository extends CrudRepository<Topic, Long> {
|
||||
|
||||
}
|
||||
|
@ -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<Discussion> 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<User> members = disc.getMembers();
|
||||
return members.contains(user);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package ovh.herisson.Clyde.Services.Msg;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import ovh.herisson.Clyde.Repositories.CourseRepository;
|
||||
import ovh.herisson.Clyde.Repositories.Msg.ForumRepository;
|
||||
import ovh.herisson.Clyde.Tables.Course;
|
||||
import ovh.herisson.Clyde.Tables.Msg.Forum;
|
||||
import ovh.herisson.Clyde.Tables.Msg.Topic;
|
||||
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class ForumService {
|
||||
|
||||
private CourseRepository courseRepo;
|
||||
private ForumRepository forumRepo;
|
||||
|
||||
public void createForum(Course c, Forum f){
|
||||
c.addForum(f);
|
||||
courseRepo.save(c);
|
||||
}
|
||||
|
||||
public void createTopic(Forum f, Topic data) {
|
||||
f.addTopic(data);
|
||||
forumRepo.save(f);
|
||||
}
|
||||
}
|
@ -1,10 +1,16 @@
|
||||
package ovh.herisson.Clyde.Tables;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
import ovh.herisson.Clyde.Tables.Msg.Forum;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.annotations.OnDelete;
|
||||
import org.hibernate.annotations.OnDeleteAction;
|
||||
|
||||
@Entity
|
||||
@Data
|
||||
public class Course {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
@ -17,6 +23,15 @@ public class Course {
|
||||
@JoinColumn(name = "Users")
|
||||
private User owner;
|
||||
|
||||
//// Extension Messagerie /////
|
||||
@OneToMany(cascade = CascadeType.ALL)
|
||||
private List<Forum> forums;
|
||||
|
||||
public void addForum(Forum f){
|
||||
forums.add(f);
|
||||
}
|
||||
///////////////////////////////
|
||||
|
||||
public Course(int credits, String title, User owner){
|
||||
this.credits = credits;
|
||||
this.title = title;
|
||||
|
@ -0,0 +1,32 @@
|
||||
package ovh.herisson.Clyde.Tables.Msg;
|
||||
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
import ovh.herisson.Clyde.Tables.User;
|
||||
|
||||
@Entity
|
||||
@Data
|
||||
public class Answers {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private int id;
|
||||
|
||||
@CreationTimestamp
|
||||
private Date creation;
|
||||
|
||||
@ManyToOne
|
||||
private Topic topic;
|
||||
|
||||
private String content;
|
||||
|
||||
@OneToOne
|
||||
private User author;
|
||||
|
||||
private boolean anonymous;
|
||||
|
||||
}
|
@ -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<User> members;
|
||||
|
||||
@OneToMany(mappedBy="discussion", orphanRemoval = true, cascade = CascadeType.ALL)
|
||||
private List<Message> 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package ovh.herisson.Clyde.Tables.Msg;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
import ovh.herisson.Clyde.Tables.Course;
|
||||
import ovh.herisson.Clyde.Tables.User;
|
||||
|
||||
@Entity
|
||||
@Data
|
||||
public class Forum {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private int id;
|
||||
|
||||
@ManyToOne
|
||||
private Course course;
|
||||
|
||||
private String name;
|
||||
|
||||
@OneToMany
|
||||
private List<Topic> topics;
|
||||
|
||||
public void addTopic(Topic t) {
|
||||
topics.add(t);
|
||||
}
|
||||
|
||||
@OneToMany
|
||||
private List<User> writers; // User who are authorized to create a post
|
||||
|
||||
@OneToMany
|
||||
private List<User> register;
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package ovh.herisson.Clyde.Tables.Msg;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
import ovh.herisson.Clyde.Tables.User;
|
||||
|
||||
@Entity
|
||||
@Data
|
||||
public class Topic {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private int id;
|
||||
|
||||
private String subject, content;
|
||||
|
||||
@OneToOne
|
||||
private User author;
|
||||
|
||||
@OneToMany(mappedBy = "topic", cascade = CascadeType.ALL)
|
||||
private List<Answers> answers;
|
||||
|
||||
private boolean locked; // true if new messages can be posted
|
||||
}
|
@ -1,7 +1,13 @@
|
||||
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;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
|
||||
@Entity
|
||||
@ -18,8 +24,18 @@ public class User {
|
||||
private String country;
|
||||
private Date birthDate;
|
||||
private String profilePictureUrl;
|
||||
private ovh.herisson.Clyde.Tables.Role role;
|
||||
private Role role;
|
||||
@JsonIgnore
|
||||
private String password;
|
||||
|
||||
////// Extension Messagerie /////
|
||||
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
|
||||
private List<Message> msgs;
|
||||
/////////////////////////////////
|
||||
|
||||
@ManyToMany( mappedBy = "members" )
|
||||
private List<Discussion> discussions;
|
||||
|
||||
public User(String lastName, String firstName, String email, String address,
|
||||
String country, Date birthDate, String profilePictureUrl, Role role, String password)
|
||||
{
|
||||
|
@ -52,7 +52,7 @@ window.addEventListener('hashchange', () => {
|
||||
<a class="icon" href="#Notifications">
|
||||
<div class="fa-solid fa-bell" style="margin-top: 7px; margin-bottom: 3px;"></div>
|
||||
</a></li>
|
||||
<li @click="active=!active" class="option"style="float: right;" title=settings>
|
||||
<li @click="active=!active" class="option"style="float: right;" title=settings>
|
||||
<a class="icon" >
|
||||
<div class="fa-solid fa-gear" style="margin-top: 7px; margin-bottom: 3px;"></div>
|
||||
<div v-if="active" class="dropdown">
|
||||
@ -86,12 +86,9 @@ window.addEventListener('hashchange', () => {
|
||||
</ul>
|
||||
</div>
|
||||
<div class="page">
|
||||
<div style=" margin:50px;">
|
||||
<Suspense>
|
||||
|
||||
<component :is="currentView" />
|
||||
<component :is="currentView" />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -99,19 +96,22 @@ window.addEventListener('hashchange', () => {
|
||||
<style scoped>
|
||||
|
||||
.container{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display:grid;
|
||||
grid-template-columns:[firstCol-start]70px[firstCol-end secondCol-start]auto[endCol];
|
||||
grid-template-rows:[firstRow-start]61px[firstRow-end secondRow-start] auto [endRow];
|
||||
|
||||
grid-template-columns:[firstCol-start]70px[firstCol-end secondCol-start] auto [endCol];
|
||||
grid-template-rows:[firstRow-start] var(--header-size) [firstRow-end secondRow-start] calc(100% - var(--header-size)) [endRow];
|
||||
grid-template-areas:
|
||||
"topBar topBar"
|
||||
"leftBar page";
|
||||
row-gap:0px;
|
||||
column-gap:0px;
|
||||
|
||||
}
|
||||
|
||||
.page {
|
||||
grid-area:page;
|
||||
place-self:center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.topBar{
|
||||
@ -154,7 +154,7 @@ window.addEventListener('hashchange', () => {
|
||||
|
||||
ul.vertical{
|
||||
list-style-type: none;
|
||||
margin-top: 61px;
|
||||
margin-top: var(--header-size);
|
||||
top:0;
|
||||
left:0;
|
||||
padding: 25px 0 0;
|
||||
@ -202,7 +202,7 @@ window.addEventListener('hashchange', () => {
|
||||
left:0;
|
||||
|
||||
position: fixed;
|
||||
height:61px;
|
||||
height:var(--header-size);
|
||||
width: 100%;
|
||||
background-color: rgb(24, 24, 24);
|
||||
}
|
||||
|
175
frontend/src/Apps/Forums.vue
Normal file
175
frontend/src/Apps/Forums.vue
Normal file
@ -0,0 +1,175 @@
|
||||
<!----------------------------------------------------
|
||||
File: Forums.vue
|
||||
Author: Anthony Debucquoy
|
||||
Scope: Extension messagerie
|
||||
Description: Forum des étudiants
|
||||
----------------------------------------------------->
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { getCourses } from '@/rest/courses.js'
|
||||
import { ForumsOfCurrentCourse, getForumsOfCourse, createForum } from '@/rest/forum.js'
|
||||
import { PostsOfCurrentForum, getPostsOfForum } from '@/rest/forum.js'
|
||||
import { fetchedPost, fetchPost } from '@/rest/forum.js'
|
||||
import { getSelf } from '@/rest/Users.js'
|
||||
|
||||
const courses = await reactive(getCourses());
|
||||
const selectedCourse = ref();
|
||||
const selectedForum = ref();
|
||||
const Role = (await getSelf()).role;
|
||||
|
||||
const addForum = ref(false);
|
||||
const addForumName = ref("");
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="app">
|
||||
<div id="ForumSelector">
|
||||
<select id="cours" value="" v-model="selectedCourse" @change="getForumsOfCourse(selectedCourse)">
|
||||
<option v-for="course in courses" :value="course.courseId">{{course.title}}</option>
|
||||
</select>
|
||||
|
||||
<select id="forum" value="" v-model="selectedForum" @change="getPostsOfForum(selectedForum)" v-if="ForumsOfCurrentCourse != null">
|
||||
<option v-for="forum in ForumsOfCurrentCourse" :value=forum.id>{{forum.name}}</option>
|
||||
</select>
|
||||
<button v-if="(Role === 'Admin' || Role === 'Teacher') && ForumsOfCurrentCourse != null " id="createPost" @click="addForum = true">+</button>
|
||||
</div>
|
||||
<div id="PostSelector" v-if="PostsOfCurrentForum != null">
|
||||
<div @click="fetchPost(post.id)" class="postItem" v-for="post in PostsOfCurrentForum" :key="post.id">{{ post.name }}</div>
|
||||
<button v-if="Role === 'Admin' || Role === 'Teacher' " id="createPost" @click="createPost()">+</button>
|
||||
</div>
|
||||
<div id="PostViewer" v-if="fetchedPost != null">
|
||||
<div id="Post">
|
||||
<h1>{{ fetchedPost.subject }}</h1>
|
||||
{{fetchedPost.content}}
|
||||
</div>
|
||||
<div id="Messages">
|
||||
<p v-for="msg in fetchedPost.messages">{{msg.author}} - {{msg.content}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="forumAdder" v-if=addForum @click.self="addForum = false">
|
||||
<div id="addForumForm">
|
||||
<label>New Forum:</label>
|
||||
<input type="text" placeholder="Name" v-model=addForumName @keyup.enter="createForum(selectedCourse, $event.target.value); addForum = false;" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
#forumAdder{
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: #00000022;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
#addForumForm{
|
||||
position: relative;
|
||||
width: 30%;
|
||||
left: calc(50% - 30% / 2);
|
||||
top: calc(50% - 10% / 2);
|
||||
border-radius: 10px;
|
||||
height: 10%;
|
||||
background-color: white;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
#app{
|
||||
display: grid;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
grid-template: 5em auto / 25% 75%;
|
||||
}
|
||||
|
||||
#ForumSelector{
|
||||
background-color: #FFFFFF0E;
|
||||
grid-column: 1 / 3;
|
||||
border-radius: 100px;
|
||||
margin: 10px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
#ForumSelector select{
|
||||
background-color: #ffa000;
|
||||
border: none;
|
||||
margin: 10px;
|
||||
border-radius: 10px;
|
||||
width: 200px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#ForumSelector button{
|
||||
background-color: green;
|
||||
border: none;
|
||||
border-radius: 25%;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#PostSelector{
|
||||
background-color: #FFFFFF0E;
|
||||
border-radius: 0 25px 25px 0;
|
||||
margin: 10px 0 10px 10px;
|
||||
overflow: hidden;
|
||||
padding: 10px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
}
|
||||
|
||||
.postItem{
|
||||
color: darkorange;
|
||||
display: flex;
|
||||
font-family: sans-serif;
|
||||
font-weight: bold;
|
||||
height: 4vh;
|
||||
margin: 5px;
|
||||
border-radius: 0 30px 30px 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid darkorange;
|
||||
}
|
||||
|
||||
.postItem:hover{
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
#PostViewer{
|
||||
background-color: #FFFFFF0E;
|
||||
border-radius: 25px;
|
||||
margin: 10px;
|
||||
|
||||
max-height: 100%;
|
||||
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
#Post{
|
||||
padding: 25px;
|
||||
|
||||
}
|
||||
|
||||
#Messages{
|
||||
padding: 25px;
|
||||
border-top: 3px dotted white;
|
||||
|
||||
}
|
||||
|
||||
#Messages > p {
|
||||
|
||||
background-color: orange;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@ -16,10 +16,9 @@
|
||||
|
||||
|
||||
<template>
|
||||
<div v-for="item of requests">
|
||||
<div style='display:flex; justify-content:center; min-width:1140px;' v-for="item of requests">
|
||||
<div class="bodu" v-if="item.state === 'Pending'">
|
||||
<div class="container">
|
||||
|
||||
<div class="id"><a>{{item.id}}</a></div>
|
||||
<div class="surname"><a>{{item.lastName}}</a></div>
|
||||
<div class="firstname"><a>{{item.firstName}}</a></div>
|
||||
@ -37,10 +36,9 @@
|
||||
height:100px;
|
||||
font-size:20px;
|
||||
display:grid;
|
||||
grid-template-columns:[firstCol-start]100px[firstCol-end secondCol-start]150px[secondCol-end thirdCol-start]200px[thirdCol-end fourthCol-start]150px[fourthCol-end]150px[fifthCol-end]150px[sixthCol-end]150px[endCol];
|
||||
grid-template-columns:10% 14.2% 19% 14.2% 14.2% 14.2% 14.2%;
|
||||
grid-template-areas:
|
||||
"id type surname firstname infos accept refuse";
|
||||
column-gap:10px;
|
||||
|
||||
}
|
||||
|
||||
@ -99,9 +97,9 @@
|
||||
|
||||
}
|
||||
|
||||
.bodu {
|
||||
width:100%;
|
||||
margin-bottom:10px;
|
||||
.bodu {
|
||||
margin-top:2%;
|
||||
width:66%;
|
||||
border:2px solid black;
|
||||
border-radius:9px;
|
||||
background-color:rgb(50,50,50);
|
||||
|
@ -56,10 +56,10 @@
|
||||
|
||||
|
||||
<template>
|
||||
<div class='loginBox'>
|
||||
|
||||
<div class="setup">
|
||||
<div v-if="loginPage">
|
||||
<form @submit.prevent=" login(outputs.email,outputs.password);goBackHome();"class="form">
|
||||
<div class='loginBox' style="margin-top:30%;">
|
||||
<form @submit.prevent="login(outputs.email,outputs.password);goBackHome();"class="form">
|
||||
<h1 style="color:rgb(239,60,168); font-family: sans-serif;">
|
||||
{{i18n("login.guest.signin")}}
|
||||
</h1>
|
||||
@ -74,14 +74,15 @@
|
||||
<div class="register">
|
||||
<a @click="loginPage=!loginPage">{{i18n("login.guest.register")}}</a>
|
||||
</div>
|
||||
<div class="inputBox">
|
||||
<div class="inputBox" style="margin-bottom:35px;">
|
||||
<input type="submit" v-model="submitValue">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div v-else>
|
||||
<form class="form">
|
||||
<div class='loginBox' style="margin-top:30%; margin-bottom:50%;">
|
||||
<form class="form">
|
||||
<h1 style="color:rgb(239,60,168); font-family: sans-serif; text-align:center;">
|
||||
{{i18n("login.guest.welcome")}}
|
||||
</h1>
|
||||
@ -128,9 +129,12 @@
|
||||
<p>{{i18n("login.guest.country")}}</p>
|
||||
<input type="text" v-model="outputs.country">
|
||||
</div>
|
||||
<form novalidate enctype="multipart/form-data" class="inputBox">
|
||||
<p>{{i18n("profile.picture").toUpperCase()}}</p>
|
||||
<input type="file" :disabled="imageSaved" @change="ppData = uploadProfilePicture($event.target.files); imageSaved = true;" accept="image/*">
|
||||
<form class="inputBox"novalidate enctype="multipart/form-data">
|
||||
<p>{{i18n("profile.picture").toUpperCase()}}</p>
|
||||
<label class="browser">
|
||||
Parcourir . . .
|
||||
<input type="file" :disabled="imageSaved" @change="ppData = uploadProfilePicture($event.target.files); imageSaved = true;" accept="image/*">
|
||||
</label>
|
||||
</form>
|
||||
<div class="inputBox">
|
||||
<p>{{i18n("Curriculum").toUpperCase()}}</p>
|
||||
@ -153,35 +157,26 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.Home{
|
||||
position:absolute;
|
||||
display: flex;
|
||||
z-index: 100;
|
||||
padding: 8px 16px;
|
||||
color:rgb(255, 255, 255);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.Home:hover{
|
||||
width:40px;
|
||||
background-color: black;
|
||||
border-radius:6px;
|
||||
color:white;
|
||||
transform: translate(0px ,1px);
|
||||
}
|
||||
.setup {
|
||||
margin-left: auto;
|
||||
margin-right:auto;
|
||||
min-width:400px;
|
||||
|
||||
width:25%;
|
||||
height:60%;
|
||||
}
|
||||
|
||||
.loginBox {
|
||||
background-color: rgb(24,24,24);
|
||||
width: 400px;
|
||||
display:flex;
|
||||
justify-content: center;
|
||||
padding: 40px;
|
||||
border-radius: 20px;
|
||||
border-radius: 5%;
|
||||
box-shadow:0 5px 25px #000000;
|
||||
|
||||
}
|
||||
@ -190,9 +185,8 @@
|
||||
width:100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items:center;
|
||||
gap: 15px;
|
||||
gap: 3%;
|
||||
}
|
||||
|
||||
|
||||
@ -200,12 +194,12 @@
|
||||
|
||||
width:100%;
|
||||
border: none;
|
||||
margin-right: 50px;
|
||||
padding-left: 10px;
|
||||
padding-top:10px;
|
||||
padding-bottom:10px;
|
||||
margin-right: 12.5%;
|
||||
padding-left: 2.5%;
|
||||
padding-top:2.5%;
|
||||
padding-bottom:2.5%;
|
||||
outline:none;
|
||||
border-radius: 4px;
|
||||
border-radius: 10px;
|
||||
font-size:1.35em;
|
||||
}
|
||||
|
||||
@ -220,8 +214,9 @@
|
||||
|
||||
.register{
|
||||
color:rgb(239,60,168);
|
||||
width: 100%;
|
||||
display:flex;
|
||||
width:70%;
|
||||
margin-bottom:20px;
|
||||
margin-top:20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -250,6 +245,21 @@ input[type=submit],button,select{
|
||||
|
||||
}
|
||||
|
||||
input[type=file]{
|
||||
display:none;
|
||||
}
|
||||
|
||||
.browser{
|
||||
display:inline-block;
|
||||
cursor:pointer;
|
||||
border-radius:20px;
|
||||
background-color:rgb(239,60,168);
|
||||
padding:5%;
|
||||
font-size:1.35em;
|
||||
font-family:sans-serif;
|
||||
background:#FFFFFF;
|
||||
}
|
||||
|
||||
button:active ,.switchpage:active{
|
||||
opacity:0.8;
|
||||
|
||||
|
@ -105,7 +105,8 @@
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="createMod">
|
||||
<form class="listElement">
|
||||
<form class="listElement" style="width:40%;margin-right:auto;margin-left:auto;">
|
||||
|
||||
<div style="margin-bottom:20px;">
|
||||
{{i18n("name")}} :
|
||||
<input v-model="toAdd.title">
|
||||
@ -125,7 +126,7 @@
|
||||
</form>
|
||||
</div>
|
||||
<div v-if="deleteMod">
|
||||
<form class="listElement">
|
||||
<form class="listElement" style="width:40%;margin-right:auto;margin-left:auto;">
|
||||
<div style="margin-bottom:20px;">
|
||||
{{i18n("courses.toDelete")}} :
|
||||
<select style="max-width:200px;" class="teacher" v-model="toRemove">
|
||||
@ -138,7 +139,7 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div v-if="!createMod && !deleteMod" v-for="item in curriculum" :key="item.title">
|
||||
<div v-if="!createMod && !deleteMod" v-for="item in curriculum" :key="item.title" style="width:50%;margin-left:auto; margin-right:auto;">
|
||||
<div v-if="editElementID !== item.title" style ="padding:15px 15px 15px 15px;">
|
||||
<button @click="editElementID = item.title; setModify(item); ">
|
||||
{{i18n("courses.modify")}}
|
||||
@ -148,7 +149,8 @@
|
||||
<button @click="editElementID= '';patchCourse(item)"> {{i18n("courses.confirm")}} </button>
|
||||
<button @click="editElementID= '';"> {{i18n("courses.back")}} </button>
|
||||
</div>
|
||||
<div class="listElement" >
|
||||
<div class="listElement" >
|
||||
|
||||
<div class="containerElement" v-if="editElementID !== item.title" >
|
||||
|
||||
<div class="name"> {{item.title}} </div>
|
||||
@ -172,17 +174,27 @@
|
||||
<style scoped>
|
||||
.body {
|
||||
width:100%;
|
||||
margin-bottom:10px;
|
||||
margin-top:3.5%;
|
||||
}
|
||||
|
||||
.infosContainer {
|
||||
min-width:350px;
|
||||
padding-bottom:50px;
|
||||
border:2px solid black;
|
||||
font-size:25px;
|
||||
color:white;
|
||||
padding:20px;
|
||||
background-color:rgb(50,50,50);
|
||||
border-radius:20px;
|
||||
}
|
||||
|
||||
.containerElement{
|
||||
justify-content:center;
|
||||
justify-content:center;
|
||||
display:grid;
|
||||
grid-template-columns:350px 350px 200px;
|
||||
grid-template-columns:38.8% 38.8% 22.4%;
|
||||
grid-template-areas:
|
||||
"name teacher credits";
|
||||
column-gap:10px;
|
||||
|
||||
}
|
||||
column-gap:10px; }
|
||||
|
||||
.name {
|
||||
grid-area:name;
|
||||
@ -200,6 +212,7 @@
|
||||
}
|
||||
|
||||
.listElement{
|
||||
min-width:625px;
|
||||
border:2px solid black;
|
||||
font-size:25px;
|
||||
color:white;
|
||||
@ -207,6 +220,7 @@
|
||||
background-color:rgb(50,50,50);
|
||||
border-radius:20px;
|
||||
margin-bottom:10px;
|
||||
|
||||
}
|
||||
|
||||
.modify{
|
||||
@ -255,10 +269,11 @@
|
||||
}
|
||||
|
||||
.listTitle{
|
||||
min-width:380px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width:400px;
|
||||
width:25%;
|
||||
margin-left:auto;
|
||||
margin-right:auto;
|
||||
border:2px solid black;
|
||||
@ -266,7 +281,8 @@
|
||||
color:white;
|
||||
padding:20px;
|
||||
background-color:rgb(50,50,50);
|
||||
border-radius:20px;margin-bottom:10px;
|
||||
border-radius:20px;
|
||||
margin-bottom:10px;
|
||||
|
||||
button:hover{
|
||||
opacity:0.8;
|
||||
|
213
frontend/src/Apps/Msg.vue
Normal file
213
frontend/src/Apps/Msg.vue
Normal file
@ -0,0 +1,213 @@
|
||||
<!----------------------------------------------------
|
||||
File: Msg.vue
|
||||
Author: Anthony Debucquoy
|
||||
Scope: Extension messagerie
|
||||
Description: Main msg page
|
||||
----------------------------------------------------->
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { discussionsList, currentDiscussion, fetchDiscussion, createDiscussion, sendMessage, updateDiscussionName, invite, removeMember} from '@/rest/msg.js'
|
||||
|
||||
const msgContent = ref("");
|
||||
const addMember = ref(false);
|
||||
const currentTitle = ref("");
|
||||
|
||||
function formatTime(date){
|
||||
return date.getHours() + ":" + date.getMinutes() + " " + date.getDate() + "/" + date.getMonth();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="msg">
|
||||
<div id="discList">
|
||||
<div @click="fetchDiscussion(discussion.id).then(e => currentTitle = currentDiscussion.name)" class="discItem" v-for="discussion in discussionsList" :key="discussion.id">{{ discussion.name }}</div>
|
||||
<button id="createDiscussion" @click="createDiscussion('New Discussion')">+</button>
|
||||
</div>
|
||||
<div id="discussion" v-if="currentDiscussion.length != 0">
|
||||
<h1 id=msgName ><input class="InputTitle" type="text" @change="updateDiscussionName(currentDiscussion.id, currentTitle)" v-model="currentTitle"></h1>
|
||||
<div id=msgs>
|
||||
<div class="msg" v-for="msg in currentDiscussion.msgs" :sender="msg.sender" :key="msg.id">
|
||||
{{ msg.content }}<br/>
|
||||
<span class="sender"><span v-if="!msg.sender">{{ msg.author.firstName }} {{ msg.author.lastName.toUpperCase() }}</span> {{formatTime(new Date(msg.created))}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id=messageBox>
|
||||
<input type="text" @keyup.enter="sendMessage(currentDiscussion.id, msgContent, null); msgContent = ''" v-model="msgContent">
|
||||
<input type="submit" @click="sendMessage(currentDiscussion.id, msgContent, null); msgContent = ''" value="send">
|
||||
</div>
|
||||
</div>
|
||||
<div id="members" v-if="currentDiscussion.length != 0">
|
||||
<div class="memberItem" v-for="member in currentDiscussion.members" @click="removeMember(currentDiscussion.id, member.regNo)" :key="member.id"><span>{{ member.firstName }} {{ member.lastName.toUpperCase() }}</span></div>
|
||||
<input type=text id="addMembers" @focus="addMember = true" @blur="addMember = false;$event.target.value = ''" @change="invite(currentDiscussion.id, $event.target.value)" :placeholder="addMember ? 'Regno' : '+'"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
div#msg{
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 20% auto 10%;
|
||||
}
|
||||
|
||||
div#discList{
|
||||
margin: 30px 0 30px 30px;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
div#members{
|
||||
margin: 30px 0;
|
||||
border-radius: 10px 0 0 10px;
|
||||
background-color: red;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
padding: 10px 0 0 10px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.InputTitle{
|
||||
all: inherit;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.discItem{
|
||||
color: darkorange;
|
||||
display: flex;
|
||||
font-family: sans-serif;
|
||||
font-weight: bold;
|
||||
height: 4vh;
|
||||
margin: 5px;
|
||||
border-radius: 0 30px 30px 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid darkorange;
|
||||
}
|
||||
|
||||
.memberItem{
|
||||
color: darkorange;
|
||||
display: flex;
|
||||
font-family: sans-serif;
|
||||
font-weight: bold;
|
||||
height: 4vh;
|
||||
margin: 5px;
|
||||
border-radius: 30px 0 0 30px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid darkorange;
|
||||
}
|
||||
|
||||
.memberItem:hover span{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.memberItem:hover{
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.memberItem:hover:before{
|
||||
color: white;
|
||||
content: "X"
|
||||
}
|
||||
|
||||
#createDiscussion{
|
||||
height: 4vh;
|
||||
margin: 5px;
|
||||
color: white;
|
||||
background-color: green;
|
||||
border-radius: 0 30px 30px 0;
|
||||
border: none;
|
||||
font-weight: 900;
|
||||
font-size: 2em;
|
||||
}
|
||||
#addMembers{
|
||||
height: 4vh;
|
||||
margin: 5px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
background-color: green;
|
||||
border-radius: 30px 0 0 30px;
|
||||
border: none;
|
||||
font-weight: 900;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
div#discussion{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 30px;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#msgName{
|
||||
text-align: center;
|
||||
display: block;
|
||||
background-color: #2a1981;
|
||||
border-radius: 5px;
|
||||
color: white;
|
||||
width: 75%;
|
||||
margin: 30px auto;
|
||||
}
|
||||
|
||||
.discItem:hover{
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
#msgs{
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.msg {
|
||||
background-color: aliceblue;
|
||||
font-family: sans-serif;
|
||||
margin: 10px;
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
max-width: 50%;
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
.sender{
|
||||
display: inline-block;
|
||||
color: gray;
|
||||
font-size: 0.5em;
|
||||
}
|
||||
|
||||
.msg[sender=true]{
|
||||
background-color: darkorange;
|
||||
align-self: end;
|
||||
}
|
||||
|
||||
#messageBox{
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#messageBox input[type="text"]{
|
||||
all: inherit;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
#messageBox input[type="submit"]{
|
||||
border: inherit;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
</style>
|
@ -210,11 +210,11 @@
|
||||
<style scoped>
|
||||
|
||||
.container{
|
||||
|
||||
min-width:675px;
|
||||
display:grid;
|
||||
grid-template-columns:200px 900px;
|
||||
grid-template-columns:10vw 50vw;
|
||||
grid-template-rows:200px auto;
|
||||
column-gap:30px;
|
||||
column-gap:2.7%;
|
||||
row-gap:45px;
|
||||
grid-template-areas:
|
||||
"profilPic globalInfos"
|
||||
@ -222,6 +222,7 @@
|
||||
}
|
||||
|
||||
.profilPic{
|
||||
width:100%;
|
||||
grid-area:profilPic;
|
||||
}
|
||||
|
||||
@ -242,13 +243,17 @@
|
||||
grid-area:minfos;
|
||||
}
|
||||
.body {
|
||||
min-width:960px;
|
||||
width:100%;
|
||||
margin-bottom:10px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
margin-top:5%;
|
||||
}
|
||||
.containerElement{
|
||||
justify-content:center;
|
||||
display:grid;
|
||||
grid-template-columns:350px 350px 200px;
|
||||
grid-template-columns:38.8% 38.8% 22.4%;
|
||||
grid-template-areas:
|
||||
"name teacher credits";
|
||||
column-gap:10px;
|
||||
@ -271,10 +276,11 @@
|
||||
}
|
||||
|
||||
.listTitle{
|
||||
min-width:197px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width:200px;
|
||||
width:8vw;
|
||||
margin-left:auto;
|
||||
margin-right:auto;
|
||||
border:2px solid black;
|
||||
@ -286,6 +292,7 @@
|
||||
}
|
||||
|
||||
.listElement{
|
||||
min-width:625px;
|
||||
border:2px solid black;
|
||||
font-size:25px;
|
||||
color:white;
|
||||
@ -296,6 +303,7 @@
|
||||
}
|
||||
|
||||
.infosContainer {
|
||||
min-width:350px;
|
||||
padding-bottom:50px;
|
||||
border:2px solid black;
|
||||
font-size:25px;
|
||||
|
@ -5,8 +5,8 @@
|
||||
|
||||
const users = await getStudents();
|
||||
</script>
|
||||
<template>
|
||||
<div v-for="item in users">
|
||||
<template style="margin-top:5%;">
|
||||
<div style="display:flex; justify-content:center; " v-for="item in users">
|
||||
<div class="bodu">
|
||||
<div class="container">
|
||||
<div class="status"><a style="margin-left:30px">{{item.status}}</a></div>
|
||||
@ -25,10 +25,9 @@
|
||||
height:100px;
|
||||
font-size:30px;
|
||||
display:grid;
|
||||
grid-template-columns:250px 250px 250px 250px 150px;
|
||||
grid-template-columns:21.7% 21.7% 21.7% 21.7% 13.1%;
|
||||
grid-template-areas:
|
||||
"status option surname firstname infos";
|
||||
column-gap:10px;
|
||||
|
||||
}
|
||||
|
||||
@ -42,21 +41,6 @@
|
||||
align-self:center;
|
||||
}
|
||||
|
||||
.refuse{
|
||||
grid-area:refuse;
|
||||
align-self:center;
|
||||
}
|
||||
|
||||
.titles {
|
||||
grid-area:titles;
|
||||
background-color:rgb(215,215,215);
|
||||
}
|
||||
.id{
|
||||
grid-area:id;
|
||||
margin-left:40px;
|
||||
align-self:center;
|
||||
}
|
||||
|
||||
.status{
|
||||
grid-area:status;
|
||||
align-self:center;
|
||||
@ -81,15 +65,15 @@
|
||||
button{
|
||||
font-size:15px;
|
||||
height:50px;
|
||||
width:100px;
|
||||
width:75%;
|
||||
border:none;
|
||||
border-radius:20px;
|
||||
|
||||
}
|
||||
|
||||
.bodu {
|
||||
width:100%;
|
||||
margin-bottom:10px;
|
||||
margin-top:2%;
|
||||
width:66%;
|
||||
border:2px solid black;
|
||||
border-radius:9px;
|
||||
background-color:rgb(50,50,50);
|
||||
|
@ -7,8 +7,8 @@
|
||||
const users = await getAllUsers();
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div v-for="item in users">
|
||||
<template style="margin-top:5%;">
|
||||
<div style="display:flex; justify-content:center; min-width:1140px;" v-for="item in users">
|
||||
<div class="bodu">
|
||||
<div class="container">
|
||||
<div class="role"><a style="margin-left:30px">{{i18n(item.role)}}</a></div>
|
||||
@ -22,23 +22,20 @@
|
||||
|
||||
<style scoped>
|
||||
.container{
|
||||
justify-content:center;
|
||||
align-items:center;
|
||||
color:white;
|
||||
height:100px;
|
||||
font-size:30px;
|
||||
display:grid;
|
||||
grid-template-columns:250px 250px 250px 150px;
|
||||
grid-template-columns:27.7% 27.7% 27.7% 16.9%;
|
||||
grid-template-areas:
|
||||
"role surname firstname infos";
|
||||
column-gap:10px;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.infos {
|
||||
|
||||
grid-area:infos;
|
||||
align-items:center;
|
||||
align-self:center;
|
||||
}
|
||||
|
||||
.role {
|
||||
@ -67,20 +64,18 @@
|
||||
button{
|
||||
font-size:15px;
|
||||
height:50px;
|
||||
width:100px;
|
||||
width:75%;
|
||||
border:none;
|
||||
border-radius:20px;
|
||||
|
||||
}
|
||||
|
||||
.bodu {
|
||||
width:100%;
|
||||
margin-bottom:10px;
|
||||
margin-top:2%;
|
||||
width:66%;
|
||||
border:2px solid black;
|
||||
border-radius:9px;
|
||||
background-color:rgb(50,50,50);
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
|
@ -1,4 +1,15 @@
|
||||
:root {
|
||||
--header-size: 61px;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: rgb(53, 25, 60);
|
||||
margin:0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ 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"
|
||||
import Forums from '@/Apps/Forums.vue'
|
||||
|
||||
const apps = {
|
||||
'/login': LoginPage,
|
||||
@ -17,12 +19,14 @@ const apps = {
|
||||
'/manage-courses' : Courses,
|
||||
'/users-list' : Users,
|
||||
'/students-list' : Students,
|
||||
'/msg' : Msg,
|
||||
'/forums': Forums,
|
||||
}
|
||||
|
||||
const appsList = {
|
||||
'Msg': { path: '#/msg', icon: 'fa-comment', text: i18n("app.messages") },
|
||||
'Notification': { path: '#/notifs', icon: 'fa-bell', text: i18n("app.notifications") },
|
||||
'Forum': { path: '#/forum', icon: 'fa-envelope', text: i18n("app.forum") },
|
||||
'Forum': { path: '#/forums', icon: 'fa-envelope', text: i18n("app.forum") },
|
||||
'Schedule': { path: '#/schedule', icon: 'fa-calendar-days', text: i18n("app.schedules") },
|
||||
'Inscription': { path: '#/inscription', icon: 'fa-users', text: i18n("app.inscription.requests") },
|
||||
'ManageCourses': { path: '#/manage-courses', icon: 'fa-book', text: i18n("app.manage.courses") },
|
||||
|
65
frontend/src/rest/forum.js
Normal file
65
frontend/src/rest/forum.js
Normal file
@ -0,0 +1,65 @@
|
||||
import { ref } from 'vue'
|
||||
import { restGet, restPost, restDelete, restPatch } from './restConsumer.js'
|
||||
|
||||
/**
|
||||
* List forums of a course
|
||||
*/
|
||||
export async function getForumsOfCourse(id){
|
||||
ForumsOfCurrentCourse.value = await restGet("/forums/" + id)
|
||||
}
|
||||
|
||||
export const ForumsOfCurrentCourse = ref();
|
||||
|
||||
export function createForum(id, name){
|
||||
restPost("/forums/" + id, {name: name}).then(_ => getForumsOfCourse(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* List post of a specified forum
|
||||
*/
|
||||
export async function getPostsOfForum(id){
|
||||
PostsOfCurrentForum.value = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Post~1"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Post~2"
|
||||
},
|
||||
]
|
||||
// PostsCurrentForum.value = await restGet("/forum/" + id);
|
||||
}
|
||||
|
||||
export const PostsOfCurrentForum = ref();
|
||||
|
||||
/**
|
||||
* Get a post and its responses
|
||||
*/
|
||||
export async function fetchPost(id){
|
||||
fetchedPost.value = {
|
||||
id: 1,
|
||||
subject: "This is the subject of the post",
|
||||
content: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.",
|
||||
messages: [
|
||||
{
|
||||
id: 1,
|
||||
author: "author~1",
|
||||
content: "J'ai pas copris le message !"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
author: "author~2",
|
||||
content: "tu as fait une faute dans ton message..."
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
author: null,
|
||||
content: "I'm anonymous noww..."
|
||||
}
|
||||
]
|
||||
}
|
||||
// fetchedPost.value = await restGet("/forum/post/" + id);
|
||||
}
|
||||
|
||||
export const fetchedPost = ref();
|
60
frontend/src/rest/msg.js
Normal file
60
frontend/src/rest/msg.js
Normal file
@ -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();
|
Reference in New Issue
Block a user