diff --git a/src/agency.ts b/src/agency.ts index 5e21603..16d17c4 100644 --- a/src/agency.ts +++ b/src/agency.ts @@ -1,4 +1,4 @@ -import { Brand, Domains } from './resources/agency' +import { Brand, Domains, Projects } from './resources/agency' import type { Blutui } from './blutui' import type { GetOptions, PostOptions } from './types' @@ -6,6 +6,7 @@ import type { GetOptions, PostOptions } from './types' export class Agency { readonly brand = new Brand(this) readonly domains = new Domains(this) + readonly projects = new Projects(this) constructor( public username: string, diff --git a/src/resources/agency/domains/domains.spec.ts b/src/resources/agency/domains/domains.spec.ts index 47e0e87..990de5f 100644 --- a/src/resources/agency/domains/domains.spec.ts +++ b/src/resources/agency/domains/domains.spec.ts @@ -1,8 +1,10 @@ import fetch from 'jest-fetch-mock' import { Blutui } from '@/blutui' import { fetchOnce, fetchURL } from '@/utils/testing' + import domainFixture from './fixtures/domain.json' -import domainListFixture from './fixtures/domainList.json' +import domainWithProjectFixture from './fixtures/domain-with-project.json' +import domainListFixture from './fixtures/domain-list.json' const accessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' @@ -34,21 +36,28 @@ describe('Domain', () => { expect(domain).toMatchObject({ object: 'domain', }) + expect(typeof domain.project).toBe('string') }) it('can retrieve a domain information with project', async () => { - fetchOnce(domainFixture) - const domain = await blutui.agency('foo').domains.get(domainFixture.id, { - expand: ['project'], - }) + fetchOnce(domainWithProjectFixture) + const domain = await blutui + .agency('foo') + .domains.get(domainWithProjectFixture.id, { + expand: ['project'], + }) expect(fetchURL()).toContain( encodeURI( - `/v1/agencies/foo/domains/${domainFixture.id}?expand[]=project` + `/v1/agencies/foo/domains/${domainWithProjectFixture.id}?expand[]=project` ) ) expect(domain).toMatchObject({ object: 'domain', }) + expect(domain.project).toMatchObject({ + id: '99bc147e-966c-4dd0-8def-de817c63cf41', + createdAt: 1720758022, + }) }) }) @@ -117,7 +126,7 @@ describe('Domain', () => { }) describe('search', () => { - it('Search for domains in your agency.', async () => { + it('can search for domains', async () => { fetchOnce(domainListFixture) await blutui.agency('foo').domains.search({ name: 'example.com' }) diff --git a/src/resources/agency/domains/domains.ts b/src/resources/agency/domains/domains.ts index 18525e0..6363625 100644 --- a/src/resources/agency/domains/domains.ts +++ b/src/resources/agency/domains/domains.ts @@ -19,6 +19,7 @@ import type { } from './interfaces' import type { DeletedResponse, + Expandable, List, ListResponse, PaginationOptions, @@ -30,18 +31,19 @@ export class Domains { /** * Get the domains list for the current agency. */ - async list(paginationOptions?: PaginationOptions): Promise> { + async list(options?: PaginationOptions): Promise> { const { data } = await this.agency.get>( 'domains', - { query: paginationOptions } + { query: options } ) + return deserializeDomainList(data) } /** - * Get a domain information by id. + * Get a domain's information by ID. */ - async get(id: string, options?: { expand: string[] }): Promise { + async get(id: string, options?: Expandable<'project'>): Promise { const { data } = await this.agency.get(`domains/${id}`, { query: options, }) @@ -110,13 +112,11 @@ export class Domains { /** * Search for domains in your agency. */ - async search( - searchDomainOptions: SearchDomainOptions - ): Promise> { + async search(payload: SearchDomainOptions): Promise> { const { data } = await this.agency.post< ListResponse, SerializedSearchDomainOptions - >('domains/search', searchDomainOptions) + >('domains/search', payload) return deserializeDomainList(data) } diff --git a/src/resources/agency/domains/fixtures/domainList.json b/src/resources/agency/domains/fixtures/domain-list.json similarity index 100% rename from src/resources/agency/domains/fixtures/domainList.json rename to src/resources/agency/domains/fixtures/domain-list.json diff --git a/src/resources/agency/domains/fixtures/domain-with-project.json b/src/resources/agency/domains/fixtures/domain-with-project.json new file mode 100644 index 0000000..accb1db --- /dev/null +++ b/src/resources/agency/domains/fixtures/domain-with-project.json @@ -0,0 +1,25 @@ +{ + "id": "9bfdb42b-1bf0-4510-978e-46aa329f8efa", + "object": "domain", + "name": "example.com", + "token": "08Q8wwsDMIuCwAsudZtXgf3ABkAwqbExgUPWUEPEuMBoWUIH0ie81R27h2elqjk1", + "project": { + "id": "99bc147e-966c-4dd0-8def-de817c63cf41", + "object": "project", + "name": "One", + "description": "One", + "image": null, + "handle": "one", + "password": "SorKVQWV", + "timezone": "UTC", + "subdomain": "one", + "primary_domain": "9bfdb42b-1bf0-4510-978e-46aa329f8efa", + "published": true, + "processed": true, + "created_at": 1720758022, + "updated_at": 1720758046 + }, + "verified_at": null, + "created_at": 1716170007, + "updated_at": 1716170007 +} diff --git a/src/resources/agency/domains/interfaces/domain.interface.ts b/src/resources/agency/domains/interfaces/domain.interface.ts index c41189e..a43e54c 100644 --- a/src/resources/agency/domains/interfaces/domain.interface.ts +++ b/src/resources/agency/domains/interfaces/domain.interface.ts @@ -1,10 +1,12 @@ +import type { Project, ProjectResponse } from '../../projects/interfaces' + export interface Domain { id: string object: 'domain' name: string token: string - project: string | Record - verifiedAt: number + project: string | null | Project + verifiedAt: number | null createdAt: number updatedAt: number } @@ -14,8 +16,8 @@ export interface DomainResponse { object: 'domain' name: string token: string - project: string | Record - verified_at: number + project: string | null | ProjectResponse + verified_at: number | null created_at: number updated_at: number } diff --git a/src/resources/agency/domains/serializers/domain.serializer.ts b/src/resources/agency/domains/serializers/domain.serializer.ts index 44dc35a..d6e1d6e 100644 --- a/src/resources/agency/domains/serializers/domain.serializer.ts +++ b/src/resources/agency/domains/serializers/domain.serializer.ts @@ -2,13 +2,17 @@ import { deserializePaginationMeta } from '@/utils/serializers' import type { List, ListResponse } from '@/types' import type { Domain, DomainResponse } from '../interfaces' +import { deserializeProject } from '../../projects/serializers' export const deserializeDomain = (domain: DomainResponse): Domain => ({ id: domain.id, object: domain.object, name: domain.name, token: domain.token, - project: domain.project, + project: + domain.project instanceof Object + ? deserializeProject(domain.project) + : domain.project, verifiedAt: domain.verified_at, createdAt: domain.created_at, updatedAt: domain.updated_at, diff --git a/src/resources/agency/index.ts b/src/resources/agency/index.ts index d4f10ac..8b83505 100644 --- a/src/resources/agency/index.ts +++ b/src/resources/agency/index.ts @@ -1,2 +1,3 @@ export { Brand } from './brand/brand' export { Domains } from './domains/domains' +export { Projects } from './projects/projects' diff --git a/src/resources/agency/project/interfaces/index.ts b/src/resources/agency/project/interfaces/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/resources/agency/project/interfaces/project.interface.ts b/src/resources/agency/project/interfaces/project.interface.ts deleted file mode 100644 index 7b43950..0000000 --- a/src/resources/agency/project/interfaces/project.interface.ts +++ /dev/null @@ -1,13 +0,0 @@ -export interface Project { - id: string - object: 'project' - name: string - description: string -} - -export interface ProjectResponse { - id: string - object: 'project' - name: string - description: string -} diff --git a/src/resources/agency/projects/fixtures/project-list.json b/src/resources/agency/projects/fixtures/project-list.json new file mode 100644 index 0000000..4bb67c9 --- /dev/null +++ b/src/resources/agency/projects/fixtures/project-list.json @@ -0,0 +1,30 @@ +{ + "object": "list", + "data": [ + { + "id": "99bc147e-966c-4dd0-8def-de817c63cf41", + "object": "project", + "name": "One", + "description": "One", + "image": null, + "handle": "one", + "password": "SorKVQWV", + "timezone": "UTC", + "subdomain": "one", + "primary_domain": "9bfdb42b-1bf0-4510-978e-46aa329f8efa", + "published": true, + "processed": true, + "created_at": 1720758022, + "updated_at": 1720758046 + } + ], + "meta": { + "hasMore": false, + "currentPage": 1, + "from": 1, + "to": 1, + "perPage": 10, + "total": 1, + "lastPage": 1 + } +} diff --git a/src/resources/agency/projects/fixtures/project-with-primary-domain.json b/src/resources/agency/projects/fixtures/project-with-primary-domain.json new file mode 100644 index 0000000..beef8ba --- /dev/null +++ b/src/resources/agency/projects/fixtures/project-with-primary-domain.json @@ -0,0 +1,25 @@ +{ + "id": "99bc147e-966c-4dd0-8def-de817c63cf41", + "object": "project", + "name": "One", + "description": "One", + "image": null, + "handle": "one", + "password": "SorKVQWV", + "timezone": "UTC", + "subdomain": "one", + "primary_domain": { + "id": "9bfdb42b-1bf0-4510-978e-46aa329f8efa", + "object": "domain", + "name": "example.com", + "token": "08Q8wwsDMIuCwAsudZtXgf3ABkAwqbExgUPWUEPEuMBoWUIH0ie81R27h2elqjk1", + "project": "9bf15409-db06-4c2c-b1ee-3a64c0074092", + "verified_at": null, + "created_at": 1716170007, + "updated_at": 1716170007 + }, + "published": true, + "processed": true, + "created_at": 1720758022, + "updated_at": 1720758046 +} diff --git a/src/resources/agency/projects/fixtures/project.json b/src/resources/agency/projects/fixtures/project.json new file mode 100644 index 0000000..96e0514 --- /dev/null +++ b/src/resources/agency/projects/fixtures/project.json @@ -0,0 +1,16 @@ +{ + "id": "99bc147e-966c-4dd0-8def-de817c63cf41", + "object": "project", + "name": "One", + "description": "One", + "image": null, + "handle": "one", + "password": "SorKVQWV", + "timezone": "UTC", + "subdomain": "one", + "primary_domain": "9bfdb42b-1bf0-4510-978e-46aa329f8efa", + "published": true, + "processed": true, + "created_at": 1720758022, + "updated_at": 1720758046 +} diff --git a/src/resources/agency/projects/interfaces/create-project-options.interface.ts b/src/resources/agency/projects/interfaces/create-project-options.interface.ts new file mode 100644 index 0000000..971199a --- /dev/null +++ b/src/resources/agency/projects/interfaces/create-project-options.interface.ts @@ -0,0 +1,15 @@ +export interface CreateProjectOptions { + name: string + handle?: string + timezone?: string + description?: string | null + subdomain?: string +} + +export interface SerializedCreateProjectOptions { + name: string + handle?: string + timezone?: string + description?: string | null + subdomain?: string +} diff --git a/src/resources/agency/projects/interfaces/index.ts b/src/resources/agency/projects/interfaces/index.ts new file mode 100644 index 0000000..debe352 --- /dev/null +++ b/src/resources/agency/projects/interfaces/index.ts @@ -0,0 +1,4 @@ +export * from './create-project-options.interface' +export * from './project.interface' +export * from './search-project-options.interface' +export * from './update-project-options.interface' diff --git a/src/resources/agency/projects/interfaces/project.interface.ts b/src/resources/agency/projects/interfaces/project.interface.ts new file mode 100644 index 0000000..5bc2ac9 --- /dev/null +++ b/src/resources/agency/projects/interfaces/project.interface.ts @@ -0,0 +1,37 @@ +import type { Domain, DomainResponse } from '../../domains/interfaces' + +export interface Project { + id: string + object: 'project' + name: string + description: string | null + image: string | null + handle: string + password: string + timezone: string + subdomain: string + primaryDomain: string | null | Domain + published: boolean + processed: boolean + createdAt: number + updatedAt: number + deletedAt?: number +} + +export interface ProjectResponse { + id: string + object: 'project' + name: string + description: string | null + image: string | null + handle: string + password: string + timezone: string + subdomain: string + primary_domain: string | null | DomainResponse + published: boolean + processed: boolean + created_at: number + updated_at: number + deleted_at?: number +} diff --git a/src/resources/agency/projects/interfaces/search-project-options.interface.ts b/src/resources/agency/projects/interfaces/search-project-options.interface.ts new file mode 100644 index 0000000..222468c --- /dev/null +++ b/src/resources/agency/projects/interfaces/search-project-options.interface.ts @@ -0,0 +1,7 @@ +export interface SearchProjectOptions { + name: string +} + +export interface SerializedSearchProjectOptions { + name: string +} diff --git a/src/resources/agency/projects/interfaces/update-project-options.interface.ts b/src/resources/agency/projects/interfaces/update-project-options.interface.ts new file mode 100644 index 0000000..82835b2 --- /dev/null +++ b/src/resources/agency/projects/interfaces/update-project-options.interface.ts @@ -0,0 +1,15 @@ +export interface UpdateProjectOptions { + name?: string + description?: string | null + password?: string + timezone?: string + primaryDomain?: string | null +} + +export interface SerializedUpdateProjectOptions { + name?: string + description?: string | null + password?: string + timezone?: string + primary_domain?: string | null +} diff --git a/src/resources/agency/projects/projects.spec.ts b/src/resources/agency/projects/projects.spec.ts new file mode 100644 index 0000000..b347b4f --- /dev/null +++ b/src/resources/agency/projects/projects.spec.ts @@ -0,0 +1,251 @@ +import fetch from 'jest-fetch-mock' +import { Blutui } from '@/blutui' +import { fetchOnce, fetchURL } from '@/utils/testing' + +import projectFixture from './fixtures/project.json' +import projectWithPrimaryDomainFixture from './fixtures/project-with-primary-domain.json' +import projectListFixture from './fixtures/project-list.json' +import domainListFixture from '../domains/fixtures/domain-list.json' + +const accessToken = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' +const blutui = new Blutui(accessToken) + +describe('Project', () => { + beforeEach(() => fetch.resetMocks()) + + describe('list', () => { + it('can retrieve a list of projects', async () => { + fetchOnce(projectListFixture) + const projects = await blutui.agency('foo').projects.list() + + expect(fetchURL()).toBe(`${blutui.baseURL}/v1/agencies/foo/projects`) + expect(projects).toMatchObject({ + object: 'list', + }) + }) + + it('can retrieve a list of projects with an expanded primary domain', async () => { + fetchOnce(projectListFixture) + const projects = await blutui + .agency('foo') + .projects.list({ expand: ['primary_domain'] }) + + expect(fetchURL()).toBe( + encodeURI( + `${blutui.baseURL}/v1/agencies/foo/projects?expand[]=primary_domain` + ) + ) + expect(projects).toMatchObject({ + object: 'list', + }) + }) + }) + + describe('get', () => { + it('can retrieve a project', async () => { + fetchOnce(projectFixture) + const project = await blutui.agency('foo').projects.get(projectFixture.id) + + expect(fetchURL()).toBe( + `${blutui.baseURL}/v1/agencies/foo/projects/${projectFixture.id}` + ) + expect(project).toMatchObject({ + id: '99bc147e-966c-4dd0-8def-de817c63cf41', + object: 'project', + }) + expect(typeof project.primaryDomain).toBe('string') + }) + + it('can retrieve a project with an expanded primary domain', async () => { + fetchOnce(projectWithPrimaryDomainFixture) + const project = await blutui + .agency('foo') + .projects.get(projectWithPrimaryDomainFixture.id, { + expand: ['primary_domain'], + }) + + expect(fetchURL()).toBe( + encodeURI( + `${blutui.baseURL}/v1/agencies/foo/projects/${projectWithPrimaryDomainFixture.id}?expand[]=primary_domain` + ) + ) + expect(project).toMatchObject({ + id: '99bc147e-966c-4dd0-8def-de817c63cf41', + object: 'project', + }) + expect(project.primaryDomain).toMatchObject({ + id: '9bfdb42b-1bf0-4510-978e-46aa329f8efa', + verifiedAt: null, + }) + }) + }) + + describe('create', () => { + it('can create a new project', async () => { + fetchOnce(projectFixture) + const project = await blutui.agency('foo').projects.create({ + name: 'One', + }) + + expect(fetchURL()).toBe(`${blutui.baseURL}/v1/agencies/foo/projects`) + expect(project).toMatchObject({ + object: 'project', + name: 'One', + }) + }) + }) + + describe('update', () => { + it('can update a project', async () => { + fetchOnce(projectFixture) + const project = await blutui + .agency('foo') + .projects.update(projectFixture.id, { + primaryDomain: projectFixture.primary_domain, + }) + + expect(fetchURL()).toBe( + `${blutui.baseURL}/v1/agencies/foo/projects/${projectFixture.id}` + ) + expect(project).toMatchObject({ + object: 'project', + primaryDomain: '9bfdb42b-1bf0-4510-978e-46aa329f8efa', + }) + }) + }) + + describe('remove', () => { + it('can archive a project', async () => { + fetchOnce({ id: projectFixture.id, object: 'project', deleted: true }) + const archivedProject = await blutui + .agency('foo') + .projects.remove(projectFixture.id) + + expect(fetchURL()).toBe( + `${blutui.baseURL}/v1/agencies/foo/projects/${projectFixture.id}` + ) + expect(archivedProject).toMatchObject({ + id: '99bc147e-966c-4dd0-8def-de817c63cf41', + object: 'project', + deleted: true, + }) + }) + }) + + describe('archived', () => { + it('can retrieve a list of archived projects', async () => { + fetchOnce(projectListFixture) + const projects = await blutui + .agency('foo') + .projects.archived({ limit: 2 }) + + expect(fetchURL()).toBe( + encodeURI(`${blutui.baseURL}/v1/agencies/foo/projects/archived?limit=2`) + ) + expect(projects).toMatchObject({ + object: 'list', + }) + }) + }) + + describe('restore', () => { + it('can restore an archived project', async () => { + fetchOnce(projectFixture) + const project = await blutui + .agency('foo') + .projects.restore(projectFixture.id) + + expect(fetchURL()).toBe( + `${blutui.baseURL}/v1/agencies/foo/projects/${projectFixture.id}/archived` + ) + expect(project).toMatchObject({ + object: 'project', + name: 'One', + }) + }) + }) + + describe('domains', () => { + it('can retrieve a list of domains for a project', async () => { + fetchOnce(domainListFixture) + const domains = await blutui + .agency('foo') + .projects.domains(projectFixture.id) + + expect(fetchURL()).toBe( + `${blutui.baseURL}/v1/agencies/foo/projects/${projectFixture.id}/domains` + ) + expect(domains).toMatchObject({ + object: 'list', + }) + }) + }) + + describe('search', () => { + it('can search for projects', async () => { + fetchOnce(projectListFixture) + const projects = await blutui + .agency('foo') + .projects.search({ name: 'One' }) + + expect(fetchURL()).toBe( + `${blutui.baseURL}/v1/agencies/foo/projects/search` + ) + expect(projects).toMatchObject({ + object: 'list', + }) + }) + }) + + describe('publish', () => { + it('can publish a project', async () => { + fetchOnce(projectFixture) + const project = await blutui + .agency('foo') + .projects.publish(projectFixture.id) + + expect(fetchURL()).toBe( + `${blutui.baseURL}/v1/agencies/foo/projects/${projectFixture.id}/publish` + ) + expect(project).toMatchObject({ + id: '99bc147e-966c-4dd0-8def-de817c63cf41', + object: 'project', + }) + }) + }) + + describe('republish', () => { + it('can republish a project', async () => { + fetchOnce(projectFixture) + const project = await blutui + .agency('foo') + .projects.republish(projectFixture.id) + + expect(fetchURL()).toBe( + `${blutui.baseURL}/v1/agencies/foo/projects/${projectFixture.id}/republish` + ) + expect(project).toMatchObject({ + id: '99bc147e-966c-4dd0-8def-de817c63cf41', + object: 'project', + }) + }) + }) + + describe('unpublish', () => { + it('can unpublish a project', async () => { + fetchOnce(projectFixture) + const project = await blutui + .agency('foo') + .projects.unpublish(projectFixture.id) + + expect(fetchURL()).toBe( + `${blutui.baseURL}/v1/agencies/foo/projects/${projectFixture.id}/unpublish` + ) + expect(project).toMatchObject({ + id: '99bc147e-966c-4dd0-8def-de817c63cf41', + object: 'project', + }) + }) + }) +}) diff --git a/src/resources/agency/projects/projects.ts b/src/resources/agency/projects/projects.ts new file mode 100644 index 0000000..e36a7a9 --- /dev/null +++ b/src/resources/agency/projects/projects.ts @@ -0,0 +1,187 @@ +import { + deserializeProject, + deserializeProjectList, + serializeCreateProjectOptions, + serializeUpdateProjectOptions, +} from './serializers' +import { deserializeDomainList } from '../domains/serializers' + +import type { Agency } from '@/agency' +import type { + CreateProjectOptions, + Project, + ProjectResponse, + SearchProjectOptions, + SerializedCreateProjectOptions, + SerializedSearchProjectOptions, + SerializedUpdateProjectOptions, + UpdateProjectOptions, +} from './interfaces' +import type { Domain, DomainResponse } from '../domains/interfaces' +import type { + DeletedResponse, + Expandable, + List, + ListResponse, + PaginationOptions, +} from '@/types' + +export class Projects { + constructor(private readonly agency: Agency) {} + + /** + * Get the projects list for the current agency. + */ + async list( + options?: PaginationOptions & Expandable<'primary_domain'> + ): Promise> { + const { data } = await this.agency.get>( + 'projects', + { + query: options, + } + ) + + return deserializeProjectList(data) + } + + /** + * Get a project's information by ID. + */ + async get( + id: string, + options?: Expandable<'primary_domain'> + ): Promise { + const { data } = await this.agency.get(`projects/${id}`, { + query: options, + }) + + return deserializeProject(data) + } + + /** + * Add a project to your agency. + */ + async create(payload: CreateProjectOptions): Promise { + const { data } = await this.agency.post< + ProjectResponse, + SerializedCreateProjectOptions + >('projects', serializeCreateProjectOptions(payload)) + + return deserializeProject(data) + } + + /** + * Update a project in your agency. + */ + async update(id: string, payload: UpdateProjectOptions): Promise { + const { data } = await this.agency.patch< + ProjectResponse, + SerializedUpdateProjectOptions + >(`projects/${id}`, serializeUpdateProjectOptions(payload)) + + return deserializeProject(data) + } + + /** + * Archive a project in your agency. + */ + async remove(id: string): Promise { + const { data } = await this.agency.delete(`projects/${id}`) + + return data + } + + /** + * Retrieve a list of archived projects in your agency. + */ + async archived(options?: PaginationOptions): Promise> { + const { data } = await this.agency.get>( + 'projects/archived', + { query: options } + ) + + return deserializeProjectList(data) + } + + /** + * Restore a project with the given ID in your agency. + */ + async restore(id: string): Promise { + const { data } = await this.agency.delete( + `projects/${id}/archived` + ) + + return deserializeProject(data) + } + + /** + * Retrieve the domains for a project in your agency. + */ + async domains( + id: string, + options?: PaginationOptions + ): Promise> { + const { data } = await this.agency.get>( + `projects/${id}/domains`, + { query: options } + ) + + return deserializeDomainList(data) + } + + /** + * Search for projects in your agency. + */ + async search( + payload: SearchProjectOptions, + options?: Expandable<'primary_domain'> + ): Promise> { + const { data } = await this.agency.post< + ListResponse, + SerializedSearchProjectOptions + >('projects/search', payload, { + query: options, + }) + + return deserializeProjectList(data) + } + + /** + * Publish a project with the given ID. + * + * NOTE: An active Blutui subscription is required to perform this action. + */ + async publish(id: string): Promise { + const { data } = await this.agency.post< + ProjectResponse, + Record + >(`projects/${id}/publish`, {}) + + return deserializeProject(data) + } + + /** + * Republish a project with the given ID. + */ + async republish(id: string): Promise { + const { data } = await this.agency.post< + ProjectResponse, + Record + >(`projects/${id}/republish`, {}) + + return deserializeProject(data) + } + + /** + * Unpublish a project with the given ID. + */ + async unpublish(id: string): Promise { + const { data } = await this.agency.post< + ProjectResponse, + Record + >(`projects/${id}/unpublish`, {}) + + return deserializeProject(data) + } +} diff --git a/src/resources/agency/projects/serializers/create-project-options.serializer.ts b/src/resources/agency/projects/serializers/create-project-options.serializer.ts new file mode 100644 index 0000000..e63565c --- /dev/null +++ b/src/resources/agency/projects/serializers/create-project-options.serializer.ts @@ -0,0 +1,14 @@ +import type { + CreateProjectOptions, + SerializedCreateProjectOptions, +} from '../interfaces' + +export const serializeCreateProjectOptions = ( + options: CreateProjectOptions +): SerializedCreateProjectOptions => ({ + name: options.name, + handle: options.handle, + timezone: options.timezone, + description: options.description, + subdomain: options.subdomain, +}) diff --git a/src/resources/agency/projects/serializers/index.ts b/src/resources/agency/projects/serializers/index.ts new file mode 100644 index 0000000..c839eca --- /dev/null +++ b/src/resources/agency/projects/serializers/index.ts @@ -0,0 +1,3 @@ +export * from './create-project-options.serializer' +export * from './project.serializer' +export * from './update-project-options.serializer' diff --git a/src/resources/agency/projects/serializers/project.serializer.ts b/src/resources/agency/projects/serializers/project.serializer.ts new file mode 100644 index 0000000..94c4670 --- /dev/null +++ b/src/resources/agency/projects/serializers/project.serializer.ts @@ -0,0 +1,34 @@ +import { deserializePaginationMeta } from '@/utils/serializers' +import { deserializeDomain } from '../../domains/serializers' + +import type { Project, ProjectResponse } from '../interfaces' +import type { List, ListResponse } from '@/types' + +export const deserializeProject = (project: ProjectResponse): Project => ({ + id: project.id, + object: project.object, + name: project.name, + description: project.description, + image: project.image, + handle: project.handle, + password: project.password, + timezone: project.timezone, + subdomain: project.subdomain, + primaryDomain: + project.primary_domain instanceof Object + ? deserializeDomain(project.primary_domain) + : project.primary_domain, + published: project.published, + processed: project.processed, + createdAt: project.created_at, + updatedAt: project.updated_at, + deletedAt: project.deleted_at, +}) + +export const deserializeProjectList = ( + projects: ListResponse +): List => ({ + object: 'list', + data: projects.data.map(deserializeProject), + meta: deserializePaginationMeta(projects.meta), +}) diff --git a/src/resources/agency/projects/serializers/update-project-options.serializer.ts b/src/resources/agency/projects/serializers/update-project-options.serializer.ts new file mode 100644 index 0000000..ecaa641 --- /dev/null +++ b/src/resources/agency/projects/serializers/update-project-options.serializer.ts @@ -0,0 +1,14 @@ +import type { + SerializedUpdateProjectOptions, + UpdateProjectOptions, +} from '../interfaces' + +export const serializeUpdateProjectOptions = ( + options: UpdateProjectOptions +): SerializedUpdateProjectOptions => ({ + name: options.name, + description: options.description, + password: options.password, + timezone: options.timezone, + primary_domain: options.primaryDomain, +}) diff --git a/src/types.ts b/src/types.ts index 1084710..53367da 100644 --- a/src/types.ts +++ b/src/types.ts @@ -43,6 +43,10 @@ export type PaginationOptions = { page?: number } +// Expand resources + +export type Expandable = { expand?: Array } + // Request Options type QueryParams = string | string[] | number | null diff --git a/src/utils/client.ts b/src/utils/client.ts index 8bdf41e..f4b4da9 100644 --- a/src/utils/client.ts +++ b/src/utils/client.ts @@ -122,10 +122,10 @@ function getQueryString(queryObj?: Record) { if (Array.isArray(value)) { for (const element of value) { if (element !== '' && element !== undefined) - sanitizedQueryObj.push([`${param}[]`, element]) + sanitizedQueryObj.push([`${param}[]`, String(element)]) } - } else if (value && typeof value === 'string') { - sanitizedQueryObj.push([param, value]) + } else if (value) { + sanitizedQueryObj.push([param, String(value)]) } }