21 Commits

Author SHA1 Message Date
76c3b76153 Merge pull request 'Messaging system' (#150) from tonitch/Clyde:msg into master
All checks were successful
Build and test backend / Build-backend (push) Successful in 3m45s
deploy to production / deploy-frontend (push) Successful in 26s
deploy to production / deploy-backend (push) Successful in 1m35s
Build and test FrontEnd / Build-frontend (push) Successful in 24s
Reviewed-on: #150
2024-04-09 17:08:57 +02:00
5bb7606721 signing and commenting
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 2m21s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 25s
2024-04-05 12:54:51 +02:00
b049c46571 Avoid title reseting mid modification
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 3m1s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 27s
2024-04-05 11:45:47 +02:00
7ca5c34afe adding timestamp
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 2m9s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 25s
2024-04-02 11:08:58 +02:00
2b9bdf8dac fixup! show sender on message
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 2m8s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 24s
2024-04-02 10:18:50 +02:00
ccb954e348 show sender on message
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 2m9s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 24s
2024-04-02 10:05:17 +02:00
ce56e37a33 redesign send bar
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 2m54s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 27s
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
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 3m35s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 26s
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
All checks were successful
Build and test backend / Build-backend (push) Successful in 2m26s
deploy to production / deploy-frontend (push) Successful in 27s
deploy to production / deploy-backend (push) Successful in 2m5s
Build and test FrontEnd / Build-frontend (push) Successful in 24s
Reviewed-on: #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
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 1m48s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 24s
2024-03-23 23:56:24 +01:00
9de4b06e75 Login 'fixed'
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 2m53s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 26s
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
acd1262955 Make app use full space
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 1m50s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 24s
2024-03-22 13:56:04 +01:00
21 changed files with 832 additions and 78 deletions

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

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

@ -1,7 +1,11 @@
package ovh.herisson.Clyde.Tables;
import jakarta.persistence.*;
import ovh.herisson.Clyde.Tables.Msg.Discussion;
import ovh.herisson.Clyde.Tables.Msg.Message;
import java.util.Date;
import java.util.List;
@Entity
@ -20,6 +24,15 @@ public class User {
private String profilePictureUrl;
private ovh.herisson.Clyde.Tables.Role role;
private String password;
////// Extension Messagerie /////
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
private List<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

@ -87,8 +87,7 @@ window.addEventListener('hashchange', () => {
</div>
<div class="page">
<Suspense>
<component :is="currentView" />
<component :is="currentView" />
</Suspense>
</div>
</div>
@ -97,6 +96,8 @@ 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];
@ -109,6 +110,8 @@ window.addEventListener('hashchange', () => {
.page {
grid-area:page;
height: 100%;
width: 100%;
place-self:center;
}

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

@ -55,10 +55,10 @@
</script>
<template style="background-color:rgba(255,255,255,0.05); border-radius:50px" >
<div class='loginBox'>
<template>
<div class="setup">
<div v-if="loginPage">
<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")}}
@ -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 v-else>
<form class="form">
</div>
<div v-else>
<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,17 +157,25 @@
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.setup {
margin-left: auto;
margin-right:auto;
min-width:400px;
width:25%;
height:60%;
}
.loginBox {
background-color: rgb(24,24,24);
width: 100%;
height:100%;
display:flex;
justify-content: center;
padding: 10%;
border-radius: 5%;
box-shadow:0 5px 25px #000000;
@ -202,8 +214,9 @@
.register{
color:rgb(239,60,168);
width: 100%;
display:flex;
width:70%;
margin-bottom:20px;
margin-top:20px;
cursor: pointer;
}
@ -232,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")}}
@ -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
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,11 @@
body {
background-color: rgb(53, 25, 60);
margin:0;
width: 100vw;
height: 100vh;
}
#app {
width: 100%;
height: 100%;
}

View File

@ -9,6 +9,7 @@ import Profil from "@/Apps/Profil.vue"
import Courses from "@/Apps/ManageCourses.vue"
import Users from "@/Apps/UsersList.vue"
import Students from "@/Apps/StudentsList.vue"
import Msg from "@/Apps/Msg.vue"
const apps = {
'/login': LoginPage,
@ -17,6 +18,7 @@ const apps = {
'/manage-courses' : Courses,
'/users-list' : Users,
'/students-list' : Students,
'/msg' : Msg,
}
const appsList = {

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