1
0
forked from PGL/Clyde

34 Commits

Author SHA1 Message Date
7b0d76dae8 Merge remote-tracking branch 'origin/master' into forum 2024-04-09 17:28:59 +02:00
61e269eb27 Linking front for forum creation 2024-04-09 17:27:26 +02:00
76c3b76153 Merge pull request 'Messaging system' (#150) from tonitch/Clyde:msg into master
Reviewed-on: PGL/Clyde#150
2024-04-09 17:08:57 +02:00
5f483216b9 indev 2024-04-08 10:02:03 +02:00
5a4d066c45 Forum and topic getter and creator endpoints 2024-04-07 18:16:05 +02:00
f9bcff6d4f Avoid password leakage
The password should never be parsed to json ever so can be safely
ignored.
2024-04-07 14:46:14 +02:00
5bb7606721 signing and commenting 2024-04-05 12:54:51 +02:00
b049c46571 Avoid title reseting mid modification 2024-04-05 11:45:47 +02:00
7bd745fd5e Adding frontend visual side 2024-04-05 08:57:19 +02:00
a96609d2ef Page to right size 2024-04-03 14:58:04 +02:00
cb750b8505 Database creation (Tables) 2024-04-02 21:40:52 +02:00
7ca5c34afe adding timestamp 2024-04-02 11:08:58 +02:00
2b9bdf8dac fixup! show sender on message 2024-04-02 10:18:50 +02:00
ccb954e348 show sender on message 2024-04-02 10:05:17 +02:00
ce56e37a33 redesign send bar 2024-03-29 14:31:22 +01:00
1c61a356a4 update when message sent 2024-03-29 14:31:08 +01:00
2bdffe6ab4 remove member of a discussion 2024-03-29 14:30:46 +01:00
729d1ad504 adding members management 2024-03-27 23:54:59 +01:00
1522d74ed3 messsaging on the frontend 2024-03-27 19:52:48 +01:00
b4499e04c7 Managing the message with backend
Using DTO to change the way discussions and message are sent to the
client
2024-03-27 19:50:52 +01:00
914f6bdf36 fix for new discussions not appearing right away 2024-03-25 09:43:45 +01:00
66e7fa24a1 Adding the discussion architectures and creating new discussions 2024-03-25 00:08:44 +01:00
7b9f021c24 first version of the frontend for messages 2024-03-24 22:33:17 +01:00
2dfa0a0ee0 base msg 2024-03-24 22:33:14 +01:00
9e0db361b8 Merge pull request 'Make app use full space' (#148) from tonitch/front/fullSpaceApp into master
Some checks failed
Build and test backend / Build-backend (push) Has been cancelled
deploy to production / deploy-frontend (push) Has been cancelled
deploy to production / deploy-backend (push) Has been cancelled
Build and test FrontEnd / Build-frontend (push) Has been cancelled
Reviewed-on: PGL/Clyde#148
Reviewed-by: Wal <karpinskiwal@gmail.com>
Reviewed-by: Maxime <231026@umons.ac.be>
2024-03-24 22:32:46 +01:00
7a13d412f1 Full screen apps 2024-03-23 23:56:24 +01:00
9de4b06e75 Login 'fixed' 2024-03-23 13:57:46 +01:00
123fa97611 Merge branch 'wal/front/temp' into tonitch/front/fullSpaceApp 2024-03-22 20:15:45 +01:00
1fad792be7 Just to merge 2024-03-22 20:14:20 +01:00
acd1262955 Make app use full space 2024-03-22 13:56:04 +01:00
3d6941ab93 adding cdn to CI 2024-03-18 21:39:31 +01:00
de72bd800c CI fix 2024-03-18 21:13:00 +01:00
b465dcfa92 Merge pull request 'Clean le projet' (#146) from Leo/Backend/Cleaning into master
Reviewed-on: PGL/Clyde#146
Reviewed-by: Wal <karpinskiwal@gmail.com>
Reviewed-by: Debucquoy Anthony <d.tonitch@gmail.com>
2024-03-18 21:06:02 +01:00
73f3df0bc6 Clean le projet 2024-03-18 20:54:16 +01:00
36 changed files with 1363 additions and 122 deletions

View File

@ -45,7 +45,7 @@ jobs:
distribution: 'temurin'
- uses: gradle/gradle-build-action@v3
- name: building
run: ./gradlew backend:build
run: ./gradlew backend:build -x test
- name: pushing to the server
run: |
echo "${{ secrets.SSH_KEY }}" > key

View File

@ -1,5 +1,6 @@
FROM eclipse-temurin:21-jdk-alpine
VOLUME /tmp
VOLUME /cdn
ENV SPRING_PROFILES_ACTIVE=prod
COPY build/libs/backend-0.0.1-SNAPSHOT.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]

View File

@ -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")

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -49,11 +49,11 @@ public class MockController {
// user part
User herobrine = new User("brine","hero","admin@admin.com","in your WalLs","ShadowsLand",new Date(0), null,Role.Admin,passwordEncoder.encode("admin"));
User joe = new User("Mama","Joe","student@student.com","roundabout","DaWarudo",new Date(0), null,Role.Student,passwordEncoder.encode("student"));
User meh = new User("Inspiration","lackOf","secretary@secretary.com","a Box","the street",new Date(0), null,Role.Secretary,passwordEncoder.encode("secretary"));
User joke = new User("CthemBalls","Lemme","teacher@teacher.com","lab","faculty",new Date(0), null,Role.Teacher,passwordEncoder.encode("teacher"));
User jojo = new User("hhoo","yeay","teacher2@teacher2.com","lab","faculty",new Date(0), null,Role.Teacher,passwordEncoder.encode("teacher"));
User herobrine = new User("brine","hero","admin@admin.com","behind","ShadowsLand",new Date(0), null,Role.Admin,passwordEncoder.encode("admin"));
User joe = new User("Mama","Joe","student@student.com","roundabout","England",new Date(0), null,Role.Student,passwordEncoder.encode("student"));
User meh = new User("Polo","Marco","secretary@secretary.com","a Box","Monaco",new Date(0), null,Role.Secretary,passwordEncoder.encode("secretary"));
User joke = new User("Gaillard","Corentin","teacher@teacher.com","lab","faculty",new Date(0), null,Role.Teacher,passwordEncoder.encode("teacher"));
User jojo = new User("Bridoux","Justin","teacher2@teacher2.com","lab","faculty",new Date(0), null,Role.Teacher,passwordEncoder.encode("teacher"));
User lena = new User("Louille","Lena","inscriptionService@InscriptionService.com","no","yes",new Date(0), null,Role.InscriptionService,passwordEncoder.encode("inscriptionService"));
mockUsers = new ArrayList<>(Arrays.asList(herobrine,joe,meh,joke,lena,jojo));
@ -72,7 +72,7 @@ public class MockController {
Course progra1 = new Course(5,"Programmation et algorithmique 1",joke);
Course chemistry1 = new Course(12, "Thermochimie",joke);
Course psycho1 = new Course(21, "rien faire t'as cru c'est psycho",joke);
Course psycho1 = new Course(21, "Neuroreaction of isolated brain cells",joke);
Course commun = new Course(2, "cours commun",joke);
courseService.save(progra1);

View File

@ -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
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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> {
}

View File

@ -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> {}

View File

@ -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> {
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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
}

View File

@ -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)
{

View File

@ -88,14 +88,14 @@ public class UserControllerTest {
tokenService.saveToken(godToken);
//Can god post herobrine himself ?
User herobrine = new User("brine","hero","herobrine@admin.com","in your WalLs","ShadowsLand",new Date(0), null,Role.Student,"test");
User herobrine = new User("brine","hero","herobrine@student.com","in your WalLs","ShadowsLand",new Date(0), null,Role.Student,"test");
with().body(herobrine).contentType(ContentType.JSON).header("Authorization", godToken.getToken()).when().request("POST", "/user").then().statusCode(201);
userRepository.delete(herobrine);
//Can noob post herobrine without authorizations (no)
User noob = new User("boon","noob","noob@admintkt.com","everywhere","every",new Date(0), null, Role.Student,"noob");
User noob = new User("boon","noob","noob@student.com","everywhere","every",new Date(0), null, Role.Student,"noob");
Token noobToken = new Token(noob, tokenService.generateNewToken(), new Date());
userRepository.save(noob);
tokenService.saveToken(noobToken);
@ -105,7 +105,7 @@ public class UserControllerTest {
@Test
public void userGetTest(){
User herobrine = new User("brine","hero","herobrine@admin.com","in your WalLs","ShadowsLand",new Date(0), null,Role.Student,"test");
User herobrine = new User("brine","hero","herobrine@student.com","in your WalLs","ShadowsLand",new Date(0), null,Role.Student,"test");
userRepository.save(herobrine);
Token t = new Token(herobrine, tokenService.generateNewToken(), new Date());

View File

@ -70,7 +70,7 @@ class TokenServiceTest {
ArrayList<Token> tokenList = new ArrayList<>();
GregorianCalendar gc = new GregorianCalendar();
User malveillant = new User("mechant", "veutdestoken", "donnezmoidestoken@mail.com", "secret", "secret", null, null, null, "secret");
User malveillant = new User("Cargo", "John", "CargoJ@mail.com", "secret", "secret", null, null, null, "secret");
userRepository.save(malveillant);
for (int i = 0; i < 20; i++){

View File

@ -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);
}

View 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>

View File

@ -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);

View File

@ -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">
<form class="inputBox"novalidate enctype="multipart/form-data">
<p>{{i18n("profile.picture").toUpperCase()}}</p>
<input type="file" :disabled="imageSaved" @change="ppData = uploadProfilePicture($event.target.files); imageSaved = true;" accept="image/*">
<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;

View File

@ -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")}}
@ -149,6 +150,7 @@
<button @click="editElementID= '';"> {{i18n("courses.back")}} </button>
</div>
<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
View 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>

View File

@ -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;

View File

@ -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);

View File

@ -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>

View File

@ -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%;
}

View File

@ -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") },

View 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
View 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();