Skip to content

Commit

Permalink
Merge pull request #258 from SELab-2/frontend
Browse files Browse the repository at this point in the history
Frontend
  • Loading branch information
Aqua-sc authored May 12, 2024
2 parents 94f3003 + aca6c9a commit 9d06277
Show file tree
Hide file tree
Showing 83 changed files with 4,661 additions and 1,906 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,13 @@ backend/app/data/*
backend/data/*
backend/tmp/*
backend/app/tmp/*
data/*

### Secrets ###
backend/app/src/main/resources/application-secrets.properties
docker.env


./startBackend.sh
startBackend.sh

Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@
import com.ugent.pidgeon.model.Auth;
import com.ugent.pidgeon.model.json.*;
import com.ugent.pidgeon.postgre.models.CourseEntity;
import com.ugent.pidgeon.postgre.models.CourseUserEntity;
import com.ugent.pidgeon.postgre.models.CourseUserId;
import com.ugent.pidgeon.postgre.models.GroupClusterEntity;
import com.ugent.pidgeon.postgre.models.GroupEntity;
import com.ugent.pidgeon.postgre.models.UserEntity;
import com.ugent.pidgeon.postgre.models.types.CourseRelation;
import com.ugent.pidgeon.postgre.models.types.UserRole;
import com.ugent.pidgeon.postgre.repository.*;
import com.ugent.pidgeon.util.*;
import java.util.Map;
import java.util.logging.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand All @@ -27,6 +32,11 @@ public class ClusterController {
GroupClusterRepository groupClusterRepository;
@Autowired
GroupRepository groupRepository;
@Autowired
GroupMemberRepository groupMemberRepository;
@Autowired
CourseUserRepository courseUserRepository;


@Autowired
private ClusterUtil clusterUtil;
Expand Down Expand Up @@ -168,6 +178,62 @@ public ResponseEntity<?> doGroupClusterUpdate(GroupClusterEntity clusterEntity,
return ResponseEntity.ok(entityToJsonConverter.clusterEntityToClusterJson(clusterEntity));
}

/**
* Fills up the groups in a cluster by providing a map of groupids with lists of userids
*
* @param clusterid identifier of a cluster
* @param auth authentication object of the requesting user
* @param clusterFillJson ClusterFillJson object containing a map of all groups and their
* members of that cluster
* @return ResponseEntity<?>
* @HttpMethod PUT
* @ApiPath /api/clusters/{clusterid}/fill
* @AllowedRoles student, teacher
*/
@PutMapping(ApiRoutes.CLUSTER_BASE_PATH + "/{clusterid}/fill")
@Transactional
@Roles({UserRole.teacher, UserRole.student})
public ResponseEntity<?> fillCluster(@PathVariable("clusterid") Long clusterid, Auth auth, @RequestBody Map<String, Long[]> clusterFillMap) {
ClusterFillJson clusterFillJson = new ClusterFillJson(clusterFillMap);
try{
CheckResult<GroupClusterEntity> checkResult = clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(clusterid, auth.getUserEntity());

if (checkResult.getStatus() != HttpStatus.OK) {
return ResponseEntity.status(checkResult.getStatus()).body(checkResult.getMessage());
}

GroupClusterEntity groupCluster = checkResult.getData();

List<GroupEntity> groups = groupRepository.findAllByClusterId(clusterid);

CheckResult<Void> jsonCheckRes = clusterUtil.checkFillClusterJson(clusterFillJson, groupCluster);
if (jsonCheckRes.getStatus() != HttpStatus.OK) {
return ResponseEntity.status(jsonCheckRes.getStatus()).body(jsonCheckRes.getMessage());
}

for(GroupEntity group: groups){
commonDatabaseActions.removeGroup(group.getId());
}

for(String groupName: clusterFillJson.getClusterGroupMembers().keySet()){
Long[] users = clusterFillJson.getClusterGroupMembers().get(groupName);
GroupEntity groupEntity = new GroupEntity(groupName, clusterid);
groupEntity = groupRepository.save(groupEntity);
for(Long userid: users){
groupMemberRepository.addMemberToGroup(groupEntity.getId(), userid);
}
}

groupCluster.setGroupAmount(clusterFillJson.getClusterGroupMembers().size());
groupClusterRepository.save(groupCluster);
return ResponseEntity.status(HttpStatus.OK).body("Filled group cluster successfully");
} catch (Exception e) {
Logger.getGlobal().severe(e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Something went wrong");
}
}


@PatchMapping(ApiRoutes.CLUSTER_BASE_PATH + "/{clusterid}")
@Roles({UserRole.teacher, UserRole.student})
public ResponseEntity<?> patchCluster(@PathVariable("clusterid") Long clusterid, Auth auth, @RequestBody GroupClusterUpdateJson clusterJson) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@ public class TestController {

/**
* Function to update the tests of a project
* @param dockerImage the docker image to use for the tests
* @param dockerTest the docker test file
* @param structureTest the structure test file
* @param projectId the id of the project to update the tests for
* @param auth the authentication object of the requesting user
* @HttpMethod POST
Expand All @@ -60,6 +57,7 @@ public class TestController {
@Roles({UserRole.teacher, UserRole.student})
public ResponseEntity<?> updateTests(
@RequestBody TestUpdateJson testJson,

@PathVariable("projectid") long projectId,
Auth auth) {
return alterTests(projectId, auth.getUserEntity(), testJson.getDockerImage(), testJson.getDockerScript(),
Expand All @@ -70,6 +68,7 @@ public ResponseEntity<?> updateTests(
@Roles({UserRole.teacher, UserRole.student})
public ResponseEntity<?> patchTests(
@RequestBody TestUpdateJson testJson,

@PathVariable("projectid") long projectId,
Auth auth) {
return alterTests(projectId, auth.getUserEntity(), testJson.getDockerImage(), testJson.getDockerScript(),
Expand All @@ -80,6 +79,7 @@ public ResponseEntity<?> patchTests(
@Roles({UserRole.teacher, UserRole.student})
public ResponseEntity<?> putTests(
@RequestBody TestUpdateJson testJson,

@PathVariable("projectid") long projectId,
Auth auth) {
return alterTests(projectId, auth.getUserEntity(), testJson.getDockerImage(), testJson.getDockerScript(),
Expand All @@ -99,7 +99,6 @@ private ResponseEntity<?> alterTests(




if (dockerImage != null && dockerImage.isBlank()) {
dockerImage = null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.ugent.pidgeon.model.json;

import java.util.HashMap;
import java.util.Map;

public class ClusterFillJson {
private final Map<String, Long[]> clusterGroupMembers;

public ClusterFillJson() {
this.clusterGroupMembers = new HashMap<>();
}

public ClusterFillJson(Map<String, Long[]> clusterGroupMembers) {
this.clusterGroupMembers = clusterGroupMembers;
}

public Map<String, Long[]> getClusterGroupMembers() {
return clusterGroupMembers;
}

}
30 changes: 30 additions & 0 deletions backend/app/src/main/java/com/ugent/pidgeon/util/ClusterUtil.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
package com.ugent.pidgeon.util;

import com.ugent.pidgeon.model.json.ClusterFillJson;
import com.ugent.pidgeon.model.json.GroupClusterCreateJson;
import com.ugent.pidgeon.model.json.GroupClusterUpdateJson;
import com.ugent.pidgeon.postgre.models.CourseEntity;
import com.ugent.pidgeon.postgre.models.CourseUserEntity;
import com.ugent.pidgeon.postgre.models.CourseUserId;
import com.ugent.pidgeon.postgre.models.GroupClusterEntity;
import com.ugent.pidgeon.postgre.models.UserEntity;
import com.ugent.pidgeon.postgre.models.types.CourseRelation;
import com.ugent.pidgeon.postgre.repository.CourseUserRepository;
import com.ugent.pidgeon.postgre.repository.GroupClusterRepository;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
Expand All @@ -17,6 +25,8 @@ public class ClusterUtil {
private GroupClusterRepository groupClusterRepository;
@Autowired
private CourseUtil courseUtil;
@Autowired
private CourseUserRepository courseUserRepository;

/**
* Check if a cluster is an individual cluster. This means that it only contains one group
Expand Down Expand Up @@ -172,4 +182,24 @@ public CheckResult<Void> checkGroupClusterCreateJson(GroupClusterCreateJson clus

return new CheckResult<>(HttpStatus.OK, "", null);
}

public CheckResult<Void> checkFillClusterJson(ClusterFillJson fillJson, GroupClusterEntity cluster) {
Collection<Long[]> members = fillJson.getClusterGroupMembers().values();

Set<Long> seen = new HashSet<>();
for (Long[] member : members) {
for (Long userId : member) {
CourseUserEntity courseUser = courseUserRepository.findById(new CourseUserId(cluster.getCourseId(), userId)).orElse(null);
if (courseUser == null || !courseUser.getRelation().equals(CourseRelation.enrolled)) {
return new CheckResult<>(HttpStatus.BAD_REQUEST, "User with id " + userId + " is not enrolled in the course", null);
}
if (seen.contains(userId)) {
return new CheckResult<>(HttpStatus.BAD_REQUEST, "Can't add a user to 2 different groups", null);
}
seen.add(userId);
}
}

return new CheckResult<>(HttpStatus.OK, "", null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public CheckResult<Pair<CourseEntity, CourseRelation>> getCourseIfUserInCourse(l
if (courseUserEntity == null && !user.getRole().equals(UserRole.admin)) {
return new CheckResult<>(HttpStatus.FORBIDDEN, "User is not part of the course", null);
}
return new CheckResult<>(HttpStatus.OK, "", new Pair<>(courseEntity, courseUserEntity.getRelation()));
return new CheckResult<>(HttpStatus.OK, "", new Pair<>(courseEntity, courseUserEntity == null ? null : courseUserEntity.getRelation()));
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,15 @@ public CheckResult<Void> canAddUserToGroup(long groupId, long userId, UserEntity
if (group == null) {
return new CheckResult<>(HttpStatus.NOT_FOUND, "Group not found", null);
}

boolean isAdmin = false;

if (user.getId() != userId) {
CheckResult<Void> admin = isAdminOfGroup(groupId, user);
if (admin.getStatus() != HttpStatus.OK) {
return admin;
}
isAdmin = true;
} else {
if (!groupRepository.userAccessToGroup(userId, groupId)) {
return new CheckResult<>(HttpStatus.FORBIDDEN, "User is not part of the course", null);
Expand Down Expand Up @@ -134,7 +138,7 @@ public CheckResult<Void> canAddUserToGroup(long groupId, long userId, UserEntity
return new CheckResult<>(HttpStatus.INTERNAL_SERVER_ERROR, "Error while checking cluster", null);
}

if (cluster.getData().getMaxSize() <= groupRepository.countUsersInGroup(groupId)) {
if (cluster.getData().getMaxSize() <= groupRepository.countUsersInGroup(groupId) && !isAdmin) {
return new CheckResult<>(HttpStatus.FORBIDDEN, "Group is full", null);
}
if (clusterUtil.isIndividualCluster(group.getClusterId())) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package com.ugent.pidgeon.controllers;

import com.ugent.pidgeon.model.json.GroupClusterJson;
import com.ugent.pidgeon.model.json.GroupJson;
import com.ugent.pidgeon.postgre.models.CourseEntity;
import com.ugent.pidgeon.postgre.models.GroupClusterEntity;
import com.ugent.pidgeon.postgre.models.GroupEntity;
import com.ugent.pidgeon.postgre.models.types.CourseRelation;
import com.ugent.pidgeon.postgre.repository.GroupClusterRepository;
import com.ugent.pidgeon.postgre.repository.GroupRepository;
import com.ugent.pidgeon.postgre.repository.GroupUserRepository;
import com.ugent.pidgeon.util.*;
import java.time.OffsetDateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand All @@ -34,8 +36,6 @@ public class ClusterControllerTest extends ControllerTest{
GroupClusterRepository groupClusterRepository;
@Mock
GroupRepository groupRepository;
@Mock
GroupUserRepository groupUserRepository;


@Mock
Expand All @@ -46,6 +46,8 @@ public class ClusterControllerTest extends ControllerTest{
private CourseUtil courseUtil;
@Mock
private CommonDatabaseActions commonDatabaseActions;
@Mock
private GroupMemberController groupMemberController;
@InjectMocks
private ClusterController clusterController;

Expand All @@ -64,7 +66,7 @@ public void setup() {
.build();

courseEntity = new CourseEntity("name", "description",2024);
groupClusterEntity = new GroupClusterEntity(1L, 20, "clustername", 5);
groupClusterEntity = new GroupClusterEntity(1L, 3, "clustername", 5);
groupEntity = new GroupEntity("groupName", 1L);
}

Expand Down Expand Up @@ -142,6 +144,67 @@ public void testUpdateCluster() throws Exception {
.andExpect(status().isBadRequest());
}

// TEST IS OUTDATED, SHOULD WORK WITH MINIMAL CHANGES
// @Test
// public void testFillCluster() throws Exception {
// String request = "{\"clusterGroupMembers\":{\"1\":[1,2,3],\"2\":[],\"3\":[4]}}";
//
// List<GroupJson> groupJsons = List.of(new GroupJson(3, 1L, "group 1", "groupclusterurl"));
// GroupClusterJson groupClusterJson = new GroupClusterJson(1L, "test cluster",
// 3, 5, OffsetDateTime.now(), groupJsons, "courseurl");
// when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any()))
// .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity));
// when(clusterUtil.getGroupClusterEntityIfNotIndividual(anyLong(), any()))
// .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity));
// when(entityToJsonConverter.clusterEntityToClusterJson(groupClusterEntity))
// .thenReturn(groupClusterJson);
// mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH+"/1/fill")
// .contentType(MediaType.APPLICATION_JSON)
// .content(request))
// .andExpect(status().isOk());
//
// when(commonDatabaseActions.removeGroup(anyLong()))
// .thenThrow(new RuntimeException("TEST ERROR"));
// mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH+"/1/fill")
// .contentType(MediaType.APPLICATION_JSON)
// .content(request))
// .andExpect(status().isInternalServerError());
//
// // a group that is too big
// request = "{\"clusterGroupMembers\":{\"1\":[1,2,3,6],\"2\":[],\"3\":[4]}}";
// mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH+"/1/fill")
// .contentType(MediaType.APPLICATION_JSON)
// .content(request))
// .andExpect(status().isBadRequest());
// // too many groups
// request = "{\"clusterGroupMembers\":{\"1\":[1,2,3],\"2\":[],\"3\":[4],\"4\":[],\"5\":[6],\"6\":[]}}";
// mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH+"/1/fill")
// .contentType(MediaType.APPLICATION_JSON)
// .content(request))
// .andExpect(status().isBadRequest());
//
// when(entityToJsonConverter.clusterEntityToClusterJson(groupClusterEntity))
// .thenReturn(null);
// mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH+"/1/fill")
// .contentType(MediaType.APPLICATION_JSON)
// .content(request))
// .andExpect(status().isNotFound());
//
// when(clusterUtil.getGroupClusterEntityIfNotIndividual(anyLong(), any()))
// .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null));
// mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH+"/1/fill")
// .contentType(MediaType.APPLICATION_JSON)
// .content(request))
// .andExpect(status().isIAmATeapot());
//
// when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any()))
// .thenReturn(new CheckResult<>(HttpStatus.UNAUTHORIZED, "", null));
// mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH+"/1/fill")
// .contentType(MediaType.APPLICATION_JSON)
// .content(request))
// .andExpect(status().isUnauthorized());
// }

@Test
public void testPatchCluster() throws Exception {
String request = "{\"name\": null, \"capacity\": null}";
Expand Down
1 change: 1 addition & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/node_modules
/.pnp
.pnp.js
package-lock.json

# testing
/coverage
Expand Down
Loading

0 comments on commit 9d06277

Please sign in to comment.