From f74a7ab4876836e9fefe8ee087dffb191dfb3d25 Mon Sep 17 00:00:00 2001 From: Jan-Peter Klein Date: Tue, 18 Feb 2025 00:35:04 +0100 Subject: [PATCH 1/3] add memberCount to GroupDTO and displayed it in SearchInputGroup and VaultDetails --- .../org/cryptomator/hub/api/AuthorityDto.java | 2 +- .../hub/api/AuthorityResource.java | 5 +- .../org/cryptomator/hub/api/GroupDto.java | 19 +++-- .../cryptomator/hub/api/GroupsResource.java | 20 ++++- .../org/cryptomator/hub/entities/Group.java | 11 +++ frontend/src/common/backend.ts | 12 ++- frontend/src/components/SearchInputGroup.vue | 25 +++++- frontend/src/components/VaultDetails.vue | 79 +++++++++++++++++-- frontend/src/i18n/en-US.json | 1 + 9 files changed, 154 insertions(+), 20 deletions(-) diff --git a/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java b/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java index cfe468b25..ef4d2a4f8 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java @@ -33,7 +33,7 @@ protected AuthorityDto(String id, Type type, String name, String pictureUrl) { static AuthorityDto fromEntity(Authority a) { return switch (a) { case User u -> UserDto.justPublicInfo(u); - case Group g -> GroupDto.fromEntity(g); + case Group g -> new GroupDto(g.getId(), g.getName(), (int) g.getMemberCount()); default -> throw new IllegalStateException("authority is not of type user or group"); }; } diff --git a/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java b/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java index f33b5c8bd..b94739afe 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java @@ -29,7 +29,10 @@ public class AuthorityResource { @NoCache @Operation(summary = "search authority by name") public List search(@QueryParam("query") @NotBlank String query) { - return authorityRepo.byName(query).map(AuthorityDto::fromEntity).toList(); + List authorities = authorityRepo.byName(query).toList(); + return authorities.stream() + .map(AuthorityDto::fromEntity) + .toList(); } @GET diff --git a/backend/src/main/java/org/cryptomator/hub/api/GroupDto.java b/backend/src/main/java/org/cryptomator/hub/api/GroupDto.java index 2f1620ab9..4e2c896b9 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/GroupDto.java +++ b/backend/src/main/java/org/cryptomator/hub/api/GroupDto.java @@ -5,12 +5,19 @@ public final class GroupDto extends AuthorityDto { - GroupDto(@JsonProperty("id") String id, @JsonProperty("name") String name) { - super(id, Type.GROUP, name, null); - } + private final int memberCount; - public static GroupDto fromEntity(Group group) { - return new GroupDto(group.getId(), group.getName()); - } + GroupDto(@JsonProperty("id") String id, @JsonProperty("name") String name, @JsonProperty("memberCount") int memberCount) { + super(id, Type.GROUP, name, null); + this.memberCount = memberCount; + } + @JsonProperty("memberCount") + public int getMemberCount() { + return memberCount; + } + + public static GroupDto fromEntity(Group group, int memberCount) { + return new GroupDto(group.getId(), group.getName(), memberCount); + } } diff --git a/backend/src/main/java/org/cryptomator/hub/api/GroupsResource.java b/backend/src/main/java/org/cryptomator/hub/api/GroupsResource.java index 78db054a5..b9b066629 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/GroupsResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/GroupsResource.java @@ -7,11 +7,15 @@ import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; + import org.cryptomator.hub.entities.Group; import org.cryptomator.hub.entities.User; import org.cryptomator.hub.validation.ValidId; import org.eclipse.microprofile.openapi.annotations.Operation; +import jakarta.ws.rs.core.Response; +import java.util.Map; +import java.util.HashMap; import java.util.List; @Path("/groups") @@ -28,7 +32,21 @@ public class GroupsResource { @Produces(MediaType.APPLICATION_JSON) @Operation(summary = "list all groups") public List getAll() { - return groupRepo.findAll().stream().map(GroupDto::fromEntity).toList(); + List groups = groupRepo.findAll().list(); + return groups.stream().map(group -> { + long memberCount = groupRepo.countMembers(group.getId()); + return new GroupDto(group.getId(), group.getName(), (int) memberCount); + }).toList(); + } + + @GET + @Path("/{groupId}/memberCount") + @RolesAllowed("user") + @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Get member count of a group") + public Response getMemberCount(@PathParam("groupId") String groupId) { + long count = userRepo.getEffectiveGroupUsers(groupId).count(); + return Response.ok(Map.of("count", count)).build(); } @GET diff --git a/backend/src/main/java/org/cryptomator/hub/entities/Group.java b/backend/src/main/java/org/cryptomator/hub/entities/Group.java index 388b87f27..5ddf81e69 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Group.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Group.java @@ -33,7 +33,18 @@ public void setMembers(Set members) { this.members = members; } + public int getMemberCount() { + return members != null ? members.size() : 0; + } + @ApplicationScoped public static class Repository implements PanacheRepositoryBase { + + public long countMembers(String groupId) { + return getEntityManager() + .createQuery("SELECT COUNT(m) FROM Group g JOIN g.members m WHERE g.id = :groupId", Long.class) + .setParameter("groupId", groupId) + .getSingleResult(); + } } } diff --git a/frontend/src/common/backend.ts b/frontend/src/common/backend.ts index a1be6a8f3..9e52a8be9 100644 --- a/frontend/src/common/backend.ts +++ b/frontend/src/common/backend.ts @@ -84,6 +84,15 @@ export type GroupDto = { id: string; name: string; pictureUrl?: string; + memberCount: number; +} + +class GroupService { + public async getMemberCount(groupId: string): Promise { + return axiosAuth.get<{ count: number }>(`/groups/${groupId}/memberCount`) + .then(response => response.data.count) + .catch(error => rethrowAndConvertIfExpected(error, 404)); + } } export type AuthorityDto = UserDto | GroupDto; @@ -385,7 +394,8 @@ const services = { billing: new BillingService(), version: new VersionService(), license: new LicenseService(), - settings: new SettingsService() + settings: new SettingsService(), + groups: new GroupService() }; export default services; diff --git a/frontend/src/components/SearchInputGroup.vue b/frontend/src/components/SearchInputGroup.vue index b8ede1749..6e59d441d 100644 --- a/frontend/src/components/SearchInputGroup.vue +++ b/frontend/src/components/SearchInputGroup.vue @@ -5,18 +5,32 @@
- - -
+ +
+ {{ selectedItem.name }} + + {{ selectedItem.memberCount }} {{ t('vaultDetails.sharedWith.members') }} + +
+
{{ item.name }} + + {{ item.memberCount }} {{ t('vaultDetails.sharedWith.members') }} +
@@ -38,6 +52,7 @@ import { Combobox, ComboboxInput, ComboboxOption, ComboboxOptions } from '@headl import { UsersIcon, XCircleIcon } from '@heroicons/vue/24/solid'; import { computed, nextTick, ref, shallowRef, watch } from 'vue'; import { debounce } from '../common/util'; +import { useI18n } from 'vue-i18n'; export type Item = { id: string; @@ -45,6 +60,8 @@ export type Item = { pictureUrl?: string; } +const { t } = useI18n({ useScope: 'global' }); + const props = defineProps<{ actionTitle: string onSearch: (query: string) => Promise diff --git a/frontend/src/components/VaultDetails.vue b/frontend/src/components/VaultDetails.vue index 42bc58201..318f959d8 100644 --- a/frontend/src/components/VaultDetails.vue +++ b/frontend/src/components/VaultDetails.vue @@ -46,7 +46,13 @@
-

{{ member.name }}

+
+

{{ member.name }}

+ + {{ groupMemberCounts.get(member.id) }} {{ t('vaultDetails.sharedWith.members') }} + +
{{ t('vaultDetails.sharedWith.badge.owner') }}
@@ -298,10 +304,25 @@ async function fetchData() { async function fetchOwnerData() { try { - (await backend.vaults.getMembers(props.vaultId)).forEach(member => members.value.set(member.id, member)); + const fetchedMembers = await backend.vaults.getMembers(props.vaultId); + for (const member of fetchedMembers) { + if (member.type === "GROUP") { + try { + const res = await backend.groups.getMemberCount(member.id); + const count = typeof res === "number" ? res : (res as { count: number })?.count ?? 0; + members.value.set(member.id, { ...member, memberCount: count }); + } catch (error) { + members.value.set(member.id, { ...member, memberCount: 0 }); + } + } else { + members.value.set(member.id, member); + } + } + await refreshTrusts(); usersRequiringAccessGrant.value = await backend.vaults.getUsersRequiringAccessGrant(props.vaultId); vaultRecoveryRequired.value = false; + const vaultKeyJwe = await backend.vaults.accessToken(props.vaultId, true); vaultKeys.value = await loadVaultKeys(vaultKeyJwe); } catch (error) { @@ -317,6 +338,16 @@ async function fetchOwnerData() { } } +const groupMemberCounts = computed(() => { + const counts = new Map(); + members.value.forEach((member) => { + if (member.type === 'GROUP') { + counts.set(member.id, member.memberCount ?? 0); + } + }); + return counts; +}); + async function loadVaultKeys(vaultKeyJwe: string): Promise { const userKeys = await userdata.decryptUserKeysWithBrowser(); return VaultKeys.decryptWithUserKey(vaultKeyJwe, userKeys.ecdhKeyPair.privateKey); @@ -465,12 +496,48 @@ function refreshVault(updatedVault: VaultDto) { emit('vaultUpdated', updatedVault); } -async function searchAuthority(query: string): Promise { - return (await backend.authorities.search(query)) - .filter(authority => !members.value.has(authority.id)) - .sort((a, b) => a.name.localeCompare(b.name)); +const searchQuery = ref(''); +const searchResults = ref>([]); + +async function onSearch() { + if (!searchQuery.value) { + searchResults.value = []; + return; + } + searchResults.value = await searchAuthority(searchQuery.value); +} + +async function searchAuthority(query: string): Promise<(AuthorityDto & { memberCount?: number })[]> { + const results = await backend.authorities.search(query); + const filtered = results.filter(authority => !members.value.has(authority.id)); + + const enhanced = await Promise.all( + filtered.map(async authority => { + if (authority.type === "GROUP") { + try { + const res = await backend.groups.getMemberCount(authority.id); + const count = typeof res === "number" ? res : (res as { count: number })?.count ?? 0; + return { ...authority, memberCount: count }; + } catch (error) { + return { ...authority, memberCount: 0 }; + } + } + return authority; + }) + ); + return enhanced.sort((a, b) => a.name.localeCompare(b.name)); } +const searchGroupMemberCounts = computed(() => { + const counts = new Map(); + searchResults.value.forEach((result) => { + if (result.type === 'GROUP') { + counts.set(result.id, result.memberCount ?? 0); + } + }); + return counts; +}); + async function updateMemberRole(member: MemberDto, role: VaultRole) { delete onUpdateVaultMembershipError.value[member.id]; try { diff --git a/frontend/src/i18n/en-US.json b/frontend/src/i18n/en-US.json index 5070daa25..d6383522e 100644 --- a/frontend/src/i18n/en-US.json +++ b/frontend/src/i18n/en-US.json @@ -257,6 +257,7 @@ "vaultDetails.sharedWith.title": "Shared with", "vaultDetails.sharedWith.badge.owner": "Owner", "vaultDetails.sharedWith.grantOwnership": "Grant Ownership", + "vaultDetails.sharedWith.members": "Members", "vaultDetails.sharedWith.revokeOwnership": "Revoke Ownership", "vaultDetails.actions.title": "Actions", "vaultDetails.actions.updatePermissions": "Update Permissions", From d5d808a7457117ce07b1e07df6784c0b38e41b83 Mon Sep 17 00:00:00 2001 From: Jan-Peter Klein Date: Tue, 18 Feb 2025 14:57:03 +0100 Subject: [PATCH 2/3] switch to effectiveGroupUsers --- .../org/cryptomator/hub/api/AuthorityDto.java | 7 ++++- .../hub/api/AuthorityResource.java | 28 +++++++++++++++++-- .../cryptomator/hub/api/GroupsResource.java | 15 +--------- .../org/cryptomator/hub/entities/Group.java | 21 +++++++------- frontend/src/common/backend.ts | 6 ++-- frontend/src/components/VaultDetails.vue | 21 +++++++------- 6 files changed, 55 insertions(+), 43 deletions(-) diff --git a/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java b/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java index ef4d2a4f8..09502844f 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java @@ -5,6 +5,8 @@ import org.cryptomator.hub.entities.Group; import org.cryptomator.hub.entities.User; +import jakarta.inject.Inject; + abstract sealed class AuthorityDto permits UserDto, GroupDto, MemberDto { public enum Type { @@ -30,10 +32,13 @@ protected AuthorityDto(String id, Type type, String name, String pictureUrl) { this.pictureUrl = pictureUrl; } + @Inject + static User.Repository userRepo; // Inject User Repository für die neue Berechnung + static AuthorityDto fromEntity(Authority a) { return switch (a) { case User u -> UserDto.justPublicInfo(u); - case Group g -> new GroupDto(g.getId(), g.getName(), (int) g.getMemberCount()); + case Group g -> new GroupDto(g.getId(), g.getName(), (int) userRepo.getEffectiveGroupUsers(g.getId()).count()); default -> throw new IllegalStateException("authority is not of type user or group"); }; } diff --git a/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java b/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java index b94739afe..fc30cb556 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java @@ -9,6 +9,8 @@ import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; import org.cryptomator.hub.entities.Authority; +import org.cryptomator.hub.entities.Group; +import org.cryptomator.hub.entities.User; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; import org.jboss.resteasy.reactive.NoCache; @@ -21,6 +23,9 @@ public class AuthorityResource { @Inject Authority.Repository authorityRepo; + + @Inject + User.Repository userRepo; // UserRepo wird hier für die Gruppen-Mitgliederanzahl benötigt @GET @Path("/search") @@ -31,7 +36,7 @@ public class AuthorityResource { public List search(@QueryParam("query") @NotBlank String query) { List authorities = authorityRepo.byName(query).toList(); return authorities.stream() - .map(AuthorityDto::fromEntity) + .map(this::convertToDto) // Neue Methode für die Konvertierung mit Member-Anzahl .toList(); } @@ -43,7 +48,24 @@ public List search(@QueryParam("query") @NotBlank String query) { @Operation(summary = "lists all authorities matching the given ids", description = "lists for each id in the list its corresponding authority. Ignores all id's where an authority cannot be found") @APIResponse(responseCode = "200") public List getSome(@QueryParam("ids") List authorityIds) { - return authorityRepo.findAllInList(authorityIds).map(AuthorityDto::fromEntity).toList(); + return authorityRepo.findAllInList(authorityIds) + .map(this::convertToDto) // Neue Methode wird hier genutzt + .toList(); + } + + /** + * Konvertiert eine Authority-Entity in das passende DTO, + * inklusive der Gruppen-Mitgliederanzahl für Gruppen. + */ + private AuthorityDto convertToDto(Authority a) { + if (a instanceof User u) { + return UserDto.justPublicInfo(u); + } else if (a instanceof Group g) { + int memberCount = (int) userRepo.getEffectiveGroupUsers(g.getId()).count(); + return new GroupDto(g.getId(), g.getName(), memberCount); + } else { + throw new IllegalStateException("authority is not of type user or group"); + } } -} \ No newline at end of file +} diff --git a/backend/src/main/java/org/cryptomator/hub/api/GroupsResource.java b/backend/src/main/java/org/cryptomator/hub/api/GroupsResource.java index b9b066629..5eeac745d 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/GroupsResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/GroupsResource.java @@ -33,20 +33,7 @@ public class GroupsResource { @Operation(summary = "list all groups") public List getAll() { List groups = groupRepo.findAll().list(); - return groups.stream().map(group -> { - long memberCount = groupRepo.countMembers(group.getId()); - return new GroupDto(group.getId(), group.getName(), (int) memberCount); - }).toList(); - } - - @GET - @Path("/{groupId}/memberCount") - @RolesAllowed("user") - @Produces(MediaType.APPLICATION_JSON) - @Operation(summary = "Get member count of a group") - public Response getMemberCount(@PathParam("groupId") String groupId) { - long count = userRepo.getEffectiveGroupUsers(groupId).count(); - return Response.ok(Map.of("count", count)).build(); + return groups.stream().map(group -> GroupDto.fromEntity(group, groupRepo.countMembers(group.getId()))).toList(); } @GET diff --git a/backend/src/main/java/org/cryptomator/hub/entities/Group.java b/backend/src/main/java/org/cryptomator/hub/entities/Group.java index 5ddf81e69..f9f802c8e 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Group.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Group.java @@ -2,13 +2,8 @@ import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; import jakarta.enterprise.context.ApplicationScoped; -import jakarta.persistence.CascadeType; -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinTable; -import jakarta.persistence.ManyToMany; -import jakarta.persistence.Table; +import jakarta.inject.Inject; +import jakarta.persistence.*; import java.util.HashSet; import java.util.Set; @@ -18,13 +13,16 @@ @DiscriminatorValue("GROUP") public class Group extends Authority { - @ManyToMany(cascade = {CascadeType.REMOVE}) + @ManyToMany(cascade = {CascadeType.REMOVE}, fetch = FetchType.LAZY) @JoinTable(name = "group_membership", joinColumns = @JoinColumn(name = "group_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "member_id", referencedColumnName = "id") ) private Set members = new HashSet<>(); + @Inject + transient Repository groupRepo; + public Set getMembers() { return members; } @@ -33,16 +31,17 @@ public void setMembers(Set members) { this.members = members; } + public int getMemberCount() { - return members != null ? members.size() : 0; + return groupRepo.countMembers(this.getId()); // Verwende das injectete Repository } @ApplicationScoped public static class Repository implements PanacheRepositoryBase { - public long countMembers(String groupId) { + public int countMembers(String groupId) { return getEntityManager() - .createQuery("SELECT COUNT(m) FROM Group g JOIN g.members m WHERE g.id = :groupId", Long.class) + .createQuery("SELECT SIZE(g.members) FROM Group g WHERE g.id = :groupId", Integer.class) .setParameter("groupId", groupId) .getSingleResult(); } diff --git a/frontend/src/common/backend.ts b/frontend/src/common/backend.ts index 9e52a8be9..2bd62a37c 100644 --- a/frontend/src/common/backend.ts +++ b/frontend/src/common/backend.ts @@ -89,9 +89,9 @@ export type GroupDto = { class GroupService { public async getMemberCount(groupId: string): Promise { - return axiosAuth.get<{ count: number }>(`/groups/${groupId}/memberCount`) - .then(response => response.data.count) - .catch(error => rethrowAndConvertIfExpected(error, 404)); + return axiosAuth.get(`/groups/${groupId}/effective-members`) + .then(response => response.data.length) + .catch(() => 0); } } diff --git a/frontend/src/components/VaultDetails.vue b/frontend/src/components/VaultDetails.vue index 318f959d8..4a8a35f3d 100644 --- a/frontend/src/components/VaultDetails.vue +++ b/frontend/src/components/VaultDetails.vue @@ -308,8 +308,7 @@ async function fetchOwnerData() { for (const member of fetchedMembers) { if (member.type === "GROUP") { try { - const res = await backend.groups.getMemberCount(member.id); - const count = typeof res === "number" ? res : (res as { count: number })?.count ?? 0; + const count = await backend.groups.getMemberCount(member.id); members.value.set(member.id, { ...member, memberCount: count }); } catch (error) { members.value.set(member.id, { ...member, memberCount: 0 }); @@ -339,15 +338,16 @@ async function fetchOwnerData() { } const groupMemberCounts = computed(() => { - const counts = new Map(); - members.value.forEach((member) => { - if (member.type === 'GROUP') { - counts.set(member.id, member.memberCount ?? 0); - } - }); - return counts; + const counts = new Map(); + members.value.forEach((member) => { + if (member.type === 'GROUP' && 'memberCount' in member) { + counts.set(member.id, member.memberCount); + } + }); + return counts; }); + async function loadVaultKeys(vaultKeyJwe: string): Promise { const userKeys = await userdata.decryptUserKeysWithBrowser(); return VaultKeys.decryptWithUserKey(vaultKeyJwe, userKeys.ecdhKeyPair.privateKey); @@ -515,8 +515,7 @@ async function searchAuthority(query: string): Promise<(AuthorityDto & { memberC filtered.map(async authority => { if (authority.type === "GROUP") { try { - const res = await backend.groups.getMemberCount(authority.id); - const count = typeof res === "number" ? res : (res as { count: number })?.count ?? 0; + const count = await backend.groups.getMemberCount(authority.id); return { ...authority, memberCount: count }; } catch (error) { return { ...authority, memberCount: 0 }; From 588843e07168f86ddb65a86cfdbac86797bca319 Mon Sep 17 00:00:00 2001 From: Jan-Peter Klein Date: Tue, 18 Feb 2025 23:18:19 +0100 Subject: [PATCH 3/3] code cleanup --- .../org/cryptomator/hub/api/AuthorityDto.java | 2 +- .../hub/api/AuthorityResource.java | 10 +++------- .../cryptomator/hub/api/GroupsResource.java | 7 +++---- .../org/cryptomator/hub/entities/Group.java | 6 ++---- frontend/src/components/SearchInputGroup.vue | 2 +- frontend/src/components/VaultDetails.vue | 20 ------------------- 6 files changed, 10 insertions(+), 37 deletions(-) diff --git a/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java b/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java index 09502844f..e09d16e0d 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java @@ -33,7 +33,7 @@ protected AuthorityDto(String id, Type type, String name, String pictureUrl) { } @Inject - static User.Repository userRepo; // Inject User Repository für die neue Berechnung + static User.Repository userRepo; static AuthorityDto fromEntity(Authority a) { return switch (a) { diff --git a/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java b/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java index fc30cb556..2189ac3b9 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java @@ -25,7 +25,7 @@ public class AuthorityResource { Authority.Repository authorityRepo; @Inject - User.Repository userRepo; // UserRepo wird hier für die Gruppen-Mitgliederanzahl benötigt + User.Repository userRepo; @GET @Path("/search") @@ -36,7 +36,7 @@ public class AuthorityResource { public List search(@QueryParam("query") @NotBlank String query) { List authorities = authorityRepo.byName(query).toList(); return authorities.stream() - .map(this::convertToDto) // Neue Methode für die Konvertierung mit Member-Anzahl + .map(this::convertToDto) .toList(); } @@ -49,14 +49,10 @@ public List search(@QueryParam("query") @NotBlank String query) { @APIResponse(responseCode = "200") public List getSome(@QueryParam("ids") List authorityIds) { return authorityRepo.findAllInList(authorityIds) - .map(this::convertToDto) // Neue Methode wird hier genutzt + .map(this::convertToDto) .toList(); } - /** - * Konvertiert eine Authority-Entity in das passende DTO, - * inklusive der Gruppen-Mitgliederanzahl für Gruppen. - */ private AuthorityDto convertToDto(Authority a) { if (a instanceof User u) { return UserDto.justPublicInfo(u); diff --git a/backend/src/main/java/org/cryptomator/hub/api/GroupsResource.java b/backend/src/main/java/org/cryptomator/hub/api/GroupsResource.java index 5eeac745d..ccaa32e7c 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/GroupsResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/GroupsResource.java @@ -2,21 +2,20 @@ import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; +import jakarta.ws.rs.core.Response; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; - import org.cryptomator.hub.entities.Group; import org.cryptomator.hub.entities.User; import org.cryptomator.hub.validation.ValidId; import org.eclipse.microprofile.openapi.annotations.Operation; -import jakarta.ws.rs.core.Response; -import java.util.Map; -import java.util.HashMap; import java.util.List; +import java.util.HashMap; +import java.util.Map; @Path("/groups") public class GroupsResource { diff --git a/backend/src/main/java/org/cryptomator/hub/entities/Group.java b/backend/src/main/java/org/cryptomator/hub/entities/Group.java index f9f802c8e..87dc88b48 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Group.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Group.java @@ -13,7 +13,7 @@ @DiscriminatorValue("GROUP") public class Group extends Authority { - @ManyToMany(cascade = {CascadeType.REMOVE}, fetch = FetchType.LAZY) + @ManyToMany(cascade = {CascadeType.REMOVE}) @JoinTable(name = "group_membership", joinColumns = @JoinColumn(name = "group_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "member_id", referencedColumnName = "id") @@ -31,14 +31,12 @@ public void setMembers(Set members) { this.members = members; } - public int getMemberCount() { - return groupRepo.countMembers(this.getId()); // Verwende das injectete Repository + return groupRepo.countMembers(this.getId()); } @ApplicationScoped public static class Repository implements PanacheRepositoryBase { - public int countMembers(String groupId) { return getEntityManager() .createQuery("SELECT SIZE(g.members) FROM Group g WHERE g.id = :groupId", Integer.class) diff --git a/frontend/src/components/SearchInputGroup.vue b/frontend/src/components/SearchInputGroup.vue index 6e59d441d..5984a1a44 100644 --- a/frontend/src/components/SearchInputGroup.vue +++ b/frontend/src/components/SearchInputGroup.vue @@ -5,7 +5,7 @@
{ return counts; }); - async function loadVaultKeys(vaultKeyJwe: string): Promise { const userKeys = await userdata.decryptUserKeysWithBrowser(); return VaultKeys.decryptWithUserKey(vaultKeyJwe, userKeys.ecdhKeyPair.privateKey); @@ -496,17 +495,8 @@ function refreshVault(updatedVault: VaultDto) { emit('vaultUpdated', updatedVault); } -const searchQuery = ref(''); const searchResults = ref>([]); -async function onSearch() { - if (!searchQuery.value) { - searchResults.value = []; - return; - } - searchResults.value = await searchAuthority(searchQuery.value); -} - async function searchAuthority(query: string): Promise<(AuthorityDto & { memberCount?: number })[]> { const results = await backend.authorities.search(query); const filtered = results.filter(authority => !members.value.has(authority.id)); @@ -527,16 +517,6 @@ async function searchAuthority(query: string): Promise<(AuthorityDto & { memberC return enhanced.sort((a, b) => a.name.localeCompare(b.name)); } -const searchGroupMemberCounts = computed(() => { - const counts = new Map(); - searchResults.value.forEach((result) => { - if (result.type === 'GROUP') { - counts.set(result.id, result.memberCount ?? 0); - } - }); - return counts; -}); - async function updateMemberRole(member: MemberDto, role: VaultRole) { delete onUpdateVaultMembershipError.value[member.id]; try {