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

Role management endpoints #145

Merged
merged 27 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7717b40
Add role repository
LuisDuarte1 Apr 5, 2023
7773353
Add RoleService and logic related to role modification
LuisDuarte1 Apr 5, 2023
5e0a429
Add role controller and tests
LuisDuarte1 Apr 5, 2023
10623bb
Merge branch 'develop' into feature/roles
LuisDuarte1 Apr 5, 2023
c1cd182
Add more tests and refactor some endpoints
LuisDuarte1 Apr 12, 2023
4840c1b
Change endpoints and create permissionsDto to keep API more semantic
LuisDuarte1 Apr 12, 2023
a17fe90
Add documentation and refactor tests
LuisDuarte1 Apr 26, 2023
a6d64c3
[no ci] Apply suggestions from code review
LuisDuarte1 Jun 21, 2023
50faa4e
[no ci] Apply test files suggestions from code review
LuisDuarte1 Jun 21, 2023
bae8512
Merge remote-tracking branch 'origin/develop' into feature/roles
LuisDuarte1 Jun 21, 2023
09ee6c4
first improvements to readability and consistency
LuisDuarte1 Jun 27, 2023
28b4118
make tests run and remove ordercolumn
LuisDuarte1 Jul 5, 2023
81faf12
fix linting
LuisDuarte1 Jul 5, 2023
b01bb25
fix docs
LuisDuarte1 Jul 12, 2023
95f3cab
fix docs again
LuisDuarte1 Jul 12, 2023
786e581
remove httpsecurity iframe bypass (due to h2 dashboard)
LuisDuarte1 Jul 12, 2023
ac1b3a7
change dto file structure
LuisDuarte1 Jul 18, 2023
7592720
Make role creation accept a past generation
LuisDuarte1 Jul 19, 2023
90adf69
Remove orderColumn from activity, make add remove and remove user ide…
LuisDuarte1 Jul 19, 2023
526b5b8
remove role repo .findByName
LuisDuarte1 Jul 19, 2023
b1c05c4
make code suggestions
LuisDuarte1 Jul 26, 2023
62907e4
remove redudant permissions call
LuisDuarte1 Jul 26, 2023
95dc712
fix tests
LuisDuarte1 Jul 26, 2023
6dcb39e
Add role update endpoint
LuisDuarte1 Aug 16, 2023
5781aca
Merge remote-tracking branch 'origin/develop' into feature/roles
LuisDuarte1 Aug 16, 2023
cbac9ff
update tests because of merge
LuisDuarte1 Aug 16, 2023
100836c
make updating logic simpler
LuisDuarte1 Aug 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package pt.up.fe.ni.website.backend.controller

import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import pt.up.fe.ni.website.backend.dto.entity.RoleDto
import pt.up.fe.ni.website.backend.dto.permissions.PermissionsDto
import pt.up.fe.ni.website.backend.dto.permissions.UserIdDto
import pt.up.fe.ni.website.backend.model.permissions.Permissions
import pt.up.fe.ni.website.backend.service.RoleService

@RestController
@RequestMapping("/roles")
class RoleController(private val roleService: RoleService) {
@GetMapping
fun getAllRoles() = roleService.getAllRoles()

@GetMapping("/{id}")
fun getRole(@PathVariable id: Long) = roleService.getRole(id)

@PostMapping
fun createNewRole(@RequestBody dto: RoleDto) = roleService.createNewRole(dto)

@DeleteMapping("/{id}")
fun deleteRole(@PathVariable id: Long) = roleService.deleteRole(id)

@PostMapping("/{id}/permissions")
fun grantPermissionToRole(
@PathVariable id: Long,
@RequestBody permissionsDto: PermissionsDto
): Map<String, String> {
roleService.grantPermissionToRole(id, permissionsDto.permissions)
return emptyMap()
}

@DeleteMapping("/{id}/permissions")
fun revokePermissionFromRole(
@PathVariable id: Long,
@RequestBody permissionsDto: PermissionsDto
): Map<String, String> {
roleService.revokePermissionFromRole(id, permissionsDto.permissions)
return emptyMap()
}

@PostMapping("/{id}/users")
fun addUserToRole(@PathVariable id: Long, @RequestBody userIdDto: UserIdDto): Map<String, String> {
roleService.addUserToRole(id, userIdDto.userId)
return emptyMap()
}

@DeleteMapping("/{id}/users")
fun removeUserFromRole(@PathVariable id: Long, @RequestBody userIdDto: UserIdDto): Map<String, String> {
roleService.removeUserFromRole(id, userIdDto.userId)
return emptyMap()
}

@PostMapping("/{id}/activities/{activityId}/permissions")
fun addPermissionToPerActivityRole(
@PathVariable id: Long,
@PathVariable activityId: Long,
@RequestBody permissionsDto: PermissionsDto
): Map<String, String> {
roleService.grantPermissionToRoleOnActivity(
id,
activityId,
Permissions(permissionsDto.permissions)
)
return emptyMap()
}

@DeleteMapping("/{id}/activities/{activityId}/permissions")
fun revokePermissionFromPerActivityRole(
@PathVariable id: Long,
@PathVariable activityId: Long,
@RequestBody permissionsDto: PermissionsDto
): Map<String, String> {
roleService.revokePermissionFromRoleOnActivity(
id,
activityId,
Permissions(permissionsDto.permissions)
)
return emptyMap()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pt.up.fe.ni.website.backend.dto.permissions

import pt.up.fe.ni.website.backend.model.permissions.Permissions

data class PermissionsDto(
val permissions: Permissions
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package pt.up.fe.ni.website.backend.dto.permissions

data class UserIdDto(
val userId: Long
)
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import jakarta.persistence.FetchType
import jakarta.persistence.GeneratedValue
import jakarta.persistence.Id
import jakarta.persistence.OneToMany
import jakarta.persistence.OrderColumn
import jakarta.validation.Valid
import pt.up.fe.ni.website.backend.utils.validation.NoDuplicateRoles
import pt.up.fe.ni.website.backend.utils.validation.SchoolYear
Expand All @@ -25,7 +24,6 @@ class Generation(
val id: Long? = null
) {
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, mappedBy = "generation")
@OrderColumn
@JsonManagedReference
@field:NoDuplicateRoles
val roles: MutableList<@Valid Role> = mutableListOf()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ import org.springframework.stereotype.Repository
import pt.up.fe.ni.website.backend.model.Role

@Repository
interface RoleRepository : CrudRepository<Role, Long>
interface RoleRepository : CrudRepository<Role, Long> {
fun findByName(name: String): Role?
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ object ErrorMessages {

const val generationAlreadyExists = "generation already exists"

const val roleAlreadyExists = "role already exists"

fun postNotFound(postId: Long): String = "post not found with id $postId"

fun postNotFound(postSlug: String): String = "post not found with slug $postSlug"
Expand All @@ -38,4 +40,10 @@ object ErrorMessages {
fun generationNotFound(year: String): String = "generation not found with year $year"

fun emailNotFound(email: String): String = "account not found with email $email"

fun roleNotFound(id: Long): String = "role not found with id $id"

fun userAlreadyHasRole(roleId: Long, userId: Long): String = "user $userId already has role $roleId"

fun userNotInRole(roleId: Long, userId: Long): String = "user $userId doesn't have role $roleId"
}
101 changes: 84 additions & 17 deletions src/main/kotlin/pt/up/fe/ni/website/backend/service/RoleService.kt
Original file line number Diff line number Diff line change
@@ -1,46 +1,113 @@
package pt.up.fe.ni.website.backend.service

import jakarta.transaction.Transactional
import org.springframework.stereotype.Service
import pt.up.fe.ni.website.backend.model.Activity
import pt.up.fe.ni.website.backend.dto.entity.RoleDto
import pt.up.fe.ni.website.backend.model.PerActivityRole
import pt.up.fe.ni.website.backend.model.Role
import pt.up.fe.ni.website.backend.model.permissions.Permission
import pt.up.fe.ni.website.backend.model.permissions.Permissions
import pt.up.fe.ni.website.backend.repository.GenerationRepository
import pt.up.fe.ni.website.backend.repository.PerActivityRoleRepository
import pt.up.fe.ni.website.backend.repository.RoleRepository
import pt.up.fe.ni.website.backend.service.activity.ActivityService

@Service
@Transactional
class RoleService(
private val roleRepository: RoleRepository,
private val perActivityRoleRepository: PerActivityRoleRepository
private val perActivityRoleRepository: PerActivityRoleRepository,
private val generationRepository: GenerationRepository,
private val accountService: AccountService,
private val activityService: ActivityService
) {

fun grantPermissionToRole(role: Role, permission: Permission) {
role.permissions.add(permission)
fun getRole(roleId: Long): Role {
val role = roleRepository.findById(roleId).orElseThrow {
throw NoSuchElementException(ErrorMessages.roleNotFound(roleId))
}
return role
}
fun getAllRoles(): List<Role> = roleRepository.findAll().toList()
fun grantPermissionToRole(roleId: Long, permissions: Permissions) {
val role = getRole(roleId)
role.permissions.addAll(permissions)
roleRepository.save(role)
}

fun revokePermissionFromRole(role: Role, permission: Permission) {
role.permissions.remove(permission)
fun revokePermissionFromRole(roleId: Long, permissions: Permissions) {
val role = getRole(roleId)
role.permissions.removeAll(permissions)
roleRepository.save(role)
}

fun grantPermissionToRoleOnActivity(role: Role, activity: Activity, permission: Permission) {
val foundActivity = activity.associatedRoles
fun grantPermissionToRoleOnActivity(roleId: Long, activityId: Long, permissions: Permissions) {
val activity = activityService.getActivityById(activityId)
val role = getRole(roleId)
val foundPerActivityRole = activity.associatedRoles
.find { it.activity == activity } ?: PerActivityRole(Permissions())
foundActivity.role = role
foundActivity.activity = activity
foundPerActivityRole.role = role
foundPerActivityRole.activity = activity

foundActivity.role = role
foundActivity.permissions.add(permission)
perActivityRoleRepository.save(foundActivity)
foundPerActivityRole.permissions.addAll(permissions)
perActivityRoleRepository.save(foundPerActivityRole)
if (activity.associatedRoles.find { it.activity == activity } == null) {
role.associatedActivities.add(foundPerActivityRole)
}
// activityRepository.save(activity)
roleRepository.save(role)
}

fun revokePermissionFromRoleOnActivity(role: Role, activity: Activity, permission: Permission) {
fun revokePermissionFromRoleOnActivity(roleId: Long, activityId: Long, permissions: Permissions) {
val activity = activityService.getActivityById(activityId)
val role = getRole(roleId)
val foundActivity = activity.associatedRoles
.find { it.role == role } ?: return

foundActivity.permissions.remove(permission)
foundActivity.permissions.removeAll(permissions)
perActivityRoleRepository.save(foundActivity)
// activityRepository.save(activity)
}

fun createNewRole(dto: RoleDto): Role {
if (roleRepository.findByName(dto.name) != null) {
throw IllegalArgumentException(ErrorMessages.roleAlreadyExists)
}
val role = dto.create()

val latestGeneration = generationRepository.findFirstByOrderBySchoolYearDesc()
?: throw IllegalArgumentException(ErrorMessages.noGenerations)

// need to set it from both sides due to testing transaction
role.generation = latestGeneration
roleRepository.save(role)
latestGeneration.roles.add(role)
generationRepository.save(latestGeneration)
return role
}

fun deleteRole(roleId: Long) {
val role = getRole(roleId)
role.generation.roles.remove(role)
generationRepository.save(role.generation)
roleRepository.delete(role)
}

fun addUserToRole(roleId: Long, userId: Long) {
val role = getRole(roleId)
val account = accountService.getAccountById(userId)
role.accounts.find { it.id == account.id }.let {
if (it != null) throw NoSuchElementException(ErrorMessages.userAlreadyHasRole(roleId, userId))
}
role.accounts.add(account)
roleRepository.save(role)
}

fun removeUserFromRole(roleId: Long, userId: Long) {
val role = getRole(roleId)
val account = accountService.getAccountById(userId)
role.accounts.find { it.id == account.id }.let {
if (it == null) throw NoSuchElementException(ErrorMessages.userNotInRole(roleId, userId))
}
role.accounts.remove(account)
roleRepository.save(role)
}
}
Loading