From 570390d602de4c85e8897b2cf2f40f769dc70e5a Mon Sep 17 00:00:00 2001 From: Chengfang Liu Date: Fri, 31 Jan 2025 11:43:03 +1300 Subject: [PATCH] support Redirects --- src/admin.ts | 3 +- src/project.ts | 3 +- src/resources/project/admin.ts | 1 + src/resources/project/index.ts | 1 + .../redirect/fixtures/redirect-list.json | 23 +++++ .../project/redirect/fixtures/redirect.json | 9 ++ .../create-redirect-options.interface.ts | 9 ++ .../project/redirect/interfaces/index.ts | 3 + .../redirect/interfaces/redirect.interface.ts | 19 ++++ .../update-redirect-options.interface.ts | 9 ++ .../project/redirect/redirect.admin.spec.ts | 88 +++++++++++++++++++ .../project/redirect/redirect.admin.ts | 87 ++++++++++++++++++ .../project/redirect/redirect.spec.ts | 43 +++++++++ src/resources/project/redirect/redirect.ts | 35 ++++++++ .../create-redirect-options.serializer.ts | 11 +++ .../project/redirect/serializers/index.ts | 3 + .../serializers/redirect.serializer.ts | 21 +++++ .../update-redirect-options.serializer.ts | 11 +++ 18 files changed, 377 insertions(+), 2 deletions(-) create mode 100644 src/resources/project/redirect/fixtures/redirect-list.json create mode 100644 src/resources/project/redirect/fixtures/redirect.json create mode 100644 src/resources/project/redirect/interfaces/create-redirect-options.interface.ts create mode 100644 src/resources/project/redirect/interfaces/index.ts create mode 100644 src/resources/project/redirect/interfaces/redirect.interface.ts create mode 100644 src/resources/project/redirect/interfaces/update-redirect-options.interface.ts create mode 100644 src/resources/project/redirect/redirect.admin.spec.ts create mode 100644 src/resources/project/redirect/redirect.admin.ts create mode 100644 src/resources/project/redirect/redirect.spec.ts create mode 100644 src/resources/project/redirect/redirect.ts create mode 100644 src/resources/project/redirect/serializers/create-redirect-options.serializer.ts create mode 100644 src/resources/project/redirect/serializers/index.ts create mode 100644 src/resources/project/redirect/serializers/redirect.serializer.ts create mode 100644 src/resources/project/redirect/serializers/update-redirect-options.serializer.ts diff --git a/src/admin.ts b/src/admin.ts index 2e43036..2267544 100644 --- a/src/admin.ts +++ b/src/admin.ts @@ -1,4 +1,4 @@ -import { Menus } from './resources/project/admin' +import { Menus, Redirects } from './resources/project/admin' import type { Blutui } from './blutui' import type { Project } from './project' @@ -6,6 +6,7 @@ import { Request } from './utils/request' export class Admin extends Request { readonly menus = new Menus(this) + readonly redirects = new Redirects(this) constructor( private readonly project: Project, diff --git a/src/project.ts b/src/project.ts index c96fa2c..3776aeb 100644 --- a/src/project.ts +++ b/src/project.ts @@ -1,4 +1,4 @@ -import { Menus } from './resources/project' +import { Menus, Redirects } from './resources/project' import type { Blutui } from './blutui' import { Admin } from './admin' @@ -7,6 +7,7 @@ import { Request } from './utils/request' export class Project extends Request { readonly admin: Admin readonly menus = new Menus(this) + readonly redirects = new Redirects(this) constructor( public handle: string, diff --git a/src/resources/project/admin.ts b/src/resources/project/admin.ts index 9163b49..f259771 100644 --- a/src/resources/project/admin.ts +++ b/src/resources/project/admin.ts @@ -1 +1,2 @@ export { Menus } from './menus/menus.admin' +export { Redirects } from './redirect/redirect.admin' diff --git a/src/resources/project/index.ts b/src/resources/project/index.ts index 7ba7d9b..4e23319 100644 --- a/src/resources/project/index.ts +++ b/src/resources/project/index.ts @@ -1 +1,2 @@ export { Menus } from './menus/menus' +export { Redirects } from './redirect/redirect' diff --git a/src/resources/project/redirect/fixtures/redirect-list.json b/src/resources/project/redirect/fixtures/redirect-list.json new file mode 100644 index 0000000..12d9e57 --- /dev/null +++ b/src/resources/project/redirect/fixtures/redirect-list.json @@ -0,0 +1,23 @@ +{ + "object": "list", + "data": [ + { + "id": "9bfdb42b-1bf0-4510-978e-46aa329f8efa", + "object": "redirect", + "from": "/old-url", + "to": "/new-url", + "created_at": "2025-01-23T14:03:00+11:00", + "updated_at": "2025-01-23T14:03:00+11:00", + "deleted_at": "2025-01-23T14:03:00+11:00" + } + ], + "meta": { + "hasMore": false, + "currentPage": 1, + "from": 1, + "to": 1, + "perPage": 10, + "total": 1, + "lastPage": 1 + } +} diff --git a/src/resources/project/redirect/fixtures/redirect.json b/src/resources/project/redirect/fixtures/redirect.json new file mode 100644 index 0000000..4e1e3b3 --- /dev/null +++ b/src/resources/project/redirect/fixtures/redirect.json @@ -0,0 +1,9 @@ +{ + "id": "9bfdb42b-1bf0-4510-978e-46aa329f8efa", + "object": "redirect", + "from": "/old-url", + "to": "/new-url", + "created_at": "2025-01-23T14:03:00+11:00", + "updated_at": "2025-01-23T14:03:00+11:00", + "deleted_at": "2025-01-23T14:03:00+11:00" +} diff --git a/src/resources/project/redirect/interfaces/create-redirect-options.interface.ts b/src/resources/project/redirect/interfaces/create-redirect-options.interface.ts new file mode 100644 index 0000000..aff7866 --- /dev/null +++ b/src/resources/project/redirect/interfaces/create-redirect-options.interface.ts @@ -0,0 +1,9 @@ +export interface CreateRedirectOptions { + from: string + to: string +} + +export interface SerializedCreateRedirectOptions { + from: string + to: string +} diff --git a/src/resources/project/redirect/interfaces/index.ts b/src/resources/project/redirect/interfaces/index.ts new file mode 100644 index 0000000..8b55262 --- /dev/null +++ b/src/resources/project/redirect/interfaces/index.ts @@ -0,0 +1,3 @@ +export * from './redirect.interface' +export * from './create-redirect-options.interface' +export * from './update-redirect-options.interface' diff --git a/src/resources/project/redirect/interfaces/redirect.interface.ts b/src/resources/project/redirect/interfaces/redirect.interface.ts new file mode 100644 index 0000000..a722aa9 --- /dev/null +++ b/src/resources/project/redirect/interfaces/redirect.interface.ts @@ -0,0 +1,19 @@ +export interface Redirect { + id: string + object: 'redirect' + from: string + to: string + createdAt: string + updatedAt: string + deletedAt?: string +} + +export interface RedirectResponse { + id: string + object: 'redirect' + from: string + to: string + created_at: string + updated_at: string + deleted_at?: string +} diff --git a/src/resources/project/redirect/interfaces/update-redirect-options.interface.ts b/src/resources/project/redirect/interfaces/update-redirect-options.interface.ts new file mode 100644 index 0000000..aa56a1e --- /dev/null +++ b/src/resources/project/redirect/interfaces/update-redirect-options.interface.ts @@ -0,0 +1,9 @@ +export interface UpdateRedirectOptions { + from?: string + to?: string +} + +export interface SerializedUpdateRedirectOptions { + from?: string + to?: string +} diff --git a/src/resources/project/redirect/redirect.admin.spec.ts b/src/resources/project/redirect/redirect.admin.spec.ts new file mode 100644 index 0000000..0a3f963 --- /dev/null +++ b/src/resources/project/redirect/redirect.admin.spec.ts @@ -0,0 +1,88 @@ +import fetch from 'jest-fetch-mock' +import { Blutui } from '@/blutui' +import { fetchOnce, fetchURL } from '@/utils/testing' + +import redirectListFixture from './fixtures/redirect-list.json' +import redirectFixture from './fixtures/redirect.json' + +const accessToken = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' +const blutui = new Blutui(accessToken) + +describe('Redirect', () => { + beforeEach(() => fetch.resetMocks()) + + describe('list', () => { + it('can retrieve a list of redirects', async () => { + fetchOnce(redirectListFixture) + const redirects = await blutui.project('foo').admin.redirects.list() + + expect(fetchURL()).toBe('https://foo.blutui.com/admin/api/redirects') + expect(redirects).toMatchObject({ + object: 'list', + }) + }) + }) + + describe('get', () => { + it('can retrieve a redirect information', async () => { + fetchOnce(redirectFixture) + const redirect = await blutui + .project('foo') + .admin.redirects.get(redirectFixture.id) + + expect(fetchURL()).toBe( + `https://foo.blutui.com/admin/api/redirects/${redirectFixture.id}` + ) + + expect(redirect).toMatchObject({ + object: 'redirect', + }) + }) + }) + + describe('create', () => { + it('can create a new redirect', async () => { + fetchOnce(redirectFixture) + const redirect = await blutui.project('foo').admin.redirects.create({ + from: '/contact', + to: '/contact-us', + }) + + expect(fetchURL()).toBe('https://foo.blutui.com/admin/api/redirects') + expect(redirect).toMatchObject({ + object: 'redirect', + }) + }) + }) + + describe('update', () => { + it('can update a redirect', async () => { + fetchOnce(redirectFixture) + const redirect = await blutui + .project('foo') + .admin.redirects.update(redirectFixture.id, { + from: '/contact', + to: '/contact-us', + }) + + expect(fetchURL()).toBe( + `https://foo.blutui.com/admin/api/redirects/${redirectFixture.id}` + ) + expect(redirect).toMatchObject({ + object: 'redirect', + }) + }) + }) + + describe('remove', () => { + it('can remove a redirect', async () => { + fetchOnce(redirectFixture) + await blutui.project('foo').admin.redirects.remove(redirectFixture.id) + + expect(fetchURL()).toBe( + `https://foo.blutui.com/admin/api/redirects/${redirectFixture.id}` + ) + }) + }) +}) diff --git a/src/resources/project/redirect/redirect.admin.ts b/src/resources/project/redirect/redirect.admin.ts new file mode 100644 index 0000000..9d8c394 --- /dev/null +++ b/src/resources/project/redirect/redirect.admin.ts @@ -0,0 +1,87 @@ +import type { Project } from '@/project' +import type { + DeletedResponse, + Expandable, + List, + ListResponse, + PaginationOptions, +} from '@/types' +import type { + Redirect, + RedirectResponse, + CreateRedirectOptions, + SerializedCreateRedirectOptions, + UpdateRedirectOptions, + SerializedUpdateRedirectOptions, +} from './interfaces' +import { + deserializeRedirect, + deserializeRedirectList, + serializeCreateRedirectOptions, + serializeUpdateRedirectOptions, +} from './serializers' +import type { Admin } from '@/admin' + +export class Redirects { + constructor(private readonly project: Project | Admin) {} + + /** + * Get the redirect list for the current project. + */ + async list(options?: PaginationOptions): Promise> { + const { data } = await this.project.get>( + 'redirects', + { query: options } + ) + + return deserializeRedirectList(data) + } + + /** + * Get a redirect's information by ID. + */ + async get(id: string, options?: Expandable<'items'>): Promise { + const { data } = await this.project.get(`redirects/${id}`, { + query: options, + }) + + return deserializeRedirect(data) + } + + /** + * Create a new redirect for a project. + * + * @param payload - The values to create the redirect + */ + async create(payload: CreateRedirectOptions): Promise { + const { data } = await this.project.post< + RedirectResponse, + SerializedCreateRedirectOptions + >('redirects', serializeCreateRedirectOptions(payload)) + + return deserializeRedirect(data) + } + + /** + * Update the redirect for the a project. + * + * @param payload - The values to update the redirect + */ + async update(id: string, payload: UpdateRedirectOptions): Promise { + const { data } = await this.project.patch< + RedirectResponse, + SerializedUpdateRedirectOptions + >(`redirects/${id}`, serializeUpdateRedirectOptions(payload)) + + return deserializeRedirect(data) + } + + /** + * Remove the redirect for the current project. + */ + async remove(id: string): Promise { + const { data } = await this.project.delete(`redirects/${id}`) + + return data + } +} diff --git a/src/resources/project/redirect/redirect.spec.ts b/src/resources/project/redirect/redirect.spec.ts new file mode 100644 index 0000000..2b17fdd --- /dev/null +++ b/src/resources/project/redirect/redirect.spec.ts @@ -0,0 +1,43 @@ +import fetch from 'jest-fetch-mock' +import { Blutui } from '@/blutui' +import { fetchOnce, fetchURL } from '@/utils/testing' + +import redirectListFixture from './fixtures/redirect-list.json' +import redirectFixture from './fixtures/redirect.json' + +const accessToken = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' +const blutui = new Blutui(accessToken) + +describe('Redirect', () => { + beforeEach(() => fetch.resetMocks()) + + describe('list', () => { + it('can retrieve a list of redirects', async () => { + fetchOnce(redirectListFixture) + const redirects = await blutui.project('foo').redirects.list() + + expect(fetchURL()).toBe('https://foo.blutui.com/api/redirects') + expect(redirects).toMatchObject({ + object: 'list', + }) + }) + }) + + describe('get', () => { + it('can retrieve a redirect information', async () => { + fetchOnce(redirectFixture) + const redirect = await blutui + .project('foo') + .redirects.get(redirectFixture.id) + + expect(fetchURL()).toBe( + `https://foo.blutui.com/api/redirects/${redirectFixture.id}` + ) + + expect(redirect).toMatchObject({ + object: 'redirect', + }) + }) + }) +}) diff --git a/src/resources/project/redirect/redirect.ts b/src/resources/project/redirect/redirect.ts new file mode 100644 index 0000000..00e618f --- /dev/null +++ b/src/resources/project/redirect/redirect.ts @@ -0,0 +1,35 @@ +import type { Project } from '@/project' +import type { Expandable, List, ListResponse, PaginationOptions } from '@/types' +import type { Redirect, RedirectResponse } from './interfaces' +import { deserializeRedirect, deserializeRedirectList } from './serializers' +import type { Admin } from '@/admin' + +export class Redirects { + constructor(private readonly project: Project | Admin) {} + + /** + * Get the redirect list for the current project. + */ + async list(options?: PaginationOptions): Promise> { + const { data } = await this.project.get>( + 'redirects', + { query: options } + ) + + return deserializeRedirectList(data) + } + + /** + * Get a redirect's information by ID. + */ + async get(id: string, options?: Expandable<'items'>): Promise { + const { data } = await this.project.get( + `redirects/${id}`, + { + query: options, + } + ) + + return deserializeRedirect(data) + } +} diff --git a/src/resources/project/redirect/serializers/create-redirect-options.serializer.ts b/src/resources/project/redirect/serializers/create-redirect-options.serializer.ts new file mode 100644 index 0000000..db15b6e --- /dev/null +++ b/src/resources/project/redirect/serializers/create-redirect-options.serializer.ts @@ -0,0 +1,11 @@ +import type { + CreateRedirectOptions, + SerializedCreateRedirectOptions, +} from '../interfaces' + +export const serializeCreateRedirectOptions = ( + options: CreateRedirectOptions +): SerializedCreateRedirectOptions => ({ + from: options.from, + to: options.to, +}) diff --git a/src/resources/project/redirect/serializers/index.ts b/src/resources/project/redirect/serializers/index.ts new file mode 100644 index 0000000..9a73ab0 --- /dev/null +++ b/src/resources/project/redirect/serializers/index.ts @@ -0,0 +1,3 @@ +export * from './redirect.serializer' +export * from './create-redirect-options.serializer' +export * from './update-redirect-options.serializer' diff --git a/src/resources/project/redirect/serializers/redirect.serializer.ts b/src/resources/project/redirect/serializers/redirect.serializer.ts new file mode 100644 index 0000000..d03f130 --- /dev/null +++ b/src/resources/project/redirect/serializers/redirect.serializer.ts @@ -0,0 +1,21 @@ +import { deserializePaginationMeta } from '@/utils/serializers' + +import type { List, ListResponse } from '@/types' +import type { Redirect, RedirectResponse } from '../interfaces' + +export const deserializeRedirect = (redirect: RedirectResponse): Redirect => ({ + id: redirect.id, + object: redirect.object, + from: redirect.from, + to: redirect.to, + createdAt: redirect.created_at, + updatedAt: redirect.updated_at, +}) + +export const deserializeRedirectList = ( + redirects: ListResponse +): List => ({ + object: redirects.object, + data: redirects.data.map(deserializeRedirect), + meta: deserializePaginationMeta(redirects.meta), +}) diff --git a/src/resources/project/redirect/serializers/update-redirect-options.serializer.ts b/src/resources/project/redirect/serializers/update-redirect-options.serializer.ts new file mode 100644 index 0000000..ade99e2 --- /dev/null +++ b/src/resources/project/redirect/serializers/update-redirect-options.serializer.ts @@ -0,0 +1,11 @@ +import type { + SerializedUpdateRedirectOptions, + UpdateRedirectOptions, +} from '../interfaces' + +export const serializeUpdateRedirectOptions = ( + options: UpdateRedirectOptions +): SerializedUpdateRedirectOptions => ({ + from: options.from, + to: options.to, +})