Skip to content

Commit

Permalink
Merge pull request #18 from penguineer/role
Browse files Browse the repository at this point in the history
Add system roles
  • Loading branch information
penguineer authored Jul 20, 2024
2 parents 5f25395 + ac7a1cc commit 7e0a6e2
Show file tree
Hide file tree
Showing 9 changed files with 248 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.penguineering.gartenplus.auth.role;

import jakarta.persistence.*;
import lombok.*;

import java.util.UUID;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = {"roleHandle", "userId"})
@IdClass(RoleMappingKey.class)
@Table(name = "role_mapping")
public class RoleMapping {
@Id
@Column(name = "role_handle")
private String roleHandle;

@Id
@Column(name = "user_id")
private UUID userId;

@Override
public String toString() {
return roleHandle + "/" + userId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.penguineering.gartenplus.auth.role;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.web.server.WebSession;

import java.io.Serializable;
import java.util.Optional;
import java.util.UUID;

@Getter
@Setter
@NoArgsConstructor
@EqualsAndHashCode
public class RoleMappingKey implements Serializable {
private String roleHandle;
private UUID userId;

public static RoleMappingKey of(String roleHandle, UUID userId) {
return new RoleMappingKey(roleHandle, userId);
}

public RoleMappingKey(String roleHandle, UUID userId) {
this.roleHandle = roleHandle;
this.userId = userId;
}

@Override
public String toString() {
return roleHandle + "/" + userId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.penguineering.gartenplus.auth.role;

import org.springframework.data.repository.CrudRepository;

import java.util.List;
import java.util.UUID;

public interface RoleMappingRepository extends CrudRepository<RoleMapping, RoleMappingKey> {
List<RoleMapping> findByUserId(UUID userId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.penguineering.gartenplus.auth.role;

import lombok.Getter;

@Getter
public enum SystemRole {
ADMINISTRATOR("administrator", "Administrator"),
MEMBER("member", "Gartenfreund"),
FRIEND("friend", "Gartenfreund-Freund");

private final String handle;
private final String displayName;

SystemRole(String handle, String displayName) {
this.handle = handle;
this.displayName = displayName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.penguineering.gartenplus.auth.role;

import com.penguineering.gartenplus.auth.user.UserRepository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SystemRoleService {
private static final Map<String, SystemRole> HANDLES;

static {
HANDLES = Arrays.stream(SystemRole.values())
.map(role -> Map.entry(role.getHandle(), role))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

private final RoleMappingRepository roleMappingRepository;
private final UserRepository userRepository;

@PersistenceContext
private EntityManager entityManager;

public SystemRoleService(
RoleMappingRepository roleMappingRepository,
UserRepository userRepository) {
this.roleMappingRepository = roleMappingRepository;
this.userRepository = userRepository;
}

@Transactional
public Set<SystemRole> getRolesForUser(UUID userId) {
return Optional.ofNullable(userId)
.filter(userRepository::existsById)
.map(roleMappingRepository::findByUserId)
.map(l -> l.stream()
.map(RoleMapping::getRoleHandle)
// ignore unknown handles
.filter(HANDLES.keySet()::contains)
.map(HANDLES::get)
.collect(Collectors.toSet()))
.orElse(Set.of());
}

@Transactional
public void setRoleForUser(SystemRole role, UUID userId) {
var mapping = new RoleMapping(role.getHandle(), userId);
roleMappingRepository.save(mapping);
}

@Transactional
public void clearRoleForUser(SystemRole role, UUID userId) {
var mapping = new RoleMapping(role.getHandle(), userId);
roleMappingRepository.delete(mapping);
}

@Transactional
public void updateRoles(UUID userId, Set<SystemRole> roles) {
var currentRoles = getRolesForUser(userId);

var toAdd = new HashSet<>(roles);
toAdd.removeAll(currentRoles);
toAdd.forEach(role -> setRoleForUser(role, userId));

var toRemove = new HashSet<>(currentRoles);
toRemove.removeAll(roles);
toRemove.forEach(role -> clearRoleForUser(role, userId));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.StreamSupport;
Expand Down Expand Up @@ -50,4 +51,27 @@ public Optional<UserEntity> getUserWithGroups(UUID userId) {
public UserEntity save(UserEntity user) {
return userRepository.save(user);
}

@Transactional
public UserDTO saveDTO(UserDTO user) {
return Optional.ofNullable(user)
.map(UserDTO::id)
//load or create entity
.flatMap(id -> Optional.of(id)
.map(userRepository::findById)
.filter(Optional::isPresent)
.map(Optional::get)
.or(() -> Optional.of(new UserEntity())))
// update entity
.map(e -> {
e.setDisplayName(user.displayName());
e.setEmail(user.email());
e.setAvatarUrl(user.avatarUrl());
return e;
})
// save entity
.map(userRepository::save)
.map(UserEntity::toDTO)
.orElse(null);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.penguineering.gartenplus.ui.content.admin;

import com.penguineering.gartenplus.auth.group.GroupEntity;
import com.penguineering.gartenplus.auth.role.SystemRole;
import com.penguineering.gartenplus.auth.role.SystemRoleService;
import com.penguineering.gartenplus.auth.user.UserDTO;
import com.penguineering.gartenplus.auth.user.UserEntity;
import com.penguineering.gartenplus.auth.user.UserEntityService;
Expand All @@ -24,7 +26,8 @@
@PageTitle("GartenPlus | Benutzerprofil")
public class ProfilePage extends GartenplusPage {
public ProfilePage(@Qualifier("user") Supplier<UserDTO> currentUser,
UserEntityService userEntityService) {
UserEntityService userEntityService,
SystemRoleService systemRoleService) {
// reload user from database with groups

Optional<UserEntity> userEntityOpt =
Expand Down Expand Up @@ -68,5 +71,15 @@ public ProfilePage(@Qualifier("user") Supplier<UserDTO> currentUser,
.map(s -> "Du bist in folgenden Gruppen: " + s)
.orElse("Du bist in keinen Gruppen");
add(new Paragraph(groupString));

var roleString = userEntityOpt
.map(UserEntity::getId)
.map(systemRoleService::getRolesForUser)
.flatMap(l -> l.stream()
.map(SystemRole::getDisplayName)
.reduce((a, b) -> a + ", " + b))
.map(s -> "Du hast folgende Rollen: " + s)
.orElse("Du hast keine Rollen");
add(new Paragraph(roleString));
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.penguineering.gartenplus.ui.content.admin.settings.users;

import com.penguineering.gartenplus.auth.role.SystemRole;
import com.penguineering.gartenplus.auth.user.UserDTO;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.checkbox.CheckboxGroup;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
Expand All @@ -12,19 +14,20 @@
import lombok.Setter;

import java.net.URI;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.*;
import java.util.function.BiConsumer;

public class UserEditor extends VerticalLayout {
private final Consumer<UserDTO> saveAction;
private final BiConsumer<UserDTO, Set<SystemRole>> saveAction;
private final Runnable cancelAction;

private final Binder<UserBean> binder = new Binder<>(UserBean.class);
private UserBean user;

private final CheckboxGroup<SystemRole> rolesCheckgroup;

public UserEditor(
Consumer<UserDTO> saveAction,
BiConsumer<UserDTO, Set<SystemRole>> saveAction,
Runnable cancelAction
) {
this.saveAction = saveAction;
Expand All @@ -51,7 +54,14 @@ public UserEditor(
TextField avatarUrl = new TextField("Avatar-URL");
avatarUrl.setWidthFull();

fieldsLayout.add(id, displayName, email, avatarUrl);
rolesCheckgroup = new CheckboxGroup<>();
rolesCheckgroup.setLabel("System-Rollen");
rolesCheckgroup.setItems(SystemRole.values());
rolesCheckgroup.setItemLabelGenerator(SystemRole::getDisplayName);
rolesCheckgroup.addValueChangeListener(e -> user.setRoles(e.getValue()));
rolesCheckgroup.setReadOnly(Objects.isNull(saveAction));

fieldsLayout.add(id, displayName, email, avatarUrl, rolesCheckgroup);

HorizontalLayout buttonsLayout = new HorizontalLayout();
buttonsLayout.setWidthFull();
Expand Down Expand Up @@ -79,16 +89,21 @@ public UserEditor(
.bind(UserBean::getDisplayName, null);
binder.forField(email).bind(UserBean::getEmail, null);
binder.forField(avatarUrl).bind(UserBean::getAvatarUrl, null);
binder.forField(rolesCheckgroup).bind(UserBean::getRoles, UserBean::setRoles);

setUser(null);
setUser(null, null);
}

public void setUser(UserDTO userDTO) {
public void setUser(UserDTO userDTO, Set<SystemRole> roles) {
this.user = Objects.isNull(userDTO)
? new UserBean(null, "", "", "")
: new UserBean(userDTO.id().toString(), userDTO.displayName(), userDTO.email(), userDTO.avatarUrl().toASCIIString());
this.user.setRoles(
new HashSet<>(
Objects.requireNonNullElse(roles, Set.of())));

binder.readBean(user);
rolesCheckgroup.setValue(this.user.getRoles());
}

private void save() {
Expand All @@ -99,7 +114,7 @@ private void save() {
user.getId() == null ? null : UUID.fromString(user.getId()),
user.getDisplayName(), user.getEmail(),
user.getAvatarUrl() == null ? null : URI.create(user.getAvatarUrl()));
this.saveAction.accept(newUser);
this.saveAction.accept(newUser, user.getRoles());
}
} catch (ValidationException e) {
// Handle validation errors
Expand All @@ -119,12 +134,14 @@ private static class UserBean {
private String displayName;
private String email;
private String avatarUrl;
private Set<SystemRole> roles;

public UserBean(String id, String displayName, String email, String avatarUrl) {
this.id = id;
this.displayName = displayName;
this.email = email;
this.avatarUrl = avatarUrl;
this.roles = new HashSet<>();
}
}
}
Loading

0 comments on commit 7e0a6e2

Please sign in to comment.