feat: add missing createInvite api endpoint

This commit is contained in:
Jeeiii
2024-04-04 23:34:01 +02:00
parent 367f7c6b0d
commit 3ebd4de898
16 changed files with 535 additions and 126 deletions

View File

@@ -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 {

View File

@@ -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}'`
)
})
})
})

View File

@@ -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
}

View File

@@ -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")

View File

@@ -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 {}

View File

@@ -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 () => {

View File

@@ -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.

View 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
}

View File

@@ -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}'`
)
})
})
})

View File

@@ -1,4 +1,5 @@
import mapEntity from "./mapEntity"
import stringifyJSON from "./stringifyJSON"
import getAndCheckAdmin from "./getAndCheckAdmin"
export { mapEntity, stringifyJSON }
export { mapEntity, stringifyJSON, getAndCheckAdmin }

View File

@@ -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
)

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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",

View File

@@ -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
}

View File

@@ -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 {