Skip to content

Commit

Permalink
Merge pull request #15 from penguineer/groups
Browse files Browse the repository at this point in the history
Add Group handling
  • Loading branch information
penguineer authored Jul 19, 2024
2 parents aa76713 + 56eb049 commit 68ab851
Show file tree
Hide file tree
Showing 14 changed files with 555 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import com.penguineering.gartenplus.auth.mapping.OidcMappingRepository;
import com.penguineering.gartenplus.auth.user.UserDTO;
import com.penguineering.gartenplus.auth.user.UserEntity;
import com.penguineering.gartenplus.auth.user.UserRepository;
import com.penguineering.gartenplus.auth.user.UserEntityService;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
Expand All @@ -18,13 +18,13 @@
@Service
public class GithubOidcUserService extends DefaultOAuth2UserService {
private final OidcMappingRepository oidcMappingRepository;
private final UserRepository userRepository;
private final UserEntityService userEntityService;

public GithubOidcUserService(
OidcMappingRepository oidcMappingRepository,
UserRepository userRepository) {
UserEntityService userEntityService) {
this.oidcMappingRepository = oidcMappingRepository;
this.userRepository = userRepository;
this.userEntityService = userEntityService;
}

@Override
Expand Down Expand Up @@ -56,7 +56,7 @@ private Optional<UserEntity> findUserByOriginId(OAuth2User origin) {
.map(this::toMappingKey)
.flatMap(oidcMappingRepository::findById)
.map(OidcMapping::getUserId)
.flatMap(userRepository::findById);
.flatMap(userEntityService::getUser);
}

private UserDTO createUser(OAuth2User origin) {
Expand All @@ -73,7 +73,7 @@ private UserDTO createUser(OAuth2User origin) {
Optional.ofNullable(origin_avatar_url)
.map(URI::create)
.ifPresent(userEntity::setAvatarUrl);
UserEntity savedUserEntity = userRepository.save(userEntity);
UserEntity savedUserEntity = userEntityService.save(userEntity);

OidcMapping oidcMapping = new OidcMapping();
oidcMapping.setIssuer("github");
Expand All @@ -96,6 +96,6 @@ private UserEntity updateUserFromOIDC(OAuth2User origin, UserEntity user) {
.map(URI::create)
.orElse(null));

return userRepository.save(user);
return userEntityService.save(user);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.penguineering.gartenplus.auth.group;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.UUID;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record GroupDTO(
@JsonProperty("id") UUID id,
@JsonProperty("name") String name,
@JsonProperty("description") String description
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.penguineering.gartenplus.auth.group;

import com.penguineering.gartenplus.auth.user.UserEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.ManyToMany;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.jpa.domain.AbstractPersistable;

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

@Getter
@Setter
@Entity(name = "systemgroup")
public class GroupEntity extends AbstractPersistable<UUID> {
String name;

String description;

@ManyToMany(
mappedBy = "groups",
fetch = jakarta.persistence.FetchType.LAZY
)
List<UserEntity> users;

public GroupDTO toDTO() {
return new GroupDTO(getId(), name, description);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.penguineering.gartenplus.auth.group;

import org.springframework.data.repository.CrudRepository;

import java.util.Optional;
import java.util.UUID;

public interface GroupRepository extends CrudRepository<GroupEntity, UUID> {
Optional<GroupEntity> findByName(String name);
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.penguineering.gartenplus.auth.user;

import com.penguineering.gartenplus.auth.group.GroupEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.ManyToMany;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.jpa.domain.AbstractPersistable;

import java.net.URI;
import java.util.List;
import java.util.UUID;

@Getter
Expand All @@ -18,6 +21,11 @@ public class UserEntity extends AbstractPersistable<UUID> {

URI avatarUrl;

@ManyToMany(
fetch = jakarta.persistence.FetchType.LAZY
)
List<GroupEntity> groups;

public static UserEntity fromDTO(UserDTO userDTO) {
UserEntity userEntity = new UserEntity();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.penguineering.gartenplus.auth.user;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import org.hibernate.Hibernate;
import org.springframework.stereotype.Service;

import java.util.Optional;
import java.util.UUID;

@Service
public class UserEntityService {
private final UserRepository userRepository;

@PersistenceContext
private EntityManager entityManager;

public UserEntityService(UserRepository userRepository) {
this.userRepository = userRepository;
}

@Transactional
public Optional<UserEntity> getUser(UUID userId) {
return Optional
.ofNullable(userId)
.flatMap(userRepository::findById);
}

@Transactional
public Optional<UserEntity> getUserWithGroups(UUID userId) {
return userRepository
.findById(userId)
.map(u -> {
Hibernate.initialize(u.getGroups());
return u;
});
}

@Transactional
public UserEntity save(UserEntity user) {
return userRepository.save(user);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ public LoggedUserView(Supplier<UserDTO> currentUser) {

subMenu.addItem(createMenuItemWithIcon("Profil", VaadinIcon.USER),
e -> navigateTo("/admin/profile"));
//subMenu.addSeparator();
//subMenu.addSeparator();
subMenu.addSeparator();
subMenu.addItem(createMenuItemWithIcon("Einstellungen", VaadinIcon.COG),
e -> navigateTo("/admin/settings"));
subMenu.addSeparator();
subMenu.addItem(createMenuItemWithIcon("Abmelden", VaadinIcon.SIGN_OUT),
e -> navigateTo("/admin/logout"));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import com.vaadin.flow.router.ParentLayout;
import com.vaadin.flow.router.RoutePrefix;
import com.vaadin.flow.router.RouterLayout;
import jakarta.annotation.security.PermitAll;

@ParentLayout(AppFrameLayout.class)
@RoutePrefix(value = "admin")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.penguineering.gartenplus.ui.content.admin;

import com.penguineering.gartenplus.auth.group.GroupEntity;
import com.penguineering.gartenplus.auth.user.UserDTO;
import com.penguineering.gartenplus.auth.user.UserEntity;
import com.penguineering.gartenplus.auth.user.UserEntityService;
import com.penguineering.gartenplus.ui.appframe.GartenplusPage;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.H2;
Expand All @@ -20,8 +23,18 @@
@PermitAll
@PageTitle("GartenPlus | Benutzerprofil")
public class ProfilePage extends GartenplusPage {
public ProfilePage(@Qualifier("user") Supplier<UserDTO> currentUser) {
Optional<UserDTO> userOpt = Optional.of(currentUser).map(Supplier::get);
public ProfilePage(@Qualifier("user") Supplier<UserDTO> currentUser,
UserEntityService userEntityService) {
// reload user from database with groups

Optional<UserEntity> userEntityOpt =
Optional.of(currentUser)
.map(Supplier::get)
.map(UserDTO::id)
.flatMap(userEntityService::getUserWithGroups);
Optional<UserDTO> userOpt = userEntityOpt
.map(UserEntity::toDTO);


add(new H2("Benutzerprofil"));
add(new Paragraph("Folgende Daten wurden vom OIDC-Provider übernommen:"));
Expand All @@ -46,5 +59,14 @@ public ProfilePage(@Qualifier("user") Supplier<UserDTO> currentUser) {
add(new Paragraph("Der Avatar wird oben rechts angezeigt."));

add(new Paragraph("Die Daten können beim OIDC-Provider geändert werden."));

var groupString = userEntityOpt
.map(UserEntity::getGroups)
.flatMap(l -> l.stream()
.map(GroupEntity::getName)
.reduce((a, b) -> a + ", " + b))
.map(s -> "Du bist in folgenden Gruppen: " + s)
.orElse("Du bist in keinen Gruppen");
add(new Paragraph(groupString));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.penguineering.gartenplus.ui.content.admin.settings;

import com.penguineering.gartenplus.ui.content.admin.AdminLayout;
import com.penguineering.gartenplus.ui.content.admin.settings.groups.GroupSettingsPage;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.HasElement;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.tabs.Tab;
import com.vaadin.flow.component.tabs.Tabs;
import com.vaadin.flow.router.*;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;

@ParentLayout(AdminLayout.class)
@RoutePrefix(value = "settings")
public class SettingsLayout extends VerticalLayout implements RouterLayout, BeforeEnterObserver {
private static final Map<String, Class<? extends Component>> targets = new LinkedHashMap<>();
static {
targets.put("Gruppen", GroupSettingsPage.class);
}

private final Div content;
private final Tabs menu;

public SettingsLayout() {
super();

menu = new Tabs();
targets.keySet().stream()
.map(Tab::new)
.forEach(menu::add);
menu.addSelectedChangeListener(this::navigateToTab);
add(menu);

content = new Div();
content.setSizeFull();
content.getStyle()
.set("margin", "0")
.set("padding", "0");
add(content);
}

private void navigateToTab(Tabs.SelectedChangeEvent event) {
Optional.of(event.getSelectedTab())
.map(Tab::getLabel)
.map(targets::get)
.ifPresent(target -> UI.getCurrent().navigate(target));
}

@Override
public void beforeEnter(BeforeEnterEvent event) {
String currentPath = event.getLocation().getPath();
targets.forEach((label, target) -> {
Route route = target.getAnnotation(Route.class);
if (route != null && currentPath.contains(route.value())) {
menu.setSelectedTab(menu.getChildren()
.filter(component -> component instanceof Tab)
.map(component -> (Tab) component)
.filter(tab -> tab.getLabel().equals(label))
.findFirst()
.orElse(null));
}
});
}

@Override
public void showRouterLayoutContent(HasElement newContent) {
// Previous content is automatically removed

newContent.getElement()
.getComponent()
.ifPresent(content::add);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.penguineering.gartenplus.ui.content.admin.settings;

import com.penguineering.gartenplus.ui.appframe.GartenplusPage;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import jakarta.annotation.security.PermitAll;

@Route(value = "", layout = SettingsLayout.class)
@PermitAll
@PageTitle("GartenPlus | Einstellungen")
public class SettingsPage extends GartenplusPage implements BeforeEnterObserver {
@Override
public void beforeEnter(BeforeEnterEvent event) {
event.getUI().navigate(event.getLocation().getPath() + "/groups");
}
}
Loading

0 comments on commit 68ab851

Please sign in to comment.