6 Commits

Author SHA1 Message Date
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
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
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
14 changed files with 458 additions and 9 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,84 @@
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.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.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;
//// 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)){
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);
}
}

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,29 @@
package ovh.herisson.Clyde.Services.Msg;
import org.springframework.beans.factory.annotation.Autowired;
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
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,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(cascade = CascadeType.ALL)
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,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

@ -3,6 +3,8 @@ package ovh.herisson.Clyde.Tables;
import jakarta.persistence.*;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
@Table(name = "Users")
@ -18,7 +20,8 @@ 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;
public User(String lastName, String firstName, String email, String address,
String country, Date birthDate, String profilePictureUrl, Role role, String password)

View File

@ -99,20 +99,19 @@ window.addEventListener('hashchange', () => {
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;
height: 100%;
width: 100%;
place-self:center;
}
.topBar{
@ -155,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;
@ -203,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,138 @@
<!----------------------------------------------------
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 } from '@/rest/forum.js'
import { PostsOfCurrentForum, getPostsOfForum } from '@/rest/forum.js'
import { fetchedPost, fetchPost } from '@/rest/forum.js'
const courses = await reactive(getCourses());
const selectedCourse = ref();
const selectedForum = 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>
</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 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>
</template>
<style scoped>
#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

@ -1,3 +1,7 @@
:root {
--header-size: 61px;
}
body {
background-color: rgb(53, 25, 60);
margin:0;

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 Forums from '@/Apps/Forums.vue'
const apps = {
'/login': LoginPage,
@ -17,12 +18,13 @@ const apps = {
'/manage-courses' : Courses,
'/users-list' : Users,
'/students-list' : Students,
'/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,71 @@
import { ref } from 'vue'
import { restGet, restPost, restDelete, restPatch } from './restConsumer.js'
/**
* List forums of a course
*/
export async function getForumsOfCourse(id){
ForumsOfCurrentCourse.value = [
{
id: 1,
name: "forum~1"
},
{
id: 2,
name: "forum~2"
},
]
// ForumsOfCurrentCourse = await restGet("/forums/" + id)
}
export const ForumsOfCurrentCourse = ref();
/**
* 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();