From d8531f09a7bf9564deb9dd75726649e8fa2b8579 Mon Sep 17 00:00:00 2001 From: Han <56923450+waddaboo@users.noreply.github.com> Date: Wed, 23 Oct 2024 19:10:43 +0800 Subject: [PATCH 01/24] feat(api): add get groups query by type --- apps/api/src/app/groups/groups.controller.ts | 11 +++++++++-- .../api/src/app/groups/groups.service.test.ts | 19 +++++++++++++++++++ apps/api/src/app/groups/groups.service.ts | 10 +++++++++- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/apps/api/src/app/groups/groups.controller.ts b/apps/api/src/app/groups/groups.controller.ts index bf1bc24e..c7654253 100644 --- a/apps/api/src/app/groups/groups.controller.ts +++ b/apps/api/src/app/groups/groups.controller.ts @@ -31,6 +31,7 @@ import { UpdateGroupsDto } from "./dto/update-groups.dto" import { GroupsService } from "./groups.service" import { mapGroupToResponseDTO } from "./groups.utils" import { RemoveGroupsDto } from "./dto/remove-groups.dto" +import { GroupType } from "./types" @ApiTags("groups") @Controller("groups") @@ -40,13 +41,19 @@ export class GroupsController { @Get() @ApiQuery({ name: "adminId", required: false, type: String }) @ApiQuery({ name: "memberId", required: false, type: String }) + @ApiQuery({ name: "type", required: false, type: String }) @ApiOperation({ description: "Returns the list of groups." }) @ApiCreatedResponse({ type: Group, isArray: true }) async getGroups( @Query("adminId") adminId: string, - @Query("memberId") memberId: string + @Query("memberId") memberId: string, + @Query("type") type: GroupType ) { - const groups = await this.groupsService.getGroups({ adminId, memberId }) + const groups = await this.groupsService.getGroups({ + adminId, + memberId, + type + }) const groupIds = groups.map((group) => group.id) const fingerprints = await this.groupsService.getFingerprints(groupIds) diff --git a/apps/api/src/app/groups/groups.service.test.ts b/apps/api/src/app/groups/groups.service.test.ts index d3f6e07d..fafc0957 100644 --- a/apps/api/src/app/groups/groups.service.test.ts +++ b/apps/api/src/app/groups/groups.service.test.ts @@ -259,6 +259,25 @@ describe("GroupsService", () => { expect(result).toHaveLength(1) }) + + it("Should return a list of groups by group type", async () => { + await groupsService.createGroup( + { + name: "OnChainGroup", + description: "This is a description", + type: "on-chain", + treeDepth: 16, + fingerprintDuration: 3600 + }, + "admin02" + ) + + const result = await groupsService.getGroups({ + type: "on-chain" + }) + + expect(result).toHaveLength(1) + }) }) describe("# getGroup", () => { diff --git a/apps/api/src/app/groups/groups.service.ts b/apps/api/src/app/groups/groups.service.ts index 72d2e264..581439a0 100644 --- a/apps/api/src/app/groups/groups.service.ts +++ b/apps/api/src/app/groups/groups.service.ts @@ -18,7 +18,7 @@ import { CreateGroupDto } from "./dto/create-group.dto" import { UpdateGroupDto } from "./dto/update-group.dto" import { Group } from "./entities/group.entity" import { Member } from "./entities/member.entity" -import { MerkleProof } from "./types" +import { GroupType, MerkleProof } from "./types" import { getAndCheckAdmin } from "../utils" @Injectable() @@ -816,6 +816,7 @@ export class GroupsService { async getGroups(filters?: { adminId?: string memberId?: string + type?: GroupType }): Promise { let where = {} @@ -834,6 +835,13 @@ export class GroupsService { } } + if (filters?.type) { + where = { + type: filters.type, + ...where + } + } + return this.groupRepository.find({ relations: { members: true }, where, From b4663fc2607e43a28b94fcd80c7d72ea80b6e3d7 Mon Sep 17 00:00:00 2001 From: Han <56923450+waddaboo@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:13:17 +0800 Subject: [PATCH 02/24] feat(api): add getGroups filter by name --- apps/api/src/app/groups/groups.controller.ts | 7 +++++-- apps/api/src/app/groups/groups.service.test.ts | 8 ++++++++ apps/api/src/app/groups/groups.service.ts | 8 ++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/apps/api/src/app/groups/groups.controller.ts b/apps/api/src/app/groups/groups.controller.ts index c7654253..214d46f4 100644 --- a/apps/api/src/app/groups/groups.controller.ts +++ b/apps/api/src/app/groups/groups.controller.ts @@ -42,17 +42,20 @@ export class GroupsController { @ApiQuery({ name: "adminId", required: false, type: String }) @ApiQuery({ name: "memberId", required: false, type: String }) @ApiQuery({ name: "type", required: false, type: String }) + @ApiQuery({ name: "name", required: false, type: String }) @ApiOperation({ description: "Returns the list of groups." }) @ApiCreatedResponse({ type: Group, isArray: true }) async getGroups( @Query("adminId") adminId: string, @Query("memberId") memberId: string, - @Query("type") type: GroupType + @Query("type") type: GroupType, + @Query("name") name: string ) { const groups = await this.groupsService.getGroups({ adminId, memberId, - type + type, + name }) const groupIds = groups.map((group) => group.id) const fingerprints = await this.groupsService.getFingerprints(groupIds) diff --git a/apps/api/src/app/groups/groups.service.test.ts b/apps/api/src/app/groups/groups.service.test.ts index fafc0957..8d668675 100644 --- a/apps/api/src/app/groups/groups.service.test.ts +++ b/apps/api/src/app/groups/groups.service.test.ts @@ -278,6 +278,14 @@ describe("GroupsService", () => { expect(result).toHaveLength(1) }) + + it("Should return a list of groups by group name", async () => { + const result = await groupsService.getGroups({ + name: "OnChainGroup" + }) + + expect(result).toHaveLength(1) + }) }) describe("# getGroup", () => { diff --git a/apps/api/src/app/groups/groups.service.ts b/apps/api/src/app/groups/groups.service.ts index 581439a0..b97656c5 100644 --- a/apps/api/src/app/groups/groups.service.ts +++ b/apps/api/src/app/groups/groups.service.ts @@ -817,6 +817,7 @@ export class GroupsService { adminId?: string memberId?: string type?: GroupType + name?: string }): Promise { let where = {} @@ -842,6 +843,13 @@ export class GroupsService { } } + if (filters?.name) { + where = { + name: filters.name, + ...where + } + } + return this.groupRepository.find({ relations: { members: true }, where, From f059b6275461b322a412963a9107250e6c77531c Mon Sep 17 00:00:00 2001 From: Han <56923450+waddaboo@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:15:00 +0800 Subject: [PATCH 03/24] feat(dashboard): auto create associated group when creating on-chain group --- .../new-group-stepper/final-preview-step.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/apps/dashboard/src/components/new-group-stepper/final-preview-step.tsx b/apps/dashboard/src/components/new-group-stepper/final-preview-step.tsx index 147df4b8..13e22d52 100644 --- a/apps/dashboard/src/components/new-group-stepper/final-preview-step.tsx +++ b/apps/dashboard/src/components/new-group-stepper/final-preview-step.tsx @@ -34,6 +34,23 @@ export default function FinalPreviewStep({ const groupId = groupIdBigNumber.toString() + const description = `This group is associated to the on-chain group ${groupId}` + const response = await bandadaAPI.createGroup( + groupId, + description, + group.type, + 16, + 3600 + ) + + if (response === null) { + setLoading(false) + alert( + "Some error occured when creating the off-chain association group" + ) + return + } + navigate(`/groups/on-chain/${groupId}`) } catch (error) { setLoading(false) From a32e31a27d845820cee7a64a1e5090f5648ecaf8 Mon Sep 17 00:00:00 2001 From: Han <56923450+waddaboo@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:36:44 +0800 Subject: [PATCH 04/24] feat(dashboard): add getGroups filter by type; add getGroupByName --- apps/dashboard/src/api/bandadaAPI.ts | 33 +++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/apps/dashboard/src/api/bandadaAPI.ts b/apps/dashboard/src/api/bandadaAPI.ts index 3ff2f726..9f96db60 100644 --- a/apps/dashboard/src/api/bandadaAPI.ts +++ b/apps/dashboard/src/api/bandadaAPI.ts @@ -40,17 +40,23 @@ export async function generateMagicLink( * @param adminId The admin id. * @returns The list of groups or null. */ -export async function getGroups(adminId?: string): Promise { +export async function getGroups( + adminId?: string, + type?: GroupType +): Promise { try { - const url = adminId - ? `${API_URL}/groups/?adminId=${adminId}` - : `${API_URL}/groups/` + const groupType = type || "off-chain" + let url = `${API_URL}/groups/?type=${groupType}` + + if (adminId) { + url += `&?adminId=${adminId}` + } const groups = await request(url) return groups.map((group: Group) => ({ ...group, - type: "off-chain" + type: groupType })) } catch (error: any) { console.error(error) @@ -59,6 +65,21 @@ export async function getGroups(adminId?: string): Promise { } } +/** + * It returns the list of groups by group name. + * @param name + * @returns The group details. + */ +export async function getGroupByName(name: string): Promise { + try { + return await request(`${API_URL}/groups/?name=${name}`) + } catch (error: any) { + console.error(error) + createAlert(error.response.data.message) + return null + } +} + /** * It returns details of a specific group. * @param groupId The group id. @@ -109,7 +130,7 @@ export async function createGroup( ] }) - return { ...groups.at(0), type: "off-chain" } + return { ...groups.at(0) } } catch (error: any) { console.error(error) createAlert(error.response.data.message) From 61f069b9b875dae9734a755f05f824213753b248 Mon Sep 17 00:00:00 2001 From: Han <56923450+waddaboo@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:37:54 +0800 Subject: [PATCH 05/24] feat(dashboard): add getAssociatedGroup for dashboard --- apps/dashboard/src/api/semaphoreAPI.ts | 47 ++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/apps/dashboard/src/api/semaphoreAPI.ts b/apps/dashboard/src/api/semaphoreAPI.ts index 3409fca8..b87eb233 100644 --- a/apps/dashboard/src/api/semaphoreAPI.ts +++ b/apps/dashboard/src/api/semaphoreAPI.ts @@ -1,6 +1,7 @@ import { SemaphoreSubgraph } from "@semaphore-protocol/data" import { Group } from "../types" import parseGroupName from "../utils/parseGroupName" +import { getGroupByName } from "./bandadaAPI" const ETHEREUM_NETWORK = import.meta.env.VITE_ETHEREUM_NETWORK @@ -63,3 +64,49 @@ export async function getGroup(groupId: string): Promise { return null } } + +/** + * It returns the details of a specific on-chain group together with the associated off-chain group details. + * @param groupId + * @returns The group details. + */ +export async function getAssociatedGroup( + groupId: string +): Promise { + try { + const group = await subgraph.getGroup(groupId, { + members: true + }) + + const bandadaGroup = await getGroupByName(group.id) + + if (bandadaGroup && bandadaGroup.length > 0) { + const members = group.members as string[] + members.push(...bandadaGroup[0].members) + + return { + id: group.id, + name: parseGroupName(group.id), + treeDepth: group.merkleTree.depth, + fingerprintDuration: 3600, + members, + admin: group.admin as string, + type: "on-chain" + } + } + + return { + id: group.id, + name: parseGroupName(group.id), + treeDepth: group.merkleTree.depth, + fingerprintDuration: 3600, + members: group.members as string[], + admin: group.admin as string, + type: "on-chain" + } + } catch (error) { + console.error(error) + + return null + } +} From d7b03e264b8a6765a013f2655296453c910399d5 Mon Sep 17 00:00:00 2001 From: Han <56923450+waddaboo@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:38:45 +0800 Subject: [PATCH 06/24] feat(dashboard): enable generateInviteLink to on-chain groups --- .../src/components/add-member-modal.tsx | 124 ++++++++++-------- 1 file changed, 67 insertions(+), 57 deletions(-) diff --git a/apps/dashboard/src/components/add-member-modal.tsx b/apps/dashboard/src/components/add-member-modal.tsx index 6fd5ed9a..4ae18fc8 100644 --- a/apps/dashboard/src/components/add-member-modal.tsx +++ b/apps/dashboard/src/components/add-member-modal.tsx @@ -147,7 +147,21 @@ ${memberIds.join("\n")} }, [onClose, _memberIds, group, signer]) const generateInviteLink = useCallback(async () => { - const inviteLink = await bandadaAPI.generateMagicLink(group.id) + let inviteLink = null + + if (group.type === "off-chain") { + inviteLink = await bandadaAPI.generateMagicLink(group.id) + } else { + const associatedGroup = await bandadaAPI.getGroupByName(group.name) + + if (associatedGroup && associatedGroup.length > 0) { + inviteLink = await bandadaAPI.generateMagicLink( + associatedGroup[0].id + ) + } else { + alert("No associated on-chain group found") + } + } if (inviteLink === null) { return @@ -212,63 +226,59 @@ ${memberIds.join("\n")} )} - {group.type === "off-chain" && ( - - - {!group.credentials - ? "Share invite link" - : "Share access link"} - - - - - - - - e.preventDefault() - } - icon={ - - } - /> - - - - - {!group.credentials && ( - - )} - - )} + e.preventDefault()} + icon={ + + } + /> + + + + + {!group.credentials && ( + + )} +