Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1632 epic create groups #1639

Closed
wants to merge 13 commits into from
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
BEGIN;


CREATE TABLE IF NOT EXISTS `fbs`.`group` (
`group_id` INT NOT NULL AUTO_INCREMENT,
`course_id` INT NOT NULL,
`name` VARCHAR(100) NOT NULL,
`membership` INT NOT NULL,
`visible` TINYINT(1) NOT NULL DEFAULT 1,
PRIMARY KEY (`group_id`),
FOREIGN KEY (`course_id`) REFERENCES `fbs`.`course`(`course_id`),
UNIQUE INDEX `groups_groupid_courseid_uindex` (`group_id`, `course_id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_0900_ai_ci;

INSERT INTO migration (number) VALUES (20);

COMMIT;


Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
BEGIN;

CREATE TABLE IF NOT EXISTS `fbs`.`user_group` (
`user_id` INT NOT NULL,
`course_id` INT NOT NULL,
`group_id` INT NOT NULL,
PRIMARY KEY (`user_id`, `course_id`, `group_id`),
INDEX `user_has_groups_users_user_id_fk` (`user_id` ASC),
CONSTRAINT `user_has_groups_users_user_id_fk`
FOREIGN KEY (`user_id`)
REFERENCES `fbs`.`user` (`user_id`)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT `user_group_course_course_id_fk`
FOREIGN KEY (`course_id`)
REFERENCES `fbs`.`course` (`course_id`)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT `user_group_group_group_id_fk`
FOREIGN KEY (`group_id`)
REFERENCES `fbs`.`group` (`group_id`)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_0900_ai_ci;

-- -----------------------------------------------------
-- trigger to check if user is participant of the course
-- -----------------------------------------------------

CREATE TRIGGER check_user_course_before_insert
BEFORE INSERT ON `user_group`
FOR EACH ROW
BEGIN
DECLARE user_in_course INT;
SELECT COUNT(*)
INTO user_in_course
FROM user_course
WHERE user_id = NEW.user_id AND course_id = NEW.course_id;

IF user_in_course = 0 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Der Nutzer gehört nicht zum angegebenen Kurs.';
END IF;
END;


INSERT INTO migration (number) VALUES (21);

COMMIT;

Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package de.thm.ii.fbs.controller

import com.fasterxml.jackson.databind.JsonNode
import de.thm.ii.fbs.controller.exception.{BadRequestException, ForbiddenException, ResourceNotFoundException}
import de.thm.ii.fbs.model.{Group, CourseRole, GlobalRole}
import de.thm.ii.fbs.services.persistence._
import de.thm.ii.fbs.services.security.AuthService
import de.thm.ii.fbs.util.JsonWrapper._

import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation._

/**
* Controller to manage rest api calls for a group resource.
*/

@RestController
@CrossOrigin
@RequestMapping (path = Array("/api/v1/courses/{cid}/groups"), produces = Array(MediaType.APPLICATION_JSON_VALUE))
class GroupController{
@Autowired
private val groupService: GroupService = null
@Autowired
private val authService: AuthService = null
@Autowired
private val courseRegistrationService: CourseRegistrationService = null

/**
* Get a group list
*
* @param cid Course Id
* @param ignoreHidden optional filter to filter only for visible groups
* @param req http request
* @param res http response
* @return group list
*/
@GetMapping(value = Array(""))
@ResponseBody
def getAll(@PathVariable ("cid") cid: Integer, @RequestParam(value = "visible", required = false)
ignoreHidden: Boolean, req: HttpServletRequest, res: HttpServletResponse): List[Group] = {
val user = authService.authorize(req, res)
val someCourseRole = courseRegistrationService.getParticipants(cid).find(_.user.id == user.id).map(_.role)
(user.globalRole, someCourseRole) match {
case (GlobalRole.ADMIN | GlobalRole.MODERATOR, _) | (_, Some(CourseRole.DOCENT)) =>
val groupList = groupService.getAll(cid, ignoreHidden = false)
groupList
case _ => throw new ForbiddenException()
}
}

/**
* Create a new group
*
* @param cid Course Id
* @param req http request
* @param res http response
* @param body contains JSON request
* @return JSON
*/
@PostMapping(value = Array(""), consumes = Array(MediaType.APPLICATION_JSON_VALUE))
@ResponseBody
def create(@PathVariable ("cid") cid: Int, req: HttpServletRequest, res: HttpServletResponse, @RequestBody body: JsonNode): Group = {
val user = authService.authorize(req, res)
val someCourseRole = courseRegistrationService.getParticipants(cid).find(_.user.id == user.id).map(_.role)
if (!(user.globalRole == GlobalRole.ADMIN || user.globalRole == GlobalRole.MODERATOR || someCourseRole.contains(CourseRole.DOCENT))) {
throw new ForbiddenException()
}
val name = Option(body.get("name")).map(_.asText())
val membership = Option(body.get("membership")).map(_.asInt())
val visible = Option(body.get("visible")).map(_.asBoolean())
(name, membership, visible) match {
case (Some(name), Some (membership), Some(visible))
=> groupService.create(Group(0, cid, name, membership, visible))
case _ => throw new BadRequestException("Malformed Request Body")
}
}

/**
* Get a single group by id
*
* @param cid Course Id
* @param gid Group id
* @param req http request
* @param res http response
* @return A single group
*/
@GetMapping(value = Array("/{gid}"))
@ResponseBody
def getOne(@PathVariable ("cid") cid: Integer, @PathVariable("gid") gid: Integer, req: HttpServletRequest, res: HttpServletResponse): Group = {
val user = authService.authorize(req, res)
val someCourseRole = courseRegistrationService.getParticipants(cid).find(_.user.id == user.id).map(_.role)

groupService.get(cid, gid) match {
case Some(group) => if (!(user.globalRole == GlobalRole.ADMIN || user.globalRole == GlobalRole.MODERATOR || someCourseRole.contains(CourseRole.DOCENT))) {
throw new ForbiddenException()
} else {
group
}
case _ => throw new ResourceNotFoundException()
}
}

/**
* Update a single group by id
*
* @param cid Course id
* @param gid Group id
* @param req http request
* @param res http response
* @param body Request Body
*/
@PutMapping(value = Array("/{gid}"))
def update(@PathVariable ("cid") cid: Integer, @PathVariable("gid") gid: Integer, req: HttpServletRequest, res: HttpServletResponse,
@RequestBody body: JsonNode): Unit = {
val user = authService.authorize(req, res)
val someCourseRole = courseRegistrationService.getParticipants(cid).find(_.user.id == user.id).map(_.role)

(user.globalRole, someCourseRole) match {
case (GlobalRole.ADMIN | GlobalRole.MODERATOR, _) | (_, Some(CourseRole.DOCENT)) =>
(body.retrive("name").asText(),
body.retrive("membership").asInt(),
body.retrive("visible").asBool()
) match {
case (Some(name), Some (membership), visible)
=> groupService.update(cid, gid, Group(gid, cid, name, membership, visible.getOrElse(true)))
case _ => throw new BadRequestException("Malformed Request Body")
}
case _ => throw new ForbiddenException()
}
}

/**
* Delete course
*
* @param cid Course id
* @param gid Group id
* @param req http request
* @param res http response
*/
@DeleteMapping(value = Array("/{gid}"))
def delete(@PathVariable ("cid") cid: Integer, @PathVariable("gid") gid: Integer, req: HttpServletRequest, res: HttpServletResponse): Unit = {
val user = authService.authorize(req, res)
val someCourseRole = courseRegistrationService.getParticipants(cid).find(_.user.id == user.id).map(_.role)

(user.globalRole, someCourseRole) match {
case (GlobalRole.ADMIN | GlobalRole.MODERATOR, _) | (_, Some(CourseRole.DOCENT)) =>
groupService.delete(cid, gid)
case _ => throw new ForbiddenException()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package de.thm.ii.fbs.controller

import de.thm.ii.fbs.controller.exception.{ForbiddenException, MembershipExceededException, ResourceNotFoundException}
import de.thm.ii.fbs.model.{CourseRole, GlobalRole, Group, Participant}
import de.thm.ii.fbs.services.persistence._
import de.thm.ii.fbs.services.security.AuthService

import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation._

/**
* Controller to manage rest api calls for group registration and group members.
*/

@RestController
@CrossOrigin
@RequestMapping(path = Array("/api/v1"), produces = Array(MediaType.APPLICATION_JSON_VALUE))
class GroupRegistrationController {
@Autowired
private val authService: AuthService = null
@Autowired
private val courseRegistrationService: CourseRegistrationService = null
@Autowired
private val groupService: GroupService = null
@Autowired
private val groupRegistrationService: GroupRegistrationService = null

/**
* Add a user to a group within a course
*
* @param cid Course id
* @param gid Group id
* @param uid User id
* @param req http request
* @param res http response
*/
@PutMapping(value = Array("/courses/{cid}/groups/{gid}/users/{uid}"), consumes = Array(MediaType.APPLICATION_JSON_VALUE))
def addUserToGroup(@PathVariable("cid") cid: Int, @PathVariable("gid") gid: Int, @PathVariable("uid") uid: Int,
req: HttpServletRequest, res: HttpServletResponse): Unit = {
val user = authService.authorize(req, res)
val hasGlobalPrivileges = user.hasRole(GlobalRole.ADMIN, GlobalRole.MODERATOR)
val hasCoursePrivileges = courseRegistrationService.getCoursePrivileges(user.id).getOrElse(cid, CourseRole.STUDENT) == CourseRole.DOCENT
if (hasGlobalPrivileges || hasCoursePrivileges || user.id == uid) {
//Check if the group is full
val currentMembership = groupRegistrationService.getGroupMembership(cid, gid)
val group = groupService.get(cid, gid)
group match {
case Some(group) => val maxMembership: Int = group.membership
if (currentMembership < maxMembership) {
groupRegistrationService.addUserToGroup(uid, cid, gid)
} else {
throw new MembershipExceededException()
}
case _ => throw new ResourceNotFoundException()
}
} else {
throw new ForbiddenException()
}
}

/**
* Remove a user from a group
*
* @param uid User id
* @param cid Course id
* @param gid Group id
* @param req http request
* @param res http response
*/
@DeleteMapping(value = Array("/courses/{cid}/groups/{gid}/users/{uid}"))
def removeUserFromGroup(@PathVariable("uid") uid: Int, @PathVariable("cid") cid: Int, @PathVariable("gid") gid: Int,
req: HttpServletRequest, res: HttpServletResponse): Unit = {
val user = authService.authorize(req, res)
val hasGlobalPrivileges = user.hasRole(GlobalRole.ADMIN, GlobalRole.MODERATOR)
val hasCoursePrivileges = courseRegistrationService.getCoursePrivileges(user.id).getOrElse(cid, CourseRole.STUDENT) == CourseRole.DOCENT
if (hasGlobalPrivileges || hasCoursePrivileges || user.id == uid) {
groupRegistrationService.removeUserFromGroup(uid, cid, gid)
} else {
throw new ForbiddenException()
}
}

/**
* Remove all users from a group
*
* @param cid Course id
* @param gid Group id
* @param req http request
* @param res http response
*/
@DeleteMapping(value = Array("/courses/{cid}/groups/{gid}/users"))
def removeUserFromGroup(@PathVariable("cid") cid: Int, @PathVariable("gid") gid: Int, req: HttpServletRequest, res: HttpServletResponse): Unit = {
val user = authService.authorize(req, res)
val hasGlobalPrivileges = user.hasRole(GlobalRole.ADMIN, GlobalRole.MODERATOR)
val hasCoursePrivileges = courseRegistrationService.getCoursePrivileges(user.id).getOrElse(cid, CourseRole.STUDENT) == CourseRole.DOCENT
if (hasGlobalPrivileges || hasCoursePrivileges) {
groupRegistrationService.removeAllUsersFromGroup(cid, gid)
} else {
throw new ForbiddenException()
}
}

/**
* Retrieve all groups of a specific user
*
* @param uid User id
* @param req http request
* @param res http response
* @return List of Groups
*/
@GetMapping(value = Array("/users/{uid}/groups"))
@ResponseBody
def getUserGroups(@PathVariable("uid") uid: Integer, req: HttpServletRequest, res: HttpServletResponse): List[Group] = {
val user = authService.authorize(req, res)
val hasGlobalPrivileges = user.hasRole(GlobalRole.ADMIN, GlobalRole.MODERATOR)
if (hasGlobalPrivileges || user.id == uid) {
groupRegistrationService.getUserGroups(uid, ignoreHidden = false)
} else {
throw new ForbiddenException()
}
}

/**
* Get all course participants which are part of a group
* @param cid Course id
* @param gid Group id
* @param req http request
* @param res http response
* @return List of course participants
*/
@GetMapping(value = Array("/courses/{cid}/groups/{gid}/participants"))
@ResponseBody
def getMembers(@PathVariable("cid") cid: Integer, @PathVariable("gid") gid: Int, req: HttpServletRequest, res: HttpServletResponse): List[Participant] = {
val user = authService.authorize(req, res)
val hasGlobalPrivileges = user.hasRole(GlobalRole.ADMIN, GlobalRole.MODERATOR)
val hasCoursePrivileges = courseRegistrationService.getCoursePrivileges(user.id).getOrElse(cid, CourseRole.STUDENT) == CourseRole.DOCENT
if (hasGlobalPrivileges || hasCoursePrivileges) {
groupRegistrationService.getMembers(cid, gid)
} else {
throw new ForbiddenException()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package de.thm.ii.fbs.controller.exception

import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ResponseStatus

/**
* ConflictException 400
* @param message A human readable String
*/
@ResponseStatus(value = HttpStatus.CONFLICT)
class MembershipExceededException(message: String = "Die Gruppe ist voll.") extends RuntimeException(message)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package de.thm.ii.fbs.model

/**
* A group
* @param id The id of the group
* @param courseId course to which the group belongs
* @param name Name of the group
* @param membership The max number of members
* @param visible The visibility of the group, false = invisible
*/

case class Group(id: Int, courseId: Int, name: String, membership: Int, visible: Boolean = true)
Loading
Loading