Compare commits

..

12 Commits

Author SHA1 Message Date
9cd54bdae9 Merge remote-tracking branch 'origin/master' into AddBranchToCurriculum
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 1m25s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 25s
2024-04-21 21:51:14 +02:00
98082f34b7 Merge pull request 'tonitch/feat/notifications' (#159) from tonitch/feat/notifications into master
All checks were successful
Build and test backend / Build-backend (push) Successful in 1m23s
deploy to production / deploy-frontend (push) Successful in 28s
deploy to production / deploy-backend (push) Successful in 56s
Build and test FrontEnd / Build-frontend (push) Successful in 26s
Reviewed-on: #159
2024-04-21 20:19:47 +02:00
43883caef0
Merge remote-tracking branch 'origin/master' into tonitch/feat/notifications
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 1m24s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 26s
2024-04-21 20:19:17 +02:00
88b057e19d
Merge branch 'master' into tonitch/feat/notifications 2024-04-21 20:16:07 +02:00
260191d790
archiving
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 1m28s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 25s
2024-04-21 20:15:47 +02:00
0b9227a822
indev
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 1m24s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 24s
2024-04-21 17:42:49 +02:00
f14d41f04d
je sais vraiment pas ce que j'ai ajouté mais amélioration jt'e jure
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 1m22s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 24s
2024-04-21 17:42:29 +02:00
70bec1a934 Merge branch 'master' into tonitch/feat/notifications
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 1m22s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 24s
2024-04-21 10:35:07 +02:00
dff225b0f5
Removing toaster at every request
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 1m22s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 23s
It is possible to explicitly request a toaster for a request by setting
its config['toast'] to true
2024-04-18 08:48:40 +02:00
8fa29460ef
base
All checks were successful
Build and test backend / Build-backend (pull_request) Successful in 1m53s
Build and test FrontEnd / Build-frontend (pull_request) Successful in 29s
2024-04-17 21:41:29 +02:00
94be706226
Merge remote-tracking branch 'origin/master' into tonitch/feat/notifications 2024-04-17 12:37:44 +02:00
881b30e5c9
indev 2024-04-17 12:00:52 +02:00
15 changed files with 219 additions and 92 deletions

View File

@ -80,7 +80,7 @@ public class ForumController {
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) || u.getRole() == Role.Admin)){
if(!(f.getWriters().contains(u) || f.getCourse().getOwner().equals(u) || u.getRole() == Role.Admin)){
return new UnauthorizedResponse<>(null);
}
forumServ.createTopic(f, data);

View File

@ -0,0 +1,61 @@
package ovh.herisson.Clyde.EndPoints;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import lombok.AllArgsConstructor;
import ovh.herisson.Clyde.Repositories.NotificationRepository;
import ovh.herisson.Clyde.Responses.UnauthorizedResponse;
import ovh.herisson.Clyde.Services.AuthenticatorService;
import ovh.herisson.Clyde.Tables.Notification;
import ovh.herisson.Clyde.Tables.User;
import ovh.herisson.Clyde.Tables.Notification.Status;
@RestController
@AllArgsConstructor
@CrossOrigin(originPatterns = "*", allowCredentials = "true")
public class NotificationController {
private AuthenticatorService authServ;
private NotificationRepository notifRepo;
@GetMapping("/notifications")
public ResponseEntity<List<Notification>> getNotifications(@RequestHeader("Authorization") String token){
User u = authServ.getUserFromToken(token);
if(u == null){
return new UnauthorizedResponse<>(null);
}
ArrayList<Notification> ret = new ArrayList<>();
for (Notification n : u.getNotifications()) {
if(!n.getStatus().equals(Status.Archived)){
ret.add(n);
}
}
return new ResponseEntity<>(ret, HttpStatus.OK);
}
@PostMapping("/notifications/{id}")
public ResponseEntity<Notification> archiveNotification(@RequestHeader("Authorization") String token, @PathVariable long id){
User u = authServ.getUserFromToken(token);
Notification n = notifRepo.findById(id).orElse(null);
if(u == null || n.getUser() != u){
return new UnauthorizedResponse<>(null);
}
n.setStatus(Status.Archived);
notifRepo.save(n);
return new ResponseEntity<>(HttpStatus.OK);
}
}

View File

@ -0,0 +1,8 @@
package ovh.herisson.Clyde.Repositories;
import org.springframework.data.repository.CrudRepository;
import ovh.herisson.Clyde.Tables.Notification;
public interface NotificationRepository extends CrudRepository<Notification, Long> {}

View File

@ -17,6 +17,8 @@ import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.util.JSONPObject;
import ovh.herisson.Clyde.Repositories.Msg.DiscussionRepository;
import ovh.herisson.Clyde.Services.UserService;
import ovh.herisson.Clyde.Tables.Notification;
import ovh.herisson.Clyde.Tables.User;
import ovh.herisson.Clyde.Tables.Msg.Discussion;
import ovh.herisson.Clyde.Tables.Msg.Message;
@ -26,6 +28,8 @@ public class DiscussionService {
@Autowired
private DiscussionRepository discRepo;
@Autowired
private UserService userServ;
public Discussion create(String name, User author){
return discRepo.save(new Discussion(name, author));
@ -42,6 +46,9 @@ public class DiscussionService {
* Create a message and link it to it's discussion
*/
public Discussion CreateMessage(Discussion disc, Message msg){
for(User u: disc.getMembers()){
userServ.Notify(u, new Notification("msg.notification.new", msg.getContent(), "/#/msg"));
}
disc.addMessage(msg);
return discRepo.save(disc);
}

View File

@ -0,0 +1,9 @@
package ovh.herisson.Clyde.Services;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class NotificationService {
}

View File

@ -4,6 +4,7 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import ovh.herisson.Clyde.Tables.RegNoGenerator;
import ovh.herisson.Clyde.Repositories.UserRepository;
import ovh.herisson.Clyde.Tables.Notification;
import ovh.herisson.Clyde.Tables.Role;
import ovh.herisson.Clyde.Tables.User;
import java.util.*;
@ -139,4 +140,10 @@ public class UserService {
public void delete(User user) {
userRepo.delete(user);
}
public void Notify(User u, Notification n){
n.setUser(u);
u.getNotifications().add(n);
userRepo.save(u);
}
}

View File

@ -28,10 +28,11 @@ public class Course {
private User owner;
//// Extension Messagerie /////
@OneToMany(cascade = CascadeType.ALL)
@OneToMany(mappedBy = "course", cascade = CascadeType.ALL)
private List<Forum> forums;
public void addForum(Forum f){
f.setCourse(this);
forums.add(f);
}
///////////////////////////////

View File

@ -2,6 +2,8 @@ package ovh.herisson.Clyde.Tables.Msg;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.Data;
import ovh.herisson.Clyde.Tables.Course;
@ -16,6 +18,7 @@ public class Forum {
private int id;
@ManyToOne
@JsonIgnore
private Course course;
private String name;

View File

@ -0,0 +1,53 @@
package ovh.herisson.Clyde.Tables;
import java.util.Date;
import org.hibernate.annotations.CreationTimestamp;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.annotation.Nullable;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@Entity
public class Notification {
public enum Status {
Unread,
Read,
Archived
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String subject;
private String body;
private Status status = Status.Unread;
private String link;
@ManyToOne
@JsonIgnore
private User user;
@CreationTimestamp
private Date creation;
public Notification(String subject, @Nullable String body, @Nullable String link){
this.subject = subject;
this.body = body;
this.link = link;
}
}

View File

@ -2,18 +2,24 @@ package ovh.herisson.Clyde.Tables;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import org.hibernate.annotations.GenericGenerator;
import ovh.herisson.Clyde.Tables.Msg.Discussion;
import ovh.herisson.Clyde.Tables.Msg.Message;
import ovh.herisson.Clyde.Tables.Notification.Status;
import java.util.Date;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
@Table(name = "Users")
@NoArgsConstructor
@Data
public class User {
@Id
@GenericGenerator(name = "userGen", type = ovh.herisson.Clyde.Tables.RegNoGenerator.class)
@ -32,13 +38,19 @@ public class User {
@JsonIgnore
private String password;
@JsonIgnore
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Notification> notifications;
////// Extension Messagerie /////
@JsonIgnore
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
private List<Message> msgs;
/////////////////////////////////
@JsonIgnore
@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)
@ -70,84 +82,4 @@ public class User {
this.role = Role.Student;
this.identityCardUrl = identityCardUrl;
}
public User() {}
public Long getRegNo(){
return this.regNo;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public Date getBirthDate() {
return birthDate;
}
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
public String getProfilePictureUrl(){return this.profilePictureUrl;}
public void setProfilePictureUrl(String profilePictureUrl){
this.profilePictureUrl = profilePictureUrl;
}
public ovh.herisson.Clyde.Tables.Role getRole() {
return role;
}
public void setRole(ovh.herisson.Clyde.Tables.Role role) {
this.role = role;
}
public String getPassword(){
return password;
}
public void setPassword(String password) {
this.password = password;
}
public void setIdentityCardUrl(String identityCardUrl) {
this.identityCardUrl = identityCardUrl;
}
public String getIdentityCardUrl() {
return identityCardUrl;
}
}

View File

@ -121,6 +121,7 @@ Curriculum=curriculum
Credits=Credits
InscriptionService=I.S.
faculty=Faculty
msg.notification.new=You have a new message
forum.create=Create forum
forum.create.name=New forum's name
forum.post.create.name=New post's title

View File

@ -121,6 +121,7 @@ Curriculum=Cursus
Credits=Credits
InscriptionService=S.I.
faculty=Faculté
msg.notification.new=Vous avez un nouveau message!
forum.create=Créer un forum
forum.create.name=Nom du forum
forum.post.create.name=Titre du post

View File

@ -3,6 +3,7 @@
import { ref } from 'vue'
import i18n, { setLang } from './i18n.js'
import { isLogged } from '@/rest/Users.js'
import { notifications, fetchNotifications, archiveNotification } from '@/rest/notifications.js'
import { appList, currentView } from '@/rest/apps.js'
var prevURL;
@ -14,16 +15,20 @@ window.onhashchange = function() {
}
const Logged = ref(isLogged());
if(Logged.value){
fetchNotifications();
}
window.addEventListener('hashchange', () => {
if((location.hash === "#/home" && prevURL === "#/login") || (location.hash === "#/home" && prevURL === "#/profil")){
window.location.reload();
}
});
const home=ref(i18n("app.home"))
const notifications=ref(i18n("app.notifications"))
const settings=ref(i18n("app.settings"))
const login=ref(i18n("app.login"))
const active=ref(false)
const notification = ref(false)
const apps = ref([])
appList().then(e => apps.value = e)
@ -44,11 +49,14 @@ window.addEventListener('hashchange', () => {
</a></li>
<li style="float: right;" title=login>
<a class="icon" href="#/login">
<div class="fa-solid fa-user" :style="Logged ? 'color: orange' : 'haha'" style="margin-top: 7px; margin-bottom: 3px; "></div>
<div class="fa-solid fa-user" :style="Logged ? 'color: red' : ''" style="margin-top: 7px; margin-bottom: 3px; "></div>
</a></li>
<li style="float: right;" title=notifications>
<a class="icon" href="#Notifications">
<div class="fa-solid fa-bell" style="margin-top: 7px; margin-bottom: 3px;"></div>
<li style="float: right;" title=notifications @click="notification = !notification">
<a class="icon">
<div class="fa-solid fa-bell" :style="notifications.length != 0 ? 'color:orange': '' " style="margin-top: 7px; margin-bottom: 3px;"></div>
<ul v-if=notification id="notification">
<li v-for="notif in notifications" @click="archiveNotification(notif.id)"> {{ i18n(notif.subject) }} - {{ notif.body }}</li>
</ul>
</a></li>
<li @click="active=!active" class="option"style="float: right;" title=settings>
<a class="icon">
@ -215,8 +223,6 @@ window.addEventListener('hashchange', () => {
background-color: black;
border-radius:6px;
color:white;
transform: translate(0px ,1px);
}
ul.vertical:hover {
@ -251,6 +257,32 @@ window.addEventListener('hashchange', () => {
content: url("./assets/angry_clyde.png")
}
#notification{
position: absolute;
top: 61px;
right: 0;
background-color: white;
width: 300px;
height: 600px;
border-radius: 10px;
margin: 10px;
}
#notification > li{
color: black;
list-style: none;
font-size: 0.4em;
display: block;
background-color: #00FF00A0;
margin: 1px;
border-radius: 42px;
padding: 10px;
}
#notification > li:hover{
background-color: #00FF0000
}
</style>
<!-- vim:set noet sts=0 sw=4 ts=2: -->

View File

@ -15,7 +15,7 @@ import { fetchedPost, fetchPost, sendAnswer } from '@/rest/forum.js'
import { getSelf } from '@/rest/Users.js'
const Role = (await getSelf()).role;
const courses = Role === 'Admin' || Role === 'Secretary' ? await reactive(getCourses()) : await reactive(getUserActualCourses());
const courses = Role === 'Admin' || Role === 'Secretary' || Role === 'Teacher' ? await reactive(getCourses(Role)) : await reactive(getUserActualCourses());
const selectedCourse = ref();
const selectedForum = ref();

View File

@ -0,0 +1,12 @@
import { ref } from 'vue'
import { restGet, restPost } from '@/rest/restConsumer.js'
export const notifications = ref([]);
export function fetchNotifications(){
restGet("/notifications").then( e => notifications.value = e );
}
export function archiveNotification(id){
restPost("/notifications/" + id).then( e => fetchNotifications() );
}