From 3e84bca779ba39ee649283818723c4559f7e05e7 Mon Sep 17 00:00:00 2001 From: Jayan Ratna Date: Tue, 10 Sep 2024 10:31:57 +1200 Subject: [PATCH 1/5] feat: Add invites resource to Agency API --- src/agency.ts | 10 ++++++- src/resources/agency/index.ts | 1 + .../agency/invites/fixtures/invite-list.json | 17 ++++++++++++ .../agency/invites/fixtures/invite.json | 3 +++ .../agency/invites/interfaces/index.ts | 1 + .../invites/interfaces/invite.interface.ts | 9 +++++++ src/resources/agency/invites/invites.spec.ts | 26 +++++++++++++++++++ src/resources/agency/invites/invites.ts | 23 ++++++++++++++++ .../agency/invites/serializers/index.ts | 1 + .../invites/serializers/invite.serializer.ts | 17 ++++++++++++ 10 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 src/resources/agency/invites/fixtures/invite-list.json create mode 100644 src/resources/agency/invites/fixtures/invite.json create mode 100644 src/resources/agency/invites/interfaces/index.ts create mode 100644 src/resources/agency/invites/interfaces/invite.interface.ts create mode 100644 src/resources/agency/invites/invites.spec.ts create mode 100644 src/resources/agency/invites/invites.ts create mode 100644 src/resources/agency/invites/serializers/index.ts create mode 100644 src/resources/agency/invites/serializers/invite.serializer.ts diff --git a/src/agency.ts b/src/agency.ts index 772b31e..89889c0 100644 --- a/src/agency.ts +++ b/src/agency.ts @@ -1,4 +1,11 @@ -import { Brand, Domains, Members, Projects, Roles } from './resources/agency' +import { + Brand, + Domains, + Invites, + Members, + Projects, + Roles, +} from './resources/agency' import type { Blutui } from './blutui' import type { GetOptions, PostOptions } from './types' @@ -6,6 +13,7 @@ import type { GetOptions, PostOptions } from './types' export class Agency { readonly brand = new Brand(this) readonly domains = new Domains(this) + readonly invites = new Invites(this) readonly members = new Members(this) readonly projects = new Projects(this) readonly roles = new Roles(this) diff --git a/src/resources/agency/index.ts b/src/resources/agency/index.ts index d17fd52..b464cdf 100644 --- a/src/resources/agency/index.ts +++ b/src/resources/agency/index.ts @@ -1,5 +1,6 @@ export { Brand } from './brand/brand' export { Domains } from './domains/domains' +export { Invites } from './invites/invites' export { Members } from './members/members' export { Projects } from './projects/projects' export { Roles } from './roles/roles' diff --git a/src/resources/agency/invites/fixtures/invite-list.json b/src/resources/agency/invites/fixtures/invite-list.json new file mode 100644 index 0000000..7330c13 --- /dev/null +++ b/src/resources/agency/invites/fixtures/invite-list.json @@ -0,0 +1,17 @@ +{ + "object": "list", + "data": [ + { + + } + ], + "meta": { + "hasMore": false, + "currentPage": 1, + "from": 1, + "to": 1, + "perPage": 10, + "total": 1, + "lastPage": 1 + } +} diff --git a/src/resources/agency/invites/fixtures/invite.json b/src/resources/agency/invites/fixtures/invite.json new file mode 100644 index 0000000..0db3279 --- /dev/null +++ b/src/resources/agency/invites/fixtures/invite.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/src/resources/agency/invites/interfaces/index.ts b/src/resources/agency/invites/interfaces/index.ts new file mode 100644 index 0000000..2448850 --- /dev/null +++ b/src/resources/agency/invites/interfaces/index.ts @@ -0,0 +1 @@ +export * from './invite.interface' diff --git a/src/resources/agency/invites/interfaces/invite.interface.ts b/src/resources/agency/invites/interfaces/invite.interface.ts new file mode 100644 index 0000000..347c1ea --- /dev/null +++ b/src/resources/agency/invites/interfaces/invite.interface.ts @@ -0,0 +1,9 @@ +export interface Invite { + id: string + object: 'invite' +} + +export interface InviteResponse { + id: string + object: 'invite' +} diff --git a/src/resources/agency/invites/invites.spec.ts b/src/resources/agency/invites/invites.spec.ts new file mode 100644 index 0000000..1023c55 --- /dev/null +++ b/src/resources/agency/invites/invites.spec.ts @@ -0,0 +1,26 @@ +import fetch from 'jest-fetch-mock' +import { Blutui } from '@/blutui' +import { fetchOnce, fetchURL } from '@/utils/testing' + +// import inviteFixture from './fixtures/invite.json' +import inviteListFixture from './fixtures/invite-list.json' + +const accessToken = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' +const blutui = new Blutui(accessToken) + +describe('Invite', () => { + beforeEach(() => fetch.resetMocks()) + + describe('list', () => { + it('can retrieve a list of invites', async () => { + fetchOnce(inviteListFixture) + const invites = await blutui.agency('foo').invites.list() + + expect(fetchURL()).toBe(`${blutui.baseURL}/v1/agencies/foo/invites`) + expect(invites).toMatchObject({ + object: 'list', + }) + }) + }) +}) diff --git a/src/resources/agency/invites/invites.ts b/src/resources/agency/invites/invites.ts new file mode 100644 index 0000000..69f771f --- /dev/null +++ b/src/resources/agency/invites/invites.ts @@ -0,0 +1,23 @@ +import { deserializeInviteList } from './serializers' + +import type { Agency } from '@/agency' +import type { Invite, InviteResponse } from './interfaces' +import type { List, ListResponse, PaginationOptions } from '@/types' + +export class Invites { + constructor(private readonly agency: Agency) {} + + /** + * Get a list of invites for the current agency. + */ + async list(options?: PaginationOptions): Promise> { + const { data } = await this.agency.get>( + 'invites', + { + query: options, + } + ) + + return deserializeInviteList(data) + } +} diff --git a/src/resources/agency/invites/serializers/index.ts b/src/resources/agency/invites/serializers/index.ts new file mode 100644 index 0000000..2336932 --- /dev/null +++ b/src/resources/agency/invites/serializers/index.ts @@ -0,0 +1 @@ +export * from './invite.serializer' diff --git a/src/resources/agency/invites/serializers/invite.serializer.ts b/src/resources/agency/invites/serializers/invite.serializer.ts new file mode 100644 index 0000000..41b6796 --- /dev/null +++ b/src/resources/agency/invites/serializers/invite.serializer.ts @@ -0,0 +1,17 @@ +import { deserializePaginationMeta } from '@/utils/serializers' + +import type { List, ListResponse } from '@/types' +import type { Invite, InviteResponse } from '../interfaces' + +export const deserializeInvite = (invite: InviteResponse): Invite => ({ + id: invite.id, + object: invite.object, +}) + +export const deserializeInviteList = ( + invites: ListResponse +): List => ({ + object: 'list', + data: invites.data.map(deserializeInvite), + meta: deserializePaginationMeta(invites.meta), +}) From a120c3812aeeb8ae0d6337e0a2916e7a9989e21f Mon Sep 17 00:00:00 2001 From: Jayan Ratna Date: Tue, 10 Sep 2024 11:19:03 +1200 Subject: [PATCH 2/5] add additional methods --- .../agency/invites/fixtures/invite.json | 15 ++++++- .../agency/invites/interfaces/index.ts | 1 + .../update-invite-options.interface.ts | 7 ++++ src/resources/agency/invites/invites.spec.ts | 29 ++++++++++++- src/resources/agency/invites/invites.ts | 41 +++++++++++++++++-- .../agency/invites/serializers/index.ts | 1 + .../update-invite-options.serializer.ts | 10 +++++ src/resources/agency/roles/roles.spec.ts | 2 +- 8 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 src/resources/agency/invites/interfaces/update-invite-options.interface.ts create mode 100644 src/resources/agency/invites/serializers/update-invite-options.serializer.ts diff --git a/src/resources/agency/invites/fixtures/invite.json b/src/resources/agency/invites/fixtures/invite.json index 0db3279..67e6dfa 100644 --- a/src/resources/agency/invites/fixtures/invite.json +++ b/src/resources/agency/invites/fixtures/invite.json @@ -1,3 +1,16 @@ { - + "id": "9cf6c82a-a457-4787-8aad-81ce8ddf3235", + "object": "invite", + "email": "mara@blutui.dev", + "role": { + "id": 3, + "object": "role", + "name": "Developer", + "description": "The frontend developer role", + "is_super": false, + "created_at": 1690330767, + "updated_at": 1721869652 + }, + "created_at": 1725837341, + "updated_at": 1725917399 } diff --git a/src/resources/agency/invites/interfaces/index.ts b/src/resources/agency/invites/interfaces/index.ts index 2448850..8cc6150 100644 --- a/src/resources/agency/invites/interfaces/index.ts +++ b/src/resources/agency/invites/interfaces/index.ts @@ -1 +1,2 @@ export * from './invite.interface' +export * from './update-invite-options.interface' diff --git a/src/resources/agency/invites/interfaces/update-invite-options.interface.ts b/src/resources/agency/invites/interfaces/update-invite-options.interface.ts new file mode 100644 index 0000000..1212412 --- /dev/null +++ b/src/resources/agency/invites/interfaces/update-invite-options.interface.ts @@ -0,0 +1,7 @@ +export interface UpdateInviteOptions { + role: number +} + +export interface SerializedUpdateInviteOptions { + role: number +} diff --git a/src/resources/agency/invites/invites.spec.ts b/src/resources/agency/invites/invites.spec.ts index 1023c55..05e6945 100644 --- a/src/resources/agency/invites/invites.spec.ts +++ b/src/resources/agency/invites/invites.spec.ts @@ -2,7 +2,7 @@ import fetch from 'jest-fetch-mock' import { Blutui } from '@/blutui' import { fetchOnce, fetchURL } from '@/utils/testing' -// import inviteFixture from './fixtures/invite.json' +import inviteFixture from './fixtures/invite.json' import inviteListFixture from './fixtures/invite-list.json' const accessToken = @@ -23,4 +23,31 @@ describe('Invite', () => { }) }) }) + + describe('update', () => { + it('can update an agency invite', async () => { + fetchOnce(inviteFixture) + const invite = await blutui + .agency('foo') + .invites.update(inviteFixture.id, { role: 3 }) + + expect(fetchURL()).toBe( + `${blutui.baseURL}/v1/agencies/foo/invites/${inviteFixture.id}` + ) + expect(invite).toMatchObject({ + object: 'invite', + }) + }) + }) + + describe('remove', () => { + it('can remove an invite', async () => { + fetchOnce(inviteFixture) + await blutui.agency('foo').invites.remove(inviteFixture.id) + + expect(fetchURL()).toBe( + `${blutui.baseURL}/v1/agencies/foo/invites/${inviteFixture.id}` + ) + }) + }) }) diff --git a/src/resources/agency/invites/invites.ts b/src/resources/agency/invites/invites.ts index 69f771f..61eee43 100644 --- a/src/resources/agency/invites/invites.ts +++ b/src/resources/agency/invites/invites.ts @@ -1,8 +1,22 @@ -import { deserializeInviteList } from './serializers' +import { + deserializeInvite, + deserializeInviteList, + serializeUpdateInviteOptions, +} from './serializers' import type { Agency } from '@/agency' -import type { Invite, InviteResponse } from './interfaces' -import type { List, ListResponse, PaginationOptions } from '@/types' +import type { + Invite, + InviteResponse, + SerializedUpdateInviteOptions, + UpdateInviteOptions, +} from './interfaces' +import type { + DeletedResponse, + List, + ListResponse, + PaginationOptions, +} from '@/types' export class Invites { constructor(private readonly agency: Agency) {} @@ -20,4 +34,25 @@ export class Invites { return deserializeInviteList(data) } + + /** + * Update a invite in the current agency. + */ + async update(id: string, payload: UpdateInviteOptions): Promise { + const { data } = await this.agency.patch< + InviteResponse, + SerializedUpdateInviteOptions + >(`invites/${id}`, serializeUpdateInviteOptions(payload)) + + return deserializeInvite(data) + } + + /** + * Remove a invite from the current agency. + */ + async remove(id: string): Promise { + const { data } = await this.agency.delete(`invites/${id}`) + + return data + } } diff --git a/src/resources/agency/invites/serializers/index.ts b/src/resources/agency/invites/serializers/index.ts index 2336932..06250e7 100644 --- a/src/resources/agency/invites/serializers/index.ts +++ b/src/resources/agency/invites/serializers/index.ts @@ -1 +1,2 @@ export * from './invite.serializer' +export * from './update-invite-options.serializer' diff --git a/src/resources/agency/invites/serializers/update-invite-options.serializer.ts b/src/resources/agency/invites/serializers/update-invite-options.serializer.ts new file mode 100644 index 0000000..0a94287 --- /dev/null +++ b/src/resources/agency/invites/serializers/update-invite-options.serializer.ts @@ -0,0 +1,10 @@ +import type { + SerializedUpdateInviteOptions, + UpdateInviteOptions, +} from '../interfaces' + +export const serializeUpdateInviteOptions = ( + options: UpdateInviteOptions +): SerializedUpdateInviteOptions => ({ + role: options.role, +}) diff --git a/src/resources/agency/roles/roles.spec.ts b/src/resources/agency/roles/roles.spec.ts index 0a3fc63..a9a2672 100644 --- a/src/resources/agency/roles/roles.spec.ts +++ b/src/resources/agency/roles/roles.spec.ts @@ -70,7 +70,7 @@ describe('Role', () => { }) describe('remove', () => { - it('can remove an role', async () => { + it('can remove a role', async () => { fetchOnce(roleFixture) await blutui.agency('foo').roles.remove(roleFixture.id) From 0abe998cec5380305a7ce827c1d30d7b7d2b6225 Mon Sep 17 00:00:00 2001 From: Jayan Ratna Date: Wed, 11 Sep 2024 10:39:01 +1200 Subject: [PATCH 3/5] add the ability to expand the role object --- .../agency/invites/fixtures/invite-list.json | 15 ++++++++++++++- .../invites/interfaces/invite.interface.ts | 10 ++++++++++ src/resources/agency/invites/invites.spec.ts | 18 ++++++++++++++++++ src/resources/agency/invites/invites.ts | 5 ++++- .../invites/serializers/invite.serializer.ts | 6 ++++++ 5 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/resources/agency/invites/fixtures/invite-list.json b/src/resources/agency/invites/fixtures/invite-list.json index 7330c13..ad7a4eb 100644 --- a/src/resources/agency/invites/fixtures/invite-list.json +++ b/src/resources/agency/invites/fixtures/invite-list.json @@ -2,7 +2,20 @@ "object": "list", "data": [ { - + "id": "9cf6c82a-a457-4787-8aad-81ce8ddf3235", + "object": "invite", + "email": "mara@blutui.dev", + "role": { + "id": 3, + "object": "role", + "name": "Developer", + "description": "The frontend developer role", + "is_super": false, + "created_at": 1690330767, + "updated_at": 1721869652 + }, + "created_at": 1725837341, + "updated_at": 1725917399 } ], "meta": { diff --git a/src/resources/agency/invites/interfaces/invite.interface.ts b/src/resources/agency/invites/interfaces/invite.interface.ts index 347c1ea..681b9d5 100644 --- a/src/resources/agency/invites/interfaces/invite.interface.ts +++ b/src/resources/agency/invites/interfaces/invite.interface.ts @@ -1,9 +1,19 @@ +import type { Role, RoleResponse } from '../../roles/interfaces' + export interface Invite { id: string object: 'invite' + email: string + role: string | Role + createdAt: number + updatedAt: number } export interface InviteResponse { id: string object: 'invite' + email: string + role: string | RoleResponse + created_at: number + updated_at: number } diff --git a/src/resources/agency/invites/invites.spec.ts b/src/resources/agency/invites/invites.spec.ts index 05e6945..1b477e3 100644 --- a/src/resources/agency/invites/invites.spec.ts +++ b/src/resources/agency/invites/invites.spec.ts @@ -22,6 +22,24 @@ describe('Invite', () => { object: 'list', }) }) + + it('can retrieve a list of invites with an expanded role', async () => { + fetchOnce(inviteListFixture) + const invites = await blutui + .agency('foo') + .invites.list({ expand: ['role'] }) + + expect(fetchURL()).toBe( + encodeURI(`${blutui.baseURL}/v1/agencies/foo/invites?expand[]=role`) + ) + + expect(invites).toMatchObject({ + object: 'list', + }) + expect(invites.data[0].role).toMatchObject({ + object: 'role', + }) + }) }) describe('update', () => { diff --git a/src/resources/agency/invites/invites.ts b/src/resources/agency/invites/invites.ts index 61eee43..214cc57 100644 --- a/src/resources/agency/invites/invites.ts +++ b/src/resources/agency/invites/invites.ts @@ -13,6 +13,7 @@ import type { } from './interfaces' import type { DeletedResponse, + Expandable, List, ListResponse, PaginationOptions, @@ -24,7 +25,9 @@ export class Invites { /** * Get a list of invites for the current agency. */ - async list(options?: PaginationOptions): Promise> { + async list( + options?: PaginationOptions & Expandable<'role'> + ): Promise> { const { data } = await this.agency.get>( 'invites', { diff --git a/src/resources/agency/invites/serializers/invite.serializer.ts b/src/resources/agency/invites/serializers/invite.serializer.ts index 41b6796..12e1919 100644 --- a/src/resources/agency/invites/serializers/invite.serializer.ts +++ b/src/resources/agency/invites/serializers/invite.serializer.ts @@ -1,4 +1,5 @@ import { deserializePaginationMeta } from '@/utils/serializers' +import { deserializeRole } from '../../roles/serializers' import type { List, ListResponse } from '@/types' import type { Invite, InviteResponse } from '../interfaces' @@ -6,6 +7,11 @@ import type { Invite, InviteResponse } from '../interfaces' export const deserializeInvite = (invite: InviteResponse): Invite => ({ id: invite.id, object: invite.object, + email: invite.email, + role: + invite.role instanceof Object ? deserializeRole(invite.role) : invite.role, + createdAt: invite.created_at, + updatedAt: invite.updated_at, }) export const deserializeInviteList = ( From e47030252400ec7ee1c603b712b70766439091db Mon Sep 17 00:00:00 2001 From: Jayan Ratna Date: Wed, 11 Sep 2024 10:39:49 +1200 Subject: [PATCH 4/5] update interface type --- src/resources/agency/invites/interfaces/invite.interface.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/resources/agency/invites/interfaces/invite.interface.ts b/src/resources/agency/invites/interfaces/invite.interface.ts index 681b9d5..56d784a 100644 --- a/src/resources/agency/invites/interfaces/invite.interface.ts +++ b/src/resources/agency/invites/interfaces/invite.interface.ts @@ -4,7 +4,7 @@ export interface Invite { id: string object: 'invite' email: string - role: string | Role + role: number | Role createdAt: number updatedAt: number } @@ -13,7 +13,7 @@ export interface InviteResponse { id: string object: 'invite' email: string - role: string | RoleResponse + role: number | RoleResponse created_at: number updated_at: number } From d3fd3a5c38c310aaf0f191ce3c7c5809dfb0b447 Mon Sep 17 00:00:00 2001 From: Jayan Ratna Date: Wed, 11 Sep 2024 11:13:25 +1200 Subject: [PATCH 5/5] add CODEOWNERS file --- .github/CODEOWNERS | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..91d70f5 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,5 @@ +# See GitHub's docs for more details: +# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners + +# TypeScript Team +* @blutui/typescript