1 Commits

Author SHA1 Message Date
e5b1a9e46f Base oauth et spring sessions 2024-03-07 14:00:50 +01:00
105 changed files with 1027 additions and 4769 deletions

View File

@ -17,16 +17,15 @@ 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 -x test run: ./gradlew backend:build
# Test-backend: Test-backend:
# runs-on: ubuntu-latest runs-on: ubuntu-latest
# steps: steps:
# - uses: actions/checkout@v4 - uses: actions/checkout@v4
# - uses: actions/setup-java@v3 - uses: actions/setup-java@v3
# with: with:
# java-version: '21' java-version: '21'
# distribution: 'temurin' distribution: 'temurin'
# - run: curl -fsSL https://get.docker.com | sh - uses: gradle/gradle-build-action@v3
# - uses: gradle/gradle-build-action@v3 - name: building
# - name: testing run: ./gradlew backend:test
# run: ./gradlew backend:test

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 -x test run: ./gradlew backend:build
- name: pushing to the server - name: pushing to the server
run: | run: |
echo "${{ secrets.SSH_KEY }}" > key echo "${{ secrets.SSH_KEY }}" > key

View File

@ -3,21 +3,15 @@
## Contexte ## Contexte
Le projet demande de pouvoir authentifier les utilisateurs présents. Le but étant de leurs associer un "contexte" Le projet demande de pouvoir authentifier les utilisateurs présents. Le but étant de leurs associer un "contexte"
(cours, informations personnelles, ...). Pour que ceux-ci puissent accomplir différentes actions nécéssitantes une (cours, informations personnelles, ...). Pour que ceux-ci puissent accomplir différentes actions nécéssitants une
identification (permission, info personelles, ...). identification (permission, personelles, ...).
## Méthode ## Méthode
Lorsque qu'un utilisateur se connecte au serveur, nous lui envoyons un token qui sera stocké dans le Lorsque q'un utilisateur se connecte au serveur, nous lui envoyons un token qui sera stocké dans le
navigateur. Ce token est unique à l'utilisateur et pourra être ré-envoyé dans les futures requêtes navigateur. Ce token est unique à l'utilisateur et pourra être ré-envoyé dans les futures requetes
pour identifier l'utilisateur. pour identifier l'utilisateur.
Ce token est donc une chaine de 64 caractères suivant la norme ISO_8859_1(8bits par cararctère) aléatoires,ce qui est d'après nos recherches suffisant.
De plus une limite de 5 token par utilisateur sera ajoutée de sorte à
1) S'assurer qu'une personne ne noie la base de donnée de tokens.
2) Ajouter une protection supplémentaire pour assurer qu'un token est bien unique à un utilisateur.
## Autres méthodes envisagée ## Autres méthodes envisagée
### Oauth2 ### Oauth2

2
backend/.gitignore vendored
View File

@ -35,5 +35,3 @@ out/
### VS Code ### ### VS Code ###
.vscode/ .vscode/
/cdn

View File

@ -1,6 +1,5 @@
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

@ -21,7 +21,6 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-mail") implementation("org.springframework.boot:spring-boot-starter-mail")
implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-web")
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-security")
implementation("com.kohlschutter.junixsocket:junixsocket-core:2.9.0") implementation("com.kohlschutter.junixsocket:junixsocket-core:2.9.0")
// implementation("org.springframework.session:spring-session-jdbc") // implementation("org.springframework.session:spring-session-jdbc")
developmentOnly("org.springframework.boot:spring-boot-devtools") developmentOnly("org.springframework.boot:spring-boot-devtools")
@ -31,11 +30,8 @@ dependencies {
testImplementation("org.springframework.boot:spring-boot-testcontainers") testImplementation("org.springframework.boot:spring-boot-testcontainers")
testImplementation("org.testcontainers:junit-jupiter") testImplementation("org.testcontainers:junit-jupiter")
testImplementation("org.testcontainers:postgresql") testImplementation("org.testcontainers:postgresql")
testImplementation("io.rest-assured:rest-assured")
testImplementation("org.hamcrest:hamcrest")
} }
tasks.register("run") { tasks.register("run") {
dependsOn(tasks.bootRun) dependsOn(tasks.bootRun)
} }
@ -44,4 +40,3 @@ tasks.register("run") {
tasks.withType<Test> { tasks.withType<Test> {
useJUnitPlatform() useJUnitPlatform()
} }

View File

@ -6,4 +6,4 @@ services:
- 'POSTGRES_USER=devel' - 'POSTGRES_USER=devel'
- 'POSTGRES_PASSWORD=devel' - 'POSTGRES_PASSWORD=devel'
ports: ports:
- '5442:5432' - '5432:5432'

View File

@ -2,9 +2,8 @@ package ovh.herisson.Clyde;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
@SpringBootApplication(exclude = { SecurityAutoConfiguration.class }) @SpringBootApplication
public class ClydeApplication { public class ClydeApplication {
public static void main(String[] args) { public static void main(String[] args) {

View File

@ -1,75 +0,0 @@
package ovh.herisson.Clyde.EndPoints;
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.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import ovh.herisson.Clyde.Services.AuthenticatorService;
import ovh.herisson.Clyde.Tables.Applications;
import ovh.herisson.Clyde.Tables.Role;
import ovh.herisson.Clyde.Tables.User;
import java.util.ArrayList;
@RestController
@CrossOrigin(originPatterns = "*", allowCredentials = "true")
public class ApplicationsController {
AuthenticatorService authServ;
public ApplicationsController(AuthenticatorService authServ){
this.authServ = authServ;
}
/** return a list of authorized applications.
* depends on the token
*/
@GetMapping("/apps")
public ResponseEntity<Iterable<Applications>> getAuthorizedApps(@RequestHeader("Authorization") String token){
return new ResponseEntity<>(getAuthorizedApplications(token), HttpStatus.OK);
}
@GetMapping("/apps/{identifier}")
public ResponseEntity<Boolean> getAppAuthorization(@PathVariable Applications identifier, @RequestHeader("Authorization") String token){
if (getAuthorizedApplications(token).contains(identifier)){
return new ResponseEntity<>(true, HttpStatus.OK);
}
return new ResponseEntity<>(false, HttpStatus.OK);
}
public ArrayList<Applications> getAuthorizedApplications(String token){
ArrayList<Applications> authorizedApps = new ArrayList<>();
//if unAuthed
authorizedApps.add(Applications.Login);
User user = authServ.getUserFromToken(token);
if(user == null)
return authorizedApps;
// if authed
authorizedApps.add(Applications.Profile);
if (!authServ.isNotIn(new Role[]{Role.Teacher,Role.Student,Role.Admin},token)) {
authorizedApps.add(Applications.Msg);
authorizedApps.add(Applications.Forum);
authorizedApps.add(Applications.Rdv);
}
//if Teacher or Secretary or Admin add ManageCourses App
if (!authServ.isNotIn(new Role[]{Role.Teacher,Role.Secretary,Role.Admin},token))
authorizedApps.add(Applications.ManageCourses);
if (!authServ.isNotIn(new Role[]{Role.InscriptionService,Role.Admin},token)){
authorizedApps.add(Applications.Inscription);
authorizedApps.add(Applications.StudentsList);}
if (!authServ.isNotIn(new Role[]{Role.Secretary,Role.Admin},token)){
authorizedApps.add(Applications.UsersList);}
return authorizedApps;
}
}

View File

@ -1,138 +0,0 @@
package ovh.herisson.Clyde.EndPoints;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ovh.herisson.Clyde.Responses.UnauthorizedResponse;
import ovh.herisson.Clyde.Services.AuthenticatorService;
import ovh.herisson.Clyde.Services.CourseService;
import ovh.herisson.Clyde.Services.ProtectionService;
import ovh.herisson.Clyde.Services.TeacherCourseService;
import ovh.herisson.Clyde.Tables.Course;
import ovh.herisson.Clyde.Tables.Role;
import ovh.herisson.Clyde.Tables.User;
import java.util.HashMap;
import java.util.Map;
@RestController
@CrossOrigin(originPatterns = "*", allowCredentials = "true")
public class CourseController {
private final CourseService courseServ;
private final TeacherCourseService teacherCourseServ;
private final AuthenticatorService authServ;
public CourseController(CourseService courseServ, TeacherCourseService teacherCourseServ, AuthenticatorService authServ) {
this.courseServ = courseServ;
this.teacherCourseServ = teacherCourseServ;
this.authServ = authServ;
}
@GetMapping("/course/{id}")
public ResponseEntity<HashMap<String,Object>> getCourse(@RequestHeader("Authorization") String token, @PathVariable long id){
if (authServ.getUserFromToken(token) == null)
return new UnauthorizedResponse<>(null);
Course foundCourse = courseServ.findById(id);
if (foundCourse == null)
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
return new ResponseEntity<>(ProtectionService.courseWithoutPassword(foundCourse), HttpStatus.OK);
}
@GetMapping("/courses")
public ResponseEntity<Iterable<HashMap<String,Object>>> getAllCourses(@RequestHeader("Authorization") String token){
if (authServ.isNotIn(new Role[]{Role.Admin,Role.Secretary},token))
return new UnauthorizedResponse<>(null);
return new ResponseEntity<>(ProtectionService.coursesWithoutPasswords(courseServ.findAll()),HttpStatus.OK);
}
@GetMapping("/courses/owned")
public ResponseEntity<Iterable<HashMap<String ,Object>>> getOwnedCourses(@RequestHeader("Authorization") String token){
if (authServ.isNotIn(new Role[]{Role.Admin,Role.Teacher},token))
return new UnauthorizedResponse<>(null);
return new ResponseEntity<>(ProtectionService.coursesWithoutPasswords(courseServ.findOwnedCourses(authServ.getUserFromToken(token))),HttpStatus.OK);
}
@GetMapping("/course/{id}/assistants")
public ResponseEntity<Iterable<HashMap<String,Object>>> getCourseAssistants(@RequestHeader("Authorization")String token, @PathVariable long id){
if (authServ.getUserFromToken(token) == null)
return new UnauthorizedResponse<>(null);
Iterable<User> assistants = teacherCourseServ.findCourseAssistants(courseServ.findById(id));
return new ResponseEntity<>(ProtectionService.usersWithoutPasswords(assistants),HttpStatus.OK);
}
@PostMapping("/course")
public ResponseEntity<Map<String ,Object>> postCourse(@RequestHeader("Authorization") String token,
@RequestBody Course course)
{
System.out.println(course);
System.out.println(token);
if (authServ.isNotIn(new Role[]{Role.Secretary,Role.Admin},token))
return new UnauthorizedResponse<>(null);
Course createdCourse = courseServ.save(course);
if (createdCourse == null)
return new ResponseEntity<>(null,HttpStatus.BAD_REQUEST);
return new ResponseEntity<>(ProtectionService.courseWithoutPassword(createdCourse), HttpStatus.CREATED);
}
@PatchMapping("/course/{id}")
public ResponseEntity<Course> patchCourse(@RequestHeader("Authorization") String token,
@RequestBody Map<String,Object> updates,
@PathVariable long id)
{
if (authServ.isNotIn(new Role[]{Role.Admin,Role.Teacher,Role.Secretary}, token))
return new UnauthorizedResponse<>(null);
if (!courseServ.modifyData(id, updates, authServ.getUserFromToken(token).getRole()))
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
return new ResponseEntity<>(HttpStatus.OK);
}
@PostMapping("/course/{id}")
public ResponseEntity<String> postTeachers(@RequestHeader("Authorization") String token,
@RequestBody Iterable<Long> teacherIds,
@PathVariable Long id)
{
if (authServ.isNotIn(new Role[]{Role.Admin,Role.Secretary}, token))
return new UnauthorizedResponse<>(null);
if (!teacherCourseServ.saveAll(teacherIds,courseServ.findById(id)))
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
return new ResponseEntity<>(HttpStatus.OK);
}
@DeleteMapping("course/{id}")
public ResponseEntity<String> deleteUser(@RequestHeader("Authorization") String token, @PathVariable Long id){
if (authServ.isNotIn(new Role[]{Role.Admin,Role.Secretary}, token))
return new UnauthorizedResponse<>(null);
Course toDelete = courseServ.findById(id);
if (toDelete == null)
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
courseServ.delete(courseServ.findById(id));
return new ResponseEntity<>(HttpStatus.OK);
}
}

View File

@ -1,97 +0,0 @@
package ovh.herisson.Clyde.EndPoints;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ovh.herisson.Clyde.Responses.UnauthorizedResponse;
import ovh.herisson.Clyde.Services.*;
import ovh.herisson.Clyde.Tables.Curriculum;
import ovh.herisson.Clyde.Tables.Role;
import java.util.Map;
@RestController
@CrossOrigin(originPatterns = "*", allowCredentials = "true")
public class CurriculumController {
private final CurriculumService curriculumServ;
private final AuthenticatorService authServ;
private final UserCurriculumService userCurriculumServ;
private final CurriculumCourseService curriculumCourseServ;
public CurriculumController(CurriculumService curriculumServ, AuthenticatorService authServ, UserCurriculumService userCurriculumServ, CurriculumCourseService curriculumCourseServ){
this.curriculumServ = curriculumServ;
this.authServ = authServ;
this.userCurriculumServ = userCurriculumServ;
this.curriculumCourseServ = curriculumCourseServ;
}
@GetMapping("/curriculum/{id}")
public ResponseEntity<Map<String,Object>> findById(@PathVariable long id){
Curriculum foundCurriculum = curriculumServ.findById(id);
if (foundCurriculum == null)
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
return new ResponseEntity<>(curriculumCourseServ.getDepthCurriculum(foundCurriculum), HttpStatus.OK);
}
@GetMapping("/curriculum")
public ResponseEntity<Map<String ,Object>> findSelfCurriculum(@RequestHeader("Authorization") String token){
if (authServ.getUserFromToken(token) == null)
return new UnauthorizedResponse<>(null);
Curriculum curriculum = userCurriculumServ.findByUser(authServ.getUserFromToken(token));
if (curriculum == null)
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
return new ResponseEntity<>(curriculumCourseServ.getDepthCurriculum(curriculum),HttpStatus.OK);
}
@GetMapping("/curriculums")
public ResponseEntity<Iterable<Map<String, Object>>> findAllIndDepth(){
return new ResponseEntity<>(curriculumCourseServ.getAllDepthCurriculum(),HttpStatus.OK);
}
@PostMapping("/curriculum")
public ResponseEntity<Curriculum> postCurriculum(@RequestHeader("Authorization") String token,@RequestBody Curriculum curriculum){
if (authServ.isNotIn(new Role[]{Role.Secretary,Role.Admin},token))
return new UnauthorizedResponse<>(null);
return new ResponseEntity<>(curriculumServ.save(curriculum),HttpStatus.CREATED);
}
@PostMapping("/curriculum/{id}")
public ResponseEntity<String> postCoursesToCurriculum(@RequestHeader("Authorization") String token,
@RequestBody Iterable<Long> coursesIds,
@PathVariable long id)
{
if (authServ.isNotIn(new Role[]{Role.Admin,Role.Secretary},token))
return new UnauthorizedResponse<>(null);
if (!curriculumCourseServ.saveAll(coursesIds, curriculumServ.findById(id)))
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
return new ResponseEntity<>(HttpStatus.OK);
}
@DeleteMapping("/curriculum/{id}")
public ResponseEntity<String > deleteCurriculum(@RequestHeader("Authorization") String token, @PathVariable Long id){
if (authServ.isNotIn(new Role[]{Role.Admin,Role.Secretary}, token))
return new UnauthorizedResponse<>(null);
Curriculum toDelete = curriculumServ.findById(id);
if (toDelete == null)
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
curriculumServ.delete(toDelete);
return new ResponseEntity<>(HttpStatus.OK);
}
}

View File

@ -1,82 +0,0 @@
package ovh.herisson.Clyde.EndPoints;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ovh.herisson.Clyde.Responses.UnauthorizedResponse;
import ovh.herisson.Clyde.Services.AuthenticatorService;
import ovh.herisson.Clyde.Services.InscriptionService;
import ovh.herisson.Clyde.Services.ProtectionService;
import ovh.herisson.Clyde.Tables.InscriptionRequest;
import ovh.herisson.Clyde.Tables.RequestState;
import ovh.herisson.Clyde.Tables.Role;
import java.util.Map;
@RestController
@CrossOrigin(originPatterns = "*", allowCredentials = "true")
public class InscriptionController {
private final InscriptionService inscriptionServ;
private final AuthenticatorService authServ;
public InscriptionController(InscriptionService inscriptionServ, AuthenticatorService authServ){
this.inscriptionServ = inscriptionServ;
this.authServ = authServ;
}
@GetMapping("/requests/register")
public ResponseEntity<Iterable<Map<String,Object>>> getAllRequests(@RequestHeader("Authorization") String token){
if (authServ.isNotIn(new Role[]{Role.Admin,Role.InscriptionService},token))
return new UnauthorizedResponse<>(null);
Iterable<InscriptionRequest> inscriptionRequests = inscriptionServ.getAll();
return new ResponseEntity<>(ProtectionService.requestsWithoutPasswords(inscriptionRequests), HttpStatus.OK);
}
@GetMapping("/request/register/{id}")
public ResponseEntity<Map<String,Object>> getById(@RequestHeader("Authorization") String token, @PathVariable long id){
if (authServ.isNotIn(new Role[]{Role.Admin,Role.InscriptionService},token))
return new UnauthorizedResponse<>(null);
InscriptionRequest foundInscriptionRequest = inscriptionServ.getById(id);
if (foundInscriptionRequest == null)
return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
return new ResponseEntity<>(ProtectionService.requestWithoutPassword(foundInscriptionRequest), HttpStatus.OK);
}
@PatchMapping("/request/register/{id}")
public ResponseEntity<InscriptionRequest> changeRequestState(@PathVariable long id,
@RequestHeader("Authorization") String token,
@RequestBody RequestState state)
{
if (authServ.isNotIn(new Role[]{Role.InscriptionService,Role.Admin},token))
return new UnauthorizedResponse<>(null);
if (!inscriptionServ.modifyState(id, state))
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
return new ResponseEntity<>(HttpStatus.OK);
}
@DeleteMapping("/request/register/{id}")
public ResponseEntity<String > deleteRequest(@RequestHeader("Authorization") String token, @PathVariable Long id){
if (authServ.isNotIn(new Role[]{Role.Admin,Role.InscriptionService}, token))
return new UnauthorizedResponse<>(null);
InscriptionRequest toDelete = inscriptionServ.getById(id);
if (toDelete == null)
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
inscriptionServ.delete(toDelete);
return new ResponseEntity<>(HttpStatus.OK);
}
}

View File

@ -1,56 +0,0 @@
package ovh.herisson.Clyde.EndPoints;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ovh.herisson.Clyde.Responses.UnauthorizedResponse;
import ovh.herisson.Clyde.Services.AuthenticatorService;
import ovh.herisson.Clyde.Services.ProtectionService;
import ovh.herisson.Clyde.Tables.InscriptionRequest;
import java.util.Date;
import java.util.Map;
@RestController
@CrossOrigin(originPatterns = "*", allowCredentials = "true")
public class LoginController {
private final AuthenticatorService authServ;
static public class RequestLogin{
private final String identifier;
private final String password;
@JsonFormat(pattern="yyyy-MM-dd'T'HH:mm:ss")
private final Date expirationDate;
public RequestLogin(String identifier, String password, Date expirationDate){
this.identifier = identifier;
this.password = password;
this.expirationDate = expirationDate;
}
}
public LoginController(AuthenticatorService authServ){
this.authServ = authServ;
}
@PostMapping(value = "/login")
public ResponseEntity<String> login(@RequestBody RequestLogin requestLogin){
String sessionToken = authServ.login(requestLogin.identifier,requestLogin.password,requestLogin.expirationDate);
if (sessionToken == null){
return new UnauthorizedResponse<>("Identifier or Password incorrect");
}
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("Set-Cookie",String.format("session_token=%s",sessionToken));
return ResponseEntity.ok().headers(responseHeaders).build();
}
@PostMapping("/register")
public ResponseEntity<Map<String,Object>> register(@RequestBody InscriptionRequest inscriptionRequest){
InscriptionRequest returnedInscriptionRequest = authServ.register(inscriptionRequest);
return new ResponseEntity<>(ProtectionService.requestWithoutPassword(returnedInscriptionRequest), HttpStatus.CREATED);
}
}

View File

@ -1,101 +0,0 @@
package ovh.herisson.Clyde.EndPoints;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.*;
import ovh.herisson.Clyde.Repositories.TokenRepository;
import ovh.herisson.Clyde.Repositories.UserRepository;
import ovh.herisson.Clyde.Services.*;
import ovh.herisson.Clyde.Tables.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
@RestController
@CrossOrigin(originPatterns = "*", allowCredentials = "true")
public class MockController {
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
public final UserRepository userRepo;
public final TokenRepository tokenRepo;
public final TokenService tokenService;
public final CurriculumCourseService CurriculumCourseService;
public final CurriculumService curriculumService;
public final CourseService courseService;
public final InscriptionService inscriptionService;
ArrayList<User> mockUsers;
public MockController(UserRepository userRepo, TokenRepository tokenRepo, TokenService tokenService, CurriculumCourseService CurriculumCourseService, CurriculumService curriculumService, CourseService courseService, InscriptionService inscriptionService){
this.tokenRepo = tokenRepo;
this.userRepo = userRepo;
this.tokenService = tokenService;
this.CurriculumCourseService = CurriculumCourseService;
this.curriculumService = curriculumService;
this.courseService = courseService;
this.inscriptionService = inscriptionService;
}
/** Saves an example of each user type by :
* email : FooRole@FooRole.com, password : FooRole and token : FooRole
* For example the admin as "admin@admin.com" as email and "admin" as both password and token
* They all have silly names
*/
@PostMapping("/mock")
public void postMock(){
// user part
User herobrine = new User("brine","hero","admin@admin.com","behind","ShadowsLand",new Date(0), null,Role.Admin,passwordEncoder.encode("admin"));
User joe = new User("Mama","Joe","student@student.com","roundabout","England",new Date(0), null,Role.Student,passwordEncoder.encode("student"));
User meh = new User("Polo","Marco","secretary@secretary.com","a Box","Monaco",new Date(0), null,Role.Secretary,passwordEncoder.encode("secretary"));
User joke = new User("Gaillard","Corentin","teacher@teacher.com","lab","faculty",new Date(0), null,Role.Teacher,passwordEncoder.encode("teacher"));
User jojo = new User("Bridoux","Justin","teacher2@teacher2.com","lab","faculty",new Date(0), null,Role.Teacher,passwordEncoder.encode("teacher"));
User lena = new User("Louille","Lena","inscriptionService@InscriptionService.com","no","yes",new Date(0), null,Role.InscriptionService,passwordEncoder.encode("inscriptionService"));
mockUsers = new ArrayList<>(Arrays.asList(herobrine,joe,meh,joke,lena,jojo));
userRepo.saveAll(mockUsers);
// Course / Curriculum part
Curriculum infoBab1 = new Curriculum(1,"info");
Curriculum chemistryBab1 = new Curriculum(1,"chemistry");
Curriculum psychologyBab1 = new Curriculum(1,"psychology");
curriculumService.save(infoBab1);
curriculumService.save(chemistryBab1);
curriculumService.save(psychologyBab1);
Course progra1 = new Course(5,"Programmation et algorithmique 1",joke);
Course chemistry1 = new Course(12, "Thermochimie",joke);
Course psycho1 = new Course(21, "Neuroreaction of isolated brain cells",joke);
Course commun = new Course(2, "cours commun",joke);
courseService.save(progra1);
courseService.save(chemistry1);
courseService.save(psycho1);
courseService.save(commun);
CurriculumCourseService.save(new CurriculumCourse(infoBab1,progra1));
CurriculumCourseService.save(new CurriculumCourse(infoBab1,commun));
CurriculumCourseService.save(new CurriculumCourse(psychologyBab1,psycho1));
CurriculumCourseService.save(new CurriculumCourse(psychologyBab1,commun));
CurriculumCourseService.save(new CurriculumCourse(chemistryBab1,commun));
CurriculumCourseService.save(new CurriculumCourse(chemistryBab1,chemistry1));
InscriptionRequest inscriptionRequest = new InscriptionRequest("helen","prenom","non","helen@gmail.com","america",new Date(),(long) 1,RequestState.Pending,"yes.png","password");
inscriptionService.save(inscriptionRequest);
}
}

View File

@ -7,7 +7,7 @@ import org.springframework.web.bind.annotation.RestController;
import ovh.herisson.Clyde.Responses.PingResponse; import ovh.herisson.Clyde.Responses.PingResponse;
@RestController @RestController
@CrossOrigin(originPatterns = "*", allowCredentials = "true") @CrossOrigin(origins = "http://localhost:5173")
public class PingController { public class PingController {
@GetMapping("/ping") @GetMapping("/ping")

View File

@ -1,36 +0,0 @@
package ovh.herisson.Clyde.EndPoints;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import ovh.herisson.Clyde.Services.StorageService;
import ovh.herisson.Clyde.Tables.FileType;
import ovh.herisson.Clyde.Tables.StorageFile;
@RestController
@CrossOrigin(originPatterns = "*", allowCredentials = "true")
public class StorageController {
private final StorageService storageServ;
public StorageController(StorageService storageServ){
this.storageServ= storageServ;
}
@PostMapping("/upload/{fileType}")
public ResponseEntity<StorageFile> handleFileUpload(@RequestParam("file") MultipartFile file, @PathVariable FileType fileType) {
StorageFile fileEntry;
try {
fileEntry = storageServ.store(file,fileType);
} catch(Exception e){
e.printStackTrace();
return new ResponseEntity<>(null,HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(fileEntry, HttpStatus.OK);
}
}

View File

@ -1,37 +0,0 @@
package ovh.herisson.Clyde.EndPoints;
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.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import ovh.herisson.Clyde.Responses.UnauthorizedResponse;
import ovh.herisson.Clyde.Services.AuthenticatorService;
import ovh.herisson.Clyde.Services.TokenService;
import ovh.herisson.Clyde.Tables.Role;
import ovh.herisson.Clyde.Tables.Token;
@RestController
@CrossOrigin(originPatterns = "*", allowCredentials = "true")
public class TokenController {
private final TokenService tokenServ;
private final AuthenticatorService authServ;
public TokenController(TokenService tokenServ, AuthenticatorService authServ){
this.tokenServ = tokenServ;
this.authServ = authServ;
}
@GetMapping("/tokens")
public ResponseEntity<Iterable<Token>> getTokens(@RequestHeader("Authorization")String token){
if (authServ.isNotIn(new Role[]{Role.Admin},token))
return new UnauthorizedResponse<>(null);
return new ResponseEntity<>(tokenServ.getAllTokens(), HttpStatus.OK);
}
}

View File

@ -1,139 +1,44 @@
package ovh.herisson.Clyde.EndPoints; package ovh.herisson.Clyde.EndPoints;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import ovh.herisson.Clyde.Responses.UnauthorizedResponse; import ovh.herisson.Clyde.Repositories.UserRepository;
import ovh.herisson.Clyde.Services.AuthenticatorService;
import ovh.herisson.Clyde.Services.ProtectionService;
import ovh.herisson.Clyde.Services.UserService;
import ovh.herisson.Clyde.Tables.Role;
import ovh.herisson.Clyde.Tables.User; import ovh.herisson.Clyde.Tables.User;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@RestController @RestController
@CrossOrigin(originPatterns = "*", allowCredentials = "true") @CrossOrigin(origins = "http://localhost:5173")
public class UserController { public class UserController {
private final UserService userService; private final UserRepository userRepo;
private final AuthenticatorService authServ;
public UserController(UserService userService, AuthenticatorService authServ){ public UserController(UserRepository userRepo){
this.userService = userService; this.userRepo = userRepo;
this.authServ = authServ;
} }
/** returns information about the connected user
*
* @param token the session token of the user
* @return the user information except his password
*/
@GetMapping("/user") @GetMapping("/user")
public ResponseEntity<HashMap<String,Object>> getUser(@RequestHeader("Authorization") String token){ public ResponseEntity<User> getUsers(@RequestHeader("Authorization") String token){
//TODO
User user = authServ.getUserFromToken(token); // Get the token thru the data base
if (user == null) return new UnauthorizedResponse<>(null); // tokenRepo.findToken(token) => User userFromToken
// si role != secretary => return error : ResponseEntity<User>(null, HttpStatus.UNAUTHORIZED)
return new ResponseEntity<>(ProtectionService.userWithoutPassword(user), HttpStatus.OK); return new ResponseEntity<User>(/**userRepo.findById(userFromToken.id),**/ HttpStatus.OK);
}
@GetMapping("/user/{id}")
public ResponseEntity<HashMap<String ,Object>> getUserById(@RequestHeader("Authorization") String token, @PathVariable Long id){
if (authServ.isNotIn(new Role[]{Role.Admin,Role.Secretary,Role.InscriptionService},token))
return new UnauthorizedResponse<>(null);
return new ResponseEntity<>(ProtectionService.userWithoutPassword(userService.getUserById(id)), HttpStatus.OK);
} }
@PostMapping("/user") @PostMapping("/user")
public ResponseEntity<Map<String ,Object>> postUser(@RequestBody User user,@RequestHeader("Authorization") String token){ public ResponseEntity<String> postUser(@RequestBody User user){
userRepo.save(user);
if (authServ.isNotIn(new Role[]{Role.Admin,Role.InscriptionService,Role.Secretary},token)) return new ResponseEntity<String>(String.format("Account created with ID:%s",user.getRegNo()),HttpStatus.CREATED);
return new UnauthorizedResponse<>(null);
return new ResponseEntity<>(ProtectionService.userWithoutPassword(userService.save(user)),HttpStatus.CREATED);
} }
@GetMapping("/users") @GetMapping("/users")
public ResponseEntity<Iterable<HashMap<String,Object>>> getAllUsers(@RequestHeader("Authorization") String token){ public Iterable<User> getAllUsers(){//TODO ne l'accepter que si c'est le secrétariat
return userRepo.findAll();
if (authServ.isNotIn(new Role[]{Role.Admin,Role.Secretary},token))
return new UnauthorizedResponse<>(null);
Role posterRole = authServ.getUserFromToken(token).getRole();
Iterable<User> users = new ArrayList<>();
if (posterRole == Role.Admin)
users = userService.getAll();
else if (posterRole == Role.Secretary)
users = userService.getAllExceptAdmins();
return new ResponseEntity<>(ProtectionService.usersWithoutPasswords(users), HttpStatus.OK);
}
/** changes the specified user's information
*
* @param updates the changes to be made
* @param token the session token of the user posting the change
* @param id the id of the user to change
* @return a string clarifying the issue (if there is any)
*/
@PatchMapping("/user/{id}")
public ResponseEntity<String> patchUser(@RequestHeader("Authorization") String token,
@RequestBody Map<String,Object> updates,
@PathVariable Long id) {
if (token == null) return new UnauthorizedResponse<>(null);
User poster = authServ.getUserFromToken(token);
if (poster == null) {return new UnauthorizedResponse<>("bad token");}
if (!userService.modifyData(id, updates, poster))
return new UnauthorizedResponse<>("there was an issue with the updates requested");
return new ResponseEntity<>(null, HttpStatus.OK);
}
@GetMapping("/teachers")
public ResponseEntity<Iterable<HashMap<String,Object>>> getAllTeachers(@RequestHeader("Authorization") String token){
if (authServ.getUserFromToken(token) == null)
return new UnauthorizedResponse<>(null);
Iterable<User> teachers = userService.getAllTeachers();
return new ResponseEntity<>(ProtectionService.usersWithoutPasswords(teachers), HttpStatus.OK);
} }
@GetMapping("/students")
public ResponseEntity<Iterable<HashMap<String,Object>>> getAllStudent(@RequestHeader("Authorization") String token){
if (authServ.getUserFromToken(token) == null)
return new UnauthorizedResponse<>(null);
Iterable<User> students = userService.getAllStudents();
return new ResponseEntity<>(ProtectionService.usersWithoutPasswords(students), HttpStatus.OK);
}
@DeleteMapping("/user/{id}")
public ResponseEntity<String> deleteStudent(@RequestHeader("Authorization") String token, @PathVariable Long id){
if (authServ.isNotIn(new Role[]{Role.Admin,Role.Secretary},token) && !id.equals(authServ.getUserFromToken(token).getRegNo()))
return new UnauthorizedResponse<>(null);
User toDelete = userService.getUserById(id);
if (toDelete == null)
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
userService.delete(toDelete);
return new ResponseEntity<>(HttpStatus.OK);
}
} }

View File

@ -6,10 +6,10 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration @Configuration
@EnableScheduling
public class JdbcConfig { public class JdbcConfig {
@Bean @Bean
@ -17,7 +17,7 @@ public class JdbcConfig {
public DataSource psqlSource(){ public DataSource psqlSource(){
DriverManagerDataSource source = new DriverManagerDataSource(); DriverManagerDataSource source = new DriverManagerDataSource();
source.setDriverClassName("org.postgresql.Driver"); source.setDriverClassName("org.postgresql.Driver");
source.setUrl("jdbc:postgresql://localhost:5442/clyde"); source.setUrl("jdbc:postgresql://localhost:5432/clyde");
source.setUsername("devel"); source.setUsername("devel");
source.setPassword("devel"); source.setPassword("devel");

View File

@ -1,15 +0,0 @@
package ovh.herisson.Clyde.Repositories;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import ovh.herisson.Clyde.Tables.Course;
import ovh.herisson.Clyde.Tables.User;
public interface CourseRepository extends CrudRepository<Course,Long> {
Course findById(long id);
@Query("select c from Course c where c.owner = ?1")
Iterable<Course> findAllOwnedCoures(User teacher);
}

View File

@ -1,17 +0,0 @@
package ovh.herisson.Clyde.Repositories;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import ovh.herisson.Clyde.Tables.Course;
import ovh.herisson.Clyde.Tables.Curriculum;
import ovh.herisson.Clyde.Tables.CurriculumCourse;
public interface CurriculumCourseRepository extends CrudRepository<CurriculumCourse,Long> {
@Query("select distinct cc.course from CurriculumCourse cc where cc.curriculum = ?1")
Iterable<Course> findCoursesByCurriculum(Curriculum curriculum);
@Query("select distinct cc.curriculum from CurriculumCourse cc")
Iterable<Curriculum> findDistinctCurriculums();
}

View File

@ -1,8 +0,0 @@
package ovh.herisson.Clyde.Repositories;
import org.springframework.data.repository.CrudRepository;
import ovh.herisson.Clyde.Tables.Curriculum;
public interface CurriculumRepository extends CrudRepository<Curriculum,Long> {
Curriculum findById(long id);
}

View File

@ -1,14 +0,0 @@
package ovh.herisson.Clyde.Repositories;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import ovh.herisson.Clyde.Tables.StorageFile;
import java.util.ArrayList;
public interface FileRepository extends CrudRepository<StorageFile,Long> {
public StorageFile getStorageFileByName(String name);
}

View File

@ -1,10 +0,0 @@
package ovh.herisson.Clyde.Repositories;
import org.springframework.data.repository.CrudRepository;
import ovh.herisson.Clyde.Tables.InscriptionRequest;
public interface InscriptionRepository extends CrudRepository<InscriptionRequest,Long> {
InscriptionRequest findById(long aLong);
}

View File

@ -1,14 +0,0 @@
package ovh.herisson.Clyde.Repositories;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import ovh.herisson.Clyde.Tables.Course;
import ovh.herisson.Clyde.Tables.TeacherCourse;
import ovh.herisson.Clyde.Tables.User;
public interface TeacherCourseRepository extends CrudRepository<TeacherCourse, Long> {
@Query("select tc.user from TeacherCourse tc where tc.course = ?1")
Iterable<User> findAllAssistantOfCourse(Course course);
}

View File

@ -1,14 +0,0 @@
package ovh.herisson.Clyde.Repositories;
import org.springframework.data.repository.CrudRepository;
import ovh.herisson.Clyde.Tables.Token;
import ovh.herisson.Clyde.Tables.User;
import java.util.ArrayList;
public interface TokenRepository extends CrudRepository<Token,Long> {
Token getByToken(String token);
ArrayList <Token> getByUserOrderByExpirationDate(User user);
}

View File

@ -1,13 +0,0 @@
package ovh.herisson.Clyde.Repositories;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import ovh.herisson.Clyde.Tables.Curriculum;
import ovh.herisson.Clyde.Tables.User;
import ovh.herisson.Clyde.Tables.UserCurriculum;
public interface UserCurriculumRepository extends CrudRepository<UserCurriculum, Long> {
@Query("select uc.curriculum from UserCurriculum uc where uc.user = ?1")
Curriculum findByUser(User student);
}

View File

@ -4,20 +4,13 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import ovh.herisson.Clyde.Tables.User; import ovh.herisson.Clyde.Tables.User;
import java.util.List;
public interface UserRepository extends CrudRepository<User, Long> { public interface UserRepository extends CrudRepository<User, Long> {
User findById(long id); User findById(long id);
User findByEmail(String email); /**
@Query(value = "select a.* from Users a ",nativeQuery = true)
Iterable<User> findAllUsers();**/
@Query("select u from User u where u.role = ovh.herisson.Clyde.Tables.Role.Teacher")
Iterable<User> findAllTeachers();
@Query("select u from User u where u.role = ovh.herisson.Clyde.Tables.Role.Student")
Iterable<User> findAllStudents();
@Query("select u from User u where u.role <> ovh.herisson.Clyde.Tables.Role.Admin")
Iterable<User> findAllExceptAdmins();
} }

View File

@ -1,12 +0,0 @@
package ovh.herisson.Clyde.Responses;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
public class UnauthorizedResponse<T> extends ResponseEntity<T> {
public UnauthorizedResponse(T message) {
super(message,HttpStatus.UNAUTHORIZED);
}
}

View File

@ -1,53 +0,0 @@
package ovh.herisson.Clyde.Services;
import org.springframework.stereotype.Service;
import ovh.herisson.Clyde.Tables.*;
import java.util.Date;
@Service
public class AuthenticatorService {
private final TokenService tokenService;
private final UserService userService;
private final InscriptionService inscriptionService;
public AuthenticatorService(TokenService tokenService, UserService userService, InscriptionService inscriptionService){
this.tokenService = tokenService;
this.userService = userService;
this.inscriptionService = inscriptionService;
}
public User getUserFromToken(String token){
return tokenService.getUserFromToken(token);
}
public String login(String identifier, String password, Date expirationDate){
User user = userService.getUser(identifier);
if (user == null){return null;}
if (!userService.checkPassword(user,password)){return null;}
String token = tokenService.generateNewToken();
tokenService.saveToken(new Token(user, token,expirationDate));
return token;
}
public InscriptionRequest register(InscriptionRequest inscriptionRequest) {
inscriptionRequest.setState(RequestState.Pending);
return inscriptionService.save(inscriptionRequest);
}
public boolean isNotIn(Role[] roles, String token){
if (token == null)
return true;
User poster = getUserFromToken(token);
if (poster == null) return true;
for (Role r:roles){
if (poster.getRole() == r)
return false;
}
return true;
}
}

View File

@ -1,83 +0,0 @@
package ovh.herisson.Clyde.Services;
import org.springframework.stereotype.Service;
import ovh.herisson.Clyde.Repositories.CourseRepository;
import ovh.herisson.Clyde.Tables.Course;
import ovh.herisson.Clyde.Tables.Role;
import ovh.herisson.Clyde.Tables.User;
import java.util.Map;
@Service
public class CourseService {
private final CourseRepository courseRepo;
public CourseService(CourseRepository courseRepo) {
this.courseRepo = courseRepo;
}
public Course save(Course course){
if (course.getOwner().getRole() != Role.Teacher)
return null;
return courseRepo.save(course);
}
public Course findById(long id){
return courseRepo.findById(id);
}
public Iterable<Course> findAll() {
return courseRepo.findAll();
}
public Iterable<Course> findOwnedCourses(User userFromToken) {
return courseRepo.findAllOwnedCoures(userFromToken);
}
public boolean modifyData(long id, Map<String, Object> updates, Role role) {
Course target = courseRepo.findById(id);
if (target == null)
return false;
if (role == Role.Teacher){
for (Map.Entry<String, Object> entry : updates.entrySet()){
if (entry.getKey().equals("title")){
target.setTitle((String) entry.getValue());
courseRepo.save(target);
return true;
}
}
}
if (role != Role.Secretary)
return false;
for (Map.Entry<String ,Object> entry: updates.entrySet()){
switch (entry.getKey()){
case "title":
target.setTitle((String) entry.getValue());
break;
case "credits":
target.setCredits((Integer) entry.getValue());
break;
case "owner":
if (((User) entry.getValue() ).getRole() != Role.Teacher)
break;
target.setOwner((User) entry.getValue());
break;
}
}
courseRepo.save(target);
return true;
}
public void delete(Course course) {
courseRepo.delete(course);
}
}

View File

@ -1,93 +0,0 @@
package ovh.herisson.Clyde.Services;
import org.springframework.stereotype.Service;
import ovh.herisson.Clyde.Repositories.CourseRepository;
import ovh.herisson.Clyde.Repositories.CurriculumCourseRepository;
import ovh.herisson.Clyde.Repositories.CurriculumRepository;
import ovh.herisson.Clyde.Tables.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@Service
public class CurriculumCourseService {
private final CurriculumCourseRepository curriculumCourseRepo;
private final CourseRepository courseRepo;
private final CurriculumRepository curriculumRepo;
public CurriculumCourseService(CurriculumCourseRepository curriculumCourseRepository, CourseRepository courseRepo, CurriculumRepository curriculumRepo) {
this.curriculumCourseRepo = curriculumCourseRepository;
this.courseRepo = courseRepo;
this.curriculumRepo = curriculumRepo;
}
public void save(CurriculumCourse curriculumCourse){
curriculumCourseRepo.save(curriculumCourse);
}
public Map<String, Object> getDepthCurriculum(Curriculum curriculum){
if (curriculum == null)
return null;
HashMap<String ,Object> toReturn = new HashMap<>();
ArrayList<Map<String ,Object>> courses = new ArrayList<>();
Iterable<Course> foundCourses = curriculumCourseRepo.findCoursesByCurriculum(curriculum);
for (Course c: foundCourses){
courses.add(ProtectionService.courseWithoutPassword(c));
}
toReturn.put("courses",courses);
toReturn.put("curriculumId", curriculum.getCurriculumId());
toReturn.put("year", curriculum.getYear());
toReturn.put("option", curriculum.getOption());
return toReturn;
}
public Iterable<Map<String, Object>> getAllDepthCurriculum(){
ArrayList<Map<String,Object>> toReturn = new ArrayList<>();
for (Curriculum curriculum : curriculumRepo.findAll()){
toReturn.add(getDepthCurriculum(curriculum));
}
return toReturn;
}
/** tries to add all courses to the curriculum
*
* @param coursesIds the ids of the courses to be added
* @param curriculum the curriculum to add the courses to
* @return if the changes were made
*/
public boolean saveAll(Iterable<Long> coursesIds, Curriculum curriculum) {
if (curriculum == null || coursesIds == null)
return false;
ArrayList<Course> toAdd = new ArrayList<>();
for (Long courseId : coursesIds){
Course course = courseRepo.findById((long) courseId);
if (course == null)
return false;
if (!toAdd.contains(course))
toAdd.add(course);
}
for (Course course : toAdd){
curriculumCourseRepo.save(new CurriculumCourse(curriculum,course));
}
return true;
}
}

View File

@ -1,25 +0,0 @@
package ovh.herisson.Clyde.Services;
import org.springframework.stereotype.Service;
import ovh.herisson.Clyde.Repositories.CurriculumRepository;
import ovh.herisson.Clyde.Tables.Curriculum;
@Service
public class CurriculumService {
private final CurriculumRepository curriculumRepo;
public CurriculumService(CurriculumRepository curriculumRepo){
this.curriculumRepo = curriculumRepo;
}
public Curriculum save(Curriculum curriculum){
return curriculumRepo.save(curriculum);
}
public Curriculum findById(long id){
return curriculumRepo.findById(id);
}
public void delete(Curriculum curriculum) {
curriculumRepo.delete(curriculum);
}
}

View File

@ -1,94 +0,0 @@
package ovh.herisson.Clyde.Services;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import ovh.herisson.Clyde.Repositories.CurriculumRepository;
import ovh.herisson.Clyde.Repositories.InscriptionRepository;
import ovh.herisson.Clyde.Repositories.UserCurriculumRepository;
import ovh.herisson.Clyde.Repositories.UserRepository;
import ovh.herisson.Clyde.Tables.InscriptionRequest;
import ovh.herisson.Clyde.Tables.RequestState;
import ovh.herisson.Clyde.Tables.User;
import ovh.herisson.Clyde.Tables.UserCurriculum;
@Service
public class InscriptionService {
private final InscriptionRepository inscriptionRepo;
private final UserRepository userRepo;
private final UserCurriculumRepository userCurriculumRepo;
private final CurriculumRepository curriculumRepo;
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
public InscriptionService(InscriptionRepository inscriptionRepo, UserRepository userRepo, UserCurriculumRepository userCurriculumRepo, CurriculumRepository curriculumRepo){
this.inscriptionRepo = inscriptionRepo;
this.userRepo = userRepo;
this.userCurriculumRepo = userCurriculumRepo;
this.curriculumRepo = curriculumRepo;
}
public InscriptionRequest save(InscriptionRequest inscriptionRequest){
inscriptionRequest.setPassword(passwordEncoder.encode(inscriptionRequest.getPassword()));
return inscriptionRepo.save(inscriptionRequest);
}
public InscriptionRequest getById(long id){
return inscriptionRepo.findById(id);
}
public Iterable<InscriptionRequest> getAll(){
return inscriptionRepo.findAll();
}
public boolean modifyState(long id, RequestState requestState) {
InscriptionRequest inscrRequest = getById(id);
if (inscrRequest == null)
return false;
// if th state is the same we don't send an email
if (requestState == inscrRequest.getState())
return false;
/** todo send an email to tell the poster of the inscrRequest (inscrRequest.getEmail())
* to notify them that the state of their request changed
* FooEmailFormat toSend = (String.format("Your request state changed from %s to %s"),
* inscrRequest.getState(), requestState)
* FooEmailSender.send(toSend, inscrRequest.getEmail())
*/
//saves the user from the request if accepted
if (requestState == RequestState.Accepted)
{
if (curriculumRepo.findById(inscrRequest.getCurriculumId()) == null)
return false;
User userFromRequest = new User(
inscrRequest.getLastName(),
inscrRequest.getFirstName(),
inscrRequest.getEmail(),
inscrRequest.getAddress(),
inscrRequest.getCountry(),
inscrRequest.getBirthDate(),
inscrRequest.getProfilePicture(),
inscrRequest.getPassword()
);
userRepo.save(userFromRequest);
userCurriculumRepo.save(new UserCurriculum(userFromRequest, curriculumRepo.findById(inscrRequest.getCurriculumId())));
}
inscrRequest.setState(requestState);
save(inscrRequest);
return true;
}
public void delete(InscriptionRequest toDelete) {
inscriptionRepo.delete(toDelete);
}
}

View File

@ -1,105 +0,0 @@
package ovh.herisson.Clyde.Services;
import ovh.herisson.Clyde.Tables.Course;
import ovh.herisson.Clyde.Tables.InscriptionRequest;
import ovh.herisson.Clyde.Tables.User;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class ProtectionService {
/** return user's data except password
* @param user the user to return
* @return all the user data without the password
*/
public static HashMap<String,Object> userWithoutPassword(User user){
if (user ==null)
return null;
HashMap<String,Object> toReturn = new HashMap<>();
toReturn.put("regNo",user.getRegNo());
toReturn.put("lastName",user.getLastName());
toReturn.put("firstName",user.getFirstName());
toReturn.put("email", user.getEmail());
toReturn.put("address",user.getAddress());
toReturn.put("birthDate",user.getBirthDate());
toReturn.put("country",user.getCountry());
toReturn.put("profilePictureUrl",user.getProfilePictureUrl());
toReturn.put("role",user.getRole());
return toReturn;
}
public static Iterable<HashMap<String ,Object>>usersWithoutPasswords(Iterable<User> users){
ArrayList<HashMap<String,Object>> toReturn = new ArrayList<>();
for (User u : users){
toReturn.add(userWithoutPassword(u));
}
return toReturn;
}
public static HashMap<String,Object> courseWithoutPassword(Course course){
if (course == null)
return null;
HashMap<String ,Object> toReturn = new HashMap<>();
toReturn.put("courseId",course.getCourseID());
toReturn.put("credits",course.getCredits());
toReturn.put("title", course.getTitle());
toReturn.put("owner", userWithoutPassword(course.getOwner()));
return toReturn;
}
public static Iterable<HashMap<String ,Object>> coursesWithoutPasswords(Iterable<Course> courses){
ArrayList<HashMap<String,Object>> toReturn = new ArrayList<>();
for (Course course: courses){
toReturn.add(ProtectionService.courseWithoutPassword(course));
}
return toReturn;
}
public static Map<String, Object> requestWithoutPassword(InscriptionRequest inscriptionRequest) {
if (inscriptionRequest == null)
return null;
Map<String, Object> toReturn = new HashMap<>();
toReturn.put("id", inscriptionRequest.getId());
toReturn.put("lastName", inscriptionRequest.getLastName());
toReturn.put("firstName", inscriptionRequest.getFirstName());
toReturn.put("address", inscriptionRequest.getAddress());
toReturn.put("email",inscriptionRequest.getEmail());
toReturn.put("birthDate", inscriptionRequest.getBirthDate());
toReturn.put("country", inscriptionRequest.getCountry());
toReturn.put("curriculum", inscriptionRequest.getCurriculumId());
toReturn.put("state", inscriptionRequest.getState());
toReturn.put("profilePictureUrl", inscriptionRequest.getProfilePicture());
return toReturn;
}
public static Iterable<Map<String ,Object>> requestsWithoutPasswords(Iterable<InscriptionRequest> inscriptionRequests){
ArrayList<Map<String,Object>> toReturn = new ArrayList<>();
for (InscriptionRequest i:inscriptionRequests){
toReturn.add(requestWithoutPassword(i));
}
return toReturn;
}
}

View File

@ -1,69 +0,0 @@
package ovh.herisson.Clyde.Services;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import ovh.herisson.Clyde.Repositories.FileRepository;
import ovh.herisson.Clyde.Tables.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Objects;
import java.util.UUID;
@Service
public class StorageService {
private final Path rootLocation = Paths.get("cdn/");
private final FileRepository fileRepo;
public StorageService(FileRepository filerepo){
this.fileRepo = filerepo;
if(!Files.exists(rootLocation)){
try {
Files.createDirectories(rootLocation);
} catch(IOException e){
e.printStackTrace();
}
}
}
public StorageFile store(MultipartFile file, FileType fileType) {
if (file == null || file.getOriginalFilename() == null)
return null;
if (file.getOriginalFilename().isEmpty()){return null;}
UUID uuid = UUID.randomUUID();
String stringUuid = uuid + "." + file.getOriginalFilename().split("\\.",2)[1];
try {
if (file.isEmpty()) {
return null;
}
Path destinationFile = this.rootLocation.resolve(Paths.get(stringUuid));
Files.copy(file.getInputStream(), destinationFile,StandardCopyOption.REPLACE_EXISTING);
}
catch (IOException e) {
return null;
}
String url = this.rootLocation.resolve(Paths.get(Objects.requireNonNull(stringUuid)))
.normalize().toString();
return fileRepo.save(new StorageFile(file.getOriginalFilename(),url, fileType));
}
public void delete(StorageFile file) throws SecurityException {
File f = new File(file.getUrl());
f.delete();
//Delete l'entité
fileRepo.delete(file);
}
}

View File

@ -1,53 +0,0 @@
package ovh.herisson.Clyde.Services;
import org.springframework.stereotype.Controller;
import ovh.herisson.Clyde.Repositories.TeacherCourseRepository;
import ovh.herisson.Clyde.Repositories.UserRepository;
import ovh.herisson.Clyde.Tables.Course;
import ovh.herisson.Clyde.Tables.Role;
import ovh.herisson.Clyde.Tables.TeacherCourse;
import ovh.herisson.Clyde.Tables.User;
import java.util.ArrayList;
@Controller
public class TeacherCourseService {
private final TeacherCourseRepository teacherCourseRepo;
private final UserRepository userRepo;
public TeacherCourseService(TeacherCourseRepository teacherCourseRepo, UserRepository userRepo) {
this.teacherCourseRepo = teacherCourseRepo;
this.userRepo = userRepo;
}
public Iterable<User> findCourseAssistants(Course course) {
if (course == null)
return null;
return teacherCourseRepo.findAllAssistantOfCourse(course);
}
public boolean saveAll(Iterable<Long> teacherIds, Course course){
if (course == null || teacherIds == null)
return false;
ArrayList<User> toAdd = new ArrayList<>();
for (Long teacherId : teacherIds){
User teacher = userRepo.findById((long) teacherId);
if ( teacher== null){
return false;
}
if (!toAdd.contains(teacher) && teacher.getRole() == Role.Teacher)
{
toAdd.add(teacher);
}
}
for (User teacher: toAdd){
teacherCourseRepo.save(new TeacherCourse(teacher,course));
}
return true;
}
}

View File

@ -1,74 +0,0 @@
package ovh.herisson.Clyde.Services;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import ovh.herisson.Clyde.Repositories.TokenRepository;
import ovh.herisson.Clyde.Tables.Token;
import ovh.herisson.Clyde.Tables.User;
import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Calendar;
@Service
public class TokenService {
private final TokenRepository tokenRepo;
public TokenService(TokenRepository tokenRepo){
this.tokenRepo = tokenRepo;
}
public Iterable<Token> getAllTokens() {
return tokenRepo.findAll();
}
public String generateNewToken(){
byte[] bytes = new byte[64];
new SecureRandom().nextBytes(bytes);
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) (((bytes[i]+256)%256 %95+ 32));
}
// will never end up in the catch because of the way that SecureRandom.nextBytes is implemented
try {
return new String(Base64.getEncoder().encode(bytes),"ISO_8859_1");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public User getUserFromToken(String token) {
Token tokenRep = tokenRepo.getByToken(token);
if (tokenRep == null)
return null;
return tokenRep.getUser();
}
public void saveToken(Token token){
//Si l'utilisateur a déja 5 token delete celui qui devait expirer le plus vite
ArrayList<Token> tokenList = tokenRepo.getByUserOrderByExpirationDate(token.getUser());
while(tokenList.size() >= 5){
tokenRepo.delete(tokenList.getFirst());
tokenList.remove(tokenList.getFirst());
}
tokenRepo.save(token);
}
//Tous les jours a minuit
@Scheduled(cron = "0 0 0 * * ?")
public void autoDeleteToken() {
for (Token t: tokenRepo.findAll()){
Calendar cal = Calendar.getInstance();
Calendar cal2 = Calendar.getInstance();
cal2.setTime(t.getExpirationDate());
if (cal.compareTo(cal2) >= 0){
tokenRepo.delete(t);
}
}
}
}

View File

@ -1,20 +0,0 @@
package ovh.herisson.Clyde.Services;
import org.springframework.stereotype.Service;
import ovh.herisson.Clyde.Repositories.UserCurriculumRepository;
import ovh.herisson.Clyde.Tables.Curriculum;
import ovh.herisson.Clyde.Tables.User;
@Service
public class UserCurriculumService {
private final UserCurriculumRepository userCurriculumRepository;
public UserCurriculumService(UserCurriculumRepository userCurriculumRepository) {
this.userCurriculumRepository = userCurriculumRepository;
}
public Curriculum findByUser(User student){
return userCurriculumRepository.findByUser(student);
}
}

View File

@ -1,134 +0,0 @@
package ovh.herisson.Clyde.Services;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import ovh.herisson.Clyde.Repositories.UserRepository;
import ovh.herisson.Clyde.Tables.Role;
import ovh.herisson.Clyde.Tables.User;
import java.util.*;
@Service
public class UserService {
private final UserRepository userRepo;
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
public UserService(UserRepository userRepo){
this.userRepo = userRepo;
}
/** return the user identified by th identifier
*
* @param identifier can be an email or the RegNo
* @return the identified user
*/
public User getUser(String identifier){
if (identifier == null)
return null;
try {
int id = Integer.parseInt(identifier);
return userRepo.findById(id);
}
catch (NumberFormatException nfe){
return userRepo.findByEmail(identifier);
}
}
/** modify the target data
* verify the permission of modifying from the poster
*
* @param poster the user wanting to modify target's data
* @param updates the changes to be made
* @param targetId the id of the user to update
* @return if the changes were done or not
*/
public boolean modifyData(long targetId, Map<String ,Object> updates, User poster){
User target = userRepo.findById(targetId);
if (target == null)
return false;
if (poster.getRegNo().equals(target.getRegNo())){
for (Map.Entry<String, Object> entry : updates.entrySet()){
switch (entry.getKey()){
case "firstName":
target.setFirstName((String) entry.getValue());
break;
case "lastName":
target.setLastName((String) entry.getValue());
break;
case "email":
target.setEmail((String) entry.getValue());
break;
case "address":
target.setAddress((String) entry.getValue());
break;
case "country":
target.setCountry((String) entry.getValue());
break;
case "birthDate":
target.setBirthDate((Date) entry.getValue());
break;
case "profilePictureUrl":
target.setProfilePictureUrl((String) entry.getValue());
break;
case "password":
target.setPassword(passwordEncoder.encode((String) entry.getValue()));
break;
}
}
userRepo.save(target);
return true;
}
// the secretary can change roles (for example if a student becomes a teacher)
else if (poster.getRole() == Role.Secretary)
{
for (Map.Entry<String, Object> entry : updates.entrySet()){
if ( entry.getKey().equals("role")) {
if (entry.getValue() == Role.Admin) {return false;}
target.setRole((Role) entry.getValue());
userRepo.save(target);
return true;
}
}
}
return false;
}
public boolean checkPassword(User user, String tryingPassword){
return passwordEncoder.matches(tryingPassword, user.getPassword());
}
public User save(User user){
user.setPassword(passwordEncoder.encode(user.getPassword()));
return userRepo.save(user);
}
public Iterable<User> getAll(){
return userRepo.findAll();
}
public Iterable<User> getAllExceptAdmins(){
return userRepo.findAllExceptAdmins();
}
public Iterable<User> getAllTeachers (){return userRepo.findAllTeachers();}
public Iterable<User> getAllStudents(){return userRepo.findAllStudents();}
public User getUserById(long id) {
return userRepo.findById(id);
}
public void delete(User user) {
userRepo.delete(user);
}
}

View File

@ -1,23 +0,0 @@
package ovh.herisson.Clyde.Tables;
public enum Applications {
// without any token
Login,
// with any token
Profile,
// Students and higher authorization
Msg,
Forum,
Rdv,
// teachers and Secretary authorization
ManageCourses,
UsersList,
// InscriptionService authorization
Inscription,
StudentsList
}

View File

@ -1,8 +1,9 @@
package ovh.herisson.Clyde.Tables; package ovh.herisson.Clyde.Tables;
import jakarta.persistence.*; import jakarta.persistence.Entity;
import org.hibernate.annotations.OnDelete; import jakarta.persistence.GeneratedValue;
import org.hibernate.annotations.OnDeleteAction; import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity @Entity
public class Course { public class Course {
@ -11,16 +12,12 @@ public class Course {
private int courseID; private int courseID;
private int credits; private int credits;
private String title; private String title;
private String faculty;
@ManyToOne(fetch = FetchType.EAGER) public Course(int credits, String title, String faculty){
@OnDelete(action = OnDeleteAction.SET_NULL)
@JoinColumn(name = "Users")
private User owner;
public Course(int credits, String title, User owner){
this.credits = credits; this.credits = credits;
this.title = title; this.title = title;
this.owner = owner; this.faculty = faculty;
} }
public Course() {} public Course() {}
@ -37,6 +34,14 @@ public class Course {
this.credits = credits; this.credits = credits;
} }
public String getFaculty() {
return faculty;
}
public void setFaculty(String faculty){
this.faculty = faculty;
}
public String getTitle() { public String getTitle() {
return title; return title;
} }
@ -44,12 +49,4 @@ public class Course {
public void setTitle(String title){ public void setTitle(String title){
this.title = title; this.title = title;
} }
public User getOwner() {
return owner;
}
public void setOwner(User owner) {
this.owner = owner;
}
} }

View File

@ -1,49 +0,0 @@
package ovh.herisson.Clyde.Tables;
import jakarta.persistence.*;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
@Entity
public class CurriculumCourse {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "Curriculum")
@OnDelete(action = OnDeleteAction.CASCADE)
private Curriculum curriculum;
@ManyToOne(fetch = FetchType.EAGER)
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinColumn(name = "Course")
private Course course;
public CurriculumCourse(Curriculum curriculum, Course course){
this.curriculum = curriculum;
this.course = course;
}
public CurriculumCourse() {}
public int getId() {
return id;
}
public Course getCourse() {
return course;
}
public void setCourse(Course course){
this.course = course;
}
public Curriculum getCurriculum() {
return curriculum;
}
public void setCurriculum(Curriculum curriculum) {
this.curriculum = curriculum;
}
}

View File

@ -6,21 +6,22 @@ import jakarta.persistence.GenerationType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
@Entity @Entity
public class Curriculum { public class Cursus {
@Id @Id
@GeneratedValue(strategy = GenerationType.AUTO) @GeneratedValue(strategy = GenerationType.AUTO)
private int curriculumId; private int cursusId;
private int year; private int year;
private String option; private String option;
public Curriculum(int year, String option){
public Cursus(int year, String option){
this.year = year; this.year = year;
this.option = option; this.option = option;
} }
public Curriculum() {} public Cursus() {}
public int getCurriculumId(){ public int getCursusId(){
return this.curriculumId; return this.cursusId;
} }
public int getYear(){ public int getYear(){

View File

@ -0,0 +1,43 @@
package ovh.herisson.Clyde.Tables;
import jakarta.persistence.*;
@Entity
public class CursusCourse {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@JoinColumn(name = "Cursus")
private int cursusId;
@JoinColumn(name = "Course")
private int courseId;
public CursusCourse(int cursusId, int courseId){
this.cursusId = cursusId;
this.courseId = courseId;
}
public CursusCourse() {}
public int getId() {
return id;
}
public int getCourseId() {
return courseId;
}
public void setCourseId(int courseId){
this.courseId = courseId;
}
public int getCursusId() {
return cursusId;
}
public void setCursusId(int cursusId) {
this.cursusId = cursusId;
}
}

View File

@ -1,8 +0,0 @@
package ovh.herisson.Clyde.Tables;
public enum FileType {
ProfilePicture,
EducationCertificate
}

View File

@ -1,121 +0,0 @@
package ovh.herisson.Clyde.Tables;
import jakarta.persistence.*;
import java.util.Date;
@Entity
public class InscriptionRequest {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String firstName;
private String lastName;
private String address;
private String email;
private String country;
private Date birthDate;
private Long curriculumId;
private RequestState state;
private String profilePicture;
private String password;
public InscriptionRequest(){}
public InscriptionRequest(String lastName, String firstName, String address, String email, String country, Date birthDate,Long curriculumId, RequestState state, String profilePicture, String password){
this.lastName = lastName;
this.firstName = firstName;
this.address = address;
this.email = email;
this.country = country;
this.birthDate = birthDate;
this.curriculumId = curriculumId;
this.state = state;
this.profilePicture = profilePicture;
this.password = password;
}
public int getId() {
return id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
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 long getCurriculumId() {
return curriculumId;
}
public void setCurriculumId(long curriculum) {
this.curriculumId = curriculum;
}
public RequestState getState() {
return state;
}
public void setState(RequestState state) {
this.state = state;
}
public String getProfilePicture() {
return profilePicture;
}
public void setProfilePicture(String profilePicture) {
this.profilePicture = profilePicture;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@ -1,78 +0,0 @@
package ovh.herisson.Clyde.Tables;
import jakarta.persistence.*;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
@Entity
public class ReInscriptionRequest {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@ManyToOne
@JoinColumn(name = "Users")
@OnDelete(action = OnDeleteAction.CASCADE)
private User user;
@ManyToOne
@JoinColumn(name = "Curriculum")
@OnDelete(action = OnDeleteAction.CASCADE)
private Curriculum newCurriculum;
private RequestState state;
//Permet de différencier les demandes de changement et une réinscription dans le même Curriculum
//Pour la réinscription on va le mettre a 0
private boolean type = false;
public ReInscriptionRequest(){}
public ReInscriptionRequest(User user, Curriculum newCurriculum, RequestState state, boolean type){
this.user = user;
this.newCurriculum = newCurriculum;
this.state = state;
this.type = type;
}
public ReInscriptionRequest(User user, Curriculum newCurriculum, RequestState state){
this.user = user;
this.newCurriculum = newCurriculum;
this.state = state;
}
public int getId() {
return id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Curriculum getNewCurriculum() {
return newCurriculum;
}
public void setNewCurriculum(Curriculum newCurriculum) {
this.newCurriculum = newCurriculum;
}
public RequestState getState() {
return state;
}
public void setState(RequestState state) {
this.state = state;
}
public boolean isType() {
return type;
}
public void setType(boolean type) {
this.type = type;
}
}

View File

@ -1,7 +0,0 @@
package ovh.herisson.Clyde.Tables;
public enum RequestState {
Accepted,
Refused,
Pending
}

View File

@ -4,6 +4,5 @@ public enum Role {
Teacher, Teacher,
Student, Student,
Admin, Admin,
InscriptionService, Secretary;
Secretary }
}

View File

@ -0,0 +1,41 @@
package ovh.herisson.Clyde.Tables;
import jakarta.persistence.*;
@Entity
public class Secretary {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@JoinColumn(name = "Users")
private int regNo;
private String faculty;
public Secretary(int regNo, String faculty){
this.regNo = regNo;
this.faculty = faculty;
}
public Secretary() {}
public int getId() {
return id;
}
public int getRegNo() {
return regNo;
}
public void setRegNo(int regNo) {
this.regNo = regNo;
}
public String getFaculty() {
return faculty;
}
public void setFaculty(String faculty) {
this.faculty = faculty;
}
}

View File

@ -1,59 +0,0 @@
package ovh.herisson.Clyde.Tables;
import jakarta.persistence.*;
@Entity
public class StorageFile {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String url;
private FileType fileType;
public StorageFile(String name, String url, FileType fileType){
this.name = name;
this.url = url;
this.fileType = fileType;
}
public StorageFile(){}
public void setId(Long id) {
this.id = id;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public FileType getFileType() {
return fileType;
}
public void setFileType(FileType fileType) {
this.fileType = fileType;
}
}

View File

@ -1,51 +0,0 @@
package ovh.herisson.Clyde.Tables;
import jakarta.persistence.*;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
@Entity
public class TeacherCourse {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@ManyToOne(fetch = FetchType.EAGER)
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinColumn(name = "Users")
private User user;
@ManyToOne(fetch = FetchType.EAGER)
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinColumn(name = "Course")
private Course course;
public TeacherCourse(User user, Course course){
this.user = user;
this.course = course;
}
public TeacherCourse() {}
public int getId() {
return id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Course getCourse() {
return course;
}
public void setCourse(Course course) {
this.course = course;
}
}

View File

@ -0,0 +1,55 @@
package ovh.herisson.Clyde.Tables;
import jakarta.persistence.*;
@Entity
public class TeacherGivenCourse {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@JoinColumn(name = "Users")
private int regNo;
@JoinColumn(name = "Course")
private int courseId;
//This flag helps make the difference between an assistant or a Teacher (who owns the course)
private boolean owned;
public TeacherGivenCourse(int regNo, int courseId, boolean owned){
this.regNo = regNo;
this.courseId = courseId;
this.owned = owned;
}
public TeacherGivenCourse() {}
public int getId() {
return id;
}
public int getRegNo() {
return regNo;
}
public void setRegNo(int regNo) {
this.regNo = regNo;
}
public int getCourseId() {
return courseId;
}
public void setCourseId(int courseId) {
this.courseId = courseId;
}
public boolean isOwned() {
return owned;
}
public void setOwned(boolean owned) {
this.owned = owned;
}
}

View File

@ -1,10 +1,6 @@
package ovh.herisson.Clyde.Tables; package ovh.herisson.Clyde.Tables;
import jakarta.persistence.*; import jakarta.persistence.*;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import java.util.Date;
@Entity @Entity
public class Token { public class Token {
@ -12,30 +8,26 @@ public class Token {
@Id @Id
private int id; private int id;
@ManyToOne(fetch = FetchType.EAGER)
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinColumn(name ="Users") @JoinColumn(name ="Users")
private User user; private int regNo;
private String token; private String token;
private Date expirationDate;
public Token(User user, String token, Date expirationDate){ public Token(int regNo, String token){
this.user = user; this.regNo = regNo;
this.token = token; this.token = token;
this.expirationDate = expirationDate;
} }
public Token(){} public Token(){}
public int getId() { public int getId() {
return id; return id;
} }
public User getUser() {
return user; public int getRegNo() {
return regNo;
} }
public void setUser(User regNo) { public void setRegNo(int regNo) {
this.user = regNo; this.regNo = regNo;
} }
public String getToken(){ public String getToken(){
@ -45,12 +37,4 @@ public class Token {
public void setToken(String data) { public void setToken(String data) {
this.token = data; this.token = data;
} }
public void setExpirationDate(Date date){
this.expirationDate = date;
}
public Date getExpirationDate(){
return expirationDate;
}
} }

View File

@ -1,55 +1,41 @@
package ovh.herisson.Clyde.Tables; package ovh.herisson.Clyde.Tables;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.util.Date; import java.util.Date;
//Classe représentant un utilisateur l'attribut password demande surement un peu de rafinement niveau sécurité
//et l'attribut tokenApi doit encore être ajouté vu qu'il faut en discuter
@Entity @Entity
//Je rajoute un s au nom de la table pour éviter les conflits avec les mots réservés
@Table(name = "Users") @Table(name = "Users")
public class User { public class User {
@Id @Id
@GeneratedValue(strategy = GenerationType.AUTO) @GeneratedValue(strategy = GenerationType.AUTO)
private Long regNo; private int regNo;
private String lastName; private String lastName;
private String firstName; private String firstName;
@Column(unique = true)
private String email; private String email;
private String address; private String address;
private String country; private String country;
private Date birthDate; private Date birthDate;
private String profilePictureUrl;
private ovh.herisson.Clyde.Tables.Role role; private ovh.herisson.Clyde.Tables.Role role;
private String password; private String password;
public User(String lastName, String firstName, String email, String address, public User(String lastName, String firstName, String email, String address, String country, Date birthDate, Role role, String password){
String country, Date birthDate, String profilePictureUrl, Role role, String password)
{
this.lastName = lastName; this.lastName = lastName;
this.firstName = firstName; this.firstName = firstName;
this.email = email; this.email = email;
this.address = address; this.address = address;
this.country = country; this.country = country;
this.birthDate = birthDate; this.birthDate = birthDate;
this.profilePictureUrl = profilePictureUrl;
this.role = role; this.role = role;
this.password = password; this.password = password;
} }
public User(String lastName, String firstName, String email, String address,
String country, Date birthDate, String profilePictureUrl, String password)
{
this.lastName = lastName;
this.firstName = firstName;
this.email = email;
this.address = address;
this.country = country;
this.birthDate = birthDate;
this.profilePictureUrl = profilePictureUrl;
this.password = password;
this.role = Role.Student;
}
public User() {} public User() {}
public Long getRegNo(){ public int getRegNo(){
return this.regNo; return this.regNo;
} }
public String getLastName() { public String getLastName() {
@ -80,8 +66,8 @@ public class User {
return address; return address;
} }
public void setAddress(String address) { public void setAddress(String adress) {
this.address = address; this.address = adress;
} }
public String getCountry() { public String getCountry() {
@ -100,11 +86,6 @@ public class User {
this.birthDate = 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() { public ovh.herisson.Clyde.Tables.Role getRole() {
return role; return role;
} }

View File

@ -1,50 +0,0 @@
package ovh.herisson.Clyde.Tables;
import jakarta.persistence.*;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
@Entity
public class UserCurriculum {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
//Un étudiant peut avoir plusieurs curriculums
@ManyToOne(fetch = FetchType.EAGER)
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinColumn(name = "Users")
private User user;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "Curriculum")
@OnDelete(action = OnDeleteAction.CASCADE)
private Curriculum curriculum;
public UserCurriculum(User user, Curriculum curriculum){
this.user = user;
this.curriculum = curriculum;
}
public UserCurriculum() {}
public int getId() {
return id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Curriculum getCurriculum() {
return curriculum;
}
public void setCurriculum(Curriculum curriculum) {
this.curriculum = curriculum;
}
}

View File

@ -0,0 +1,42 @@
package ovh.herisson.Clyde.Tables;
import jakarta.persistence.*;
@Entity
public class UserCursus {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@JoinColumn(name = "Users")
private int regNo;
@JoinColumn(name = "Cursus")
private int cursusId;
public UserCursus(int regNo, int cursusId){
this.regNo = regNo;
this.cursusId = cursusId;
}
public UserCursus() {}
public int getId() {
return id;
}
public int getRegNo() {
return regNo;
}
public void setRegNo(int regNo) {
this.regNo = regNo;
}
public int getCursusId() {
return cursusId;
}
public void setCursusId(int cursusId) {
this.cursusId = cursusId;
}
}

View File

@ -1,3 +1,2 @@
spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.sql.init.mode=always

View File

@ -8,7 +8,6 @@ class ClydeApplicationTests {
@Test @Test
void contextLoads() { void contextLoads() {
} }
} }

View File

@ -1,105 +0,0 @@
package ovh.herisson.Clyde.Endpoints;
import com.github.dockerjava.api.model.ExposedPort;
import com.github.dockerjava.api.model.HostConfig;
import com.github.dockerjava.api.model.PortBinding;
import com.github.dockerjava.api.model.Ports;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.Assert;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.ActiveProfiles;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import ovh.herisson.Clyde.ClydeApplication;
import ovh.herisson.Clyde.EndPoints.StorageController;
import ovh.herisson.Clyde.Repositories.FileRepository;
import ovh.herisson.Clyde.Services.StorageService;
import ovh.herisson.Clyde.Tables.FileType;
import ovh.herisson.Clyde.Tables.StorageFile;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import static io.restassured.RestAssured.with;
@Testcontainers
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles(value ="dev")
public class StorageControllerTest {
@LocalServerPort
private Integer port;
@Autowired
private ClydeApplication controller;
@Autowired
private StorageController storageController;
@Autowired
private StorageService storageService;
@Autowired
private FileRepository fileRepository;
@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:alpine")
.withDatabaseName("clyde")
.withUsername("devel")
.withPassword("devel")
.withCreateContainerCmdModifier(cmd -> cmd.withHostConfig(new HostConfig().withPortBindings(new PortBinding(Ports.Binding.bindPort(5442), new ExposedPort(5432)))));
@BeforeAll
static void beforeAll(){
postgres.start();
}
@AfterAll
static void afterAll(){
postgres.stop();
}
@BeforeEach
void setup(){
RestAssured.baseURI = "http://localhost:" + port;
}
@AfterEach
void afterEach(){
fileRepository.deleteAll();
}
@Test
public void uploadFileTest() throws IOException {
File mmf = new File("test.txt");
mmf.createNewFile();
FileWriter fw = new FileWriter(mmf);
fw.write("Ceci est un test");
fw.close();
with().multiPart("file", mmf).pathParam("fileType", FileType.ProfilePicture).when().request("POST", "/upload/{fileType}").then().statusCode(200);
StorageFile sf = fileRepository.getStorageFileByName("test.txt");
//On vérifie que le fichier a bien été stocké dans la db
Assert.assertFalse(sf == null);
//On vérifie que le fichier a bel et bien été stocké dans le dossier cdn
File extFile = new File(sf.getUrl());
Assert.assertTrue(extFile.exists());
Assert.assertEquals("cdn", extFile.getParent());
//On delete tranquillement les fichiers (la méthode delete a déja été testée dans StorageServiceTest)
storageService.delete(sf);
mmf.delete();
}
}

View File

@ -1,149 +0,0 @@
package ovh.herisson.Clyde.Endpoints;
import com.github.dockerjava.api.model.ExposedPort;
import com.github.dockerjava.api.model.HostConfig;
import com.github.dockerjava.api.model.PortBinding;
import com.github.dockerjava.api.model.Ports;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.Assert;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.test.context.ActiveProfiles;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import ovh.herisson.Clyde.ClydeApplication;
import ovh.herisson.Clyde.Repositories.TokenRepository;
import ovh.herisson.Clyde.Repositories.UserRepository;
import ovh.herisson.Clyde.Responses.UnauthorizedResponse;
import ovh.herisson.Clyde.Services.TokenService;
import ovh.herisson.Clyde.Tables.Role;
import ovh.herisson.Clyde.Tables.Token;
import ovh.herisson.Clyde.Tables.User;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import static io.restassured.RestAssured.with;
import static org.hamcrest.Matchers.equalTo;
@Testcontainers
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles(value = "dev")
public class UserControllerTest {
@LocalServerPort
private Integer port;
@Autowired
private ClydeApplication controller;
@Autowired
private TokenService tokenService;
@Autowired
private UserRepository userRepository;
@Autowired
private TokenRepository tokenRepository;
@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:alpine")
.withDatabaseName("clyde")
.withUsername("devel")
.withPassword("devel")
.withCreateContainerCmdModifier(cmd -> cmd.withHostConfig(new HostConfig().withPortBindings(new PortBinding(Ports.Binding.bindPort(5442), new ExposedPort(5432)))));
@BeforeAll
static void beforeAll(){
postgres.start();
}
@AfterAll
static void afterAll(){
postgres.stop();
}
@BeforeEach
void setup(){
RestAssured.baseURI = "http://localhost:" + port;
}
@AfterEach
void aftereach(){
tokenRepository.deleteAll();
userRepository.deleteAll();
}
@Test
//Verifie qu'un user qui n'a pas les permissions admin ou secretaire ne peut pas post
public void userPostTest(){
User god = new User("god","god","admin@admin.com","everywhere","every",new Date(0), null, Role.Admin,"goddoesntneedpassword");
Token godToken = new Token(god, tokenService.generateNewToken(), new Date());
userRepository.save(god);
tokenService.saveToken(godToken);
//Can god post herobrine himself ?
User herobrine = new User("brine","hero","herobrine@student.com","in your WalLs","ShadowsLand",new Date(0), null,Role.Student,"test");
with().body(herobrine).contentType(ContentType.JSON).header("Authorization", godToken.getToken()).when().request("POST", "/user").then().statusCode(201);
userRepository.delete(herobrine);
//Can noob post herobrine without authorizations (no)
User noob = new User("boon","noob","noob@student.com","everywhere","every",new Date(0), null, Role.Student,"noob");
Token noobToken = new Token(noob, tokenService.generateNewToken(), new Date());
userRepository.save(noob);
tokenService.saveToken(noobToken);
with().body(herobrine).contentType(ContentType.JSON).header("Authorization", noobToken.getToken()).when().request("POST", "/user").then().statusCode(401);
}
@Test
public void userGetTest(){
User herobrine = new User("brine","hero","herobrine@student.com","in your WalLs","ShadowsLand",new Date(0), null,Role.Student,"test");
userRepository.save(herobrine);
Token t = new Token(herobrine, tokenService.generateNewToken(), new Date());
tokenRepository.save(t);
with().header("Authorization", t.getToken()).when().request("GET", "/user").then().assertThat().statusCode(200).body("firstName",equalTo("hero"));
}
@Test
public void userPatchTest(){
User herobrine = new User("brine","hero","herobrine@admin.com","in your WalLs","ShadowsLand",new Date(0), null,Role.Admin,"test");
userRepository.save(herobrine);
Token t = new Token(herobrine, tokenService.generateNewToken(), new Date());
tokenRepository.save(t);
Map<String, Object> data = new HashMap<String, Object>();
data.put("firstName", "test");
//Teste que le patch s'execute bien
with().body(data).contentType(ContentType.JSON).header("Authorization", t.getToken()).pathParam("id", herobrine.getRegNo()).when().request("PATCH", "/user/{id}").then().statusCode(200);
//Teste que la donnée a bien été altérée
with().header("Authorization", t.getToken()).when().request("GET", "/user").then().body("firstName",equalTo("test"));
}
@Test
public void UserDeleteTest(){
User god = new User("god","god","admin@admin.com","everywhere","every",new Date(0), null, Role.Admin,"goddoesntneedpassword");
Token godToken = new Token(god, tokenService.generateNewToken(), new Date());
userRepository.save(god);
tokenService.saveToken(godToken);
User herobrine = new User("brine","hero","herobrine@admin.com","in your WalLs","ShadowsLand",new Date(0), null,Role.Student,"test");
userRepository.save(herobrine);
with().header("Authorization", godToken.getToken()).pathParam("id", herobrine.getRegNo()).when().request("DELETE", "/user/{id}").then().statusCode(200);
Assert.assertEquals(userRepository.existsById(herobrine.getRegNo()), false);
}
}

View File

@ -1,41 +0,0 @@
package ovh.herisson.Clyde.Repositories;
import org.junit.Assert;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.TestPropertySource;
import ovh.herisson.Clyde.Tables.Role;
import ovh.herisson.Clyde.Tables.User;
import java.util.GregorianCalendar;
@DataJpaTest
@TestPropertySource(properties = {
"spring.test.database.replace=none",
"spring.datasource.url=jdbc:tc:postgresql:16-alpine:///db"
})
public class UserRepoTest {
@Autowired
UserRepository userRepo;
@BeforeEach
public void setup(){
User herobrine = new User("brine","hero","admin@admin.com","in your WalLs","ShadowsLand", new GregorianCalendar(2005, 4, 3).getTime(), null, Role.Admin,"admin");
userRepo.save(herobrine);
}
@AfterEach
public void clean(){
userRepo.deleteAll();
}
@Test
public void usertest(){
Assert.assertEquals("brine", userRepo.findById(1).getLastName());
Assert.assertTrue(new GregorianCalendar(2005, 4, 3).getTime().equals(userRepo.findById(1).getBirthDate()));
}
}

View File

@ -1,80 +0,0 @@
package ovh.herisson.Clyde.Services;
import org.junit.Assert;
import org.junit.Before;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.TestPropertySource;
import org.springframework.web.multipart.MultipartFile;
import org.testcontainers.shaded.com.google.common.net.MediaType;
import ovh.herisson.Clyde.Repositories.FileRepository;
import ovh.herisson.Clyde.Tables.FileType;
import ovh.herisson.Clyde.Tables.StorageFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
@DataJpaTest
@TestPropertySource(properties = {
"spring.test.database.replace=none",
"spring.datasource.url=jdbc:tc:postgresql:16-alpine:///db"
})
public class StorageServiceTest {
@Autowired
FileRepository fileRepo;
StorageService ss;
@BeforeEach
public void setup(){
if (ss == null){
ss = new StorageService(fileRepo);
}
}
@AfterEach
public void aftereach(){
fileRepo.deleteAll();
}
@Test
//Check si le fichier est bien sauvegardé dans la DB et si le fichier est bien sauvegardé au bon endroit
public void saveFile(){
//Test si le directory a bien été crée a l'init du fileService
Path rootloc = Paths.get("cdn/");
Assert.assertTrue(Files.exists(rootloc));
}
@Test
public void deleteFile() throws IOException {
File file = new File("cdn/test.txt");
file.createNewFile();
//On vérifie que le fichier a bien été crée
Assert.assertTrue(file.exists());
//StorageFile représentant le fichier
StorageFile sf = new StorageFile("testfile",file.getPath(), FileType.ProfilePicture);
fileRepo.save(sf);
//Check that the storagefile is properly saved
StorageFile resp = fileRepo.getStorageFileByName("testfile");
Assert.assertEquals(sf, resp);
ss.delete(sf);
//On vérifie que le fichier a bien été delete et que le StorageFile a été delete de la DB
Assert.assertFalse(file.exists());
resp = fileRepo.getStorageFileByName("testfile");
Assert.assertEquals(null, resp);
}
}

View File

@ -1,93 +0,0 @@
package ovh.herisson.Clyde.Services;
import org.junit.Assert;
import org.junit.Before;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.TestPropertySource;
import ovh.herisson.Clyde.Repositories.TokenRepository;
import ovh.herisson.Clyde.Repositories.UserRepository;
import ovh.herisson.Clyde.Tables.Role;
import ovh.herisson.Clyde.Tables.Token;
import ovh.herisson.Clyde.Tables.User;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import static org.junit.jupiter.api.Assertions.*;
@DataJpaTest
@TestPropertySource(properties = {
"spring.test.database.replace=none",
"spring.datasource.url=jdbc:tc:postgresql:16-alpine:///db"
})
class TokenServiceTest {
@Autowired
TokenRepository tokenRepository;
@Autowired
UserRepository userRepository;
@AfterEach
public void aftereach(){
tokenRepository.deleteAll();
userRepository.deleteAll();
}
@Test
void saveToken() {
User herobrine = userRepository.findById(1);
User herobrinenul = new User("brine","heronul","pasadmin@student.com","in your WalLs","ShadowsLand", new GregorianCalendar(2005, 4, 3).getTime(), null, Role.Admin,"admin");
TokenService tokenService = new TokenService(tokenRepository);
Token testToken = new Token(herobrine, tokenService.generateNewToken(), new Date());
tokenService.saveToken(testToken);
Iterable<Token> t = tokenService.getAllTokens();
Token tok = t.iterator().next();
Assert.assertEquals(herobrine, tok.getUser());
Assert.assertNotEquals(herobrinenul, tok.getUser());
}
@Test
void saveTokenLimit(){
TokenService tokenService = new TokenService(tokenRepository);
//On va stocker les token qu'on va sauvegarder au préalable dans une liste pour tester que les tokens remplacés sont bien ceux avec la date d'expi la plus jeune
//A la fin il ne devrait donc rester que les 5 derniers tokens de tokenList
ArrayList<Token> tokenList = new ArrayList<>();
GregorianCalendar gc = new GregorianCalendar();
User malveillant = new User("Cargo", "John", "CargoJ@mail.com", "secret", "secret", null, null, null, "secret");
userRepository.save(malveillant);
for (int i = 0; i < 20; i++){
gc.add(Calendar.DAY_OF_WEEK, 1);
Token t = new Token(malveillant, tokenService.generateNewToken(), gc.getTime());
tokenList.add(t);
tokenService.saveToken(t);
}
//Testons les tokens
ArrayList <Token> resp = tokenRepository.getByUserOrderByExpirationDate(malveillant);
Assert.assertTrue(resp.size() == 5);
for (int i = 1; i <= resp.size(); i++){
Assert.assertEquals(tokenList.get(tokenList.size()-i), resp.get(resp.size()-i));
}
}
}

View File

@ -1,3 +0,0 @@
spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect

View File

@ -19,3 +19,9 @@ npm run dev
```sh ```sh
npm run build npm run build
``` ```
### Run Unit Tests with [Vitest](https://vitest.dev/)
```sh
npm run test:unit
```

View File

@ -18,3 +18,8 @@ var dev = tasks.register<NpmTask>("dev") {
tasks.register<NpmTask>("run") { tasks.register<NpmTask>("run") {
dependsOn(dev) dependsOn(dev)
} }
tasks.register<NpmTask>("test") {
dependsOn(tasks.npmInstall)
args.set(listOf("run", "test:unit"))
}

11
frontend/login/index.html Normal file
View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Clyde - Login</title>
</head>
<body>
<div id="app"></div>
<script type=module src="/src/login.js"></script>
</body>
</html>

View File

@ -8,7 +8,6 @@
"name": "clyde", "name": "clyde",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"vite-plugin-top-level-await": "^1.4.1",
"vue": "^3.4.15", "vue": "^3.4.15",
"vue3-toastify": "^0.2.1" "vue3-toastify": "^0.2.1"
}, },
@ -36,6 +35,7 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"aix" "aix"
@ -51,6 +51,7 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@ -66,6 +67,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@ -81,6 +83,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@ -96,6 +99,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@ -111,6 +115,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@ -126,6 +131,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"freebsd" "freebsd"
@ -141,6 +147,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"freebsd" "freebsd"
@ -156,6 +163,7 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -171,6 +179,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -186,6 +195,7 @@
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -201,6 +211,7 @@
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -216,6 +227,7 @@
"cpu": [ "cpu": [
"mips64el" "mips64el"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -231,6 +243,7 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -246,6 +259,7 @@
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -261,6 +275,7 @@
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -276,6 +291,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -291,6 +307,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"netbsd" "netbsd"
@ -306,6 +323,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"openbsd" "openbsd"
@ -321,6 +339,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"sunos" "sunos"
@ -336,6 +355,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@ -351,6 +371,7 @@
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@ -366,6 +387,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@ -379,22 +401,6 @@
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
}, },
"node_modules/@rollup/plugin-virtual": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz",
"integrity": "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==",
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.12.0", "version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz",
@ -402,6 +408,7 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@ -414,6 +421,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@ -426,6 +434,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@ -438,6 +447,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@ -450,6 +460,7 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -462,6 +473,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -474,6 +486,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -486,6 +499,7 @@
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -498,6 +512,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -510,6 +525,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -522,6 +538,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@ -534,6 +551,7 @@
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@ -546,212 +564,17 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
] ]
}, },
"node_modules/@swc/core": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.5.tgz",
"integrity": "sha512-4/JGkG4b1Z/QwCGgx+Ub46MlzrsZvBk5JSkxm9PcZ4bSX81c+4Y94Xm3iLp5Ka8NxzS5rD4mJSpcYuN3Tw0ceg==",
"hasInstallScript": true,
"dependencies": {
"@swc/counter": "^0.1.2",
"@swc/types": "^0.1.5"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/swc"
},
"optionalDependencies": {
"@swc/core-darwin-arm64": "1.4.5",
"@swc/core-darwin-x64": "1.4.5",
"@swc/core-linux-arm-gnueabihf": "1.4.5",
"@swc/core-linux-arm64-gnu": "1.4.5",
"@swc/core-linux-arm64-musl": "1.4.5",
"@swc/core-linux-x64-gnu": "1.4.5",
"@swc/core-linux-x64-musl": "1.4.5",
"@swc/core-win32-arm64-msvc": "1.4.5",
"@swc/core-win32-ia32-msvc": "1.4.5",
"@swc/core-win32-x64-msvc": "1.4.5"
},
"peerDependencies": {
"@swc/helpers": "^0.5.0"
},
"peerDependenciesMeta": {
"@swc/helpers": {
"optional": true
}
}
},
"node_modules/@swc/core-darwin-arm64": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.5.tgz",
"integrity": "sha512-toMSkbByHNfGXESyY1aiq5L3KutgijrNWB/THgdHIA1aIbwtrgMdFQfxpSE+INuuvWYi/Fxarv86EnU7ewbI0Q==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-darwin-x64": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.4.5.tgz",
"integrity": "sha512-LN8cbnmb4Gav8UcbBc+L/DEthmzCWZz22rQr6fIEHMN+f0d71fuKnV0ca0hoKbpZn33dlzUmXQE53HRjlRUQbw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.5.tgz",
"integrity": "sha512-suRFkhBWmOQxlM4frpos1uqjmHfaEI8FuJ0LL5+yRE7IunNDeQJBKujGZt6taeuxo1KqC0N0Ajr8IluN2wrKpA==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.5.tgz",
"integrity": "sha512-mLKxasQArDGmR6k9c0tkPVUdoo8VfUecocMG1Mx9NYvpidJNaZ3xq9nYM77v7uq1fQqrs/59DM1fJTNRWvv/UQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-musl": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.5.tgz",
"integrity": "sha512-pgKuyRP7S29U/HMDTx+x8dFcklWxwB9cHFNCNWSE6bS4vHR93jc4quwPX9OEQX5CVHxm+c8+xof043I4OGkAXw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-gnu": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.5.tgz",
"integrity": "sha512-srR+YN86Oerzoghd0DPCzTbTp08feeJPSr9kkNdmtQWENOa4l/9cJV3+XY6vviw0sEjezPmYnc3SwRxJRaxvEw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-musl": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.5.tgz",
"integrity": "sha512-aSf41LZtDeG5VXI4RCnzcu0UInPyNm3ip8Kw+sCK+sSqW9o7DgBkyqqbip3RZq84fNUHBQQQQdKXetltsyRRqw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.5.tgz",
"integrity": "sha512-vU3k8JwRUlTkJMfJQY9E4VvLrsIFOpfhnvbuXB84Amo1cJsz+bYQcC6RSvY7qpaDzDKFdUGbJco4uZTRoRf7Mg==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.5.tgz",
"integrity": "sha512-856YRh3frRK2XbrSjDOFBgoAqWJLNRkaEtfGzXfeEoyJlOz0BFsSJHxKlHAFkxRfHe2li9DJRUQFTEhXn4OUWw==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-x64-msvc": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.5.tgz",
"integrity": "sha512-j1+kV7jmWY1+NbXAvxAEW165781yLXVZKLcoXIZKmw18EatqMF6w8acg1gDG8C+Iw5aWLkRZVS4pijSh7+DtCQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="
},
"node_modules/@swc/types": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz",
"integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw=="
},
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
}, },
"node_modules/@vitejs/plugin-vue": { "node_modules/@vitejs/plugin-vue": {
"version": "5.0.4", "version": "5.0.4",
@ -973,6 +796,7 @@
"version": "0.19.12", "version": "0.19.12",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
"integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"bin": { "bin": {
"esbuild": "bin/esbuild" "esbuild": "bin/esbuild"
@ -1024,6 +848,7 @@
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"optional": true, "optional": true,
"os": [ "os": [
@ -1265,6 +1090,7 @@
"version": "4.12.0", "version": "4.12.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz",
"integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==",
"dev": true,
"dependencies": { "dependencies": {
"@types/estree": "1.0.5" "@types/estree": "1.0.5"
}, },
@ -1376,22 +1202,11 @@
"requires-port": "^1.0.0" "requires-port": "^1.0.0"
} }
}, },
"node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/vite": { "node_modules/vite": {
"version": "5.1.3", "version": "5.1.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz",
"integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==",
"dev": true,
"dependencies": { "dependencies": {
"esbuild": "^0.19.3", "esbuild": "^0.19.3",
"postcss": "^8.4.35", "postcss": "^8.4.35",
@ -1442,19 +1257,6 @@
} }
} }
}, },
"node_modules/vite-plugin-top-level-await": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.4.1.tgz",
"integrity": "sha512-hogbZ6yT7+AqBaV6lK9JRNvJDn4/IJvHLu6ET06arNfo0t2IsyCaon7el9Xa8OumH+ESuq//SDf8xscZFE0rWw==",
"dependencies": {
"@rollup/plugin-virtual": "^3.0.2",
"@swc/core": "^1.3.100",
"uuid": "^9.0.1"
},
"peerDependencies": {
"vite": ">=2.8"
}
},
"node_modules/vue": { "node_modules/vue": {
"version": "3.4.19", "version": "3.4.19",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.19.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.19.tgz",

View File

@ -9,7 +9,6 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"vite-plugin-top-level-await": "^1.4.1",
"vue": "^3.4.15", "vue": "^3.4.15",
"vue3-toastify": "^0.2.1" "vue3-toastify": "^0.2.1"
}, },

View File

@ -1,9 +1,11 @@
# English translations (some examples to remove)
login.guest.signin=Sign in login.guest.signin=Sign in
login.guest.register=Register login.guest.register=Register
login.guest.alregister=Already Registered login.guest.alregister=Already Registered
login.guest.welcome=WELCOME TO THE UNIVERSITY login.guest.welcome=WELCOME TO THE UNIVERSITY
login.guest.email=E-MAIL login.guest.email=E-MAIL
login.guest.firstname=FIRSTNAME login.guest.firstname= FIRSTNAME
login.guest.surname=SURNAME login.guest.surname=SURNAME
login.guest.country=COUNTRY login.guest.country=COUNTRY
login.guest.address=ADDRESS login.guest.address=ADDRESS
@ -11,44 +13,5 @@ login.guest.password=PASSWORD
login.guest.nextpage=Next Page login.guest.nextpage=Next Page
login.guest.lastpage=Last Page login.guest.lastpage=Last Page
login.guest.submit=Submit login.guest.submit=Submit
login.guest.birthday=BIRTHDAY
login.guest.confirm=CONFIRM #=====================================================
login.cPassword=Confirm Password
login.password=Password
app.home=Home
app.login=Login
app.notifications=Notifications
app.settings=Settings
app.messages=Messages
app.forum=Forum
app.schedules=Schedules
app.inscription.requests=Inscription Requests
app.manage.courses=Manage Courses
app.language=Language
app.manage.profile=Manage profile
app.studentList=Students List
app.users=Users
request.moreInfos=More Infos
request.accept=Accept
request.refuse=Refuse
courses.createCourse=Create course
courses.deleteCourse=Delete course
courses.modify=Modify
courses.toDelete=Course to Delete
courses.confirm=Confirm
courses.back=Back
profile.modify.data=Modify personnal data
profile.reRegister=Re-register
profile.unRegister=Unregister
profile.course.list=Courses list
profile.address=Address
profile.picture=Profile picture
profile.change.curriculum=Change curriculum
name=Name
Teacher=Teacher
Student=Student
Secretary=Secretary
Curriculum=curriculum
Credits=Credits
InscriptionService=I.S.
faculty=Faculty

View File

@ -1,54 +1,17 @@
# Traductions françaises (Quelques examples a enlever)
login.guest.signin=SE CONNECTER login.guest.signin=SE CONNECTER
login.guest.register=S'enregistrer login.guest.register=S'enregistrer
login.guest.alregister=Déjà Enregistré login.guest.alregister=Déjà Enregistré
login.guest.welcome=BIENVENUE A L'UNIVERSITE login.guest.welcome=BIENVENUE A L'UNIVERSITE
login.guest.email=E-MAIL login.guest.email=E-MAIL
login.guest.firstname=PRENOM login.guest.firstname= PRENOM
login.guest.surname=NOM login.guest.surname= NOM
login.guest.country=PAYS login.guest.country= PAYS
login.guest.address=ADRESSE login.guest.address=ADRESSE
login.guest.password=MOT DE PASSE login.guest.password= MOT DE PASSE
login.guest.nextpage=Prochaine Page login.guest.nextpage=Prochaine Page
login.guest.lastpage=Derniere Page login.guest.lastpage=Derniere Page
login.guest.submit=Envoyer login.guest.submit=Envoyer
login.guest.birthday=DATE DE NAISSANCE
login.guest.confirm=CONFIRMER #=====================================================
login.cPassword=Confirmer mot de passe
login.password=Mot de passe
app.home=Home
app.login=Se connecter
app.notifications=Notifications
app.settings=Options
app.messages=Messages
app.forum=Forum
app.schedules=Horaires
app.inscription.requests=Demandes d'Inscription
app.manage.courses=Gérer les cours
app.language=Langue
app.manage.profile=Gérer le profil
app.studentList=Liste des étudiants
app.users=Utilisateurs
request.moreInfos=Plus d'Infos
request.accept=Accepter
request.refuse=Refuser
courses.createCourse=Créer un cours
courses.deleteCourse=Supprimer un cours
courses.modify=Modifier
courses.toDelete=Cours à supprimer
courses.confirm=Confirmer
courses.back=Retour
profile.modify.data=Modifier données personnelles
profile.reRegister=Réinsciption
profile.unRegister=Désinscription
profile.course.list=Liste des cours
profile.address=Adresse
profile.picture=Photo de profil
profile.change.curriculum=Changer cursus
name=Nom
Teacher=Enseignant
Student=Etudiant
Secretary=Secrétaire
Curriculum=Cursus
Credits=Credits
InscriptionService=S.I.
faculty=Faculté

View File

@ -1,34 +1,15 @@
<script setup> <script setup>
import { toast } from 'vue3-toastify'; import 'https://kit.fontawesome.com/fb3bbd0a95.js'
import { ref } from 'vue' import { toast } from 'vue3-toastify';
import i18n, { setLang } from './i18n.js' import { ref } from 'vue'
import { isLogged } from '@/rest/Users.js'
import { appList, currentView } from '@/rest/apps.js' const test = ref();
var prevURL;
var currentURL = window.location.hash;
window.onhashchange = function() {
prevURL = currentURL;
currentURL = window.location.hash;
}
const Logged = ref(isLogged());
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 apps = ref([])
appList().then(e => apps.value = e)
async function draw(e) {
test.value = (await fetch("http://localhost:8080/ping"));
test.value = await test.value.json();
toast(test.value['txt']);
}
</script> </script>
<template> <template>
@ -36,60 +17,54 @@ window.addEventListener('hashchange', () => {
<div class="topBar"> <div class="topBar">
<ul class="horizontal"> <ul class="horizontal">
<li title=home> <li title="Home">
<a class="icon" href="#home"> <a href="#home">
<img class="clyde" src="/Clyde.png" style="width: 40px; height: auto; margin-top:4px"> <img @click="draw" src="./assets/Clyde.png" style="width: 40px; height: auto; margin-top:4px">
</a></li> </a></li>
<li title=home> <li title="Home">
<a class="icon" href="#home"> <a href="#home">
<div class=" fa-solid fa-house" style="margin-top: 7px; margin-bottom: 3px;"></div> <div class=" fa-solid fa-house" style="margin-top: 7px; margin-bottom: 3px;"></div>
</a></li> </a></li>
<li style="float: right;" title=login> <li style="float: right;" title="Account">
<a class="icon" href="#/login"> <a 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="margin-top: 7px; margin-bottom: 3px;"></div>
</a></li> </a></li>
<li style="float: right;" title=notifications> <li style="float: right;" title="Notifications">
<a class="icon" href="#Notifications"> <a 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 style="float: right;" title="Options">
<a class="icon" > <div class="item">
<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></li>
<div class="dropdown-content">{{i18n("app.language")}}</div>
<ul style="list-style-type:none;">
<li style=" margin-bottom:10px;margin-right:20px;float:left; font-size:10px; color:black;">
<button @:click="setLang('en');" href="#home">EN</button>
</li>
<li style="float:left;font-size:10px; color:black;"><button @:click="setLang('fr');" href="#home">FR</button></li>
</ul>
<div style='align-items:center;'>
<a style="cursor:pointer;font-size:20px;" href="#/profil" class="dropdown-content">
{{i18n("app.manage.profile")}}
</a>
</div>
</div>
</a></li>
</ul> </ul>
</div> </div>
<div class="leftBar"> <div class="leftBar">
<ul class="vertical"> <ul class="vertical">
<li v-for="app in apps"> <li style="margin-top: 25px;" >
<a :href="app.path"> <a href="#Messages">
<div class="fa-solid" :class="app.icon" style="font-size: 40px;"></div> <div class="fa-solid fa-comment" style="font-size: 40px;"></div>
<div class="text">{{app.text}}</div> <div class="text">Messages</div>
</a> </a></li>
</li> <li >
<a href="#Notifications">
<div class="fa-solid fa-bell" style="font-size: 40px;" ></div>
<div class="text">Notifications</div>
</a></li>
<li >
<a href="#Schedule">
<div class="fa-solid fa-calendar-days" style="font-size: 40px;"></div>
<div class="text">Schedules</div>
</a></li>
<li ><a href="#Forum">
<div class="fa-solid fa-envelope" style="font-size: 40px;" ></div>
<div class="text">Forum</div></a></li>
</ul> </ul>
</div> </div>
<div class="page"> <div class="page">
<Suspense> <div style="background-color: rgb(239,50,168); margin:50px;">Il FAUDRA INSERER LA PAGE ICI</div>
<component :is="currentView" />
</Suspense>
</div> </div>
</div> </div>
</template> </template>
@ -119,37 +94,8 @@ window.addEventListener('hashchange', () => {
.leftBar{ .leftBar{
grid-area:leftBar; grid-area:leftBar;
} }
.option {
position: relative;
display: block;
}
.dropdown {
margin-top:55px;
width:160px;
display: inline-block;
height:110px;
font-size: 13px;
position: absolute;
z-index: 1;
background-color:white;
}
.dropdown-content {
min-width: 160px;
text-align: center;
margin-bottom: 10px;
right:0;
color:rgb(50,50,50);
text-decoration: none;
font-size:24px
}
.dropdown-content div{
display:block;
}
ul.vertical{ ul.vertical{
list-style-type: none; list-style-type: none;
margin-top: 61px; margin-top: 61px;
@ -166,7 +112,7 @@ window.addEventListener('hashchange', () => {
transition-duration: .3s; transition-duration: .3s;
} }
li a{ .item,li a{
display: flex; display: flex;
padding: 8px 16px; padding: 8px 16px;
color:rgb(255, 255, 255); color:rgb(255, 255, 255);
@ -202,6 +148,7 @@ window.addEventListener('hashchange', () => {
position: fixed; position: fixed;
height:61px; height:61px;
width: 100%; width: 100%;
overflow: hidden;
background-color: rgb(24, 24, 24); background-color: rgb(24, 24, 24);
} }
@ -212,7 +159,8 @@ window.addEventListener('hashchange', () => {
} }
ul.horizontal li a.icon:hover:not(.active){ ul.horizontal .item:hover, li a:hover:not(.active){
background-color: black; background-color: black;
border-radius:6px; border-radius:6px;
color:white; color:white;
@ -230,6 +178,7 @@ window.addEventListener('hashchange', () => {
} }
.text { .text {
position: absolute;
right: 0%; right: 0%;
width: 0%; width: 0%;
opacity: 0; opacity: 0;
@ -241,15 +190,8 @@ window.addEventListener('hashchange', () => {
ul.vertical:hover .text { ul.vertical:hover .text {
opacity: 1; opacity: 1;
width: 60%; width: 70%;
transition-duration: .3s; transition-duration: .3s;
padding-left: 15px; padding-left: 5px;
} }
.clyde:hover{
content: url("./assets/angry_clyde.png")
}
</style> </style>
<!-- vim:set noet sts=0 sw=4 ts=2: -->

View File

@ -1,113 +0,0 @@
<script setup>
import i18n from "@/i18n.js"
import {ref} from 'vue'
import {validateRegister, getAllRegisters } from '@/rest/ServiceInscription.js'
const requests = ref(await getAllRegisters());
console.log(requests);
async function upPage(id,review){
await validateRegister(id,review);
requests.value = await getAllRegisters();
}
</script>
<template>
<div 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>
<div class="infos"><button style="background-color:rgb(105,05,105);" >{{i18n("request.moreInfos")}}</button></div>
<div class="accept"><button @click="upPage(item.id,'Accepted')" style="background-color:rgb(0,105,50);">{{i18n("request.accept")}}</button></div>
<div class="refuse"><button @click="upPage(item.id,'Refused')" style="background-color:rgb(105,0,0);">{{i18n("request.refuse")}}</button></div>
</div>
</div>
</div>
</template>
<style scoped>
.container{
color:white;
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-areas:
"id type surname firstname infos accept refuse";
column-gap:10px;
}
.infos {
grid-area:infos;
align-self:center;
}
.accept{
grid-area:accept;
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;
}
.type{
grid-area:type;
align-self:center;
}
.surname{
grid-area:surname;
align-self:center;
white-space: nowrap;
overflow: hidden;
text-overflow:ellipsis;
}
.firstname{
grid-area:firstname;
align-self:center;
white-space: nowrap;
overflow: hidden;
text-overflow:ellipsis;
}
button{
font-size:15px;
height:50px;
width:100px;
border:none;
border-radius:20px;
}
.bodu {
width:100%;
margin-bottom:10px;
border:2px solid black;
border-radius:9px;
background-color:rgb(50,50,50);
}
</style>

View File

@ -1,242 +0,0 @@
<script setup>
import {reactive, ref } from 'vue'
import i18n from '@/i18n.js'
import { login , register , disconnect, isLogged} from '@/rest/Users.js'
import { getAllCurriculums } from '@/rest/curriculum.js'
import { uploadProfilePicture } from '@/rest/uploads.js'
import {toast} from 'vue3-toastify'
import 'vue3-toastify/dist/index.css';
const loginPage= ref(true)
const page = ref(0)
const outputs = reactive({
surname:null,
firstname:null,
password:null,
birthday:null,
email:null,
address:null,
country:null,
curriculum:null,
})
const submitValue= ref(i18n("login.guest.submit"))
const passwordConfirm=ref("")
const imageSaved = ref(false)
const ppData = ref(false)
const curricula= await getAllCurriculums();
function goBackHome(){
setTimeout(() => {
window.location.href="#/home";
}, "500");
}
function verifyInputs(pass){
if(pass==passwordConfirm.value){
page.value++;
return toast('Password and Confirm Password are correct.', {
type: "success",});
}
else{
return toast('Password and Confirm Password are different',{type: "error",});
}
}
if (isLogged()){
disconnect();
window.location.reload();}
</script>
<template style="background-color:rgba(255,255,255,0.05); border-radius:50px" >
<div class='loginBox'>
<div v-if="loginPage">
<form @submit.prevent="login(outputs.email,outputs.password);goBackHome();"class="form">
<h1 style="color:rgb(239,60,168); font-family: sans-serif;">
{{i18n("login.guest.signin")}}
</h1>
<div class="inputBox">
<p>ID / {{i18n("login.guest.email")}}</p>
<input type="text" v-model="outputs.email">
</div>
<div class="inputBox">
<p>{{i18n("login.guest.password")}}</p>
<input type="password" v-model="outputs.password">
</div>
<div class="register">
<a @click="loginPage=!loginPage">{{i18n("login.guest.register")}}</a>
</div>
<div class="inputBox">
<input type="submit" v-model="submitValue">
</div>
</form>
</div>
<div v-else>
<form class="form">
<h1 style="color:rgb(239,60,168); font-family: sans-serif; text-align:center;">
{{i18n("login.guest.welcome")}}
</h1>
<div v-if="page === 0">
<div class="inputBox">
<p>{{i18n("login.guest.surname")}}</p>
<input type="text" v-model="outputs.surname">
</div>
<div class="inputBox">
<p>{{i18n("login.guest.firstname")}}</p>
<input type="text" v-model="outputs.firstname">
</div>
<div class="inputBox">
<p>{{i18n("login.guest.birthday")}}</p>
<input type="date" v-model="outputs.birthday">
</div>
<div class="inputBox">
<p>{{i18n("login.guest.password")}}</p>
<input type="password" v-model="outputs.password">
</div>
<div class="inputBox">
<p>{{i18n("login.guest.confirm")}} {{i18n("login.guest.password")}}</p>
<input type="password" v-model="passwordConfirm">
</div>
<div class="switchpage">
<button @click="verifyInputs(outputs.password);">{{i18n("login.guest.nextpage")}}</button>
</div>
<div @click="(loginPage=!loginPage) && (page=0)" class="register">
<a>{{i18n("login.guest.alregister")}}</a>
</div>
</div>
<div v-else>
<div class="inputBox">
<p>{{i18n("login.guest.email")}}</p>
<input type="mail" v-model="outputs.email">
</div>
<div class="inputBox">
<p>{{i18n("login.guest.address")}}</p>
<input type="text" v-model="outputs.address">
</div>
<div class="inputBox">
<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>
<div class="inputBox">
<p>{{i18n("Curriculum").toUpperCase()}}</p>
<select v-model="outputs.curriculum">
<option v-for="item in curricula">{{item.curriculumId}}</option>
</select>
</div>
<div style="align-self:center;" class="inputBox">
<button style="margin-top:25px;" @click="register(outputs.firstname, outputs.surname, outputs.birthday, outputs.password, outputs.email, outputs.address, outputs.country, outputs.curriculum, ppData);">
{{i18n("login.guest.submit")}}
</button>
</div>
<div class="switchpage">
<button @click="page--">{{i18n("login.guest.lastpage")}}</button>
</div>
<div @click="(loginPage=!loginPage) && (page=0)" class="register">
<a>{{i18n("login.guest.alregister")}}</a>
</div>
</div>
</form>
</div>
</div>
</template>
<style scoped>
.loginBox {
background-color: rgb(24,24,24);
width: 100%;
height:100%;
justify-content: center;
padding: 10%;
border-radius: 5%;
box-shadow:0 5px 25px #000000;
}
.form {
position:relative;
width:100%;
display: flex;
flex-direction: column;
align-items:center;
gap: 3%;
}
.inputBox input,button,select {
width:100%;
border: none;
margin-right: 12.5%;
padding-left: 2.5%;
padding-top:2.5%;
padding-bottom:2.5%;
outline:none;
border-radius: 10px;
font-size:1.35em;
}
.inputBox p{
position:relative;
z-index: 100;
font-family:sans-serif ;
color:rgb(239,60,168);
transition:0.5;
}
.register{
color:rgb(239,60,168);
width: 100%;
display:flex;
cursor: pointer;
}
.switchpage{
width:100px;
background:rgb(255, 0 255);
border: none;
padding-right:0;
padding-top:10px;
padding-bottom:10px;
outline:none;
border-radius: 4px;
font-size:0.8em;
align-self:right;
}
input[type=submit],button,select{
margin-bottom:20px;
background-color: rgb(239,60,168);
cursor: pointer;
padding:10px;
font-size:1.35em;
border:none;
border-radius:20px;
}
button:active ,.switchpage:active{
opacity:0.8;
}
</style>

View File

@ -1,275 +0,0 @@
<script setup>
import i18n from "@/i18n.js"
import {reactive , ref} from 'vue'
import { getCourses,deleteCourse,alterCourse,createCourse } from "@/rest/courses.js"
import {getUser, getSelf, getTeachers } from "@/rest/Users.js"
const self = await getSelf();
const curriculum = ref(await getCourses(self.role));
const profList = await getTeachers();
const createMod = ref(false)
const deleteMod = ref(false)
const editElementID = ref("")
function editItem(id){
editElementID = id;
}
//Juste pour montrer le Create Mode
const pattern = {
"title":null,
"credits":null,
"owner":null,
}
let toModify = Object.assign({},pattern);
let toAdd = Object.assign({}, pattern);
async function addToCourse(){
let isnull= false;
for(const [key, value] of Object.entries(toAdd)){
console.log(toAdd.owner);
if(value === null){
isnull=true;
}
}
if (!isnull){
await createCourse(toAdd.title,toAdd.credits,toAdd.owner);
toAdd= Object.assign({},pattern);
curriculum.value = await getCourses(self.role);
}}
function setModify(item){
for(const el in profList){
if(profList[el].regNo === item.owner.regNo){
toModify.owner= profList[el];
}
}
toModify.credits= item.credits;
toModify.title= item.title;
}
async function patchCourse(course){
for (let element in toModify){
console.log(toModify,1)
console.log(toModify[element],2)
if (element =="owner" && (toModify[element].regNo != course.owner.regNo)){
await alterCourse(course.courseId,{owner:toModify[element].regNo});
}
else if(element == "title" && (toModify[element] != course.title)){
await alterCourse(course.courseId,{title:toModify[element]});
}
else if(element == "credits" && (parseInt(toModify[element]) != course.credits)){
await alterCourse(course.courseId,{credits:parseInt(toModify[element])});
}
}
toModify= Object.assign({},pattern);
curriculum.value = await getCourses(self.role);
}
//Juste pour montrer le Delete Mode
let toRemove = null;
async function removeCourse(course) {
await deleteCourse(course.courseId)
curriculum.value = await getCourses(self.role);
toRemove = null;
}
</script>
<template>
<div class="body">
<div v-if="!deleteMod && !createMod && (self.role !== 'Teacher')" class="listTitle buttonGrid">
<button class="create" @click="editElementID= '';createMod = true;">
{{i18n("courses.createCourse")}}
</button>
<button class="delete" @click="deleteMod=true" >
{{i18n("courses.deleteCourse")}}
</button>
</div>
<div v-if="createMod">
<form class="listElement">
<div style="margin-bottom:20px;">
{{i18n("name")}} :
<input v-model="toAdd.title">
</div>
<div style="margin-bottom:20px;">
{{i18n("Teacher")}} :
<select style="max-width:200px;" class="teacher" v-model="toAdd.owner">
<option v-for="item in profList" :value="item">{{item.lastName}}</option>
</select>
</div>
<div style="margin-bottom:20px;">
{{i18n("Credits")}} :
<input v-model="toAdd.credits">
</div>
<button class="create" @click="createMod=!createMod; addToCourse();"> {{i18n("courses.confirm")}} </button>
<button style="float:right;" @click="createMod=!createMod">{{i18n("courses.back")}}</button>
</form>
</div>
<div v-if="deleteMod">
<form class="listElement">
<div style="margin-bottom:20px;">
{{i18n("courses.toDelete")}} :
<select style="max-width:200px;" class="teacher" v-model="toRemove">
<option v-for="item in curriculum" :value='item'>{{item.title}}</option>
</select>
</div>
<button class="delete" @click="deleteMod=!deleteMod; removeCourse(toRemove);"> {{i18n("courses.deleteCourse")}} </button>
<button style="float:right;" @click="deleteMod=!deleteMod"> {{i18n("courses.back")}}</button>
</form>
</div>
<div v-if="!createMod && !deleteMod" v-for="item in curriculum" :key="item.title">
<div v-if="editElementID !== item.title" style ="padding:15px 15px 15px 15px;">
<button @click="editElementID = item.title; setModify(item); ">
{{i18n("courses.modify")}}
</button>
</div>
<div v-else>
<button @click="editElementID= '';patchCourse(item)"> {{i18n("courses.confirm")}} </button>
<button @click="editElementID= '';"> {{i18n("courses.back")}} </button>
</div>
<div class="listElement" >
<div class="containerElement" v-if="editElementID !== item.title" >
<div class="name"> {{item.title}} </div>
<div class="teacher">{{item.owner.lastName}}</div>
<div class="credits">{{i18n("Credits")}}:{{item.credits}}</div>
</div>
<div class="containerElement"v-else>
<input style="max-width:200px;" class="name" v-model="toModify.title">
<select v-if="self.role === 'Secretary'" style="max-width:200px;" class="teacher" v-model="toModify.owner">
<option v-for="(item,index) in profList" :value='item'>{{item.lastName}}</option>
</select>
<div v-else class="teacher">{{item.owner.lastName}}</div>
<input v-if="self.role==='Secretary'"style="max-width:100px;"class="credits" v-model="toModify.credits">
<div v-else class="credits">{{i18n("Credits")}}:{{item.credits}}</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.body {
width:100%;
margin-bottom:10px;
}
.containerElement{
justify-content:center;
display:grid;
grid-template-columns:350px 350px 200px;
grid-template-areas:
"name teacher credits";
column-gap:10px;
}
.name {
grid-area:name;
align-self:center;
}
.teacher{
grid-area:teacher;
align-self:center;
}
.credits{
grid-area:credits;
align-self:center;
}
.listElement{
border:2px solid black;
font-size:25px;
color:white;
padding:20px;
background-color:rgb(50,50,50);
border-radius:20px;
margin-bottom:10px;
}
.modify{
font-size:25px;
padding:10px 10px 10px 10px;
background-color: rgb(239,60,168);
cursor: pointer;
border:none;
border-radius:15px;
}
input, select{
padding:10px 10px 10px 10px;
font-size:25px;
cursor: pointer;
border:none;
border-radius:15px;
}
button{
font-size:15px;
height:50px;
width:100px;
border:none;
border-radius:20px;
}
.buttonGrid{
display:grid;
grid-template-columns: auto auto;
column-gap:50px;
grid-template-areas:
"create delete";
}
.create{
grid-area:create;
background-color:rgb(0,200,0);
}
.delete{
grid-area:delete;
background-color:rgb(200,0,0);
}
.listTitle{
display: flex;
justify-content: center;
align-items: center;
width:400px;
margin-left:auto;
margin-right:auto;
border:2px solid black;
font-size:25px;
color:white;
padding:20px;
background-color:rgb(50,50,50);
border-radius:20px;margin-bottom:10px;
button:hover{
opacity:0.8;
}
}
</style>

View File

@ -1,321 +0,0 @@
<script setup>
import {reactive, ref } from 'vue'
import {getSelf,alterSelf,disconnect,deleteUser} from '../rest/Users.js'
import {getSelfCurriculum, getAllCurriculums} from '../rest/curriculum.js'
import {getCourses} from "../rest/courses.js"
import i18n from "@/i18n.js"
import { uploadProfilePicture } from '@/rest/uploads.js'
const user = ref(await getSelf());
const UserCurriculum = ref("");
const curricula = ref (await getAllCurriculums());
if(user.value.role === "Student"){
UserCurriculum.value = await getSelfCurriculum();
}
if(user.role === "Teacher"){
UserCurriculum.value = await getCourses("Teacher");
}
const modif = ref(false);
const curric = ref(false);
const reg = ref(false);
const pattern = {
profilPictureUrl:null,
email:null,
address:null,
password:null,
};
const patternInfos ={
email: null,
password: null,
passwordConfirm:null,
id:null,
}
let toModify= Object.assign({}, pattern);
let personnalInfos = Object.assign({}, patternInfos);
function resetInputs(inputs,list){
inputs=Object.assign({},list);
}
async function ChangeInfos(){
for (let element in toModify){
if (element =="email" && (toModify[element] !== null)){
await alterSelf(user.value.regNo,{email : toModify[element]});
}
if (element =="profilPictureUrl" && (toModify[element] !== null)){
await alterSelf(user.value.regNo,{ profilPictureUrl : toModify[element]});
}
else if(element == "address" && (toModify[element] !== null)){
await alterSelf(user.value.regNo,{address : toModify[element]});
}
else if(element == "password" && (toModify[element] !== null)){
await alterSelf(user.value.regNo,{password : toModify[element]});
}
}
toModify= Object.assign({},pattern);
user.value = await getSelf()
}
function setModify(item){
toModify.address = item.address;
toModify.profilPictureUrl = item.profilPictureUrl;
toModify.email= item.email;
toModify.password= item.password;
}
async function unRegister(){
deleteUser(user.value.regNo);
disconnect()
setTimeout(() => {
window.location.href="#/home";
}, "500");
}
function getPP(){
if(user.value.profilePictureUrl === null){
return "/Clyde.png"
}
return user.profilePictureUrl
}
</script>
<template>
<div class="body">
<div class="container">
<div class="profilPic">
<img class="subContainter" :src=getPP()>
</div>
<div class="globalInfos">
<div v-if="modif==false && curric==false && reg==false " class="infosContainer" >
<div>
{{user.firstName}} {{user.lastName}}
</div>
<div>
E-mail: {{user.email}}
</div>
<div v-if="user.role==='Student'">
{{user.option}} {{i18n(user.role)}}
</div>
<div v-else>
Role: {{i18n((user.role))}}
</div>
<div>
<button @click="modif=!modif; setModify(user)"> {{i18n("profile.modify.data")}} </button>
</div>
<div v-if="(user.role==='Student')">
<button @click="reg=!reg">{{i18n("profile.reRegister")}}</button>
<button @click="unRegister()" style="float:right;background-color:rgb(150,0,0);">{{i18n("profile.unRegister")}}</button>
</div>
<div v-if="(user.role==='Student')">
<button @click="curric=!curric">{{i18n("profile.change.curriculum")}}</button>
</div>
</div>
<div v-else-if="modif" class="infosContainer">
<div>
{{i18n("profile.picture")}}:
<input type="file" @change="user.profilPicture = uploadProfilePicture($event.target.files);" accept="image/*">
</div>
<div>
E-mail:
<input type="mail" v-model="toModify.email" />
</div>
<div>
{{i18n("profile.address")}}:
<input type="text" v-model="toModify.id">
</div>
<div>
{{i18n("login.password")}}:
<input type="password" v-model="toModify.password">
</div>
<div>
{{i18n("login.cPassword")}}:
<input type="password" v-model="toModify.passwordConfirm">
</div>
<div>
<button @click=" modif=!modif; ChangeInfos();">{{i18n("courses.confirm")}}</button>
<button @click="modif=!modif; resetInputs(toModify,pattern);" style="float:right;">{{i18n("courses.back")}}</button>
</div>
</div>
<div v-else-if="curric" class="infosContainer">
<div style="height:40px;">
{{i18n("Curriculum")}}:
<select v-model="curriculum" >
<option v-for="item in curricula" style="font-size:20px;" :value="item">{{item.option}}</option>
</select>
</div>
<div>
<button @click=" curric=!curric;">{{i18n("courses.confirm")}}</button>
<button @click="curric=!curric; resetInputs(personnalInfos,patternInfos);" style="float:right;">{{i18n("courses.back")}}</button>
</div>
</div>
<div v-else-if="reg" class="infosContainer">
<div>
E-mail:
<input type="mail" v-model="toModify.email" />
</div>
<div>
ID :
<input type="text" v-model="toModify.id">
</div>
<div>
{{i18n("login.password")}}:
<input type="password" v-model="toModify.password">
</div>
<div>
{{i18n("login.cPassword")}}:
<input type="password" id="confirm">
</div>
<div>
<button @click=" reg=!reg;">{{i18n("courses.confirm")}}</button>
<button @click=" reg=!reg; resetInputs(personnalInfos,patternInfos);" style="float:right;">{{i18n("courses.back")}}</button>
</div>
</div>
</div>
<div v-if="modif==false && curric==false && reg==false "class="moreInfos">
<div v-if="(user.role ==='Student')">
<div class="listTitle">
{{i18n("profile.course.list")}}
</div>
<div class="listElement" v-for="item in UserCurriculum.courses">
<div class=" containerElement">
<div class="name"> {{item.title}} </div>
<div class="teacher">{{item.owner.lastName}}</div>
<div class="credits">Credits:{{item.credits}}</div>
</div>
</div>
</div>
<div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.container{
display:grid;
grid-template-columns:200px 900px;
grid-template-rows:200px auto;
column-gap:30px;
row-gap:45px;
grid-template-areas:
"profilPic globalInfos"
"minfos minfos";
}
.profilPic{
grid-area:profilPic;
}
.globalInfos {
grid-area:globalInfos;
align-self :center;
}
.subContainter{
width:100%;
background-color:rgb(50,50,50);
border-radius:20px;
border:4px solid black;
}
.moreInfos {
grid-area:minfos;
}
.body {
width:100%;
margin-bottom:10px;
}
.containerElement{
justify-content:center;
display:grid;
grid-template-columns:350px 350px 200px;
grid-template-areas:
"name teacher credits";
column-gap:10px;
}
.name {
grid-area:name;
align-self:center;
}
.teacher{
grid-area:teacher;
align-self:center;
}
.credits{
grid-area:credits;
align-self:center;
}
.listTitle{
display: flex;
justify-content: center;
align-items: center;
width:200px;
margin-left:auto;
margin-right:auto;
border:2px solid black;
font-size:25px;
color:white;
padding:20px;
background-color:rgb(50,50,50);
border-radius:20px;margin-bottom:10px;
}
.listElement{
border:2px solid black;
font-size:25px;
color:white;
padding:20px;
background-color:rgb(50,50,50);
border-radius:20px;
margin-bottom:10px;
}
.infosContainer {
padding-bottom:50px;
border:2px solid black;
font-size:25px;
color:white;
padding:20px;
background-color:rgb(50,50,50);
border-radius:20px;
}
button{
border:none;
background-color:rgb(239, 60, 168);
border-radius:10px;
height:35px;
margin-top:10px;
}
button:hover{
opacity:0.8;
}
</style>

View File

@ -1,100 +0,0 @@
<script setup>
import i18n from "@/i18n.js"
import { reactive } from 'vue'
import { getStudents } from '../rest/Users.js'
const users = await getStudents();
</script>
<template>
<div v-for="item in users">
<div class="bodu">
<div class="container">
<div class="status"><a style="margin-left:30px">{{item.status}}</a></div>
<div class="option"><a>{{item.role}}</a></div>
<div class="surname"><a>{{item.lastName}}</a></div>
<div class="firstname"><a>{{item.firstName}}</a></div>
<div class="infos"><button style="background-color:rgb(105,05,105);" >{{i18n("request.moreInfos")}} </button></div>
</div>
</div>
</div>
</template>
<style scoped>
.container{
color:white;
height:100px;
font-size:30px;
display:grid;
grid-template-columns:250px 250px 250px 250px 150px;
grid-template-areas:
"status option surname firstname infos";
column-gap:10px;
}
.infos {
grid-area:infos;
align-self:center;
}
.option{
grid-area:option;
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;
}
.surname{
grid-area:surname;
align-self:center;
white-space: nowrap;
overflow: hidden;
text-overflow:ellipsis;
}
.firstname{
grid-area:firstname;
align-self:center;
white-space: nowrap;
overflow: hidden;
text-overflow:ellipsis;
}
button{
font-size:15px;
height:50px;
width:100px;
border:none;
border-radius:20px;
}
.bodu {
width:100%;
margin-bottom:10px;
border:2px solid black;
border-radius:9px;
background-color:rgb(50,50,50);
}
</style>

View File

@ -1,86 +0,0 @@
<script setup>
import i18n from "@/i18n.js"
import { reactive } from 'vue'
import { getAllUsers } from '../rest/Users.js'
const users = await getAllUsers();
</script>
<template>
<div v-for="item in users">
<div class="bodu">
<div class="container">
<div class="role"><a style="margin-left:30px">{{i18n(item.role)}}</a></div>
<div class="surname"><a>{{item.lastName}}</a></div>
<div class="firstname"><a>{{item.firstName}}</a></div>
<div class="infos"><button style="background-color:rgb(105,05,105);" >{{i18n("request.moreInfos")}} </button></div>
</div>
</div>
</div>
</template>
<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-areas:
"role surname firstname infos";
column-gap:10px;
}
.infos {
grid-area:infos;
align-items:center;
}
.role {
grid-area:role;
align-self:center;
}
.surname{
grid-area:surname;
align-self:center;
white-space: nowrap;
overflow: hidden;
text-overflow:ellipsis;
}
.firstname{
grid-area:firstname;
align-self:center;
white-space: nowrap;
overflow: hidden;
text-overflow:ellipsis;
}
button{
font-size:15px;
height:50px;
width:100px;
border:none;
border-radius:20px;
}
.bodu {
width:100%;
margin-bottom:10px;
border:2px solid black;
border-radius:9px;
background-color:rgb(50,50,50);
}
</style>

226
frontend/src/Login.vue Normal file
View File

@ -0,0 +1,226 @@
<script setup>
import { ref } from 'vue'
import i18n from './i18n.js'
const login= ref(true)
const page = ref(0)
const emailID=ref("")
const passwordIN=ref("")
const surname=ref("")
const firstname=ref("")
const passwordOUT=ref("")
const emailOUT=ref("")
const address=ref("")
const country=ref("")
const cursus=ref("")
const inputs = [{_emailID:emailID},{_passwordIN:passwordIN}]
const outputs= [{_surname:surname},{_firstname:firstname},{_passwordOUT:passwordOUT},{_emailOUT:emailOUT},{_address:address},{_country:country},{_cursus:cursus}]
</script>
<template>
<body>
<div class="Home">
<a href="/">
<img @click="draw" src="./assets/Clyde.png" style="width: 40px; height: auto; margin-top:4px">
</a>
</div>
<div class="logBoxCenterer">
<div class='loginBox'>
<div v-if="login">
<div class="form">
<h1 style="color:rgb(239,60,168); font-family: sans-serif;">{{i18n("login.guest.signin")}}</h1>
<div class="inputBox">
<p>ID / {{i18n("login.guest.email")}}</p>
<input type="text" v-model="emailID">
</div>
<div class="inputBox">
<p>{{i18n("login.guest.password")}}</p>
<input type="password" v-model="passwordIN">
</div>
<div class="register">
<a @click="login=!login">{{i18n("login.guest.register")}}</a>
</div>
<div class="inputBox">
<button @click="console.log(inputs)">{{i18n("login.guest.submit")}}</button>
</div>
</div>
</div>
<div v-else>
<div class="form">
<h1 style="color:rgb(239,60,168); font-family: sans-serif; text-align:center;">{{i18n("login.guest.welcome")}}</h1>
<div v-if="page === 0">
<div class="inputBox">
<p>{{i18n("login.guest.surname")}}</p>
<input type="text" v-model="surname">
</div>
<div class="inputBox">
<p>{{i18n("login.guest.firstname")}}</p>
<input type="text" v-model="firstname">
</div>
<div class="inputBox">
<p>{{i18n("login.guest.password")}}</p>
<input type="password" v-model="passwordOUT">
</div>
<div class="inputBox">
<p>Cursus</p>
<select v-model="cursus">
<option value="Chemistry">Chemistry</option>
<option value="Psycho">Psychology</option>
<option value="IT">IT</option>
</select>
</div>
<div class="switchpage">
<button @click="page++">{{i18n("login.guest.nextpage")}}</button>
</div>
<div @click="(login=!login) && (page=0)" class="register">
<a>{{i18n("login.guest.alregister")}}</a>
</div>
</div>
<div v-else>
<div class="inputBox">
<p>{{i18n("login.guest.email")}}</p>
<input type="mail" v-model="emailOUT">
</div>
<div class="inputBox">
<p>{{i18n("login.guest.address")}}</p>
<input type="text" v-model="address">
</div>
<div class="inputBox">
<p>{{i18n("login.guest.country")}}</p>
<input type="text" v-model="country">
</div>
<div style="align-self:center;" class="inputBox">
<button style="margin-top:25px;" @click="console.log(outputs)">{{i18n("login.guest.submit")}}</button>
</div>
<div class="switchpage">
<button @click="page--">{{i18n("login.guest.lastpage")}}</button>
</div>
<div @click="(login=!login) && (page=0)" class="register">
<a>{{i18n("login.guest.alregister")}}</a>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</template>
<style scoped>
.Home{
display: flex;
padding: 8px 16px;
color:rgb(255, 255, 255);
text-decoration: none;
}
.Home:hover{
width:40px;
background-color: black;
border-radius:6px;
color:white;
transform: translate(0px ,1px);
}
.logBoxCenterer {
position: absolute;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.loginBox {
background-color: rgb(24,24,24);
position : absolute;
width: 400px;
display:flex;
justify-content: center;
padding: 40px;
border-radius: 20px;
box-shadow:0 5px 25px #000000;
}
.form {
position:relative;
width:100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items:center;
gap: 15px;
}
.inputBox input,button,select {
width:100%;
background:rgb(255, 0 255);
border: none;
margin-right: 50px;
padding-left: 10px;
padding-top:10px;
padding-bottom:10px;
outline:none;
border-radius: 4px;
font-size:1.35em;
}
.inputBox p{
position:relative;
z-index: 100;
font-family:sans-serif ;
color:rgb(239,60,168);
transition:0.5;
}
.register{
color:rgb(239,60,168);
width: 100%;
display:flex;
cursor: pointer;
}
.switchpage{
width:100px;
background:rgb(255, 0 255);
border: none;
padding-right:0;
padding-top:10px;
padding-bottom:10px;
outline:none;
border-radius: 4px;
font-size:0.8em;
align-self:right;
}
button,select{
margin-bottom:20px;
background-color: rgb(239,60,168);
cursor: pointer;
padding:10px;
font-size:1.35em;
border:none;
border-radius:20px;
}
button:active ,.switchpage:active{
opacity:0.8;
}
</style>

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,86 @@
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

View File

@ -0,0 +1,44 @@
<script setup>
defineProps({
msg: {
type: String,
required: true
}
})
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
Youve successfully created a project with
<a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
</h3>
</div>
</template>
<style scoped>
h1 {
font-weight: 500;
font-size: 2.6rem;
position: relative;
top: -10px;
}
h3 {
font-size: 1.2rem;
}
.greetings h1,
.greetings h3 {
text-align: center;
}
@media (min-width: 1024px) {
.greetings h1,
.greetings h3 {
text-align: left;
}
}
</style>

View File

@ -0,0 +1,88 @@
<script setup>
import WelcomeItem from './WelcomeItem.vue'
import DocumentationIcon from './icons/IconDocumentation.vue'
import ToolingIcon from './icons/IconTooling.vue'
import EcosystemIcon from './icons/IconEcosystem.vue'
import CommunityIcon from './icons/IconCommunity.vue'
import SupportIcon from './icons/IconSupport.vue'
</script>
<template>
<WelcomeItem>
<template #icon>
<DocumentationIcon />
</template>
<template #heading>Documentation</template>
Vues
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
provides you with all information you need to get started.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<ToolingIcon />
</template>
<template #heading>Tooling</template>
This project is served and bundled with
<a href="https://vitejs.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
recommended IDE setup is
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> +
<a href="https://github.com/johnsoncodehk/volar" target="_blank" rel="noopener">Volar</a>. If
you need to test your components and web pages, check out
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> and
<a href="https://on.cypress.io/component" target="_blank" rel="noopener"
>Cypress Component Testing</a
>.
<br />
More instructions are available in <code>README.md</code>.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<EcosystemIcon />
</template>
<template #heading>Ecosystem</template>
Get official tools and libraries for your project:
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
you need more resources, we suggest paying
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
a visit.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<CommunityIcon />
</template>
<template #heading>Community</template>
Got stuck? Ask your question on
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>, our official
Discord server, or
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
>StackOverflow</a
>. You should also subscribe to
<a href="https://news.vuejs.org" target="_blank" rel="noopener">our mailing list</a> and follow
the official
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
twitter account for latest news in the Vue world.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<SupportIcon />
</template>
<template #heading>Support Vue</template>
As an independent project, Vue relies on community backing for its sustainability. You can help
us by
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
</WelcomeItem>
</template>

View File

@ -0,0 +1,87 @@
<template>
<div class="item">
<i>
<slot name="icon"></slot>
</i>
<div class="details">
<h3>
<slot name="heading"></slot>
</h3>
<slot></slot>
</div>
</div>
</template>
<style scoped>
.item {
margin-top: 2rem;
display: flex;
position: relative;
}
.details {
flex: 1;
margin-left: 1rem;
}
i {
display: flex;
place-items: center;
place-content: center;
width: 32px;
height: 32px;
color: var(--color-text);
}
h3 {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 0.4rem;
color: var(--color-heading);
}
@media (min-width: 1024px) {
.item {
margin-top: 0;
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
}
i {
top: calc(50% - 25px);
left: -26px;
position: absolute;
border: 1px solid var(--color-border);
background: var(--color-background);
border-radius: 8px;
width: 50px;
height: 50px;
}
.item:before {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
bottom: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:after {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
top: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:first-of-type:before {
display: none;
}
.item:last-of-type:after {
display: none;
}
}
</style>

View File

@ -0,0 +1,11 @@
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import HelloWorld from '../HelloWorld.vue'
describe('HelloWorld', () => {
it('renders properly', () => {
const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
expect(wrapper.text()).toContain('Hello Vitest')
})
})

View File

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>
</svg>
</template>

View File

@ -0,0 +1,19 @@
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"
></path>
</svg>
</template>

View File

@ -9,11 +9,8 @@
* *
*/ */
import { getCookie, setCookie } from './utils.js';
import { reactive } from 'vue';
const default_lang = "EN"; const default_lang = "EN";
const langs = reactive({}); let langs;
/** /**
@ -28,13 +25,33 @@ const langs = reactive({});
export default function i18n(key, options) { export default function i18n(key, options) {
let ret = langs[key]; let ret = langs[key];
if(options != null){ if(options != null){
for (let option in options) { for (let key in options) {
ret = ret.replaceAll("$" + option, options[option]); ret = ret.replaceAll("$" + key, options[key]);
} }
} }
return ret; return ret;
} }
//
// Those functions are utility functions use by previous exported functions.
//
/**
* Return the content of a cookie with specified key
* @param key cookie name
*/
function getCookie(key){
key = key + "="
let cookies = decodeURIComponent(document.cookie).split(";");
for (let el of cookies) {
el = el.trimStart();
if(el.indexOf(key) == 0){
return el.substr(key.length, el.length);
}
}
return "";
}
/** /**
* Function that load the file with translation from the specified lang and return a dictionnary * Function that load the file with translation from the specified lang and return a dictionnary
* @param select the language to load. could be null to fetch the cookies for an answer * @param select the language to load. could be null to fetch the cookies for an answer
@ -48,16 +65,13 @@ export async function loadLangs(lang){
const content = await (await fetch(filename)).text(); const content = await (await fetch(filename)).text();
const lines = content.split("\n"); const lines = content.split("\n");
let filteredLines = {};
for (let line of lines) { for (let line of lines) {
if(!line.trim().startsWith("#") && line.trim() != ""){ if(!line.trim().startsWith("#") && line.trim() != ""){
let split = line.indexOf("=") let split = line.indexOf("=")
langs[line.substr(0, split)] = line.substr(split+1, line.length); filteredLines[line.substr(0, split)] = line.substr(split+1, line.length);
}; };
} }
langs = filteredLines;
} }
await loadLangs(); await loadLangs();
export async function setLang(lang){
setCookie("lang", lang);
await loadLangs();
}

6
frontend/src/login.js Normal file
View File

@ -0,0 +1,6 @@
import './assets/main.css'
import { createApp } from 'vue'
import App from './Login.vue'
createApp(App).mount('#app')

View File

@ -1,6 +1,5 @@
import './assets/main.css' import './assets/main.css'
import 'vue3-toastify/dist/index.css'; import 'vue3-toastify/dist/index.css';
import 'https://kit.fontawesome.com/fb3bbd0a95.js'
import { createApp } from 'vue' import { createApp } from 'vue'
import App from './App.vue' import App from './App.vue'

View File

@ -1,45 +0,0 @@
/**
* functions to handle register requests.
*
* TODO: On time of writing, the backend doesn't support these endpoints so it could be modified in the future.
*/
import {restGet, restPatch} from './restConsumer.js'
/**
* create a new register requests that can be recovered by the registering service
* TODO: add info in the Object (I don't know what will be needed)
*/
export async function createRegister(){
return restPost("/request/register");
}
/**
* list all register request in a list of Objects
* Shall return a list of
* - id
* - type
* - lastName
* - firstName
* - address
* - country
* - birthdate
* - email
* - curriculum
* - degree
*/
export async function getRegisters(id){
if(id != null)
return restGet("/request/register/" + id);
return restGet("/request/register")
}
export async function getAllRegisters(){
return restGet("/requests/register")
}
/**
* Change the state of a requests.
*/
export async function validateRegister(id, state){
return restPatch("/request/register/" + id, state);
}

View File

@ -1,152 +0,0 @@
import { restGet, restPost, restPatch, restDelete} from './restConsumer.js'
import { getCookie, setCookie } from '@/utils.js'
export async function login(user, pass, exp){
return restPost("/login", {identifier: user, password: pass, expirationDate: exp});
}
export function isLogged(){
return getCookie("session_token") != ""
}
export function disconnect(){
setCookie("session_token", ";expires= Thu, 01 Jan 1970 00:00:01 GMT")
}
/**
* Register a user (tokenless)
*
* @param firstname
* @param lastname
* @param birthdate
* @param password
* @param mail
* @param address
* @param country
* @param curriculum
* @param imageId id of the image in database returned when uploaded
*/
export async function register(firstname, lastname, birthDate, password, email, address, country, curriculumId, imageId){
return restPost("/register", {
firstName: firstname,
lastName: lastname,
birthDate: birthDate,
password: password,
email: email,
address: address,
country: country,
curriculumId: curriculumId,
profilePictureUrl: imageId,
});
}
/**
* Register a user (by secretary)
*
* @param firstname
* @param lastname
* @param birthdate
* @param password
* @param mail
* @param address
* @param country
* @param imageId id of the image in database returned when uploaded
*
* PS: the password is not is not required as it is generated by the backend and sent to the user
* by mail. it's up to the user to change it if he cares about security
*/
export async function createUser(firstname, lastname, birthDate, email, address, country, role, imageId){
return restPost("/user", {
firstname: firstname,
lastname: lastname,
birthDate: birthDate,
password: password,
email: email,
address: address,
country: country,
});
}
/**
* get informations on a specific user.
* Leaving the id empty will return the user's value based on his token
* if the user is not authenticated. then an empty array should be returned
*/
export async function getUser(id){
const endpoint = "/user/" + id;
return restGet(endpoint);
}
/**
* Alterable datas of user.
* usage by secretary
*
* @param id regno of the user
* @param data data to change
*
* - lastName
* - firstName
* - birthDate
* - role
* - email
* - photo
* - Adress
*/
export async function alterUser(id, data){
return restPatch("/user/" + id, data);
}
/**
* Reserved for secretary roles. Allow to list all user on the plateform
*/
export async function getAllUsers(){
return restGet("/users");
}
/**
* Return the list of teachers
*
* @return a list of teachers
* each elements is of the form
* - id
* - name
* - role
*/
export async function getTeachers(){
return restGet("/teachers")
}
export async function getStudents(){
return restGet("/students")
}
/**
* Get informations about yourself
* - RegNo
* - FirstName / LastName
* - email
* - adressId (?)
* - birthDate
* - role
*/
export async function getSelf(){
return restGet("/user");
}
/**
* Alterable datas are
* - email
* - photo
* - Adress
* - Password
*/
export async function alterSelf(id,data){
console.log(data)
return restPatch("/user/"+id, data);
}
export async function deleteUser(id){
return restDelete("/user/" + id)
}

View File

@ -1,66 +0,0 @@
import { restGet } from './restConsumer.js'
import { ref, computed } from 'vue'
import i18n from '@/i18n.js'
// Liste des apps
import LoginPage from '@/Apps/Login.vue'
import Inscription from "@/Apps/Inscription.vue"
import Profil from "@/Apps/Profil.vue"
import Courses from "@/Apps/ManageCourses.vue"
import Users from "@/Apps/UsersList.vue"
import Students from "@/Apps/StudentsList.vue"
const apps = {
'/login': LoginPage,
'/inscription': Inscription,
'/profil': Profil,
'/manage-courses' : Courses,
'/users-list' : Users,
'/students-list' : Students,
}
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") },
'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") },
'StudentsList':{ path: '#/students-list',icon: 'fa-users',text: i18n("app.studentList")},
'UsersList':{ path: '#/users-list',icon: 'fa-users',text: i18n("app.users")},
}
const currentPath = ref(window.location.hash)
export const currentView = computed(() => {
return apps[currentPath.value.slice(1) || '/']
})
/**
* Return the list of app accesible by a logged (or not)
* user.
*/
export async function appList(){
let ret = [];
let userAppList = await restGet("/apps");
for (let userapp in userAppList) {
if(appsList[userAppList[userapp]] != null){
ret.push(appsList[userAppList[userapp]])
}
}
return ret;
}
/**
* Check if the specified page is authorized for the
* user
*/
export async function checkPage(page){
return restGet("/apps/" + page)
}
window.addEventListener('hashchange', () => {
currentPath.value = window.location.hash
})
// vim:set noet sts=0 sw=4 ts=2 tw=2:

View File

@ -1,73 +0,0 @@
/**
* Courses API
*/
import { restGet, restPost, restDelete, restPatch } from './restConsumer.js'
/**
* Create a new course
*/
export async function createCourse(name, credits, owner){
console.log(owner);
return restPost("/course", {title: name, credits: credits, owner} )
}
/**
* Delete the specified course
*/
export async function deleteCourse(id){
return restDelete("/course/" + id);
}
/**
* Get informations on a particular course
*
* @param id identification of the course
*
* @return all atribute of the specified course
* - name
* - credits
* - faculty
* - teacher
* - assistants : list
*/
export async function getCourse(id){
return restGet("/course/" + id);
}
/**
* Get the list of courses to display on secretary's option
*
* @return list of courses of the form
* - id
* - name
* - credits
* - facutly
* - teacher
* - Assistants
*/
export async function getCourses(role){
if(role==="Teacher"){
return restGet("/courses/owned")
}
return restGet("/courses")
}
/**
* Change the options of a course
*
* @param id the id of the course
* @param changes Object with value to changes
*
* The changes object can contain:
* - name
* - credits
* - faculty
* - teacher
* - assistants: should be a list and will replace all assistants
*/
export async function alterCourse(id, changes){
return restPatch("/course/" + id, changes);
}

Some files were not shown because too many files have changed in this diff Show More