diff --git a/src/blutui.ts b/src/blutui.ts index 9e5c0aa..ae6886b 100644 --- a/src/blutui.ts +++ b/src/blutui.ts @@ -10,7 +10,7 @@ import { } from './exceptions' import { Client } from './utils/client' -import { User } from './resources' +import { Agencies, User } from './resources' import type { BlutuiOptions, @@ -30,6 +30,7 @@ export class Blutui { private readonly client: Client private readonly _agencies: Record = {} + readonly agencies = new Agencies(this) readonly user = new User(this) /** diff --git a/src/resources/agencies/agencies.spec.ts b/src/resources/agencies/agencies.spec.ts new file mode 100644 index 0000000..5402a95 --- /dev/null +++ b/src/resources/agencies/agencies.spec.ts @@ -0,0 +1,60 @@ +import fetch from 'jest-fetch-mock' +import { Blutui } from '@/blutui' +import { fetchOnce, fetchURL } from '@/utils/testing' + +import agencyFixture from './fixtures/agency.json' +import agencyListFixture from './fixtures/agency-list.json' + +const accessToken = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' +const blutui = new Blutui(accessToken) + +describe('Agency', () => { + beforeEach(() => fetch.resetMocks()) + + describe('list', () => { + it('can retrieve a list of agencies', async () => { + fetchOnce(agencyListFixture) + const agencies = await blutui.agencies.list() + + expect(fetchURL()).toBe(`${blutui.baseURL}/v1/agencies`) + expect(agencies).toMatchObject({ + object: 'list', + }) + }) + }) + + describe('get', () => { + it('can retrieve an agency', async () => { + fetchOnce(agencyFixture) + const agency = await blutui.agencies.get(agencyFixture.id) + + expect(fetchURL()).toBe( + `${blutui.baseURL}/v1/agencies/${agencyFixture.id}` + ) + expect(agency).toMatchObject({ + id: '9af3accc-1536-4336-8cc3-3b3b2a96c18a', + object: 'agency', + isAgency: false, + }) + }) + }) + + describe('update', () => { + it('can update an agency', async () => { + fetchOnce(agencyFixture) + const agency = await blutui.agencies.update(agencyFixture.id, { + name: 'Foo', + }) + + expect(fetchURL()).toBe( + `${blutui.baseURL}/v1/agencies/${agencyFixture.id}` + ) + expect(agency).toMatchObject({ + id: '9af3accc-1536-4336-8cc3-3b3b2a96c18a', + object: 'agency', + isAwsCustomer: false, + }) + }) + }) +}) diff --git a/src/resources/agencies/agencies.ts b/src/resources/agencies/agencies.ts new file mode 100644 index 0000000..74e7a96 --- /dev/null +++ b/src/resources/agencies/agencies.ts @@ -0,0 +1,51 @@ +import { + deserializeAgency, + deserializeAgencyList, + serializeUpdateAgencyOptions, +} from './serializers' + +import type { Blutui } from '@/blutui' +import type { + Agency, + AgencyResponse, + SerializedUpdateAgencyOptions, + UpdateAgencyOptions, +} from './interfaces' +import type { List, ListResponse, PaginationOptions } from '@/types' + +export class Agencies { + constructor(private readonly blutui: Blutui) {} + + /** + * Retrieve a list of agencies you belong to. + */ + async list(options?: PaginationOptions): Promise> { + const { data } = await this.blutui.get>( + 'agencies', + { query: options } + ) + + return deserializeAgencyList(data) + } + + /** + * Get an agency you belong to, by ID. + */ + async get(id: string): Promise { + const { data } = await this.blutui.get(`agencies/${id}`) + + return deserializeAgency(data) + } + + /** + * Update an agency you belong to, by ID. + */ + async update(id: string, payload: UpdateAgencyOptions): Promise { + const { data } = await this.blutui.patch< + AgencyResponse, + SerializedUpdateAgencyOptions + >(`agencies/${id}`, serializeUpdateAgencyOptions(payload)) + + return deserializeAgency(data) + } +} diff --git a/src/resources/agencies/fixtures/agency-list.json b/src/resources/agencies/fixtures/agency-list.json new file mode 100644 index 0000000..ed6d12b --- /dev/null +++ b/src/resources/agencies/fixtures/agency-list.json @@ -0,0 +1,31 @@ +{ + "object": "list", + "data": [ + { + "id": "9af3accc-1536-4336-8cc3-3b3b2a96c18a", + "object": "agency", + "name": "Flume", + "email": "jayan@flume.agency", + "slug": "flume", + "url": "https://flume.agency", + "location": "NZ", + "timezone": "UTC", + "avatar": null, + "description": null, + "trial_mode": false, + "is_agency": false, + "is_aws_customer": false, + "created_at": 1703713667, + "updated_at": 1721087098 + } + ], + "meta": { + "hasMore": false, + "currentPage": 1, + "from": 1, + "to": 1, + "perPage": 10, + "total": 1, + "lastPage": 1 + } +} diff --git a/src/resources/agencies/fixtures/agency.json b/src/resources/agencies/fixtures/agency.json new file mode 100644 index 0000000..1921827 --- /dev/null +++ b/src/resources/agencies/fixtures/agency.json @@ -0,0 +1,17 @@ +{ + "id": "9af3accc-1536-4336-8cc3-3b3b2a96c18a", + "object": "agency", + "name": "Flume", + "email": "jayan@flume.agency", + "slug": "flume", + "url": "https://flume.agency", + "location": "NZ", + "timezone": "UTC", + "avatar": null, + "description": null, + "trial_mode": false, + "is_agency": false, + "is_aws_customer": false, + "created_at": 1703713667, + "updated_at": 1721087098 +} diff --git a/src/resources/agencies/interfaces/agency.interface.ts b/src/resources/agencies/interfaces/agency.interface.ts new file mode 100644 index 0000000..d08d4ea --- /dev/null +++ b/src/resources/agencies/interfaces/agency.interface.ts @@ -0,0 +1,37 @@ +export interface Agency { + id: string + object: 'agency' + name: string + email: string + slug: string + url: string | null + location: string + timezone: string + avatar: string | null + description: string | null + trialMode: boolean + isAgency: boolean + isAwsCustomer: boolean + createdAt: number + updatedAt: number + deletedAt?: number +} + +export interface AgencyResponse { + id: string + object: 'agency' + name: string + email: string + slug: string + url: string | null + location: string + timezone: string + avatar: string | null + description: string | null + trial_mode: boolean + is_agency: boolean + is_aws_customer: boolean + created_at: number + updated_at: number + deleted_at?: number +} diff --git a/src/resources/agencies/interfaces/index.ts b/src/resources/agencies/interfaces/index.ts new file mode 100644 index 0000000..885ea8d --- /dev/null +++ b/src/resources/agencies/interfaces/index.ts @@ -0,0 +1,2 @@ +export * from './agency.interface' +export * from './update-agency-options.interface' diff --git a/src/resources/agencies/interfaces/update-agency-options.interface.ts b/src/resources/agencies/interfaces/update-agency-options.interface.ts new file mode 100644 index 0000000..2108bb6 --- /dev/null +++ b/src/resources/agencies/interfaces/update-agency-options.interface.ts @@ -0,0 +1,19 @@ +export interface UpdateAgencyOptions { + name?: string + description?: string | null + email?: string + location?: string + url?: string | null + timezone?: string + avatar?: string | null +} + +export interface SerializedUpdateAgencyOptions { + name?: string + description?: string | null + email?: string + location?: string + url?: string | null + timezone?: string + avatar?: string | null +} diff --git a/src/resources/agencies/serializers/agency.serializer.ts b/src/resources/agencies/serializers/agency.serializer.ts new file mode 100644 index 0000000..88abfbf --- /dev/null +++ b/src/resources/agencies/serializers/agency.serializer.ts @@ -0,0 +1,31 @@ +import { deserializePaginationMeta } from '@/utils/serializers' + +import type { List, ListResponse } from '@/types' +import type { Agency, AgencyResponse } from '../interfaces' + +export const deserializeAgency = (agency: AgencyResponse): Agency => ({ + id: agency.id, + object: agency.object, + name: agency.name, + email: agency.email, + slug: agency.slug, + url: agency.url, + location: agency.location, + timezone: agency.timezone, + avatar: agency.avatar, + description: agency.description, + trialMode: agency.trial_mode, + isAgency: agency.is_agency, + isAwsCustomer: agency.is_aws_customer, + createdAt: agency.created_at, + updatedAt: agency.updated_at, + deletedAt: agency.deleted_at, +}) + +export const deserializeAgencyList = ( + agencies: ListResponse +): List => ({ + object: agencies.object, + data: agencies.data.map(deserializeAgency), + meta: deserializePaginationMeta(agencies.meta), +}) diff --git a/src/resources/agencies/serializers/index.ts b/src/resources/agencies/serializers/index.ts new file mode 100644 index 0000000..9a5b365 --- /dev/null +++ b/src/resources/agencies/serializers/index.ts @@ -0,0 +1,2 @@ +export * from './agency.serializer' +export * from './update-agency-options.serializer' diff --git a/src/resources/agencies/serializers/update-agency-options.serializer.ts b/src/resources/agencies/serializers/update-agency-options.serializer.ts new file mode 100644 index 0000000..74cfe95 --- /dev/null +++ b/src/resources/agencies/serializers/update-agency-options.serializer.ts @@ -0,0 +1,16 @@ +import type { + SerializedUpdateAgencyOptions, + UpdateAgencyOptions, +} from '../interfaces' + +export const serializeUpdateAgencyOptions = ( + options: UpdateAgencyOptions +): SerializedUpdateAgencyOptions => ({ + name: options.name, + description: options.description, + email: options.email, + location: options.location, + url: options.url, + timezone: options.timezone, + avatar: options.avatar, +}) diff --git a/src/resources/agency/projects/serializers/project.serializer.ts b/src/resources/agency/projects/serializers/project.serializer.ts index 94c4670..236a2f6 100644 --- a/src/resources/agency/projects/serializers/project.serializer.ts +++ b/src/resources/agency/projects/serializers/project.serializer.ts @@ -28,7 +28,7 @@ export const deserializeProject = (project: ProjectResponse): Project => ({ export const deserializeProjectList = ( projects: ListResponse ): List => ({ - object: 'list', + object: projects.object, data: projects.data.map(deserializeProject), meta: deserializePaginationMeta(projects.meta), }) diff --git a/src/resources/index.ts b/src/resources/index.ts index 06a4a3b..aaa7f4c 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -1 +1,2 @@ +export { Agencies } from './agencies/agencies' export { User } from './user/user' diff --git a/src/types.ts b/src/types.ts index 53367da..76320ab 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,10 @@ +// Blutui Options + export interface BlutuiOptions { apiHostname?: string + request?: { + fetch?: typeof fetch + } } // List