From 99ce230c273f5203d6e90fb38bf1548b6e96c890 Mon Sep 17 00:00:00 2001 From: Han <56923450+waddaboo@users.noreply.github.com> Date: Tue, 17 Dec 2024 19:11:48 +0800 Subject: [PATCH 1/6] feat(api): add addMemberToGroups; add getGroups filter groupIds --- .../groups/dto/add-member-to-groups.dto.ts | 9 + apps/api/src/app/groups/groups.controller.ts | 66 ++++- .../api/src/app/groups/groups.service.test.ts | 234 ++++++++++++++++++ apps/api/src/app/groups/groups.service.ts | 102 +++++++- 4 files changed, 406 insertions(+), 5 deletions(-) create mode 100644 apps/api/src/app/groups/dto/add-member-to-groups.dto.ts diff --git a/apps/api/src/app/groups/dto/add-member-to-groups.dto.ts b/apps/api/src/app/groups/dto/add-member-to-groups.dto.ts new file mode 100644 index 00000000..b94f169c --- /dev/null +++ b/apps/api/src/app/groups/dto/add-member-to-groups.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from "@nestjs/swagger" +import { ArrayNotEmpty, IsArray } from "class-validator" + +export class AddMemberToGroupsDto { + @IsArray() + @ArrayNotEmpty() + @ApiProperty() + readonly groupIds: string[] +} diff --git a/apps/api/src/app/groups/groups.controller.ts b/apps/api/src/app/groups/groups.controller.ts index bf1bc24e..9b123d4a 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 { AddMemberToGroupsDto } from "./dto/add-member-to-groups.dto" @ApiTags("groups") @Controller("groups") @@ -40,15 +41,26 @@ export class GroupsController { @Get() @ApiQuery({ name: "adminId", required: false, type: String }) @ApiQuery({ name: "memberId", required: false, type: String }) + @ApiQuery({ + name: "groupIds", + required: false, + type: String, + isArray: true + }) @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("groupIds") groupIds: string[] ) { - const groups = await this.groupsService.getGroups({ adminId, memberId }) - const groupIds = groups.map((group) => group.id) - const fingerprints = await this.groupsService.getFingerprints(groupIds) + const groups = await this.groupsService.getGroups({ + adminId, + memberId, + groupIds + }) + const groupsIds = groups.map((group) => group.id) + const fingerprints = await this.groupsService.getFingerprints(groupsIds) return groups.map((group, index) => mapGroupToResponseDTO(group, fingerprints[index]) @@ -365,6 +377,52 @@ export class GroupsController { throw new NotImplementedException() } + @Post("/member/:member") + @ApiBody({ type: AddMemberToGroupsDto }) + @ApiHeader({ name: "x-api-key", required: true }) + @ApiCreatedResponse({ isArray: true, type: Group }) + @ApiOperation({ + description: + "Adds a member to multiple groups. Requires an API Key in the headers or a valid session." + }) + async addMemberToGroups( + @Param("member") memberId: string, + @Body() dto: AddMemberToGroupsDto, + @Headers() headers: Headers, + @Req() req: Request + ) { + let groups = [] + const groupsToResponseDTO = [] + + const apiKey = headers["x-api-key"] as string + + if (apiKey) { + groups = await this.groupsService.addMemberToGroupsWithAPIKey( + dto.groupIds, + memberId, + apiKey + ) + } else if (req.session.adminId) { + groups = await this.groupsService.addMemberToGroupsManually( + dto.groupIds, + memberId, + req.session.adminId + ) + } else { + throw new NotImplementedException() + } + + for await (const group of groups) { + const fingerprint = await this.groupsService.getFingerprint( + group.id + ) + + groupsToResponseDTO.push(mapGroupToResponseDTO(group, fingerprint)) + } + + return groupsToResponseDTO + } + @Delete(":group/members/:member") @ApiHeader({ name: "x-api-key", required: true }) @ApiOperation({ diff --git a/apps/api/src/app/groups/groups.service.test.ts b/apps/api/src/app/groups/groups.service.test.ts index 95792863..2abe06a0 100644 --- a/apps/api/src/app/groups/groups.service.test.ts +++ b/apps/api/src/app/groups/groups.service.test.ts @@ -266,6 +266,34 @@ describe("GroupsService", () => { expect(result).toHaveLength(1) }) + + it("Should return a list of groups by group ids", async () => { + const group1 = await groupsService.createGroup( + { + name: "Group Id 1", + description: "This is a description", + treeDepth: 16, + fingerprintDuration: 3600 + }, + "admin" + ) + + const group2 = await groupsService.createGroup( + { + name: "Group Id 2", + description: "This is a description", + treeDepth: 16, + fingerprintDuration: 3600 + }, + "admin" + ) + + const result = await groupsService.getGroups({ + groupIds: [group1.id, group2.id] + }) + + expect(result).toHaveLength(2) + }) }) describe("# getGroup", () => { @@ -1487,6 +1515,212 @@ describe("GroupsService", () => { }) }) + describe("# addMemberToGroupsManually", () => { + let groups: Array + let admin: Admin + + beforeAll(async () => { + admin = await adminsService.create({ + id: "admin", + address: "0x" + }) + + groups = await groupsService.createGroupsManually( + [ + { + name: "Multiple Group 1", + description: "This is a new group", + treeDepth: 16, + fingerprintDuration: 3600 + }, + { + name: "Multiple Group 2", + description: "This is a new group", + treeDepth: 16, + fingerprintDuration: 3600 + } + ], + admin.id + ) + }) + + it("Should add a member to multiple groups manually", async () => { + const multipleGroups = + await groupsService.addMemberToGroupsManually( + groups.map((g) => g.id), + "123123", + admin.id + ) + + expect(multipleGroups).toHaveLength(2) + }) + + it("Should not add a member to the groups if the group is a credential group", async () => { + const _group = await groupsService.createGroup( + { + name: "Multiple Group Credential Group", + description: "This is a new group", + treeDepth: 16, + fingerprintDuration: 3600, + credentials: { + id: "GITHUB_FOLLOWERS", + criteria: { + minFollowers: 5 + } + } + }, + admin.id + ) + + const fun = groupsService.addMemberToGroupsManually( + [groups[0].id, _group.id], + "456456", + admin.id + ) + + await expect(fun).rejects.toThrow( + `The group '${_group.name}' is a credential group. You cannot manually add members to a credential group.` + ) + }) + + it("Should not add a member to multiple groups if the member already exists", async () => { + const fun = groupsService.addMemberToGroupsManually( + groups.map((g) => g.id), + "123123", + admin.id + ) + + await expect(fun).rejects.toThrow( + `Member '123123' already exists in the group '${groups[0].id}'` + ) + }) + + it("Should not add a member to multiple groups if the admin is the wrong admin", async () => { + const fun = groupsService.addMemberToGroupsManually( + groups.map((g) => g.id), + "123123", + "wrong-admin" + ) + + await expect(fun).rejects.toThrow( + `You are not the admin of the group '${groups[0].id}'` + ) + }) + }) + + describe("# addMembersToGroupsWithAPIKey", () => { + let groups: Array + let admin: Admin + let apiKey: string + + beforeAll(async () => { + admin = await adminsService.create({ + id: "admin", + address: "0x" + }) + + apiKey = await adminsService.updateApiKey( + admin.id, + ApiKeyActions.Generate + ) + + groups = await groupsService.createGroupsManually( + [ + { + name: "Multiple Group API 1", + description: "This is a new group", + treeDepth: 16, + fingerprintDuration: 3600 + }, + { + name: "Multiple Group API 2", + description: "This is a new group", + treeDepth: 16, + fingerprintDuration: 3600 + } + ], + admin.id + ) + }) + + it("Should add a member to multiple groups via API", async () => { + const multipleGroups = + await groupsService.addMemberToGroupsWithAPIKey( + groups.map((g) => g.id), + "123123", + apiKey + ) + + expect(multipleGroups).toHaveLength(2) + }) + + it("Should not add a member to the groups if the group is a credential group", async () => { + const _group = await groupsService.createGroup( + { + name: "Multiple Group Credential Group API", + description: "This is a new group", + treeDepth: 16, + fingerprintDuration: 3600, + credentials: { + id: "GITHUB_FOLLOWERS", + criteria: { + minFollowers: 5 + } + } + }, + admin.id + ) + + const fun = groupsService.addMemberToGroupsWithAPIKey( + [groups[0].id, _group.id], + "456456", + apiKey + ) + + await expect(fun).rejects.toThrow( + `The group '${_group.name}' is a credential group. You cannot add members to a credential group using an API Key.` + ) + }) + + it("Should not add a member to multiple groups if the member already exists", async () => { + const fun = groupsService.addMemberToGroupsWithAPIKey( + groups.map((g) => g.id), + "123123", + apiKey + ) + + await expect(fun).rejects.toThrow( + `Member '123123' already exists in the group '${groups[0].id}'` + ) + }) + + it("Should not add a member to multiple groups if the API key is invalid", async () => { + const fun = groupsService.addMemberToGroupsWithAPIKey( + groups.map((g) => g.id), + "123123", + "invalid-apikey" + ) + + await expect(fun).rejects.toThrow( + `Invalid API key or API access not enabled for admin '${admin.id}'` + ) + }) + + it("Should not add a member to multiple groups if the API key is disabled", async () => { + await adminsService.updateApiKey(admin.id, ApiKeyActions.Disable) + + const fun = groupsService.addMemberToGroupsWithAPIKey( + groups.map((g) => g.id), + "123123", + apiKey + ) + + await expect(fun).rejects.toThrow( + `Invalid API key or API access not enabled for admin '${admin.id}'` + ) + }) + }) + describe("# createGroupManually", () => { const groupDto: CreateGroupDto = { id: "1", diff --git a/apps/api/src/app/groups/groups.service.ts b/apps/api/src/app/groups/groups.service.ts index c0c7eae2..b6936e28 100644 --- a/apps/api/src/app/groups/groups.service.ts +++ b/apps/api/src/app/groups/groups.service.ts @@ -11,7 +11,7 @@ import { } from "@nestjs/common" import { InjectRepository } from "@nestjs/typeorm" import { Group as CachedGroup } from "@semaphore-protocol/group" -import { Repository } from "typeorm" +import { Repository, In } from "typeorm" import { InvitesService } from "../invites/invites.service" import { AdminsService } from "../admins/admins.service" import { CreateGroupDto } from "./dto/create-group.dto" @@ -552,6 +552,98 @@ export class GroupsService { return this.addMembers(groupId, memberIds) } + /** + * Add a member to multiple groups manually as an admin. + * @param groupIds Array of group ids to be added. + * @param memberId Member id. + * @param adminId Group admin id. + * @returns Array of groups of added member. + */ + async addMemberToGroupsManually( + groupIds: string[], + memberId: string, + adminId: string + ): Promise { + for await (const groupId of groupIds) { + const group = await this.getGroup(groupId) + + if (group.adminId !== adminId) { + throw new UnauthorizedException( + `You are not the admin of the group '${groupId}'` + ) + } + + if (group.credentials !== null) { + throw new Error( + `The group '${group.name}' is a credential group. You cannot manually add members to a credential group.` + ) + } + + if (this.isGroupMember(groupId, memberId)) { + throw new BadRequestException( + `Member '${memberId}' already exists in the group '${groupId}'` + ) + } + } + + for await (const groupId of groupIds) { + await this.addMember(groupId, memberId) + } + + return this.getGroups({ groupIds }) + } + + /** + * Add a member to multiple groups using API Key. + * @param groupIds Array of group ids to be added. + * @param memberId Member id. + * @param apiKey API key for the group. + * @returns Array of groups of added member. + */ + async addMemberToGroupsWithAPIKey( + groupIds: string[], + memberId: string, + apiKey: string + ): Promise { + for await (const groupId of groupIds) { + const group = await this.getGroup(groupId) + + const admin = await this.adminsService.findOne({ + id: group.adminId + }) + + if (!admin) { + throw new BadRequestException( + `Invalid admin for group '${groupId}'` + ) + } + + if (!admin.apiEnabled || admin.apiKey !== apiKey) { + throw new BadRequestException( + `Invalid API key or API access not enabled for admin '${admin.id}'` + ) + } + + if (group.credentials !== null) { + throw new Error( + `The group '${group.name}' is a credential group. You cannot add members to a credential group using an API Key.` + ) + } + + if (this.isGroupMember(groupId, memberId)) { + throw new BadRequestException( + `Member '${memberId}' already exists in the group '${groupId}'` + ) + } + } + + for await (const groupId of groupIds) { + await this.addMember(groupId, memberId) + } + + return this.getGroups({ groupIds }) + } + /** * Add a member to the group. * @param groupId Group id. @@ -820,6 +912,7 @@ export class GroupsService { async getGroups(filters?: { adminId?: string memberId?: string + groupIds?: string[] }): Promise { let where = {} @@ -838,6 +931,13 @@ export class GroupsService { } } + if (filters?.groupIds) { + where = { + id: In(filters.groupIds), + ...where + } + } + return this.groupRepository.find({ relations: { members: true }, where, From aa1dc14d5270d2fa8445e450e853d2ee55afa39e Mon Sep 17 00:00:00 2001 From: Han <56923450+waddaboo@users.noreply.github.com> Date: Tue, 17 Dec 2024 19:13:50 +0800 Subject: [PATCH 2/6] feat(api-sdk): add addMemberToGroupsByApiKey --- libs/api-sdk/src/apiSdk.ts | 25 ++++- libs/api-sdk/src/groups.ts | 28 +++++ libs/api-sdk/src/index.test.ts | 198 ++++++++++++++++++++++----------- 3 files changed, 186 insertions(+), 65 deletions(-) diff --git a/libs/api-sdk/src/apiSdk.ts b/libs/api-sdk/src/apiSdk.ts index 822564dc..faf7dda0 100644 --- a/libs/api-sdk/src/apiSdk.ts +++ b/libs/api-sdk/src/apiSdk.ts @@ -25,7 +25,8 @@ import { getGroupsByAdminId, getGroupsByMemberId, getCredentialGroupJoinUrl, - getMultipleCredentialsGroupJoinUrl + getMultipleCredentialsGroupJoinUrl, + addMemberToGroupsByApiKey } from "./groups" import { createInvite, getInvite, redeemInvite } from "./invites" @@ -314,6 +315,28 @@ export default class ApiSdk { await addMemberByInviteCode(this._config, groupId, memberId, inviteCode) } + /** + * Adds a member to multiple groups using an API Key. + * @param groupIds Array of group ids. + * @param memberId Member id. + * @param apiKey API Key of the admin of the group. + * @returns Array of the groups of added member. + */ + async addMemberToGroupsByApiKey( + groupIds: string[], + memberId: string, + apiKey: string + ): Promise { + const groups = await addMemberToGroupsByApiKey( + this._config, + groupIds, + memberId, + apiKey + ) + + return groups + } + /** * Removes a member from a group using an API Key. * @param groupId Group id. diff --git a/libs/api-sdk/src/groups.ts b/libs/api-sdk/src/groups.ts index 4cc9481f..fa47596e 100644 --- a/libs/api-sdk/src/groups.ts +++ b/libs/api-sdk/src/groups.ts @@ -375,6 +375,34 @@ export async function addMemberByInviteCode( await request(requestUrl, newConfig) } +/** + * Adds a member to multiple groups. + * @param groupIds Array of group ids. + * @param memberId Member id. + * @param apiKey API Key of the admin. + * @returns Array of the groups of added member. + */ +export function addMemberToGroupsByApiKey( + config: object, + groupIds: string[], + memberId: string, + apiKey: string +): Promise { + const newConfig: any = { + method: "post", + data: { + groupIds + }, + ...config + } + + newConfig.headers["x-api-key"] = apiKey + + const req = request(`${url}/member/${memberId}`, newConfig) + + return req +} + /** * Removes a member from a group using an API Key. * @param groupId Group id. diff --git a/libs/api-sdk/src/index.test.ts b/libs/api-sdk/src/index.test.ts index 6d3203d8..cca8f38e 100644 --- a/libs/api-sdk/src/index.test.ts +++ b/libs/api-sdk/src/index.test.ts @@ -795,86 +795,156 @@ describe("Bandada API SDK", () => { ) expect(res).toBeUndefined() }) - describe("#removeMemberByApiKey", () => { - it("Should remove a member from a group using an API Key", async () => { - requestMocked.mockImplementationOnce(() => - Promise.resolve() - ) + }) + + describe("#addMemberToGroups", () => { + it("Should add a member to multiple groups using an API Key", async () => { + const expectedGroups: Array = [ + { + name: "Group1", + description: "This is a new group", + treeDepth: 16, + fingerprintDuration: 3600 + }, + { + name: "Group2", + description: "This is a new group", + treeDepth: 32, + fingerprintDuration: 7200 + } + ] - const groupId = "10402173435763029700781503965100" - const memberId = "1" - const apiKey = "70f07d0d-6aa2-4fe1-b4b9-06c271a641dc" + requestMocked.mockImplementationOnce(() => + Promise.resolve([ + { + id: "10402173435763029700781503965100", + name: "Group1", + description: "This is a new group", + admin: "0xdf558148e66850ac48dbe2c8119b0eefa7d08bfd19c997c90a142eb97916b847", + treeDepth: 16, + fingerprintDuration: 3600, + createdAt: "2023-07-15T08:21:05.000Z", + members: ["1"], + credentials: null + }, + { + id: "20402173435763029700781503965200", + name: "Group2", + description: "This is a new group", + admin: "0xdf558148e66850ac48dbe2c8119b0eefa7d08bfd19c997c90a142eb97916b847", + treeDepth: 32, + fingerprintDuration: 7200, + createdAt: "2023-07-15T08:21:05.000Z", + members: ["1"], + credentials: null + } + ]) + ) + + const groupIds = [ + "10402173435763029700781503965100", + "20402173435763029700781503965200" + ] + const memberId = "1" + const apiKey = "70f07d0d-6aa2-4fe1-b4b9-06c271a641dc" + + const apiSdk: ApiSdk = new ApiSdk(SupportedUrl.DEV) + const groups = await apiSdk.addMemberToGroupsByApiKey( + groupIds, + memberId, + apiKey + ) - const apiSdk: ApiSdk = new ApiSdk(SupportedUrl.DEV) - const res = await apiSdk.removeMemberByApiKey( - groupId, - memberId, - apiKey + groups.forEach((group: Group, i: number) => { + expect(group.description).toBe( + expectedGroups[i].description + ) + expect(group.name).toBe(expectedGroups[i].name) + expect(group.treeDepth).toBe(expectedGroups[i].treeDepth) + expect(group.fingerprintDuration).toBe( + expectedGroups[i].fingerprintDuration ) - expect(res).toBeUndefined() + expect(group.members).toHaveLength(1) + expect(group.credentials).toBeNull() }) }) + }) - describe("#removeMembersByApiKey", () => { - it("Should remove multiple members from a group using an API Key", async () => { - requestMocked.mockImplementationOnce(() => - Promise.resolve() - ) + describe("#removeMemberByApiKey", () => { + it("Should remove a member from a group using an API Key", async () => { + requestMocked.mockImplementationOnce(() => Promise.resolve()) - const groupId = "10402173435763029700781503965100" - const memberIds = ["1", "2", "3"] - const apiKey = "70f07d0d-6aa2-4fe1-b4b9-06c271a641dc" + const groupId = "10402173435763029700781503965100" + const memberId = "1" + const apiKey = "70f07d0d-6aa2-4fe1-b4b9-06c271a641dc" - const apiSdk: ApiSdk = new ApiSdk(SupportedUrl.DEV) - const res = await apiSdk.removeMembersByApiKey( - groupId, - memberIds, - apiKey - ) - expect(res).toBeUndefined() - }) + const apiSdk: ApiSdk = new ApiSdk(SupportedUrl.DEV) + const res = await apiSdk.removeMemberByApiKey( + groupId, + memberId, + apiKey + ) + expect(res).toBeUndefined() }) + }) - describe("#getCredentialGroupJoinUrl", () => { - it("Should generate a custom url for joining a credential group", async () => { - const dashboardUrl = DashboardUrl.DEV - const groupId = "10402173435763029700781503965100" - const commitment = "1" - const providerName = "github" - const redirectUri = "http://localhost:3003" - - const apiSdk: ApiSdk = new ApiSdk(SupportedUrl.DEV) - const res = apiSdk.getCredentialGroupJoinUrl( - dashboardUrl, - groupId, - commitment, - providerName, - redirectUri - ) + describe("#removeMembersByApiKey", () => { + it("Should remove multiple members from a group using an API Key", async () => { + requestMocked.mockImplementationOnce(() => Promise.resolve()) - const url = `${dashboardUrl}/credentials?group=${groupId}&member=${commitment}&provider=${providerName}&redirect_uri=${redirectUri}?redirect=true` + const groupId = "10402173435763029700781503965100" + const memberIds = ["1", "2", "3"] + const apiKey = "70f07d0d-6aa2-4fe1-b4b9-06c271a641dc" - expect(res).toBe(url) - }) + const apiSdk: ApiSdk = new ApiSdk(SupportedUrl.DEV) + const res = await apiSdk.removeMembersByApiKey( + groupId, + memberIds, + apiKey + ) + expect(res).toBeUndefined() }) + }) - describe("#getMultipleCredentialGroupJoinUrl", () => { - it("Should generate a custom url for joining a multiple credential group", async () => { - const dashboardUrl = DashboardUrl.DEV - const groupId = "10402173435763029700781503965100" - const commitment = "1" - - const apiSdk: ApiSdk = new ApiSdk(SupportedUrl.DEV) - const res = apiSdk.getMultipleCredentialsGroupJoinUrl( - dashboardUrl, - groupId, - commitment - ) + describe("#getCredentialGroupJoinUrl", () => { + it("Should generate a custom url for joining a credential group", async () => { + const dashboardUrl = DashboardUrl.DEV + const groupId = "10402173435763029700781503965100" + const commitment = "1" + const providerName = "github" + const redirectUri = "http://localhost:3003" - const url = `${dashboardUrl}/credentials?group=${groupId}&member=${commitment}&type=multiple` + const apiSdk: ApiSdk = new ApiSdk(SupportedUrl.DEV) + const res = apiSdk.getCredentialGroupJoinUrl( + dashboardUrl, + groupId, + commitment, + providerName, + redirectUri + ) - expect(res).toBe(url) - }) + const url = `${dashboardUrl}/credentials?group=${groupId}&member=${commitment}&provider=${providerName}&redirect_uri=${redirectUri}?redirect=true` + + expect(res).toBe(url) + }) + }) + + describe("#getMultipleCredentialGroupJoinUrl", () => { + it("Should generate a custom url for joining a multiple credential group", async () => { + const dashboardUrl = DashboardUrl.DEV + const groupId = "10402173435763029700781503965100" + const commitment = "1" + + const apiSdk: ApiSdk = new ApiSdk(SupportedUrl.DEV) + const res = apiSdk.getMultipleCredentialsGroupJoinUrl( + dashboardUrl, + groupId, + commitment + ) + + const url = `${dashboardUrl}/credentials?group=${groupId}&member=${commitment}&type=multiple` + + expect(res).toBe(url) }) }) }) From 1e10c3afb15414ba1e6926d8a35302e2b6a29374 Mon Sep 17 00:00:00 2001 From: Han <56923450+waddaboo@users.noreply.github.com> Date: Tue, 17 Dec 2024 19:36:01 +0800 Subject: [PATCH 3/6] feat(api-sdk): add getGroupsByGroupIds --- libs/api-sdk/src/apiSdk.ts | 12 ++++++++ libs/api-sdk/src/groups.ts | 35 ++++++++++++++++++++++ libs/api-sdk/src/index.test.ts | 53 ++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) diff --git a/libs/api-sdk/src/apiSdk.ts b/libs/api-sdk/src/apiSdk.ts index faf7dda0..7bff7fb4 100644 --- a/libs/api-sdk/src/apiSdk.ts +++ b/libs/api-sdk/src/apiSdk.ts @@ -24,6 +24,7 @@ import { removeMembersByApiKey, getGroupsByAdminId, getGroupsByMemberId, + getGroupsByGroupIds, getCredentialGroupJoinUrl, getMultipleCredentialsGroupJoinUrl, addMemberToGroupsByApiKey @@ -113,6 +114,17 @@ export default class ApiSdk { return groups } + /** + * Returns the list of groups by group ids. + * @param groupIds Group ids. + * @returns List of groups by group ids. + */ + async getGroupsByGroupIds(groupIds: string[]): Promise { + const groups = await getGroupsByGroupIds(this._config, groupIds) + + return groups + } + /** * Creates a group using the API key. * @param groupCreationDetails Data to create the group. diff --git a/libs/api-sdk/src/groups.ts b/libs/api-sdk/src/groups.ts index fa47596e..3ba4fe87 100644 --- a/libs/api-sdk/src/groups.ts +++ b/libs/api-sdk/src/groups.ts @@ -95,6 +95,41 @@ export async function getGroupsByMemberId( return groups } +/** + * Returns the list of groups by group ids. + * @param groupIds Group ids. + * @returns List of groups by group ids. + */ +export async function getGroupsByGroupIds( + config: object, + groupIds: string[] +): Promise { + let requestUrl = `${url}?` + + for (const groupId of groupIds) { + requestUrl += `&groupIds=${groupId}` + } + + let groups = await request(requestUrl, config) + + groups = groups.map((group: any) => { + let credentials + + try { + credentials = JSON.parse(group.credentials) + } catch (error) { + credentials = null + } + + return { + ...group, + credentials + } + }) + + return groups +} + /** * Creates one or more groups with the provided details. * @param groupsCreationDetails Data to create the groups. diff --git a/libs/api-sdk/src/index.test.ts b/libs/api-sdk/src/index.test.ts index cca8f38e..8f184c1b 100644 --- a/libs/api-sdk/src/index.test.ts +++ b/libs/api-sdk/src/index.test.ts @@ -604,6 +604,59 @@ describe("Bandada API SDK", () => { expect(groups[0].credentials).toBeNull() }) }) + describe("getGroupsByGroupIds", () => { + it("Should return all groups by group ids", async () => { + requestMocked.mockImplementationOnce(() => + Promise.resolve([ + { + id: "10402173435763029700781503965100", + name: "Group1", + description: "This is a new group", + admin: "0xdf558148e66850ac48dbe2c8119b0eefa7d08bfd19c997c90a142eb97916b847", + treeDepth: 16, + fingerprintDuration: 3600, + createdAt: "2023-07-15T08:21:05.000Z", + members: [], + credentials: null + } + ]) + ) + + const groupIds = ["10402173435763029700781503965100"] + + const apiSdk: ApiSdk = new ApiSdk(SupportedUrl.DEV) + const groups: Group[] = await apiSdk.getGroupsByGroupIds( + groupIds + ) + expect(groups).toHaveLength(1) + }) + it("Should return all groups by group ids and null in the credentials that don't have a valid JSON string", async () => { + requestMocked.mockImplementationOnce(() => + Promise.resolve([ + { + id: "10402173435763029700781503965100", + name: "Group1", + description: "This is a new group", + admin: "0xdf558148e66850ac48dbe2c8119b0eefa7d08bfd19c997c90a142eb97916b847", + treeDepth: 16, + fingerprintDuration: 3600, + createdAt: "2023-07-15T08:21:05.000Z", + members: [], + credentials: {} + } + ]) + ) + + const groupIds = ["10402173435763029700781503965100"] + + const apiSdk: ApiSdk = new ApiSdk(SupportedUrl.DEV) + const groups: Group[] = await apiSdk.getGroupsByGroupIds( + groupIds + ) + expect(groups).toHaveLength(1) + expect(groups[0].credentials).toBeNull() + }) + }) describe("#getGroup", () => { it("Should return a group", async () => { requestMocked.mockImplementationOnce(() => From fc3c9f64178e66ab237a607c4a38703c3ec1d4e5 Mon Sep 17 00:00:00 2001 From: Han <56923450+waddaboo@users.noreply.github.com> Date: Tue, 17 Dec 2024 19:37:31 +0800 Subject: [PATCH 4/6] docs: update docs --- apps/docs/docs/api-sdk.md | 29 +++++++++++++++++++++++++++++ libs/api-sdk/README.md | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/apps/docs/docs/api-sdk.md b/apps/docs/docs/api-sdk.md index 9bd13bdf..e746a355 100644 --- a/apps/docs/docs/api-sdk.md +++ b/apps/docs/docs/api-sdk.md @@ -322,6 +322,21 @@ const memberId = "1" const groups = await apiSdk.getGroupsByMemberId(memberId) ``` +## Get groups by group ids + +\# **getGroupByGroupIds**(): _Promise\_ + +Returns the list of groups by group ids. + +```ts +const groupIds = [ + "10402173435763029700781503965100", + "20402173435763029700781503965200" +] + +const groups = await apiSdk.getGroupsByGroupIds(groupIds) +``` + ## Is group member \# **isGroupMember**(): _Promise\_ @@ -390,6 +405,20 @@ const inviteCode = "MQYS4UR5" await apiSdk.addMemberByInviteCode(groupId, memberId, inviteCode) ``` +## Add member to groups using an API Key + +\# **addMemberToGroupsByApiKey**(): _Promise\_ + +Adds a member to multiple groups using an API Key. + +```ts +const groupIds = ["10402173435763029700781503965100", "20402173435763029700781503965200"] +const memberId = "1" +const apiKey = "70f07d0d-6aa2-4fe1-b4b9-06c271a641dc" + +await apiSdk.addMemberToGroupsByApiKey(groupIds, memberId, apiKey) +``` + ## Remove member using an API Key \# **removeMemberByApiKey**(): _Promise\_ diff --git a/libs/api-sdk/README.md b/libs/api-sdk/README.md index 21e01288..8de25a89 100644 --- a/libs/api-sdk/README.md +++ b/libs/api-sdk/README.md @@ -345,6 +345,21 @@ const memberId = "1" const groups = await apiSdk.getGroupsByMemberId(memberId) ``` +## Get groups by group ids + +\# **getGroupByGroupIds**(): _Promise\_ + +Returns the list of groups by group ids. + +```ts +const groupIds = [ + "10402173435763029700781503965100", + "20402173435763029700781503965200" +] + +const groups = await apiSdk.getGroupsByGroupIds(groupIds) +``` + ## Is group member \# **isGroupMember**(): _Promise\_ @@ -413,6 +428,23 @@ const inviteCode = "MQYS4UR5" await apiSdk.addMemberByInviteCode(groupId, memberId, inviteCode) ``` +## Add member to groups using an API Key + +\# **addMemberToGroupsByApiKey**(): _Promise\_ + +Adds a member to multiple groups using an API Key. + +```ts +const groupIds = [ + "10402173435763029700781503965100", + "20402173435763029700781503965200" +] +const memberId = "1" +const apiKey = "70f07d0d-6aa2-4fe1-b4b9-06c271a641dc" + +await apiSdk.addMemberToGroupsByApiKey(groupIds, memberId, apiKey) +``` + ## Remove member using an API Key \# **removeMemberByApiKey**(): _Promise\_ From f9faf9e67eac69fdff07f2d8210bb3eedb8425cb Mon Sep 17 00:00:00 2001 From: Han <56923450+waddaboo@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:02:53 +0800 Subject: [PATCH 5/6] feat(api,api-sdk): update api url to be consistent --- apps/api/src/app/groups/groups.controller.ts | 2 +- libs/api-sdk/src/groups.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/src/app/groups/groups.controller.ts b/apps/api/src/app/groups/groups.controller.ts index 9b123d4a..c598b7a9 100644 --- a/apps/api/src/app/groups/groups.controller.ts +++ b/apps/api/src/app/groups/groups.controller.ts @@ -377,7 +377,7 @@ export class GroupsController { throw new NotImplementedException() } - @Post("/member/:member") + @Post("/members/:member") @ApiBody({ type: AddMemberToGroupsDto }) @ApiHeader({ name: "x-api-key", required: true }) @ApiCreatedResponse({ isArray: true, type: Group }) diff --git a/libs/api-sdk/src/groups.ts b/libs/api-sdk/src/groups.ts index 3ba4fe87..54f73954 100644 --- a/libs/api-sdk/src/groups.ts +++ b/libs/api-sdk/src/groups.ts @@ -433,7 +433,7 @@ export function addMemberToGroupsByApiKey( newConfig.headers["x-api-key"] = apiKey - const req = request(`${url}/member/${memberId}`, newConfig) + const req = request(`${url}/members/${memberId}`, newConfig) return req } From 6a36eaf14e523a7ee828e25beb98f8b2ddef1dac Mon Sep 17 00:00:00 2001 From: Han <56923450+waddaboo@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:22:52 +0800 Subject: [PATCH 6/6] feat(api,api-sdk): update return response to void --- apps/api/src/app/groups/groups.controller.ts | 20 ++----- apps/docs/docs/api-sdk.md | 2 +- libs/api-sdk/README.md | 2 +- libs/api-sdk/src/apiSdk.ts | 6 +-- libs/api-sdk/src/groups.ts | 2 +- libs/api-sdk/src/index.test.ts | 57 ++------------------ 6 files changed, 11 insertions(+), 78 deletions(-) diff --git a/apps/api/src/app/groups/groups.controller.ts b/apps/api/src/app/groups/groups.controller.ts index c598b7a9..a6a1ea0a 100644 --- a/apps/api/src/app/groups/groups.controller.ts +++ b/apps/api/src/app/groups/groups.controller.ts @@ -380,7 +380,6 @@ export class GroupsController { @Post("/members/:member") @ApiBody({ type: AddMemberToGroupsDto }) @ApiHeader({ name: "x-api-key", required: true }) - @ApiCreatedResponse({ isArray: true, type: Group }) @ApiOperation({ description: "Adds a member to multiple groups. Requires an API Key in the headers or a valid session." @@ -390,20 +389,17 @@ export class GroupsController { @Body() dto: AddMemberToGroupsDto, @Headers() headers: Headers, @Req() req: Request - ) { - let groups = [] - const groupsToResponseDTO = [] - + ): Promise { const apiKey = headers["x-api-key"] as string if (apiKey) { - groups = await this.groupsService.addMemberToGroupsWithAPIKey( + await this.groupsService.addMemberToGroupsWithAPIKey( dto.groupIds, memberId, apiKey ) } else if (req.session.adminId) { - groups = await this.groupsService.addMemberToGroupsManually( + await this.groupsService.addMemberToGroupsManually( dto.groupIds, memberId, req.session.adminId @@ -411,16 +407,6 @@ export class GroupsController { } else { throw new NotImplementedException() } - - for await (const group of groups) { - const fingerprint = await this.groupsService.getFingerprint( - group.id - ) - - groupsToResponseDTO.push(mapGroupToResponseDTO(group, fingerprint)) - } - - return groupsToResponseDTO } @Delete(":group/members/:member") diff --git a/apps/docs/docs/api-sdk.md b/apps/docs/docs/api-sdk.md index e746a355..3988ddc2 100644 --- a/apps/docs/docs/api-sdk.md +++ b/apps/docs/docs/api-sdk.md @@ -407,7 +407,7 @@ await apiSdk.addMemberByInviteCode(groupId, memberId, inviteCode) ## Add member to groups using an API Key -\# **addMemberToGroupsByApiKey**(): _Promise\_ +\# **addMemberToGroupsByApiKey**(): _Promise\_ Adds a member to multiple groups using an API Key. diff --git a/libs/api-sdk/README.md b/libs/api-sdk/README.md index 8de25a89..2318d2b3 100644 --- a/libs/api-sdk/README.md +++ b/libs/api-sdk/README.md @@ -430,7 +430,7 @@ await apiSdk.addMemberByInviteCode(groupId, memberId, inviteCode) ## Add member to groups using an API Key -\# **addMemberToGroupsByApiKey**(): _Promise\_ +\# **addMemberToGroupsByApiKey**(): _Promise\_ Adds a member to multiple groups using an API Key. diff --git a/libs/api-sdk/src/apiSdk.ts b/libs/api-sdk/src/apiSdk.ts index 7bff7fb4..95282f5a 100644 --- a/libs/api-sdk/src/apiSdk.ts +++ b/libs/api-sdk/src/apiSdk.ts @@ -338,15 +338,13 @@ export default class ApiSdk { groupIds: string[], memberId: string, apiKey: string - ): Promise { - const groups = await addMemberToGroupsByApiKey( + ): Promise { + await addMemberToGroupsByApiKey( this._config, groupIds, memberId, apiKey ) - - return groups } /** diff --git a/libs/api-sdk/src/groups.ts b/libs/api-sdk/src/groups.ts index 54f73954..e8a0be35 100644 --- a/libs/api-sdk/src/groups.ts +++ b/libs/api-sdk/src/groups.ts @@ -422,7 +422,7 @@ export function addMemberToGroupsByApiKey( groupIds: string[], memberId: string, apiKey: string -): Promise { +): Promise { const newConfig: any = { method: "post", data: { diff --git a/libs/api-sdk/src/index.test.ts b/libs/api-sdk/src/index.test.ts index 8f184c1b..8f8cc5ce 100644 --- a/libs/api-sdk/src/index.test.ts +++ b/libs/api-sdk/src/index.test.ts @@ -852,47 +852,7 @@ describe("Bandada API SDK", () => { describe("#addMemberToGroups", () => { it("Should add a member to multiple groups using an API Key", async () => { - const expectedGroups: Array = [ - { - name: "Group1", - description: "This is a new group", - treeDepth: 16, - fingerprintDuration: 3600 - }, - { - name: "Group2", - description: "This is a new group", - treeDepth: 32, - fingerprintDuration: 7200 - } - ] - - requestMocked.mockImplementationOnce(() => - Promise.resolve([ - { - id: "10402173435763029700781503965100", - name: "Group1", - description: "This is a new group", - admin: "0xdf558148e66850ac48dbe2c8119b0eefa7d08bfd19c997c90a142eb97916b847", - treeDepth: 16, - fingerprintDuration: 3600, - createdAt: "2023-07-15T08:21:05.000Z", - members: ["1"], - credentials: null - }, - { - id: "20402173435763029700781503965200", - name: "Group2", - description: "This is a new group", - admin: "0xdf558148e66850ac48dbe2c8119b0eefa7d08bfd19c997c90a142eb97916b847", - treeDepth: 32, - fingerprintDuration: 7200, - createdAt: "2023-07-15T08:21:05.000Z", - members: ["1"], - credentials: null - } - ]) - ) + requestMocked.mockImplementationOnce(() => Promise.resolve()) const groupIds = [ "10402173435763029700781503965100", @@ -902,24 +862,13 @@ describe("Bandada API SDK", () => { const apiKey = "70f07d0d-6aa2-4fe1-b4b9-06c271a641dc" const apiSdk: ApiSdk = new ApiSdk(SupportedUrl.DEV) - const groups = await apiSdk.addMemberToGroupsByApiKey( + const res = await apiSdk.addMemberToGroupsByApiKey( groupIds, memberId, apiKey ) - groups.forEach((group: Group, i: number) => { - expect(group.description).toBe( - expectedGroups[i].description - ) - expect(group.name).toBe(expectedGroups[i].name) - expect(group.treeDepth).toBe(expectedGroups[i].treeDepth) - expect(group.fingerprintDuration).toBe( - expectedGroups[i].fingerprintDuration - ) - expect(group.members).toHaveLength(1) - expect(group.credentials).toBeNull() - }) + expect(res).toBeUndefined() }) })