mirror of
https://github.com/AtHeartEngineering/bandada.git
synced 2026-01-09 00:48:03 -05:00
feat: add missing createInvite api endpoint
This commit is contained in:
@@ -19,7 +19,7 @@ import { UpdateGroupDto } from "./dto/update-group.dto"
|
||||
import { Group } from "./entities/group.entity"
|
||||
import { Member } from "./entities/member.entity"
|
||||
import { MerkleProof } from "./types"
|
||||
import { getAndCheckAdmin } from "./groups.utils"
|
||||
import { getAndCheckAdmin } from "../utils"
|
||||
|
||||
@Injectable()
|
||||
export class GroupsService {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ScheduleModule } from "@nestjs/schedule"
|
||||
import { Test } from "@nestjs/testing"
|
||||
import { TypeOrmModule } from "@nestjs/typeorm"
|
||||
import { ApiKeyActions } from "@bandada/utils"
|
||||
import { Invite } from "../invites/entities/invite.entity"
|
||||
import { InvitesService } from "../invites/invites.service"
|
||||
import { OAuthAccount } from "../credentials/entities/credentials-account.entity"
|
||||
@@ -11,11 +10,10 @@ import { GroupsService } from "./groups.service"
|
||||
import { AdminsService } from "../admins/admins.service"
|
||||
import { AdminsModule } from "../admins/admins.module"
|
||||
import { Admin } from "../admins/entities/admin.entity"
|
||||
import { mapGroupToResponseDTO, getAndCheckAdmin } from "./groups.utils"
|
||||
import { mapGroupToResponseDTO } from "./groups.utils"
|
||||
|
||||
describe("Groups utils", () => {
|
||||
let groupsService: GroupsService
|
||||
let adminsService: AdminsService
|
||||
|
||||
beforeAll(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
@@ -37,7 +35,6 @@ describe("Groups utils", () => {
|
||||
}).compile()
|
||||
|
||||
groupsService = await module.resolve(GroupsService)
|
||||
adminsService = await module.resolve(AdminsService)
|
||||
|
||||
await groupsService.initialize()
|
||||
})
|
||||
@@ -70,68 +67,4 @@ describe("Groups utils", () => {
|
||||
expect(fingerprint).toBe("12345")
|
||||
})
|
||||
})
|
||||
|
||||
describe("# getAndCheckAdmin", () => {
|
||||
const groupId = "1"
|
||||
let apiKey = ""
|
||||
let admin: Admin = {} as any
|
||||
|
||||
beforeAll(async () => {
|
||||
admin = await adminsService.create({
|
||||
id: groupId,
|
||||
address: "0x00"
|
||||
})
|
||||
|
||||
apiKey = await adminsService.updateApiKey(
|
||||
admin.id,
|
||||
ApiKeyActions.Generate
|
||||
)
|
||||
|
||||
admin = await adminsService.findOne({ id: admin.id })
|
||||
})
|
||||
|
||||
it("Should successfully check and return the admin", async () => {
|
||||
const checkedAdmin = await getAndCheckAdmin(adminsService, apiKey)
|
||||
|
||||
expect(checkedAdmin.id).toBe(admin.id)
|
||||
expect(checkedAdmin.address).toBe(admin.address)
|
||||
expect(checkedAdmin.apiKey).toBe(admin.apiKey)
|
||||
expect(checkedAdmin.apiEnabled).toBe(admin.apiEnabled)
|
||||
expect(checkedAdmin.username).toBe(admin.username)
|
||||
})
|
||||
|
||||
it("Should throw if the API Key or admin is invalid", async () => {
|
||||
const fun = getAndCheckAdmin(adminsService, "wrong")
|
||||
|
||||
await expect(fun).rejects.toThrow(
|
||||
`Invalid API key or invalid admin for the groups`
|
||||
)
|
||||
})
|
||||
|
||||
it("Should throw if the API Key or admin is invalid (w/ group identifier)", async () => {
|
||||
const fun = getAndCheckAdmin(adminsService, "wrong", groupId)
|
||||
|
||||
await expect(fun).rejects.toThrow(
|
||||
`Invalid API key or invalid admin for the group '${groupId}'`
|
||||
)
|
||||
})
|
||||
|
||||
it("Should throw if the API Key is invalid or API access is disabled", async () => {
|
||||
await adminsService.updateApiKey(admin.id, ApiKeyActions.Disable)
|
||||
|
||||
const fun = getAndCheckAdmin(adminsService, apiKey)
|
||||
|
||||
await expect(fun).rejects.toThrow(
|
||||
`Invalid API key or API access not enabled for admin '${admin.id}'`
|
||||
)
|
||||
})
|
||||
|
||||
it("Should throw if the API Key is invalid or API access is disabled (w/ group identifier)", async () => {
|
||||
const fun = getAndCheckAdmin(adminsService, apiKey, groupId)
|
||||
|
||||
await expect(fun).rejects.toThrow(
|
||||
`Invalid API key or API access not enabled for admin '${admin.id}'`
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import { BadRequestException } from "@nestjs/common"
|
||||
import { Group } from "./entities/group.entity"
|
||||
import { Admin } from "../admins/entities/admin.entity"
|
||||
import { AdminsService } from "../admins/admins.service"
|
||||
|
||||
export function mapGroupToResponseDTO(group: Group, fingerprint: string = "") {
|
||||
const dto = {
|
||||
@@ -19,27 +16,3 @@ export function mapGroupToResponseDTO(group: Group, fingerprint: string = "") {
|
||||
|
||||
return dto
|
||||
}
|
||||
|
||||
export async function getAndCheckAdmin(
|
||||
adminService: AdminsService,
|
||||
apiKey: string,
|
||||
groupId?: string
|
||||
): Promise<Admin> {
|
||||
const admin = await adminService.findOne({ apiKey })
|
||||
|
||||
if (!apiKey || !admin) {
|
||||
throw new BadRequestException(
|
||||
groupId
|
||||
? `Invalid API key or invalid admin for the group '${groupId}'`
|
||||
: `Invalid API key or invalid admin for the groups`
|
||||
)
|
||||
}
|
||||
|
||||
if (!admin.apiEnabled || admin.apiKey !== apiKey) {
|
||||
throw new BadRequestException(
|
||||
`Invalid API key or API access not enabled for admin '${admin.id}'`
|
||||
)
|
||||
}
|
||||
|
||||
return admin
|
||||
}
|
||||
|
||||
@@ -2,14 +2,17 @@ import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Headers,
|
||||
NotImplementedException,
|
||||
Param,
|
||||
Post,
|
||||
Req,
|
||||
UseGuards
|
||||
} from "@nestjs/common"
|
||||
import {
|
||||
ApiBody,
|
||||
ApiCreatedResponse,
|
||||
ApiExcludeEndpoint,
|
||||
ApiHeader,
|
||||
ApiOperation,
|
||||
ApiTags
|
||||
} from "@nestjs/swagger"
|
||||
@@ -30,17 +33,36 @@ export class InvitesController {
|
||||
@Post()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(ThrottlerGuard)
|
||||
@ApiExcludeEndpoint()
|
||||
@ApiBody({ type: CreateInviteDto })
|
||||
@ApiHeader({ name: "x-api-key", required: true })
|
||||
@ApiCreatedResponse({ type: Invite })
|
||||
@ApiOperation({
|
||||
description: "Creates a new group invite with a unique code."
|
||||
})
|
||||
async createInvite(
|
||||
@Headers() headers: Headers,
|
||||
@Req() req: Request,
|
||||
@Body() dto: CreateInviteDto
|
||||
): Promise<string> {
|
||||
const { code } = await this.invitesService.createInvite(
|
||||
dto,
|
||||
req.session.adminId
|
||||
)
|
||||
): Promise<Invite> {
|
||||
let invite: Invite
|
||||
|
||||
return code
|
||||
const apiKey = headers["x-api-key"] as string
|
||||
|
||||
if (apiKey) {
|
||||
invite = await this.invitesService.createInviteWithApiKey(
|
||||
dto,
|
||||
apiKey
|
||||
)
|
||||
} else if (req.session.adminId) {
|
||||
invite = await this.invitesService.createInviteManually(
|
||||
dto,
|
||||
req.session.adminId
|
||||
)
|
||||
} else {
|
||||
throw new NotImplementedException()
|
||||
}
|
||||
|
||||
return invite
|
||||
}
|
||||
|
||||
@Get(":code")
|
||||
|
||||
@@ -5,15 +5,17 @@ import { Invite } from "./entities/invite.entity"
|
||||
import { InvitesController } from "./invites.controller"
|
||||
import { InvitesService } from "./invites.service"
|
||||
import { AdminsModule } from "../admins/admins.module"
|
||||
import { AdminsService } from "../admins/admins.service"
|
||||
import { Admin } from "../admins/entities/admin.entity"
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
forwardRef(() => GroupsModule),
|
||||
TypeOrmModule.forFeature([Invite]),
|
||||
TypeOrmModule.forFeature([Invite, Admin]),
|
||||
AdminsModule
|
||||
],
|
||||
controllers: [InvitesController],
|
||||
providers: [InvitesService],
|
||||
providers: [InvitesService, AdminsService],
|
||||
exports: [InvitesService]
|
||||
})
|
||||
export class InvitesModule {}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ScheduleModule } from "@nestjs/schedule"
|
||||
import { Test } from "@nestjs/testing"
|
||||
import { TypeOrmModule } from "@nestjs/typeorm"
|
||||
import { ApiKeyActions } from "@bandada/utils"
|
||||
import { Group } from "../groups/entities/group.entity"
|
||||
import { Member } from "../groups/entities/member.entity"
|
||||
import { GroupsService } from "../groups/groups.service"
|
||||
@@ -8,6 +9,8 @@ import { OAuthAccount } from "../credentials/entities/credentials-account.entity
|
||||
import { Invite } from "./entities/invite.entity"
|
||||
import { InvitesService } from "./invites.service"
|
||||
import { AdminsModule } from "../admins/admins.module"
|
||||
import { AdminsService } from "../admins/admins.service"
|
||||
import { Admin } from "../admins/entities/admin.entity"
|
||||
|
||||
jest.mock("@bandada/utils", () => {
|
||||
const originalModule = jest.requireActual("@bandada/utils")
|
||||
@@ -28,7 +31,9 @@ jest.mock("@bandada/utils", () => {
|
||||
describe("InvitesService", () => {
|
||||
let invitesService: InvitesService
|
||||
let groupsService: GroupsService
|
||||
let adminsService: AdminsService
|
||||
let groupId: string
|
||||
let admin: Admin
|
||||
|
||||
beforeAll(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
@@ -38,19 +43,29 @@ describe("InvitesService", () => {
|
||||
type: "sqlite",
|
||||
database: ":memory:",
|
||||
dropSchema: true,
|
||||
entities: [Group, Invite, Member, OAuthAccount],
|
||||
entities: [Group, Invite, Member, OAuthAccount, Admin],
|
||||
synchronize: true
|
||||
})
|
||||
}),
|
||||
TypeOrmModule.forFeature([Group, Invite, Member]),
|
||||
TypeOrmModule.forFeature([Group, Invite, Member, Admin]),
|
||||
ScheduleModule.forRoot(),
|
||||
AdminsModule
|
||||
],
|
||||
providers: [GroupsService, InvitesService]
|
||||
providers: [GroupsService, InvitesService, AdminsService]
|
||||
}).compile()
|
||||
|
||||
invitesService = await module.resolve(InvitesService)
|
||||
groupsService = await module.resolve(GroupsService)
|
||||
adminsService = await module.resolve(AdminsService)
|
||||
|
||||
admin = await adminsService.create({
|
||||
id: "admin",
|
||||
address: "0x"
|
||||
})
|
||||
|
||||
await adminsService.updateApiKey(admin.id, ApiKeyActions.Generate)
|
||||
|
||||
admin = await adminsService.findOne({ id: admin.id })
|
||||
|
||||
const group = await groupsService.createGroup(
|
||||
{
|
||||
@@ -59,7 +74,7 @@ describe("InvitesService", () => {
|
||||
treeDepth: 16,
|
||||
fingerprintDuration: 3600
|
||||
},
|
||||
"admin"
|
||||
admin.id
|
||||
)
|
||||
|
||||
groupId = group.id
|
||||
@@ -71,7 +86,7 @@ describe("InvitesService", () => {
|
||||
group,
|
||||
code,
|
||||
isRedeemed: redeemed
|
||||
} = await invitesService.createInvite({ groupId }, "admin")
|
||||
} = await invitesService.createInvite({ groupId }, admin.id)
|
||||
|
||||
expect(redeemed).toBeFalsy()
|
||||
expect(code).toHaveLength(8)
|
||||
@@ -98,12 +113,225 @@ describe("InvitesService", () => {
|
||||
}
|
||||
}
|
||||
},
|
||||
"admin"
|
||||
admin.id
|
||||
)
|
||||
|
||||
const fun = invitesService.createInvite(
|
||||
{ groupId: group.id },
|
||||
"admin"
|
||||
admin.id
|
||||
)
|
||||
|
||||
await expect(fun).rejects.toThrow(
|
||||
"Credential groups cannot be accessed via invites"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# createInviteManually", () => {
|
||||
it("Should create an invite manually", async () => {
|
||||
const {
|
||||
group,
|
||||
code,
|
||||
isRedeemed: redeemed
|
||||
} = await invitesService.createInviteManually({ groupId }, admin.id)
|
||||
|
||||
expect(redeemed).toBeFalsy()
|
||||
expect(code).toHaveLength(8)
|
||||
expect(group.treeDepth).toBe(16)
|
||||
})
|
||||
|
||||
it("Should not create an invite if the given identifier does not belong to an admin", async () => {
|
||||
const fun = invitesService.createInviteManually(
|
||||
{ groupId },
|
||||
"wrong-admin"
|
||||
)
|
||||
|
||||
await expect(fun).rejects.toThrow("You are not an admin")
|
||||
})
|
||||
|
||||
it("Should not create an invite if the admin is the wrong one", async () => {
|
||||
const admin2 = await adminsService.create({
|
||||
id: "admin2",
|
||||
address: "0x02"
|
||||
})
|
||||
|
||||
await groupsService.createGroup(
|
||||
{
|
||||
name: "Group2",
|
||||
description: "This is a description",
|
||||
treeDepth: 16,
|
||||
fingerprintDuration: 3600,
|
||||
credentials: {
|
||||
id: "GITHUB_FOLLOWERS",
|
||||
criteria: {
|
||||
minFollowers: 12
|
||||
}
|
||||
}
|
||||
},
|
||||
admin2.id
|
||||
)
|
||||
|
||||
const fun = invitesService.createInviteManually(
|
||||
{ groupId },
|
||||
admin2.id
|
||||
)
|
||||
|
||||
await expect(fun).rejects.toThrow("You are not the admin")
|
||||
})
|
||||
|
||||
it("Should not create an invite if the group is a credential group", async () => {
|
||||
const admin3 = await adminsService.create({
|
||||
id: "admin3",
|
||||
address: "0x04"
|
||||
})
|
||||
|
||||
const group = await groupsService.createGroup(
|
||||
{
|
||||
name: "Group3",
|
||||
description: "This is a description",
|
||||
treeDepth: 16,
|
||||
fingerprintDuration: 3600,
|
||||
credentials: {
|
||||
id: "GITHUB_FOLLOWERS",
|
||||
criteria: {
|
||||
minFollowers: 12
|
||||
}
|
||||
}
|
||||
},
|
||||
admin3.id
|
||||
)
|
||||
|
||||
const fun = invitesService.createInviteManually(
|
||||
{ groupId: group.id },
|
||||
admin3.id
|
||||
)
|
||||
|
||||
await expect(fun).rejects.toThrow(
|
||||
"Credential groups cannot be accessed via invites"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("# createInviteWithApiKey", () => {
|
||||
it("Should create an invite manually", async () => {
|
||||
const {
|
||||
group,
|
||||
code,
|
||||
isRedeemed: redeemed
|
||||
} = await invitesService.createInviteWithApiKey(
|
||||
{ groupId },
|
||||
admin.apiKey
|
||||
)
|
||||
|
||||
expect(redeemed).toBeFalsy()
|
||||
expect(code).toHaveLength(8)
|
||||
expect(group.treeDepth).toBe(16)
|
||||
})
|
||||
|
||||
it("Should not create an invite if the given api key is invalid", async () => {
|
||||
const fun = invitesService.createInviteWithApiKey(
|
||||
{ groupId },
|
||||
"wrong-apikey"
|
||||
)
|
||||
|
||||
await expect(fun).rejects.toThrow(
|
||||
`Invalid API key or invalid admin for the group '${groupId}'`
|
||||
)
|
||||
})
|
||||
|
||||
it("Should not create an invite if the given api key does not belong to an admin", async () => {
|
||||
const oldApiKey = admin.apiKey
|
||||
|
||||
await adminsService.updateApiKey(admin.id, ApiKeyActions.Generate)
|
||||
|
||||
const fun = invitesService.createInviteWithApiKey(
|
||||
{ groupId },
|
||||
oldApiKey
|
||||
)
|
||||
|
||||
await expect(fun).rejects.toThrow(
|
||||
`Invalid API key or invalid admin for the group '${groupId}'`
|
||||
)
|
||||
})
|
||||
|
||||
it("Should not create an invite if the given api key is disabled", async () => {
|
||||
await adminsService.updateApiKey(admin.id, ApiKeyActions.Disable)
|
||||
|
||||
admin = await adminsService.findOne({ id: admin.id })
|
||||
|
||||
const fun = invitesService.createInviteWithApiKey(
|
||||
{ groupId },
|
||||
admin.apiKey
|
||||
)
|
||||
|
||||
await expect(fun).rejects.toThrow(
|
||||
`Invalid API key or API access not enabled for admin '${admin.id}'`
|
||||
)
|
||||
})
|
||||
|
||||
it("Should not create an invite if the admin is the wrong one", async () => {
|
||||
let admin2 = await adminsService.create({
|
||||
id: "admin2",
|
||||
address: "0x02"
|
||||
})
|
||||
|
||||
await adminsService.updateApiKey(admin2.id, ApiKeyActions.Generate)
|
||||
|
||||
admin2 = await adminsService.findOne({ id: admin2.id })
|
||||
|
||||
await groupsService.createGroup(
|
||||
{
|
||||
name: "Group2",
|
||||
description: "This is a description",
|
||||
treeDepth: 16,
|
||||
fingerprintDuration: 3600,
|
||||
credentials: {
|
||||
id: "GITHUB_FOLLOWERS",
|
||||
criteria: {
|
||||
minFollowers: 12
|
||||
}
|
||||
}
|
||||
},
|
||||
admin2.id
|
||||
)
|
||||
|
||||
const fun = invitesService.createInviteWithApiKey(
|
||||
{ groupId },
|
||||
admin2.apiKey
|
||||
)
|
||||
|
||||
await expect(fun).rejects.toThrow("You are not the admin")
|
||||
})
|
||||
|
||||
it("Should not create an invite if the group is a credential group", async () => {
|
||||
let admin3 = await adminsService.create({
|
||||
id: "admin3",
|
||||
address: "0x04"
|
||||
})
|
||||
|
||||
await adminsService.updateApiKey(admin3.id, ApiKeyActions.Generate)
|
||||
|
||||
admin3 = await adminsService.findOne({ id: admin3.id })
|
||||
|
||||
const group = await groupsService.createGroup(
|
||||
{
|
||||
name: "Group3",
|
||||
description: "This is a description",
|
||||
treeDepth: 16,
|
||||
fingerprintDuration: 3600,
|
||||
credentials: {
|
||||
id: "GITHUB_FOLLOWERS",
|
||||
criteria: {
|
||||
minFollowers: 12
|
||||
}
|
||||
}
|
||||
},
|
||||
admin3.id
|
||||
)
|
||||
|
||||
const fun = invitesService.createInviteWithApiKey(
|
||||
{ groupId: group.id },
|
||||
admin3.apiKey
|
||||
)
|
||||
|
||||
await expect(fun).rejects.toThrow(
|
||||
@@ -116,7 +344,7 @@ describe("InvitesService", () => {
|
||||
it("Should get an invite", async () => {
|
||||
const { code } = await invitesService.createInvite(
|
||||
{ groupId },
|
||||
"admin"
|
||||
admin.id
|
||||
)
|
||||
|
||||
const invite = await invitesService.getInvite(code)
|
||||
@@ -137,7 +365,7 @@ describe("InvitesService", () => {
|
||||
let invite: Invite
|
||||
|
||||
beforeAll(async () => {
|
||||
invite = await invitesService.createInvite({ groupId }, "admin")
|
||||
invite = await invitesService.createInvite({ groupId }, admin.id)
|
||||
})
|
||||
|
||||
it("Should not redeem an invite if group name does not match", async () => {
|
||||
|
||||
@@ -11,6 +11,8 @@ import { Repository } from "typeorm"
|
||||
import { GroupsService } from "../groups/groups.service"
|
||||
import { CreateInviteDto } from "./dto/create-invite.dto"
|
||||
import { Invite } from "./entities/invite.entity"
|
||||
import { getAndCheckAdmin } from "../utils"
|
||||
import { AdminsService } from "../admins/admins.service"
|
||||
|
||||
@Injectable()
|
||||
export class InvitesService {
|
||||
@@ -18,9 +20,46 @@ export class InvitesService {
|
||||
@InjectRepository(Invite)
|
||||
private readonly inviteRepository: Repository<Invite>,
|
||||
@Inject(forwardRef(() => GroupsService))
|
||||
private readonly groupsService: GroupsService
|
||||
private readonly groupsService: GroupsService,
|
||||
private readonly adminsService: AdminsService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Create a new group invite using API Key.
|
||||
* @param dto External parameters used to create a new group invite.
|
||||
* @param apiKey the API Key.
|
||||
* @returns The group invite.
|
||||
*/
|
||||
async createInviteWithApiKey(
|
||||
dto: CreateInviteDto,
|
||||
apiKey: string
|
||||
): Promise<Invite> {
|
||||
const admin = await getAndCheckAdmin(
|
||||
this.adminsService,
|
||||
apiKey,
|
||||
dto.groupId
|
||||
)
|
||||
|
||||
return this.createInvite(dto, admin.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new group invite manually without using API Key.
|
||||
* @param dto External parameters used to create a new group invite.
|
||||
* @param adminId Group admin id.
|
||||
* @returns The group invite.
|
||||
*/
|
||||
async createInviteManually(
|
||||
dto: CreateInviteDto,
|
||||
adminId: string
|
||||
): Promise<Invite> {
|
||||
const admin = await this.adminsService.findOne({ id: adminId })
|
||||
|
||||
if (!admin) throw new BadRequestException(`You are not an admin`)
|
||||
|
||||
return this.createInvite(dto, adminId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new group invite with a unique code. Group invites can only be
|
||||
* created by group admins.
|
||||
|
||||
27
apps/api/src/app/utils/getAndCheckAdmin.ts
Normal file
27
apps/api/src/app/utils/getAndCheckAdmin.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { BadRequestException } from "@nestjs/common"
|
||||
import { AdminsService } from "../admins/admins.service"
|
||||
import { Admin } from "../admins/entities/admin.entity"
|
||||
|
||||
export default async function getAndCheckAdmin(
|
||||
adminService: AdminsService,
|
||||
apiKey: string,
|
||||
groupId?: string
|
||||
): Promise<Admin> {
|
||||
const admin = await adminService.findOne({ apiKey })
|
||||
|
||||
if (!apiKey || !admin) {
|
||||
throw new BadRequestException(
|
||||
groupId
|
||||
? `Invalid API key or invalid admin for the group '${groupId}'`
|
||||
: `Invalid API key or invalid admin for the groups`
|
||||
)
|
||||
}
|
||||
|
||||
if (!admin.apiEnabled || admin.apiKey !== apiKey) {
|
||||
throw new BadRequestException(
|
||||
`Invalid API key or API access not enabled for admin '${admin.id}'`
|
||||
)
|
||||
}
|
||||
|
||||
return admin
|
||||
}
|
||||
@@ -1,7 +1,49 @@
|
||||
import { ScheduleModule } from "@nestjs/schedule"
|
||||
import { Test } from "@nestjs/testing"
|
||||
import { TypeOrmModule } from "@nestjs/typeorm"
|
||||
import { ApiKeyActions } from "@bandada/utils"
|
||||
import { Invite } from "../invites/entities/invite.entity"
|
||||
import { InvitesService } from "../invites/invites.service"
|
||||
import { OAuthAccount } from "../credentials/entities/credentials-account.entity"
|
||||
import { AdminsService } from "../admins/admins.service"
|
||||
import { AdminsModule } from "../admins/admins.module"
|
||||
import { Admin } from "../admins/entities/admin.entity"
|
||||
import { GroupsService } from "../groups/groups.service"
|
||||
import { Group } from "../groups/entities/group.entity"
|
||||
import { Member } from "../groups/entities/member.entity"
|
||||
import mapEntity from "./mapEntity"
|
||||
import stringifyJSON from "./stringifyJSON"
|
||||
import getAndCheckAdmin from "./getAndCheckAdmin"
|
||||
|
||||
describe("Utils", () => {
|
||||
let groupsService: GroupsService
|
||||
let adminsService: AdminsService
|
||||
|
||||
beforeAll(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [
|
||||
TypeOrmModule.forRootAsync({
|
||||
useFactory: () => ({
|
||||
type: "sqlite",
|
||||
database: ":memory:",
|
||||
dropSchema: true,
|
||||
entities: [Group, Invite, Member, OAuthAccount, Admin],
|
||||
synchronize: true
|
||||
})
|
||||
}),
|
||||
TypeOrmModule.forFeature([Group, Invite, Member, Admin]),
|
||||
ScheduleModule.forRoot(),
|
||||
AdminsModule
|
||||
],
|
||||
providers: [GroupsService, InvitesService, AdminsService]
|
||||
}).compile()
|
||||
|
||||
groupsService = await module.resolve(GroupsService)
|
||||
adminsService = await module.resolve(AdminsService)
|
||||
|
||||
await groupsService.initialize()
|
||||
})
|
||||
|
||||
describe("# mapEntity", () => {
|
||||
it("Should map a DB entity", async () => {
|
||||
const entity = mapEntity({ id: 1, a: 2 }) as any
|
||||
@@ -18,4 +60,68 @@ describe("Utils", () => {
|
||||
expect(entity.a).toBe("143234")
|
||||
})
|
||||
})
|
||||
|
||||
describe("# getAndCheckAdmin", () => {
|
||||
const groupId = "1"
|
||||
let apiKey = ""
|
||||
let admin: Admin = {} as any
|
||||
|
||||
beforeAll(async () => {
|
||||
admin = await adminsService.create({
|
||||
id: groupId,
|
||||
address: "0x00"
|
||||
})
|
||||
|
||||
apiKey = await adminsService.updateApiKey(
|
||||
admin.id,
|
||||
ApiKeyActions.Generate
|
||||
)
|
||||
|
||||
admin = await adminsService.findOne({ id: admin.id })
|
||||
})
|
||||
|
||||
it("Should successfully check and return the admin", async () => {
|
||||
const checkedAdmin = await getAndCheckAdmin(adminsService, apiKey)
|
||||
|
||||
expect(checkedAdmin.id).toBe(admin.id)
|
||||
expect(checkedAdmin.address).toBe(admin.address)
|
||||
expect(checkedAdmin.apiKey).toBe(admin.apiKey)
|
||||
expect(checkedAdmin.apiEnabled).toBe(admin.apiEnabled)
|
||||
expect(checkedAdmin.username).toBe(admin.username)
|
||||
})
|
||||
|
||||
it("Should throw if the API Key or admin is invalid", async () => {
|
||||
const fun = getAndCheckAdmin(adminsService, "wrong")
|
||||
|
||||
await expect(fun).rejects.toThrow(
|
||||
`Invalid API key or invalid admin for the groups`
|
||||
)
|
||||
})
|
||||
|
||||
it("Should throw if the API Key or admin is invalid (w/ group identifier)", async () => {
|
||||
const fun = getAndCheckAdmin(adminsService, "wrong", groupId)
|
||||
|
||||
await expect(fun).rejects.toThrow(
|
||||
`Invalid API key or invalid admin for the group '${groupId}'`
|
||||
)
|
||||
})
|
||||
|
||||
it("Should throw if the API Key is invalid or API access is disabled", async () => {
|
||||
await adminsService.updateApiKey(admin.id, ApiKeyActions.Disable)
|
||||
|
||||
const fun = getAndCheckAdmin(adminsService, apiKey)
|
||||
|
||||
await expect(fun).rejects.toThrow(
|
||||
`Invalid API key or API access not enabled for admin '${admin.id}'`
|
||||
)
|
||||
})
|
||||
|
||||
it("Should throw if the API Key is invalid or API access is disabled (w/ group identifier)", async () => {
|
||||
const fun = getAndCheckAdmin(adminsService, apiKey, groupId)
|
||||
|
||||
await expect(fun).rejects.toThrow(
|
||||
`Invalid API key or API access not enabled for admin '${admin.id}'`
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import mapEntity from "./mapEntity"
|
||||
import stringifyJSON from "./stringifyJSON"
|
||||
import getAndCheckAdmin from "./getAndCheckAdmin"
|
||||
|
||||
export { mapEntity, stringifyJSON }
|
||||
export { mapEntity, stringifyJSON, getAndCheckAdmin }
|
||||
|
||||
@@ -73,7 +73,7 @@ export default function HomePage(): JSX.Element {
|
||||
const identityCommitment = identity.getCommitment().toString()
|
||||
|
||||
const hasJoined = await isGroupMember(
|
||||
invite.groupId,
|
||||
invite.group.id,
|
||||
identityCommitment
|
||||
)
|
||||
|
||||
@@ -89,7 +89,7 @@ export default function HomePage(): JSX.Element {
|
||||
}
|
||||
|
||||
const response = await addMemberByInviteCode(
|
||||
invite.groupId,
|
||||
invite.group.id,
|
||||
identityCommitment,
|
||||
inviteCode
|
||||
)
|
||||
|
||||
@@ -20,14 +20,14 @@ export async function generateMagicLink(
|
||||
clientUrl?: string
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
const code = await request(`${API_URL}/invites`, {
|
||||
const invite = await request(`${API_URL}/invites`, {
|
||||
method: "POST",
|
||||
data: {
|
||||
groupId
|
||||
}
|
||||
})
|
||||
|
||||
return (clientUrl || CLIENT_INVITES_URL).replace("\\", code)
|
||||
return (clientUrl || CLIENT_INVITES_URL).replace("\\", invite.code)
|
||||
} catch (error: any) {
|
||||
console.error(error)
|
||||
createAlert(error.response.data.message)
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
removeMemberByApiKey,
|
||||
removeMembersByApiKey
|
||||
} from "./groups"
|
||||
import { getInvite } from "./invites"
|
||||
import { createInvite, getInvite } from "./invites"
|
||||
|
||||
export default class ApiSdk {
|
||||
private _url: string
|
||||
@@ -304,13 +304,25 @@ export default class ApiSdk {
|
||||
await removeMembersByApiKey(this._config, groupId, memberIds, apiKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new group invite.
|
||||
* @param groupId The group identifier.
|
||||
* @param apiKey The api key.
|
||||
* @returns Specific invite.
|
||||
*/
|
||||
async createInvite(groupId: string, apiKey: string): Promise<Invite> {
|
||||
const invite = await createInvite(this._config, groupId, apiKey)
|
||||
|
||||
return invite
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific invite.
|
||||
* @param inviteCode Invite code.
|
||||
* @returns Specific invite.
|
||||
*/
|
||||
async getInvite(inviteCode: string): Promise<Invite> {
|
||||
const invite = getInvite(this._config, inviteCode)
|
||||
const invite = await getInvite(this._config, inviteCode)
|
||||
|
||||
return invite
|
||||
}
|
||||
|
||||
@@ -521,10 +521,53 @@ describe("Bandada API SDK", () => {
|
||||
})
|
||||
})
|
||||
describe("Invites", () => {
|
||||
it("# createInvite", async () => {
|
||||
const groupId = "95633257675970239314311768035433"
|
||||
const groupName = "Group 1"
|
||||
const group = {
|
||||
id: groupId,
|
||||
name: groupName,
|
||||
description: "This is Group 1",
|
||||
adminId:
|
||||
"0x63229164c457584616006e31d1e171e6cdd4163695bc9c4bf0227095998ffa4c",
|
||||
treeDepth: 16,
|
||||
fingerprintDuration: 3600,
|
||||
credentials: null,
|
||||
apiEnabled: false,
|
||||
apiKey: null,
|
||||
createdAt: "2023-08-09T18:09:53.000Z",
|
||||
updatedAt: "2023-08-09T18:09:53.000Z"
|
||||
}
|
||||
const apiKey = "70f07d0d-6aa2-4fe1-b4b9-06c271a641dc"
|
||||
const inviteId = 1
|
||||
const inviteCode = "C5VAG4HD"
|
||||
const inviteCreatedAt = "2023-08-09T18:10:02.000Z"
|
||||
|
||||
requestMocked.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
id: inviteId,
|
||||
code: inviteCode,
|
||||
isRedeemed: false,
|
||||
createdAt: inviteCreatedAt,
|
||||
group
|
||||
})
|
||||
)
|
||||
|
||||
apiSdk = new ApiSdk(SupportedUrl.DEV)
|
||||
const invite: Invite = await apiSdk.createInvite(groupId, apiKey)
|
||||
|
||||
expect(invite.id).toBe(inviteId)
|
||||
expect(invite.code).toBe(inviteCode)
|
||||
expect(invite.createdAt).toBe(inviteCreatedAt)
|
||||
expect(invite.code).toBe(inviteCode)
|
||||
expect(invite.group).toStrictEqual(group)
|
||||
})
|
||||
|
||||
describe("# getInvite", () => {
|
||||
it("Should return an invite", async () => {
|
||||
requestMocked.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
id: 1,
|
||||
code: "C5VAG4HD",
|
||||
isRedeemed: false,
|
||||
createdAt: "2023-08-09T18:10:02.000Z",
|
||||
|
||||
@@ -20,3 +20,27 @@ export async function getInvite(
|
||||
|
||||
return invite
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates one new group invite.
|
||||
* @param groupId The group identifier.
|
||||
* @param apiKey API Key of the admin.
|
||||
* @returns Invite.
|
||||
*/
|
||||
export async function createInvite(
|
||||
config: object,
|
||||
groupId: string,
|
||||
apiKey: string
|
||||
): Promise<Invite> {
|
||||
const newConfig: any = {
|
||||
method: "post",
|
||||
data: groupId,
|
||||
...config
|
||||
}
|
||||
|
||||
newConfig.headers["x-api-key"] = apiKey
|
||||
|
||||
const req = await request(url, newConfig)
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
@@ -44,12 +44,11 @@ type GroupSummary = {
|
||||
}
|
||||
|
||||
export type Invite = {
|
||||
id: number
|
||||
code: string
|
||||
isRedeemed: boolean
|
||||
createdAt: Date
|
||||
group: GroupSummary
|
||||
groupName: string
|
||||
groupId: string
|
||||
createdAt: Date
|
||||
}
|
||||
|
||||
export enum SupportedUrl {
|
||||
|
||||
Reference in New Issue
Block a user