1
0
forked from PGL/Clyde

20 Commits

Author SHA1 Message Date
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
26 changed files with 730 additions and 113 deletions

View File

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

View File

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

View File

@ -16,6 +16,8 @@ repositories {
} }
dependencies { dependencies {
compileOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok")
implementation("org.springframework.boot:spring-boot-starter-jdbc") implementation("org.springframework.boot:spring-boot-starter-jdbc")
implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-mail") implementation("org.springframework.boot:spring-boot-starter-mail")

View File

@ -0,0 +1,26 @@
package ovh.herisson.Clyde.DTO.Msg;
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,23 @@
package ovh.herisson.Clyde.DTO.Msg;
import lombok.AllArgsConstructor;
import lombok.Data;
import ovh.herisson.Clyde.Tables.User;
import ovh.herisson.Clyde.Tables.Msg.Message;
@Data
@AllArgsConstructor
public class MessagesDTO {
private long id;
private String content;
private User author;
private boolean sender;
//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);
}
}

View File

@ -49,11 +49,11 @@ public class MockController {
// user part // 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 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","DaWarudo",new Date(0), null,Role.Student,passwordEncoder.encode("student")); User joe = new User("Mama","Joe","student@student.com","roundabout","England",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 meh = new User("Polo","Marco","secretary@secretary.com","a Box","Monaco",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 joke = new User("Gaillard","Corentin","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 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")); 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)); 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 progra1 = new Course(5,"Programmation et algorithmique 1",joke);
Course chemistry1 = new Course(12, "Thermochimie",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); Course commun = new Course(2, "cours commun",joke);
courseService.save(progra1); courseService.save(progra1);

View File

@ -0,0 +1,114 @@
package ovh.herisson.Clyde.EndPoints.Msg;
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){
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,14 @@
package ovh.herisson.Clyde.Repositories.Msg;
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,8 @@
package ovh.herisson.Clyde.Repositories.Msg;
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,31 @@
package ovh.herisson.Clyde.Services.Msg;
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));
}
public Iterable<Discussion> getOwned(User author){
return discRepo.findByMembership(author.getRegNo());
}
public Discussion CreateMessage(Discussion disc, Message msg){
disc.addMessage(msg);
return discRepo.save(disc);
}
}

View File

@ -0,0 +1,62 @@
package ovh.herisson.Clyde.Tables.Msg;
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,46 @@
package ovh.herisson.Clyde.Tables.Msg;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.CascadeType;
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 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;
@ManyToOne
private User author;
public User getAuthor() {
return author;
}
@OneToOne
private Message response;
@ManyToOne(optional = false)
@JsonIgnore
private Discussion discussion;
//TODO: Attachment
}

View File

@ -1,7 +1,11 @@
package ovh.herisson.Clyde.Tables; package ovh.herisson.Clyde.Tables;
import jakarta.persistence.*; import jakarta.persistence.*;
import ovh.herisson.Clyde.Tables.Msg.Discussion;
import ovh.herisson.Clyde.Tables.Msg.Message;
import java.util.Date; import java.util.Date;
import java.util.List;
@Entity @Entity
@ -20,6 +24,15 @@ public class User {
private String profilePictureUrl; private String profilePictureUrl;
private ovh.herisson.Clyde.Tables.Role role; private ovh.herisson.Clyde.Tables.Role role;
private String password; 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, public User(String lastName, String firstName, String email, String address,
String country, Date birthDate, String profilePictureUrl, Role role, String password) String country, Date birthDate, String profilePictureUrl, Role role, String password)
{ {

View File

@ -88,14 +88,14 @@ public class UserControllerTest {
tokenService.saveToken(godToken); tokenService.saveToken(godToken);
//Can god post herobrine himself ? //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); with().body(herobrine).contentType(ContentType.JSON).header("Authorization", godToken.getToken()).when().request("POST", "/user").then().statusCode(201);
userRepository.delete(herobrine); userRepository.delete(herobrine);
//Can noob post herobrine without authorizations (no) //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()); Token noobToken = new Token(noob, tokenService.generateNewToken(), new Date());
userRepository.save(noob); userRepository.save(noob);
tokenService.saveToken(noobToken); tokenService.saveToken(noobToken);
@ -105,7 +105,7 @@ public class UserControllerTest {
@Test @Test
public void userGetTest(){ 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); userRepository.save(herobrine);
Token t = new Token(herobrine, tokenService.generateNewToken(), new Date()); Token t = new Token(herobrine, tokenService.generateNewToken(), new Date());

View File

@ -70,7 +70,7 @@ class TokenServiceTest {
ArrayList<Token> tokenList = new ArrayList<>(); ArrayList<Token> tokenList = new ArrayList<>();
GregorianCalendar gc = new GregorianCalendar(); 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); userRepository.save(malveillant);
for (int i = 0; i < 20; i++){ for (int i = 0; i < 20; i++){

View File

@ -52,7 +52,7 @@ window.addEventListener('hashchange', () => {
<a class="icon" href="#Notifications"> <a class="icon" href="#Notifications">
<div class="fa-solid fa-bell" style="margin-top: 7px; margin-bottom: 3px;"></div> <div class="fa-solid fa-bell" style="margin-top: 7px; margin-bottom: 3px;"></div>
</a></li> </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" > <a class="icon" >
<div class="fa-solid fa-gear" style="margin-top: 7px; margin-bottom: 3px;"></div> <div class="fa-solid fa-gear" style="margin-top: 7px; margin-bottom: 3px;"></div>
<div v-if="active" class="dropdown"> <div v-if="active" class="dropdown">
@ -86,12 +86,9 @@ window.addEventListener('hashchange', () => {
</ul> </ul>
</div> </div>
<div class="page"> <div class="page">
<div style=" margin:50px;">
<Suspense> <Suspense>
<component :is="currentView" />
<component :is="currentView" />
</Suspense> </Suspense>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -99,6 +96,8 @@ window.addEventListener('hashchange', () => {
<style scoped> <style scoped>
.container{ .container{
height: 100%;
width: 100%;
display:grid; display:grid;
grid-template-columns:[firstCol-start]70px[firstCol-end secondCol-start]auto[endCol]; 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-rows:[firstRow-start]61px[firstRow-end secondRow-start] auto [endRow];
@ -111,6 +110,8 @@ window.addEventListener('hashchange', () => {
.page { .page {
grid-area:page; grid-area:page;
height: 100%;
width: 100%;
place-self:center; place-self:center;
} }

View File

@ -16,10 +16,9 @@
<template> <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="bodu" v-if="item.state === 'Pending'">
<div class="container"> <div class="container">
<div class="id"><a>{{item.id}}</a></div> <div class="id"><a>{{item.id}}</a></div>
<div class="surname"><a>{{item.lastName}}</a></div> <div class="surname"><a>{{item.lastName}}</a></div>
<div class="firstname"><a>{{item.firstName}}</a></div> <div class="firstname"><a>{{item.firstName}}</a></div>
@ -37,10 +36,9 @@
height:100px; height:100px;
font-size:20px; font-size:20px;
display:grid; 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: grid-template-areas:
"id type surname firstname infos accept refuse"; "id type surname firstname infos accept refuse";
column-gap:10px;
} }
@ -99,9 +97,9 @@
} }
.bodu { .bodu {
width:100%; margin-top:2%;
margin-bottom:10px; width:66%;
border:2px solid black; border:2px solid black;
border-radius:9px; border-radius:9px;
background-color:rgb(50,50,50); background-color:rgb(50,50,50);

View File

@ -56,10 +56,10 @@
<template> <template>
<div class='loginBox'> <div class="setup">
<div v-if="loginPage"> <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;"> <h1 style="color:rgb(239,60,168); font-family: sans-serif;">
{{i18n("login.guest.signin")}} {{i18n("login.guest.signin")}}
</h1> </h1>
@ -74,14 +74,15 @@
<div class="register"> <div class="register">
<a @click="loginPage=!loginPage">{{i18n("login.guest.register")}}</a> <a @click="loginPage=!loginPage">{{i18n("login.guest.register")}}</a>
</div> </div>
<div class="inputBox"> <div class="inputBox" style="margin-bottom:35px;">
<input type="submit" v-model="submitValue"> <input type="submit" v-model="submitValue">
</div> </div>
</form> </form>
</div> </div>
</div>
<div v-else> <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;"> <h1 style="color:rgb(239,60,168); font-family: sans-serif; text-align:center;">
{{i18n("login.guest.welcome")}} {{i18n("login.guest.welcome")}}
</h1> </h1>
@ -128,9 +129,12 @@
<p>{{i18n("login.guest.country")}}</p> <p>{{i18n("login.guest.country")}}</p>
<input type="text" v-model="outputs.country"> <input type="text" v-model="outputs.country">
</div> </div>
<form novalidate enctype="multipart/form-data" class="inputBox"> <form class="inputBox"novalidate enctype="multipart/form-data">
<p>{{i18n("profile.picture").toUpperCase()}}</p> <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> </form>
<div class="inputBox"> <div class="inputBox">
<p>{{i18n("Curriculum").toUpperCase()}}</p> <p>{{i18n("Curriculum").toUpperCase()}}</p>
@ -153,35 +157,26 @@
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div>
</template> </template>
<style scoped> <style scoped>
.Home{
position:absolute;
display: flex;
z-index: 100;
padding: 8px 16px;
color:rgb(255, 255, 255);
text-decoration: none;
}
.Home:hover{ .setup {
width:40px; margin-left: auto;
background-color: black; margin-right:auto;
border-radius:6px; min-width:400px;
color:white;
transform: translate(0px ,1px);
}
width:25%;
height:60%;
}
.loginBox { .loginBox {
background-color: rgb(24,24,24); background-color: rgb(24,24,24);
width: 400px;
display:flex; display:flex;
justify-content: center; justify-content: center;
padding: 40px; border-radius: 5%;
border-radius: 20px;
box-shadow:0 5px 25px #000000; box-shadow:0 5px 25px #000000;
} }
@ -190,9 +185,8 @@
width:100%; width:100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center;
align-items:center; align-items:center;
gap: 15px; gap: 3%;
} }
@ -200,12 +194,12 @@
width:100%; width:100%;
border: none; border: none;
margin-right: 50px; margin-right: 12.5%;
padding-left: 10px; padding-left: 2.5%;
padding-top:10px; padding-top:2.5%;
padding-bottom:10px; padding-bottom:2.5%;
outline:none; outline:none;
border-radius: 4px; border-radius: 10px;
font-size:1.35em; font-size:1.35em;
} }
@ -220,8 +214,9 @@
.register{ .register{
color:rgb(239,60,168); color:rgb(239,60,168);
width: 100%; width:70%;
display:flex; margin-bottom:20px;
margin-top:20px;
cursor: pointer; 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{ button:active ,.switchpage:active{
opacity:0.8; opacity:0.8;

View File

@ -105,7 +105,8 @@
</button> </button>
</div> </div>
<div v-if="createMod"> <div v-if="createMod">
<form class="listElement"> <form class="listElement" style="width:40%;margin-right:auto;margin-left:auto;">
<div style="margin-bottom:20px;"> <div style="margin-bottom:20px;">
{{i18n("name")}} : {{i18n("name")}} :
<input v-model="toAdd.title"> <input v-model="toAdd.title">
@ -125,7 +126,7 @@
</form> </form>
</div> </div>
<div v-if="deleteMod"> <div v-if="deleteMod">
<form class="listElement"> <form class="listElement" style="width:40%;margin-right:auto;margin-left:auto;">
<div style="margin-bottom:20px;"> <div style="margin-bottom:20px;">
{{i18n("courses.toDelete")}} : {{i18n("courses.toDelete")}} :
<select style="max-width:200px;" class="teacher" v-model="toRemove"> <select style="max-width:200px;" class="teacher" v-model="toRemove">
@ -138,7 +139,7 @@
</form> </form>
</div> </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;"> <div v-if="editElementID !== item.title" style ="padding:15px 15px 15px 15px;">
<button @click="editElementID = item.title; setModify(item); "> <button @click="editElementID = item.title; setModify(item); ">
{{i18n("courses.modify")}} {{i18n("courses.modify")}}
@ -148,7 +149,8 @@
<button @click="editElementID= '';patchCourse(item)"> {{i18n("courses.confirm")}} </button> <button @click="editElementID= '';patchCourse(item)"> {{i18n("courses.confirm")}} </button>
<button @click="editElementID= '';"> {{i18n("courses.back")}} </button> <button @click="editElementID= '';"> {{i18n("courses.back")}} </button>
</div> </div>
<div class="listElement" > <div class="listElement" >
<div class="containerElement" v-if="editElementID !== item.title" > <div class="containerElement" v-if="editElementID !== item.title" >
<div class="name"> {{item.title}} </div> <div class="name"> {{item.title}} </div>
@ -172,17 +174,27 @@
<style scoped> <style scoped>
.body { .body {
width:100%; 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{ .containerElement{
justify-content:center; justify-content:center;
display:grid; display:grid;
grid-template-columns:350px 350px 200px; grid-template-columns:38.8% 38.8% 22.4%;
grid-template-areas: grid-template-areas:
"name teacher credits"; "name teacher credits";
column-gap:10px; column-gap:10px; }
}
.name { .name {
grid-area:name; grid-area:name;
@ -200,6 +212,7 @@
} }
.listElement{ .listElement{
min-width:625px;
border:2px solid black; border:2px solid black;
font-size:25px; font-size:25px;
color:white; color:white;
@ -207,6 +220,7 @@
background-color:rgb(50,50,50); background-color:rgb(50,50,50);
border-radius:20px; border-radius:20px;
margin-bottom:10px; margin-bottom:10px;
} }
.modify{ .modify{
@ -255,10 +269,11 @@
} }
.listTitle{ .listTitle{
min-width:380px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width:400px; width:25%;
margin-left:auto; margin-left:auto;
margin-right:auto; margin-right:auto;
border:2px solid black; border:2px solid black;
@ -266,7 +281,8 @@
color:white; color:white;
padding:20px; padding:20px;
background-color:rgb(50,50,50); background-color:rgb(50,50,50);
border-radius:20px;margin-bottom:10px; border-radius:20px;
margin-bottom:10px;
button:hover{ button:hover{
opacity:0.8; opacity:0.8;

201
frontend/src/Apps/Msg.vue Normal file
View File

@ -0,0 +1,201 @@
<!----------------------------------------------------
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);
</script>
<template>
<div id="msg">
<div id="discList">
<div @click="fetchDiscussion(discussion.id)" 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, currentDiscussion.name)" v-model="currentDiscussion.name"></h1>
<div id=msgs>
<div class="msg" v-for="msg in currentDiscussion.msgs" :sender="msg.sender" :key="msg.id">
{{ msg.content }}
</div>
</div>
<div id=messageBox>
<input type="text" v-model="msgContent">
<input type="submit" @click="sendMessage(currentDiscussion.id, msgContent, null)" 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;
}
.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> <style scoped>
.container{ .container{
min-width:675px;
display:grid; display:grid;
grid-template-columns:200px 900px; grid-template-columns:10vw 50vw;
grid-template-rows:200px auto; grid-template-rows:200px auto;
column-gap:30px; column-gap:2.7%;
row-gap:45px; row-gap:45px;
grid-template-areas: grid-template-areas:
"profilPic globalInfos" "profilPic globalInfos"
@ -222,6 +222,7 @@
} }
.profilPic{ .profilPic{
width:100%;
grid-area:profilPic; grid-area:profilPic;
} }
@ -242,13 +243,17 @@
grid-area:minfos; grid-area:minfos;
} }
.body { .body {
min-width:960px;
width:100%; width:100%;
margin-bottom:10px; display:flex;
align-items:center;
justify-content:center;
margin-top:5%;
} }
.containerElement{ .containerElement{
justify-content:center; justify-content:center;
display:grid; display:grid;
grid-template-columns:350px 350px 200px; grid-template-columns:38.8% 38.8% 22.4%;
grid-template-areas: grid-template-areas:
"name teacher credits"; "name teacher credits";
column-gap:10px; column-gap:10px;
@ -271,10 +276,11 @@
} }
.listTitle{ .listTitle{
min-width:197px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width:200px; width:8vw;
margin-left:auto; margin-left:auto;
margin-right:auto; margin-right:auto;
border:2px solid black; border:2px solid black;
@ -286,6 +292,7 @@
} }
.listElement{ .listElement{
min-width:625px;
border:2px solid black; border:2px solid black;
font-size:25px; font-size:25px;
color:white; color:white;
@ -296,6 +303,7 @@
} }
.infosContainer { .infosContainer {
min-width:350px;
padding-bottom:50px; padding-bottom:50px;
border:2px solid black; border:2px solid black;
font-size:25px; font-size:25px;

View File

@ -5,8 +5,8 @@
const users = await getStudents(); const users = await getStudents();
</script> </script>
<template> <template style="margin-top:5%;">
<div v-for="item in users"> <div style="display:flex; justify-content:center; " v-for="item in users">
<div class="bodu"> <div class="bodu">
<div class="container"> <div class="container">
<div class="status"><a style="margin-left:30px">{{item.status}}</a></div> <div class="status"><a style="margin-left:30px">{{item.status}}</a></div>
@ -25,10 +25,9 @@
height:100px; height:100px;
font-size:30px; font-size:30px;
display:grid; 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: grid-template-areas:
"status option surname firstname infos"; "status option surname firstname infos";
column-gap:10px;
} }
@ -42,21 +41,6 @@
align-self:center; 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{ .status{
grid-area:status; grid-area:status;
align-self:center; align-self:center;
@ -81,15 +65,15 @@
button{ button{
font-size:15px; font-size:15px;
height:50px; height:50px;
width:100px; width:75%;
border:none; border:none;
border-radius:20px; border-radius:20px;
} }
.bodu { .bodu {
width:100%; margin-top:2%;
margin-bottom:10px; width:66%;
border:2px solid black; border:2px solid black;
border-radius:9px; border-radius:9px;
background-color:rgb(50,50,50); background-color:rgb(50,50,50);

View File

@ -7,8 +7,8 @@
const users = await getAllUsers(); const users = await getAllUsers();
</script> </script>
<template> <template style="margin-top:5%;">
<div v-for="item in users"> <div style="display:flex; justify-content:center; min-width:1140px;" v-for="item in users">
<div class="bodu"> <div class="bodu">
<div class="container"> <div class="container">
<div class="role"><a style="margin-left:30px">{{i18n(item.role)}}</a></div> <div class="role"><a style="margin-left:30px">{{i18n(item.role)}}</a></div>
@ -22,23 +22,20 @@
<style scoped> <style scoped>
.container{ .container{
justify-content:center;
align-items:center;
color:white; color:white;
height:100px; height:100px;
font-size:30px; font-size:30px;
display:grid; display:grid;
grid-template-columns:250px 250px 250px 150px; grid-template-columns:27.7% 27.7% 27.7% 16.9%;
grid-template-areas: grid-template-areas:
"role surname firstname infos"; "role surname firstname infos";
column-gap:10px;
} }
.infos { .infos {
grid-area:infos; grid-area:infos;
align-items:center; align-self:center;
} }
.role { .role {
@ -67,20 +64,18 @@
button{ button{
font-size:15px; font-size:15px;
height:50px; height:50px;
width:100px; width:75%;
border:none; border:none;
border-radius:20px; border-radius:20px;
} }
.bodu { .bodu {
width:100%; margin-top:2%;
margin-bottom:10px; width:66%;
border:2px solid black; border:2px solid black;
border-radius:9px; border-radius:9px;
background-color:rgb(50,50,50); background-color:rgb(50,50,50);
} }
</style> </style>

View File

@ -1,4 +1,11 @@
body { body {
background-color: rgb(53, 25, 60); background-color: rgb(53, 25, 60);
margin:0; 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 Courses from "@/Apps/ManageCourses.vue"
import Users from "@/Apps/UsersList.vue" import Users from "@/Apps/UsersList.vue"
import Students from "@/Apps/StudentsList.vue" import Students from "@/Apps/StudentsList.vue"
import Msg from "@/Apps/Msg.vue"
const apps = { const apps = {
'/login': LoginPage, '/login': LoginPage,
@ -17,6 +18,7 @@ const apps = {
'/manage-courses' : Courses, '/manage-courses' : Courses,
'/users-list' : Users, '/users-list' : Users,
'/students-list' : Students, '/students-list' : Students,
'/msg' : Msg,
} }
const appsList = { const appsList = {

55
frontend/src/rest/msg.js Normal file
View File

@ -0,0 +1,55 @@
/*******************************************************
* 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([]);
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);
}
await fetchDiscussions();