From 02e4b5f97e37abab25c7795ea4398f1e427c6ec3 Mon Sep 17 00:00:00 2001 From: Chengfang Liu Date: Tue, 3 Dec 2024 13:45:16 +1300 Subject: [PATCH 01/21] feat: Update release workflow to automatically set and commit version based on Git reference --- .github/workflows/release.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3eaef14..65e77f2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,6 +14,14 @@ jobs: name: Publish to NPM steps: + - name: Update blutui.ts version + run: sed -i "s/const VERSION = '.*';/const VERSION = '${{ github.ref_name }}';/g" src/blutui.ts + + - name: Commit version change + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "Update version to v${{ github.ref_name }}" + - name: Checkout code uses: actions/checkout@v4 From 946e56744af79a5829959f3e2a698e1288be0d09 Mon Sep 17 00:00:00 2001 From: Chengfang Liu Date: Tue, 3 Dec 2024 13:50:32 +1300 Subject: [PATCH 02/21] fix: Move checkout step to the correct position in release workflow --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 65e77f2..2646182 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,6 +14,9 @@ jobs: name: Publish to NPM steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Update blutui.ts version run: sed -i "s/const VERSION = '.*';/const VERSION = '${{ github.ref_name }}';/g" src/blutui.ts @@ -22,9 +25,6 @@ jobs: with: commit_message: "Update version to v${{ github.ref_name }}" - - name: Checkout code - uses: actions/checkout@v4 - - name: Setup Node uses: actions/setup-node@v4 with: From fa112b8a45ceeea72a6fdb439775a3a5c51c072b Mon Sep 17 00:00:00 2001 From: Chengfang Liu Date: Tue, 3 Dec 2024 14:10:35 +1300 Subject: [PATCH 03/21] fix: Update release workflow to include push changes step --- .github/workflows/release.yml | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2646182..50ba442 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,22 +25,5 @@ jobs: with: commit_message: "Update version to v${{ github.ref_name }}" - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: 20 - registry-url: 'https://registry.npmjs.org' - - - name: Clean install dependencies - run: | - npm ci - - - name: Run tests - run: | - npm run test - - - name: Publish to NPM - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: | - npm publish + - name: Push changes + run: git push From 1167ff965afe31cbdc18a5d1e0768a94d9c51eb2 Mon Sep 17 00:00:00 2001 From: Chengfang Liu Date: Thu, 19 Dec 2024 15:06:40 +1300 Subject: [PATCH 04/21] WIP --- src/blutui.ts | 15 +++++ src/project.ts | 56 ++++++++++++++++ src/resources/project/index.ts | 1 + .../create-domain-options.interface.ts | 9 +++ .../project/menus/interfaces/index.ts | 5 ++ .../menus/interfaces/menu.interface.ts | 43 ++++++++++++ .../search-domain-options.interface.ts | 7 ++ .../update-domain-options.interface.ts | 7 ++ .../verify-domain-response.interface.ts | 7 ++ src/resources/project/menus/menus.ts | 67 +++++++++++++++++++ .../create-domain-options.serializer.ts | 11 +++ .../menus/serializers/domain.serializer.ts | 27 ++++++++ .../project/menus/serializers/index.ts | 3 + .../update-domain-options.serializer.ts | 10 +++ src/resources/site/index.ts | 0 15 files changed, 268 insertions(+) create mode 100644 src/project.ts create mode 100644 src/resources/project/index.ts create mode 100644 src/resources/project/menus/interfaces/create-domain-options.interface.ts create mode 100644 src/resources/project/menus/interfaces/index.ts create mode 100644 src/resources/project/menus/interfaces/menu.interface.ts create mode 100644 src/resources/project/menus/interfaces/search-domain-options.interface.ts create mode 100644 src/resources/project/menus/interfaces/update-domain-options.interface.ts create mode 100644 src/resources/project/menus/interfaces/verify-domain-response.interface.ts create mode 100644 src/resources/project/menus/menus.ts create mode 100644 src/resources/project/menus/serializers/create-domain-options.serializer.ts create mode 100644 src/resources/project/menus/serializers/domain.serializer.ts create mode 100644 src/resources/project/menus/serializers/index.ts create mode 100644 src/resources/project/menus/serializers/update-domain-options.serializer.ts delete mode 100644 src/resources/site/index.ts diff --git a/src/blutui.ts b/src/blutui.ts index 21828cb..f44f9f9 100644 --- a/src/blutui.ts +++ b/src/blutui.ts @@ -1,4 +1,5 @@ import { Agency } from './agency' +import { Project } from './project' import { FetchException, GenericServerException, @@ -29,6 +30,7 @@ export class Blutui { readonly baseURL: string private readonly client: Client private readonly _agencies: Record = {} + private readonly _projects: Record = {} readonly agencies = new Agencies(this) readonly user = new User(this) @@ -85,6 +87,19 @@ export class Blutui { return this._agencies[username] } + /** + * Get a Blutui Project instance for the given agency. + * + * @param handle - The project's handle, if the handle is different as the project's subdomain, should pass the subdomain + */ + project(handle: string): Project { + if (!this._projects[handle]) { + this._projects[handle] = new Project(handle, this) + } + + return this._projects[handle] + } + async get( path: string, options: GetOptions = {} diff --git a/src/project.ts b/src/project.ts new file mode 100644 index 0000000..21179ff --- /dev/null +++ b/src/project.ts @@ -0,0 +1,56 @@ +import { + Menu +} from './resources/project' + +import type { Blutui } from './blutui' +import type { GetOptions, PostOptions } from './types' + +export class Project { + readonly menu = new Menu(this) + + constructor( + public handle: string, + private readonly blutui: Blutui + ) {} + + async get(path: string, options: GetOptions = {}) { + return await this.blutui.get(this.getAgencyPath(path), options) + } + + async post( + path: string, + entity: Entity, + options: PostOptions = {} + ) { + return await this.blutui.post( + this.getAgencyPath(path), + entity, + options + ) + } + + async patch( + path: string, + entity: Entity, + options: PostOptions = {} + ) { + return await this.blutui.patch( + this.getAgencyPath(path), + entity, + options + ) + } + + async delete(path: string, options: PostOptions = {}) { + return await this.blutui.delete(this.getAgencyPath(path), options) + } + + /** + * Get the path for the current agency. + */ + private getAgencyPath(path: string): string { + const newPath = path.startsWith('/') ? path.replace('/', '') : path + + return `/agencies/${this.username}/${newPath}` + } +} diff --git a/src/resources/project/index.ts b/src/resources/project/index.ts new file mode 100644 index 0000000..7ba7d9b --- /dev/null +++ b/src/resources/project/index.ts @@ -0,0 +1 @@ +export { Menus } from './menus/menus' diff --git a/src/resources/project/menus/interfaces/create-domain-options.interface.ts b/src/resources/project/menus/interfaces/create-domain-options.interface.ts new file mode 100644 index 0000000..6e90bfe --- /dev/null +++ b/src/resources/project/menus/interfaces/create-domain-options.interface.ts @@ -0,0 +1,9 @@ +export interface CreateDomainOptions { + name: string + project?: string | null +} + +export interface SerializedCreateDomainOptions { + name: string + project?: string | null +} diff --git a/src/resources/project/menus/interfaces/index.ts b/src/resources/project/menus/interfaces/index.ts new file mode 100644 index 0000000..57054de --- /dev/null +++ b/src/resources/project/menus/interfaces/index.ts @@ -0,0 +1,5 @@ +export * from './menu.interface' +// export * from './create-menu-options.interface' +// export * from './update-menu-options.interface' +// export * from './search-menu-options.interface' +// export * from './verify-menu-response.interface' diff --git a/src/resources/project/menus/interfaces/menu.interface.ts b/src/resources/project/menus/interfaces/menu.interface.ts new file mode 100644 index 0000000..efe041f --- /dev/null +++ b/src/resources/project/menus/interfaces/menu.interface.ts @@ -0,0 +1,43 @@ +export interface Menu { + id: string + object: 'menu' + name: string + items: MenuItem[] + createdAt: number + updatedAt: number +} + +export interface MenuResponse { + id: string + object: 'menu' + name: string + items: MenuItemResponse[] + created_at: number + updated_at: number +} + +export interface MenuItem { + id: string + object: 'menu_item' + label: string + url: string + active: boolean + isNewTab: boolean + order: number + items: MenuItem[] + createdAt: number + updatedAt: number +} + +export interface MenuItemResponse { + id: string + object: 'menu_item' + label: string + url: string + active: boolean + is_new_tab: boolean + order: number + items: MenuItemResponse[] + created_at: number + updated_at: number +} diff --git a/src/resources/project/menus/interfaces/search-domain-options.interface.ts b/src/resources/project/menus/interfaces/search-domain-options.interface.ts new file mode 100644 index 0000000..f4e5537 --- /dev/null +++ b/src/resources/project/menus/interfaces/search-domain-options.interface.ts @@ -0,0 +1,7 @@ +export interface SearchDomainOptions { + name: string +} + +export interface SerializedSearchDomainOptions { + name: string +} diff --git a/src/resources/project/menus/interfaces/update-domain-options.interface.ts b/src/resources/project/menus/interfaces/update-domain-options.interface.ts new file mode 100644 index 0000000..919eece --- /dev/null +++ b/src/resources/project/menus/interfaces/update-domain-options.interface.ts @@ -0,0 +1,7 @@ +export interface UpdateDomainOptions { + project: string | null +} + +export interface SerializedUpdateDomainOptions { + project: string | null +} diff --git a/src/resources/project/menus/interfaces/verify-domain-response.interface.ts b/src/resources/project/menus/interfaces/verify-domain-response.interface.ts new file mode 100644 index 0000000..212b45b --- /dev/null +++ b/src/resources/project/menus/interfaces/verify-domain-response.interface.ts @@ -0,0 +1,7 @@ +// Domain Verify Response + +export interface DomainVerifyResponse { + object: 'domain_state' + verified: boolean + message: string +} diff --git a/src/resources/project/menus/menus.ts b/src/resources/project/menus/menus.ts new file mode 100644 index 0000000..56b9cf2 --- /dev/null +++ b/src/resources/project/menus/menus.ts @@ -0,0 +1,67 @@ +import type { Project } from '@/project' +import type { List, ListResponse, PaginationOptions } from '@/types' + +export class Menus { + constructor(private readonly project: Project) {} + + /** + * Get the menu list for the current project. + */ + async list(options?: PaginationOptions): Promise> { + const { data } = await this.project.get>( + 'menus', + { query: options } + ) + + return deserializeMenuList(data) + } + + /** + * Get a domain's information by ID. + */ + async get(id: string, options?: Expandable<'project'>): Promise { + const { data } = await this.agency.get(`domains/${id}`, { + query: options, + }) + + return deserializeDomain(data) + } + + + // /** + // * Create a new brand for the current agency. + // * + // * @param payload - The values to create the brand + // */ + // async create(payload: CreateBrandOptions): Promise { + // const { data } = await this.agency.post< + // BrandResponse, + // SerializedCreateBrandOptions + // >('brand', serializeCreateBrandOptions(payload)) + + // return deserializeBrand(data) + // } + + // /** + // * Update the brand for the current agency. + // * + // * @param payload - The values to update the brand + // */ + // async update(payload: UpdateBrandOptions): Promise { + // const { data } = await this.agency.patch< + // BrandResponse, + // SerializedUpdateBrandOptions + // >('brand', serializeUpdateBrandOptions(payload)) + + // return deserializeBrand(data) + // } + + // /** + // * Remove the brand for the current agency. + // */ + // async remove(): Promise { + // const { data } = await this.agency.delete('brand') + + // return data + // } +} diff --git a/src/resources/project/menus/serializers/create-domain-options.serializer.ts b/src/resources/project/menus/serializers/create-domain-options.serializer.ts new file mode 100644 index 0000000..a8740ca --- /dev/null +++ b/src/resources/project/menus/serializers/create-domain-options.serializer.ts @@ -0,0 +1,11 @@ +import type { + CreateDomainOptions, + SerializedCreateDomainOptions, +} from '../interfaces' + +export const serializeCreateDomainOptions = ( + options: CreateDomainOptions +): SerializedCreateDomainOptions => ({ + name: options.name, + project: options.project, +}) diff --git a/src/resources/project/menus/serializers/domain.serializer.ts b/src/resources/project/menus/serializers/domain.serializer.ts new file mode 100644 index 0000000..5d344e6 --- /dev/null +++ b/src/resources/project/menus/serializers/domain.serializer.ts @@ -0,0 +1,27 @@ +import { deserializePaginationMeta } from '@/utils/serializers' +import { deserializeProject } from '../../projects/serializers' + +import type { List, ListResponse } from '@/types' +import type { Domain, DomainResponse } from '../interfaces' + +export const deserializeDomain = (domain: DomainResponse): Domain => ({ + id: domain.id, + object: domain.object, + name: domain.name, + token: domain.token, + project: + domain.project instanceof Object + ? deserializeProject(domain.project) + : domain.project, + verifiedAt: domain.verified_at, + createdAt: domain.created_at, + updatedAt: domain.updated_at, +}) + +export const deserializeDomainList = ( + domains: ListResponse +): List => ({ + object: domains.object, + data: domains.data.map(deserializeDomain), + meta: deserializePaginationMeta(domains.meta), +}) diff --git a/src/resources/project/menus/serializers/index.ts b/src/resources/project/menus/serializers/index.ts new file mode 100644 index 0000000..a6ba20e --- /dev/null +++ b/src/resources/project/menus/serializers/index.ts @@ -0,0 +1,3 @@ +export * from './domain.serializer' +export * from './create-domain-options.serializer' +export * from './update-domain-options.serializer' diff --git a/src/resources/project/menus/serializers/update-domain-options.serializer.ts b/src/resources/project/menus/serializers/update-domain-options.serializer.ts new file mode 100644 index 0000000..d6ea65b --- /dev/null +++ b/src/resources/project/menus/serializers/update-domain-options.serializer.ts @@ -0,0 +1,10 @@ +import type { + SerializedUpdateDomainOptions, + UpdateDomainOptions, +} from '../interfaces' + +export const serializeUpdateDomainOptions = ( + options: UpdateDomainOptions +): SerializedUpdateDomainOptions => ({ + project: options.project, +}) diff --git a/src/resources/site/index.ts b/src/resources/site/index.ts deleted file mode 100644 index e69de29..0000000 From fe40af5e5f64e338f7e395e4654e753e4119a1e2 Mon Sep 17 00:00:00 2001 From: Michael-Macbook Date: Tue, 24 Dec 2024 12:14:07 +1300 Subject: [PATCH 05/21] menu public API --- src/project.ts | 18 +++-- .../project/menus/fixtures/menu-list.json | 21 ++++++ .../menus/fixtures/menu-with-items.json | 18 +++++ .../project/menus/fixtures/menu.json | 7 ++ .../menus/interfaces/menu.interface.ts | 8 +-- src/resources/project/menus/menus.spec.ts | 67 +++++++++++++++++++ src/resources/project/menus/menus.ts | 15 +++-- .../menus/serializers/domain.serializer.ts | 27 -------- .../project/menus/serializers/index.ts | 6 +- .../menus/serializers/menu.serializer.ts | 41 ++++++++++++ src/utils/client.ts | 7 +- 11 files changed, 183 insertions(+), 52 deletions(-) create mode 100644 src/resources/project/menus/fixtures/menu-list.json create mode 100644 src/resources/project/menus/fixtures/menu-with-items.json create mode 100644 src/resources/project/menus/fixtures/menu.json create mode 100644 src/resources/project/menus/menus.spec.ts delete mode 100644 src/resources/project/menus/serializers/domain.serializer.ts create mode 100644 src/resources/project/menus/serializers/menu.serializer.ts diff --git a/src/project.ts b/src/project.ts index 21179ff..846c2ce 100644 --- a/src/project.ts +++ b/src/project.ts @@ -1,12 +1,10 @@ -import { - Menu -} from './resources/project' +import { Menus } from './resources/project' import type { Blutui } from './blutui' import type { GetOptions, PostOptions } from './types' export class Project { - readonly menu = new Menu(this) + readonly menus = new Menus(this) constructor( public handle: string, @@ -14,7 +12,7 @@ export class Project { ) {} async get(path: string, options: GetOptions = {}) { - return await this.blutui.get(this.getAgencyPath(path), options) + return await this.blutui.get(this.getProjectPath(path), options) } async post( @@ -23,7 +21,7 @@ export class Project { options: PostOptions = {} ) { return await this.blutui.post( - this.getAgencyPath(path), + this.getProjectPath(path), entity, options ) @@ -35,22 +33,22 @@ export class Project { options: PostOptions = {} ) { return await this.blutui.patch( - this.getAgencyPath(path), + this.getProjectPath(path), entity, options ) } async delete(path: string, options: PostOptions = {}) { - return await this.blutui.delete(this.getAgencyPath(path), options) + return await this.blutui.delete(this.getProjectPath(path), options) } /** * Get the path for the current agency. */ - private getAgencyPath(path: string): string { + private getProjectPath(path: string): string { const newPath = path.startsWith('/') ? path.replace('/', '') : path - return `/agencies/${this.username}/${newPath}` + return `https://${this.handle}.blutui.com/api/${newPath}` } } diff --git a/src/resources/project/menus/fixtures/menu-list.json b/src/resources/project/menus/fixtures/menu-list.json new file mode 100644 index 0000000..f4feeaa --- /dev/null +++ b/src/resources/project/menus/fixtures/menu-list.json @@ -0,0 +1,21 @@ +{ + "object": "list", + "data": [ + { + "id": "9bfdb42b-1bf0-4510-978e-46aa329f8efa", + "object": "menu", + "name": "Primary Menu", + "created_at": 1716170007, + "updated_at": 1716170007 + } + ], + "meta": { + "hasMore": false, + "currentPage": 1, + "from": 1, + "to": 1, + "perPage": 10, + "total": 1, + "lastPage": 1 + } +} diff --git a/src/resources/project/menus/fixtures/menu-with-items.json b/src/resources/project/menus/fixtures/menu-with-items.json new file mode 100644 index 0000000..c9ab0bd --- /dev/null +++ b/src/resources/project/menus/fixtures/menu-with-items.json @@ -0,0 +1,18 @@ +{ + "id": "9bfdb42b-1bf0-4510-978e-46aa329f8efa", + "object": "menu", + "name": "Primary Menu", + "items": [{ + "id": "99bc147e-966c-4dd0-8def-de817c63cf41", + "object": "menu_item", + "label": "Contact", + "url": "/contact", + "active": true, + "is_new_tab": false, + "order": 1, + "created_at": 1720758022, + "updated_at": 1720758046 + }], + "created_at": 1716170007, + "updated_at": 1716170007 +} diff --git a/src/resources/project/menus/fixtures/menu.json b/src/resources/project/menus/fixtures/menu.json new file mode 100644 index 0000000..713e678 --- /dev/null +++ b/src/resources/project/menus/fixtures/menu.json @@ -0,0 +1,7 @@ +{ + "id": "9bfdb42b-1bf0-4510-978e-46aa329f8efa", + "object": "menu", + "name": "Primary Menu", + "created_at": 1716170007, + "updated_at": 1716170007 +} diff --git a/src/resources/project/menus/interfaces/menu.interface.ts b/src/resources/project/menus/interfaces/menu.interface.ts index efe041f..4c84c9b 100644 --- a/src/resources/project/menus/interfaces/menu.interface.ts +++ b/src/resources/project/menus/interfaces/menu.interface.ts @@ -2,7 +2,7 @@ export interface Menu { id: string object: 'menu' name: string - items: MenuItem[] + items?: MenuItem[] createdAt: number updatedAt: number } @@ -11,7 +11,7 @@ export interface MenuResponse { id: string object: 'menu' name: string - items: MenuItemResponse[] + items?: MenuItemResponse[] created_at: number updated_at: number } @@ -24,7 +24,7 @@ export interface MenuItem { active: boolean isNewTab: boolean order: number - items: MenuItem[] + items?: MenuItem[] createdAt: number updatedAt: number } @@ -37,7 +37,7 @@ export interface MenuItemResponse { active: boolean is_new_tab: boolean order: number - items: MenuItemResponse[] + items?: MenuItemResponse[] created_at: number updated_at: number } diff --git a/src/resources/project/menus/menus.spec.ts b/src/resources/project/menus/menus.spec.ts new file mode 100644 index 0000000..8c057a4 --- /dev/null +++ b/src/resources/project/menus/menus.spec.ts @@ -0,0 +1,67 @@ +import fetch from 'jest-fetch-mock' +import { Blutui } from '@/blutui' +import { fetchOnce, fetchURL } from '@/utils/testing' + +import menuListFixture from './fixtures/menu-list.json' +import menuFixture from './fixtures/menu.json' +import menuWithItemsFixture from './fixtures/menu-with-items.json' + +const accessToken = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' +const blutui = new Blutui(accessToken) + +describe('Menu', () => { + beforeEach(() => fetch.resetMocks()) + + describe('list', () => { + it('can retrieve a list of menus', async () => { + fetchOnce(menuListFixture) + const menus = await blutui.project('foo').menus.list() + + expect(fetchURL()).toBe(`https://foo.blutui.com/api/menus`) + expect(menus).toMatchObject({ + object: 'list', + }) + }) + }) + + describe('get', () => { + it('can retrieve a menu information', async () => { + fetchOnce(menuFixture) + const menu = await blutui.project('foo').menus.get(menuFixture.id) + + expect(fetchURL()).toBe(`https://foo.blutui.com/api/menus/${menuFixture.id}`) + + expect(menu).toMatchObject({ + object: 'menu', + }) + expect(menu.items).toBe(undefined) + }) + + it('can retrieve a menu information with project', async () => { + fetchOnce(menuWithItemsFixture) + const menu = await blutui + .project('foo') + .menus.get(menuWithItemsFixture.id, { + expand: ['items'], + }) + expect(fetchURL()).toBe( + encodeURI( + `https://foo.blutui.com/api/menus/${menuWithItemsFixture.id}?expand[]=items` + ) + ) + expect(menu).toMatchObject({ + object: 'menu', + }) + + // menu.items is an array of objects + expect(menu.items).toMatchObject([ + { + object: 'menu_item', + id: '99bc147e-966c-4dd0-8def-de817c63cf41', + createdAt: 1720758022, + }, + ]) + }) + }) +}) diff --git a/src/resources/project/menus/menus.ts b/src/resources/project/menus/menus.ts index 56b9cf2..230b568 100644 --- a/src/resources/project/menus/menus.ts +++ b/src/resources/project/menus/menus.ts @@ -1,13 +1,15 @@ import type { Project } from '@/project' -import type { List, ListResponse, PaginationOptions } from '@/types' +import type { Expandable, List, ListResponse, PaginationOptions } from '@/types' +import type { Menu, MenuResponse } from './interfaces' +import { deserializeMenu, deserializeMenuList } from './serializers' export class Menus { constructor(private readonly project: Project) {} - /** + /** * Get the menu list for the current project. */ - async list(options?: PaginationOptions): Promise> { + async list(options?: PaginationOptions): Promise> { const { data } = await this.project.get>( 'menus', { query: options } @@ -19,15 +21,14 @@ export class Menus { /** * Get a domain's information by ID. */ - async get(id: string, options?: Expandable<'project'>): Promise { - const { data } = await this.agency.get(`domains/${id}`, { + async get(id: string, options?: Expandable<'items'>): Promise { + const { data } = await this.project.get(`menus/${id}`, { query: options, }) - return deserializeDomain(data) + return deserializeMenu(data) } - // /** // * Create a new brand for the current agency. // * diff --git a/src/resources/project/menus/serializers/domain.serializer.ts b/src/resources/project/menus/serializers/domain.serializer.ts deleted file mode 100644 index 5d344e6..0000000 --- a/src/resources/project/menus/serializers/domain.serializer.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { deserializePaginationMeta } from '@/utils/serializers' -import { deserializeProject } from '../../projects/serializers' - -import type { List, ListResponse } from '@/types' -import type { Domain, DomainResponse } from '../interfaces' - -export const deserializeDomain = (domain: DomainResponse): Domain => ({ - id: domain.id, - object: domain.object, - name: domain.name, - token: domain.token, - project: - domain.project instanceof Object - ? deserializeProject(domain.project) - : domain.project, - verifiedAt: domain.verified_at, - createdAt: domain.created_at, - updatedAt: domain.updated_at, -}) - -export const deserializeDomainList = ( - domains: ListResponse -): List => ({ - object: domains.object, - data: domains.data.map(deserializeDomain), - meta: deserializePaginationMeta(domains.meta), -}) diff --git a/src/resources/project/menus/serializers/index.ts b/src/resources/project/menus/serializers/index.ts index a6ba20e..aa784d0 100644 --- a/src/resources/project/menus/serializers/index.ts +++ b/src/resources/project/menus/serializers/index.ts @@ -1,3 +1,3 @@ -export * from './domain.serializer' -export * from './create-domain-options.serializer' -export * from './update-domain-options.serializer' +export * from './menu.serializer' +// export * from './create-domain-options.serializer' +// export * from './update-domain-options.serializer' diff --git a/src/resources/project/menus/serializers/menu.serializer.ts b/src/resources/project/menus/serializers/menu.serializer.ts new file mode 100644 index 0000000..4c311e0 --- /dev/null +++ b/src/resources/project/menus/serializers/menu.serializer.ts @@ -0,0 +1,41 @@ +import { deserializePaginationMeta } from '@/utils/serializers' + +import type { List, ListResponse } from '@/types' +import type { + Menu, + MenuItem, + MenuItemResponse, + MenuResponse, +} from '../interfaces' + +export const deserializeMenu = (menu: MenuResponse): Menu => ({ + id: menu.id, + object: menu.object, + name: menu.name, + ...(menu.items !== undefined && { items: deserializeMenuItem(menu.items) }), + createdAt: menu.created_at, + updatedAt: menu.updated_at, +}) + +export const deserializeMenuItem = (items: MenuItemResponse[]): MenuItem[] => { + return items.map((item) => ({ + id: item.id, + object: item.object, + label: item.label, + url: item.url, + active: item.active, + isNewTab: item.is_new_tab, + order: item.order, + ...(item.items !== undefined && { items: deserializeMenuItem(item.items) }), + createdAt: item.created_at, + updatedAt: item.updated_at, + })) +} + +export const deserializeMenuList = ( + menus: ListResponse +): List => ({ + object: menus.object, + data: menus.data.map(deserializeMenu), + meta: deserializePaginationMeta(menus.meta), +}) diff --git a/src/utils/client.ts b/src/utils/client.ts index 0c77d50..69a501d 100644 --- a/src/utils/client.ts +++ b/src/utils/client.ts @@ -90,7 +90,12 @@ export class Client { private getResourceURL(path: string, params?: Record) { const queryString = getQueryString(params) const url = new URL( - [this.pathUsingVersion(path), queryString].filter(Boolean).join('?'), + [ + path.startsWith('http') ? path : this.pathUsingVersion(path), + queryString, + ] + .filter(Boolean) + .join('?'), this.baseURL ) return url.toString() From 479701fc2bd69fe9f3e1a28c3536779a7aa4b1bd Mon Sep 17 00:00:00 2001 From: Michael-Macbook Date: Tue, 24 Dec 2024 12:16:51 +1300 Subject: [PATCH 06/21] fix tests --- src/resources/project/menus/menus.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/project/menus/menus.spec.ts b/src/resources/project/menus/menus.spec.ts index 8c057a4..0d0e7db 100644 --- a/src/resources/project/menus/menus.spec.ts +++ b/src/resources/project/menus/menus.spec.ts @@ -18,7 +18,7 @@ describe('Menu', () => { fetchOnce(menuListFixture) const menus = await blutui.project('foo').menus.list() - expect(fetchURL()).toBe(`https://foo.blutui.com/api/menus`) + expect(fetchURL()).toBe('https://foo.blutui.com/api/menus') expect(menus).toMatchObject({ object: 'list', }) From f29d41f1eafaef03b9a76a4cc077b5883d52e94d Mon Sep 17 00:00:00 2001 From: Michael-Macbook Date: Tue, 24 Dec 2024 12:18:30 +1300 Subject: [PATCH 07/21] remove comment --- src/resources/project/menus/menus.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/resources/project/menus/menus.spec.ts b/src/resources/project/menus/menus.spec.ts index 0d0e7db..db35282 100644 --- a/src/resources/project/menus/menus.spec.ts +++ b/src/resources/project/menus/menus.spec.ts @@ -30,7 +30,9 @@ describe('Menu', () => { fetchOnce(menuFixture) const menu = await blutui.project('foo').menus.get(menuFixture.id) - expect(fetchURL()).toBe(`https://foo.blutui.com/api/menus/${menuFixture.id}`) + expect(fetchURL()).toBe( + `https://foo.blutui.com/api/menus/${menuFixture.id}` + ) expect(menu).toMatchObject({ object: 'menu', @@ -54,7 +56,6 @@ describe('Menu', () => { object: 'menu', }) - // menu.items is an array of objects expect(menu.items).toMatchObject([ { object: 'menu_item', From 6d9730dd999dbcbce2dbe9f41e1f9c370253ae42 Mon Sep 17 00:00:00 2001 From: Michael-Macbook Date: Tue, 24 Dec 2024 14:44:26 +1300 Subject: [PATCH 08/21] reset release.yml --- .github/workflows/release.yml | 25 ++++-- src/admin.ts | 54 ++++++++++++ src/project.ts | 2 + .../create-domain-options.interface.ts | 9 -- .../create-menu-options.interface.ts | 19 +++++ .../project/menus/menus.admin.spec.ts | 82 +++++++++++++++++++ src/resources/project/menus/menus.ts | 27 +++--- 7 files changed, 188 insertions(+), 30 deletions(-) create mode 100644 src/admin.ts delete mode 100644 src/resources/project/menus/interfaces/create-domain-options.interface.ts create mode 100644 src/resources/project/menus/interfaces/create-menu-options.interface.ts create mode 100644 src/resources/project/menus/menus.admin.spec.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 50ba442..3eaef14 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,13 +17,22 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Update blutui.ts version - run: sed -i "s/const VERSION = '.*';/const VERSION = '${{ github.ref_name }}';/g" src/blutui.ts - - - name: Commit version change - uses: stefanzweifel/git-auto-commit-action@v5 + - name: Setup Node + uses: actions/setup-node@v4 with: - commit_message: "Update version to v${{ github.ref_name }}" + node-version: 20 + registry-url: 'https://registry.npmjs.org' + + - name: Clean install dependencies + run: | + npm ci + + - name: Run tests + run: | + npm run test - - name: Push changes - run: git push + - name: Publish to NPM + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + npm publish diff --git a/src/admin.ts b/src/admin.ts new file mode 100644 index 0000000..8b51ac7 --- /dev/null +++ b/src/admin.ts @@ -0,0 +1,54 @@ +import { Menus } from './resources/project' + +import type { Blutui } from './blutui' +import type { GetOptions, PostOptions } from './types' +import type { Project } from './project' + +export class Admin { + readonly menus = new Menus(this) + + constructor( + private readonly project: Project, + private readonly blutui: Blutui + ) {} + + async get(path: string, options: GetOptions = {}) { + return await this.blutui.get(this.getProjectPath(path), options) + } + + async post( + path: string, + entity: Entity, + options: PostOptions = {} + ) { + return await this.blutui.post( + this.getProjectPath(path), + entity, + options + ) + } + + async patch( + path: string, + entity: Entity, + options: PostOptions = {} + ) { + return await this.blutui.patch( + this.getProjectPath(path), + entity, + options + ) + } + + async delete(path: string, options: PostOptions = {}) { + return await this.blutui.delete(this.getProjectPath(path), options) + } + + /** + * Get the path for the current agency. + */ + private getProjectPath(path: string): string { + const newPath = path.startsWith('/') ? path.replace('/', '') : path + return `https://${this.project.handle}.blutui.com/admin/api/${newPath}` + } +} diff --git a/src/project.ts b/src/project.ts index 846c2ce..61419e3 100644 --- a/src/project.ts +++ b/src/project.ts @@ -1,9 +1,11 @@ import { Menus } from './resources/project' import type { Blutui } from './blutui' +import { Admin } from './admin' import type { GetOptions, PostOptions } from './types' export class Project { + readonly admin = new Admin(this, this.blutui) readonly menus = new Menus(this) constructor( diff --git a/src/resources/project/menus/interfaces/create-domain-options.interface.ts b/src/resources/project/menus/interfaces/create-domain-options.interface.ts deleted file mode 100644 index 6e90bfe..0000000 --- a/src/resources/project/menus/interfaces/create-domain-options.interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface CreateDomainOptions { - name: string - project?: string | null -} - -export interface SerializedCreateDomainOptions { - name: string - project?: string | null -} diff --git a/src/resources/project/menus/interfaces/create-menu-options.interface.ts b/src/resources/project/menus/interfaces/create-menu-options.interface.ts new file mode 100644 index 0000000..a3c60f9 --- /dev/null +++ b/src/resources/project/menus/interfaces/create-menu-options.interface.ts @@ -0,0 +1,19 @@ +export interface CreateMenuOptions { + handle: string + name: string + items?: CreateMenuItemOptions[] +} + +export interface SerializedCreateMenuOptions { + handle: string + name: string + items?: string | null +} + +export interface CreateMenuItemOptions { + label: string + url: string + is_new_tab: boolean + active: boolean + items?: CreateMenuItemOptions[] +} diff --git a/src/resources/project/menus/menus.admin.spec.ts b/src/resources/project/menus/menus.admin.spec.ts new file mode 100644 index 0000000..1ff61d1 --- /dev/null +++ b/src/resources/project/menus/menus.admin.spec.ts @@ -0,0 +1,82 @@ +import fetch from 'jest-fetch-mock' +import { Blutui } from '@/blutui' +import { fetchOnce, fetchURL } from '@/utils/testing' + +import menuListFixture from './fixtures/menu-list.json' +import menuFixture from './fixtures/menu.json' +import menuWithItemsFixture from './fixtures/menu-with-items.json' + +const accessToken = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' +const blutui = new Blutui(accessToken) + +describe('Menu', () => { + beforeEach(() => fetch.resetMocks()) + + describe('list', () => { + it('can retrieve a list of menus', async () => { + fetchOnce(menuListFixture) + const menus = await blutui.project('foo').admin.menus.list() + + expect(fetchURL()).toBe('https://foo.blutui.com/admin/api/menus') + expect(menus).toMatchObject({ + object: 'list', + }) + }) + }) + + describe('get', () => { + it('can retrieve a menu information', async () => { + fetchOnce(menuFixture) + const menu = await blutui.project('foo').admin.menus.get(menuFixture.id) + + expect(fetchURL()).toBe( + `https://foo.blutui.com/admin/api/menus/${menuFixture.id}` + ) + + expect(menu).toMatchObject({ + object: 'menu', + }) + expect(menu.items).toBe(undefined) + }) + + it('can retrieve a menu information with project', async () => { + fetchOnce(menuWithItemsFixture) + const menu = await blutui + .project('foo') + .admin.menus.get(menuWithItemsFixture.id, { + expand: ['items'], + }) + expect(fetchURL()).toBe( + encodeURI( + `https://foo.blutui.com/admin/api/menus/${menuWithItemsFixture.id}?expand[]=items` + ) + ) + expect(menu).toMatchObject({ + object: 'menu', + }) + + expect(menu.items).toMatchObject([ + { + object: 'menu_item', + id: '99bc147e-966c-4dd0-8def-de817c63cf41', + createdAt: 1720758022, + }, + ]) + }) + }) + + describe('create', () => { + it('can create a new menu', async () => { + fetchOnce(menuFixture) + const menu = await blutui.project('foo').admin.menus.create({ + name: 'Main Menu', + }) + + expect(fetchURL()).toBe('https://foo.blutui.com/admin/api/menus') + expect(menu).toMatchObject({ + object: 'menu', + }) + }) + }) +}) diff --git a/src/resources/project/menus/menus.ts b/src/resources/project/menus/menus.ts index 230b568..b749c21 100644 --- a/src/resources/project/menus/menus.ts +++ b/src/resources/project/menus/menus.ts @@ -2,9 +2,10 @@ import type { Project } from '@/project' import type { Expandable, List, ListResponse, PaginationOptions } from '@/types' import type { Menu, MenuResponse } from './interfaces' import { deserializeMenu, deserializeMenuList } from './serializers' +import type { Admin } from '@/admin' export class Menus { - constructor(private readonly project: Project) {} + constructor(private readonly project: Project | Admin) {} /** * Get the menu list for the current project. @@ -29,19 +30,19 @@ export class Menus { return deserializeMenu(data) } - // /** - // * Create a new brand for the current agency. - // * - // * @param payload - The values to create the brand - // */ - // async create(payload: CreateBrandOptions): Promise { - // const { data } = await this.agency.post< - // BrandResponse, - // SerializedCreateBrandOptions - // >('brand', serializeCreateBrandOptions(payload)) + /** + * Create a new brand for the current agency. + * + * @param payload - The values to create the brand + */ + async create(payload: CreateMenuOptions): Promise { + const { data } = await this.project.post< + MenuResponse, + SerializedCreateMenuOptions + >('menus', serializeCreateMenuOptions(payload)) - // return deserializeBrand(data) - // } + return deserializeMenu(data) + } // /** // * Update the brand for the current agency. From ee416e8d477b237e9aa5ef5c36e2d05b839a4d5b Mon Sep 17 00:00:00 2001 From: Michael-Macbook Date: Tue, 24 Dec 2024 15:00:11 +1300 Subject: [PATCH 09/21] project('foo').admin.menu.create --- .../create-menu-options.interface.ts | 2 +- .../project/menus/interfaces/index.ts | 2 +- .../project/menus/menus.admin.spec.ts | 33 ++++++++++++++++++- src/resources/project/menus/menus.ts | 13 ++++++-- .../create-domain-options.serializer.ts | 11 ------- .../create-menu-options.serializer.ts | 12 +++++++ .../project/menus/serializers/index.ts | 2 +- 7 files changed, 58 insertions(+), 17 deletions(-) delete mode 100644 src/resources/project/menus/serializers/create-domain-options.serializer.ts create mode 100644 src/resources/project/menus/serializers/create-menu-options.serializer.ts diff --git a/src/resources/project/menus/interfaces/create-menu-options.interface.ts b/src/resources/project/menus/interfaces/create-menu-options.interface.ts index a3c60f9..4a85d76 100644 --- a/src/resources/project/menus/interfaces/create-menu-options.interface.ts +++ b/src/resources/project/menus/interfaces/create-menu-options.interface.ts @@ -7,7 +7,7 @@ export interface CreateMenuOptions { export interface SerializedCreateMenuOptions { handle: string name: string - items?: string | null + items?: CreateMenuItemOptions[] } export interface CreateMenuItemOptions { diff --git a/src/resources/project/menus/interfaces/index.ts b/src/resources/project/menus/interfaces/index.ts index 57054de..907c80e 100644 --- a/src/resources/project/menus/interfaces/index.ts +++ b/src/resources/project/menus/interfaces/index.ts @@ -1,5 +1,5 @@ export * from './menu.interface' -// export * from './create-menu-options.interface' +export * from './create-menu-options.interface' // export * from './update-menu-options.interface' // export * from './search-menu-options.interface' // export * from './verify-menu-response.interface' diff --git a/src/resources/project/menus/menus.admin.spec.ts b/src/resources/project/menus/menus.admin.spec.ts index 1ff61d1..a3791a8 100644 --- a/src/resources/project/menus/menus.admin.spec.ts +++ b/src/resources/project/menus/menus.admin.spec.ts @@ -70,7 +70,8 @@ describe('Menu', () => { it('can create a new menu', async () => { fetchOnce(menuFixture) const menu = await blutui.project('foo').admin.menus.create({ - name: 'Main Menu', + name: 'Primary Menu', + handle: 'primary-menu', }) expect(fetchURL()).toBe('https://foo.blutui.com/admin/api/menus') @@ -78,5 +79,35 @@ describe('Menu', () => { object: 'menu', }) }) + + it('can create a new menu with items', async () => { + fetchOnce(menuWithItemsFixture) + const menu = await blutui.project('foo').admin.menus.create({ + name: 'Primary Menu', + handle: 'primary-menu', + items: [ + { + label: 'Contact', + url: '/contact', + is_new_tab: false, + active: true, + }, + ], + }) + + expect(fetchURL()).toBe('https://foo.blutui.com/admin/api/menus') + expect(menu).toMatchObject({ + object: 'menu', + items: [ + { + object: 'menu_item', + label: 'Contact', + url: '/contact', + isNewTab: false, + active: true, + }, + ], + }) + }) }) }) diff --git a/src/resources/project/menus/menus.ts b/src/resources/project/menus/menus.ts index b749c21..18d9cec 100644 --- a/src/resources/project/menus/menus.ts +++ b/src/resources/project/menus/menus.ts @@ -1,7 +1,16 @@ import type { Project } from '@/project' import type { Expandable, List, ListResponse, PaginationOptions } from '@/types' -import type { Menu, MenuResponse } from './interfaces' -import { deserializeMenu, deserializeMenuList } from './serializers' +import type { + Menu, + MenuResponse, + CreateMenuOptions, + SerializedCreateMenuOptions, +} from './interfaces' +import { + deserializeMenu, + deserializeMenuList, + serializeCreateMenuOptions, +} from './serializers' import type { Admin } from '@/admin' export class Menus { diff --git a/src/resources/project/menus/serializers/create-domain-options.serializer.ts b/src/resources/project/menus/serializers/create-domain-options.serializer.ts deleted file mode 100644 index a8740ca..0000000 --- a/src/resources/project/menus/serializers/create-domain-options.serializer.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { - CreateDomainOptions, - SerializedCreateDomainOptions, -} from '../interfaces' - -export const serializeCreateDomainOptions = ( - options: CreateDomainOptions -): SerializedCreateDomainOptions => ({ - name: options.name, - project: options.project, -}) diff --git a/src/resources/project/menus/serializers/create-menu-options.serializer.ts b/src/resources/project/menus/serializers/create-menu-options.serializer.ts new file mode 100644 index 0000000..27fcb3e --- /dev/null +++ b/src/resources/project/menus/serializers/create-menu-options.serializer.ts @@ -0,0 +1,12 @@ +import type { + CreateMenuOptions, + SerializedCreateMenuOptions, +} from '../interfaces' + +export const serializeCreateMenuOptions = ( + options: CreateMenuOptions +): SerializedCreateMenuOptions => ({ + name: options.name, + handle: options.handle, + items: options.items, +}) diff --git a/src/resources/project/menus/serializers/index.ts b/src/resources/project/menus/serializers/index.ts index aa784d0..f92287d 100644 --- a/src/resources/project/menus/serializers/index.ts +++ b/src/resources/project/menus/serializers/index.ts @@ -1,3 +1,3 @@ export * from './menu.serializer' -// export * from './create-domain-options.serializer' +export * from './create-menu-options.serializer' // export * from './update-domain-options.serializer' From 76fc63f36812a82a4b615180c6aaede25ec671bd Mon Sep 17 00:00:00 2001 From: Michael-Macbook Date: Fri, 27 Dec 2024 11:01:04 +1300 Subject: [PATCH 10/21] project('foo').admin.menu --- .../project/menus/interfaces/index.ts | 4 +- .../search-domain-options.interface.ts | 7 --- .../update-domain-options.interface.ts | 7 --- .../update-menu-options.interface.ts | 19 +++++++ .../verify-domain-response.interface.ts | 7 --- .../project/menus/menus.admin.spec.ts | 55 +++++++++++++++++++ src/resources/project/menus/menus.ts | 55 +++++++++++-------- .../project/menus/serializers/index.ts | 2 +- .../update-domain-options.serializer.ts | 10 ---- .../update-menu-options.serializer.ts | 12 ++++ 10 files changed, 120 insertions(+), 58 deletions(-) delete mode 100644 src/resources/project/menus/interfaces/search-domain-options.interface.ts delete mode 100644 src/resources/project/menus/interfaces/update-domain-options.interface.ts create mode 100644 src/resources/project/menus/interfaces/update-menu-options.interface.ts delete mode 100644 src/resources/project/menus/interfaces/verify-domain-response.interface.ts delete mode 100644 src/resources/project/menus/serializers/update-domain-options.serializer.ts create mode 100644 src/resources/project/menus/serializers/update-menu-options.serializer.ts diff --git a/src/resources/project/menus/interfaces/index.ts b/src/resources/project/menus/interfaces/index.ts index 907c80e..76863da 100644 --- a/src/resources/project/menus/interfaces/index.ts +++ b/src/resources/project/menus/interfaces/index.ts @@ -1,5 +1,3 @@ export * from './menu.interface' export * from './create-menu-options.interface' -// export * from './update-menu-options.interface' -// export * from './search-menu-options.interface' -// export * from './verify-menu-response.interface' +export * from './update-menu-options.interface' diff --git a/src/resources/project/menus/interfaces/search-domain-options.interface.ts b/src/resources/project/menus/interfaces/search-domain-options.interface.ts deleted file mode 100644 index f4e5537..0000000 --- a/src/resources/project/menus/interfaces/search-domain-options.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface SearchDomainOptions { - name: string -} - -export interface SerializedSearchDomainOptions { - name: string -} diff --git a/src/resources/project/menus/interfaces/update-domain-options.interface.ts b/src/resources/project/menus/interfaces/update-domain-options.interface.ts deleted file mode 100644 index 919eece..0000000 --- a/src/resources/project/menus/interfaces/update-domain-options.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface UpdateDomainOptions { - project: string | null -} - -export interface SerializedUpdateDomainOptions { - project: string | null -} diff --git a/src/resources/project/menus/interfaces/update-menu-options.interface.ts b/src/resources/project/menus/interfaces/update-menu-options.interface.ts new file mode 100644 index 0000000..2ed46dd --- /dev/null +++ b/src/resources/project/menus/interfaces/update-menu-options.interface.ts @@ -0,0 +1,19 @@ +export interface UpdateMenuOptions { + handle?: string + name?: string + items?: UpdateMenuItemOptions[] +} + +export interface SerializedUpdateMenuOptions { + handle?: string + name?: string + items?: UpdateMenuItemOptions[] +} + +export interface UpdateMenuItemOptions { + label: string + url: string + is_new_tab: boolean + active: boolean + items?: UpdateMenuItemOptions[] +} diff --git a/src/resources/project/menus/interfaces/verify-domain-response.interface.ts b/src/resources/project/menus/interfaces/verify-domain-response.interface.ts deleted file mode 100644 index 212b45b..0000000 --- a/src/resources/project/menus/interfaces/verify-domain-response.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Domain Verify Response - -export interface DomainVerifyResponse { - object: 'domain_state' - verified: boolean - message: string -} diff --git a/src/resources/project/menus/menus.admin.spec.ts b/src/resources/project/menus/menus.admin.spec.ts index a3791a8..6235e8a 100644 --- a/src/resources/project/menus/menus.admin.spec.ts +++ b/src/resources/project/menus/menus.admin.spec.ts @@ -110,4 +110,59 @@ describe('Menu', () => { }) }) }) + + describe('update', () => { + it('can update a menu', async () => { + fetchOnce(menuFixture) + const menu = await blutui + .project('foo') + .admin.menus.update(menuFixture.id, { + name: 'Primary Menu Updated', + handle: 'primary-menu-updated', + }) + + expect(fetchURL()).toBe( + `https://foo.blutui.com/admin/api/menus/${menuFixture.id}` + ) + expect(menu).toMatchObject({ + object: 'menu', + }) + }) + + it('can update a menu with items', async () => { + fetchOnce(menuFixture) + const menu = await blutui + .project('foo') + .admin.menus.update(menuFixture.id, { + name: 'Primary Menu Updated', + handle: 'primary-menu-updated', + items: [ + { + label: 'Contact', + url: '/contact', + is_new_tab: false, + active: true, + }, + ], + }) + + expect(fetchURL()).toBe( + `https://foo.blutui.com/admin/api/menus/${menuFixture.id}` + ) + expect(menu).toMatchObject({ + object: 'menu', + }) + }) + }) + + describe('remove', () => { + it('can remove a menu', async () => { + fetchOnce(menuFixture) + await blutui.project('foo').admin.menus.remove(menuFixture.id) + + expect(fetchURL()).toBe( + `https://foo.blutui.com/admin/api/menus/${menuFixture.id}` + ) + }) + }) }) diff --git a/src/resources/project/menus/menus.ts b/src/resources/project/menus/menus.ts index 18d9cec..016d6a3 100644 --- a/src/resources/project/menus/menus.ts +++ b/src/resources/project/menus/menus.ts @@ -1,15 +1,24 @@ import type { Project } from '@/project' -import type { Expandable, List, ListResponse, PaginationOptions } from '@/types' +import type { + DeletedResponse, + Expandable, + List, + ListResponse, + PaginationOptions, +} from '@/types' import type { Menu, MenuResponse, CreateMenuOptions, SerializedCreateMenuOptions, + UpdateMenuOptions, + SerializedUpdateMenuOptions, } from './interfaces' import { deserializeMenu, deserializeMenuList, serializeCreateMenuOptions, + serializeUpdateMenuOptions, } from './serializers' import type { Admin } from '@/admin' @@ -29,7 +38,7 @@ export class Menus { } /** - * Get a domain's information by ID. + * Get a menu's information by ID. */ async get(id: string, options?: Expandable<'items'>): Promise { const { data } = await this.project.get(`menus/${id}`, { @@ -40,9 +49,9 @@ export class Menus { } /** - * Create a new brand for the current agency. + * Create a new menu for a project. * - * @param payload - The values to create the brand + * @param payload - The values to create the menu */ async create(payload: CreateMenuOptions): Promise { const { data } = await this.project.post< @@ -53,26 +62,26 @@ export class Menus { return deserializeMenu(data) } - // /** - // * Update the brand for the current agency. - // * - // * @param payload - The values to update the brand - // */ - // async update(payload: UpdateBrandOptions): Promise { - // const { data } = await this.agency.patch< - // BrandResponse, - // SerializedUpdateBrandOptions - // >('brand', serializeUpdateBrandOptions(payload)) + /** + * Update the menu for the a project. + * + * @param payload - The values to update the menu + */ + async update(id: string, payload: UpdateMenuOptions): Promise { + const { data } = await this.project.patch< + MenuResponse, + SerializedUpdateMenuOptions + >(`menus/${id}`, serializeUpdateMenuOptions(payload)) - // return deserializeBrand(data) - // } + return deserializeMenu(data) + } - // /** - // * Remove the brand for the current agency. - // */ - // async remove(): Promise { - // const { data } = await this.agency.delete('brand') + /** + * Remove the menu for the current project. + */ + async remove(id: string): Promise { + const { data } = await this.project.delete(`menus/${id}`) - // return data - // } + return data + } } diff --git a/src/resources/project/menus/serializers/index.ts b/src/resources/project/menus/serializers/index.ts index f92287d..8be2d31 100644 --- a/src/resources/project/menus/serializers/index.ts +++ b/src/resources/project/menus/serializers/index.ts @@ -1,3 +1,3 @@ export * from './menu.serializer' export * from './create-menu-options.serializer' -// export * from './update-domain-options.serializer' +export * from './update-menu-options.serializer' diff --git a/src/resources/project/menus/serializers/update-domain-options.serializer.ts b/src/resources/project/menus/serializers/update-domain-options.serializer.ts deleted file mode 100644 index d6ea65b..0000000 --- a/src/resources/project/menus/serializers/update-domain-options.serializer.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { - SerializedUpdateDomainOptions, - UpdateDomainOptions, -} from '../interfaces' - -export const serializeUpdateDomainOptions = ( - options: UpdateDomainOptions -): SerializedUpdateDomainOptions => ({ - project: options.project, -}) diff --git a/src/resources/project/menus/serializers/update-menu-options.serializer.ts b/src/resources/project/menus/serializers/update-menu-options.serializer.ts new file mode 100644 index 0000000..e7e03da --- /dev/null +++ b/src/resources/project/menus/serializers/update-menu-options.serializer.ts @@ -0,0 +1,12 @@ +import type { + SerializedUpdateMenuOptions, + UpdateMenuOptions, +} from '../interfaces' + +export const serializeUpdateMenuOptions = ( + options: UpdateMenuOptions +): SerializedUpdateMenuOptions => ({ + name: options.name, + handle: options.handle, + items: options.items, +}) From 9a28db76f1adf997128e365a65fd2e7827704734 Mon Sep 17 00:00:00 2001 From: Michael-Macbook Date: Fri, 27 Dec 2024 11:29:14 +1300 Subject: [PATCH 11/21] add admin.ts --- src/admin.ts | 2 +- src/resources/project/admin.ts | 1 + src/resources/project/menus/menus.admin.ts | 87 ++++++++++++++++++++++ src/resources/project/menus/menus.ts | 61 +-------------- 4 files changed, 92 insertions(+), 59 deletions(-) create mode 100644 src/resources/project/admin.ts create mode 100644 src/resources/project/menus/menus.admin.ts diff --git a/src/admin.ts b/src/admin.ts index 8b51ac7..fef775a 100644 --- a/src/admin.ts +++ b/src/admin.ts @@ -1,4 +1,4 @@ -import { Menus } from './resources/project' +import { Menus } from './resources/project/admin' import type { Blutui } from './blutui' import type { GetOptions, PostOptions } from './types' diff --git a/src/resources/project/admin.ts b/src/resources/project/admin.ts new file mode 100644 index 0000000..9163b49 --- /dev/null +++ b/src/resources/project/admin.ts @@ -0,0 +1 @@ +export { Menus } from './menus/menus.admin' diff --git a/src/resources/project/menus/menus.admin.ts b/src/resources/project/menus/menus.admin.ts new file mode 100644 index 0000000..016d6a3 --- /dev/null +++ b/src/resources/project/menus/menus.admin.ts @@ -0,0 +1,87 @@ +import type { Project } from '@/project' +import type { + DeletedResponse, + Expandable, + List, + ListResponse, + PaginationOptions, +} from '@/types' +import type { + Menu, + MenuResponse, + CreateMenuOptions, + SerializedCreateMenuOptions, + UpdateMenuOptions, + SerializedUpdateMenuOptions, +} from './interfaces' +import { + deserializeMenu, + deserializeMenuList, + serializeCreateMenuOptions, + serializeUpdateMenuOptions, +} from './serializers' +import type { Admin } from '@/admin' + +export class Menus { + constructor(private readonly project: Project | Admin) {} + + /** + * Get the menu list for the current project. + */ + async list(options?: PaginationOptions): Promise> { + const { data } = await this.project.get>( + 'menus', + { query: options } + ) + + return deserializeMenuList(data) + } + + /** + * Get a menu's information by ID. + */ + async get(id: string, options?: Expandable<'items'>): Promise { + const { data } = await this.project.get(`menus/${id}`, { + query: options, + }) + + return deserializeMenu(data) + } + + /** + * Create a new menu for a project. + * + * @param payload - The values to create the menu + */ + async create(payload: CreateMenuOptions): Promise { + const { data } = await this.project.post< + MenuResponse, + SerializedCreateMenuOptions + >('menus', serializeCreateMenuOptions(payload)) + + return deserializeMenu(data) + } + + /** + * Update the menu for the a project. + * + * @param payload - The values to update the menu + */ + async update(id: string, payload: UpdateMenuOptions): Promise { + const { data } = await this.project.patch< + MenuResponse, + SerializedUpdateMenuOptions + >(`menus/${id}`, serializeUpdateMenuOptions(payload)) + + return deserializeMenu(data) + } + + /** + * Remove the menu for the current project. + */ + async remove(id: string): Promise { + const { data } = await this.project.delete(`menus/${id}`) + + return data + } +} diff --git a/src/resources/project/menus/menus.ts b/src/resources/project/menus/menus.ts index 016d6a3..df34995 100644 --- a/src/resources/project/menus/menus.ts +++ b/src/resources/project/menus/menus.ts @@ -1,25 +1,7 @@ import type { Project } from '@/project' -import type { - DeletedResponse, - Expandable, - List, - ListResponse, - PaginationOptions, -} from '@/types' -import type { - Menu, - MenuResponse, - CreateMenuOptions, - SerializedCreateMenuOptions, - UpdateMenuOptions, - SerializedUpdateMenuOptions, -} from './interfaces' -import { - deserializeMenu, - deserializeMenuList, - serializeCreateMenuOptions, - serializeUpdateMenuOptions, -} from './serializers' +import type { Expandable, List, ListResponse, PaginationOptions } from '@/types' +import type { Menu, MenuResponse } from './interfaces' +import { deserializeMenu, deserializeMenuList } from './serializers' import type { Admin } from '@/admin' export class Menus { @@ -47,41 +29,4 @@ export class Menus { return deserializeMenu(data) } - - /** - * Create a new menu for a project. - * - * @param payload - The values to create the menu - */ - async create(payload: CreateMenuOptions): Promise { - const { data } = await this.project.post< - MenuResponse, - SerializedCreateMenuOptions - >('menus', serializeCreateMenuOptions(payload)) - - return deserializeMenu(data) - } - - /** - * Update the menu for the a project. - * - * @param payload - The values to update the menu - */ - async update(id: string, payload: UpdateMenuOptions): Promise { - const { data } = await this.project.patch< - MenuResponse, - SerializedUpdateMenuOptions - >(`menus/${id}`, serializeUpdateMenuOptions(payload)) - - return deserializeMenu(data) - } - - /** - * Remove the menu for the current project. - */ - async remove(id: string): Promise { - const { data } = await this.project.delete(`menus/${id}`) - - return data - } } From fca20e088f91a17b81bdfc4f7e27ef2b9a46dc23 Mon Sep 17 00:00:00 2001 From: Michael-Macbook Date: Fri, 27 Dec 2024 12:00:41 +1300 Subject: [PATCH 12/21] add a request class --- src/admin.ts | 43 +++++-------------------------------------- src/agency.ts | 44 ++++++-------------------------------------- src/project.ts | 43 +++++-------------------------------------- src/request.ts | 45 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 114 deletions(-) create mode 100644 src/request.ts diff --git a/src/admin.ts b/src/admin.ts index fef775a..de0cab7 100644 --- a/src/admin.ts +++ b/src/admin.ts @@ -1,53 +1,20 @@ import { Menus } from './resources/project/admin' import type { Blutui } from './blutui' -import type { GetOptions, PostOptions } from './types' import type { Project } from './project' +import { Request } from './request' -export class Admin { +export class Admin extends Request { readonly menus = new Menus(this) constructor( private readonly project: Project, - private readonly blutui: Blutui - ) {} - - async get(path: string, options: GetOptions = {}) { - return await this.blutui.get(this.getProjectPath(path), options) - } - - async post( - path: string, - entity: Entity, - options: PostOptions = {} - ) { - return await this.blutui.post( - this.getProjectPath(path), - entity, - options - ) - } - - async patch( - path: string, - entity: Entity, - options: PostOptions = {} + protected readonly blutui: Blutui ) { - return await this.blutui.patch( - this.getProjectPath(path), - entity, - options - ) - } - - async delete(path: string, options: PostOptions = {}) { - return await this.blutui.delete(this.getProjectPath(path), options) + super(blutui) } - /** - * Get the path for the current agency. - */ - private getProjectPath(path: string): string { + protected getRequestPath(path: string): string { const newPath = path.startsWith('/') ? path.replace('/', '') : path return `https://${this.project.handle}.blutui.com/admin/api/${newPath}` } diff --git a/src/agency.ts b/src/agency.ts index c057768..d9e33a3 100644 --- a/src/agency.ts +++ b/src/agency.ts @@ -9,9 +9,10 @@ import { } from './resources/agency' import type { Blutui } from './blutui' -import type { GetOptions, PostOptions } from './types' -export class Agency { +import { Request } from './request' + +export class Agency extends Request { readonly brand = new Brand(this) readonly cassettes = new Cassettes(this) readonly domains = new Domains(this) @@ -22,45 +23,12 @@ export class Agency { constructor( public username: string, - private readonly blutui: Blutui - ) {} - - async get(path: string, options: GetOptions = {}) { - return await this.blutui.get(this.getAgencyPath(path), options) - } - - async post( - path: string, - entity: Entity, - options: PostOptions = {} + protected readonly blutui: Blutui ) { - return await this.blutui.post( - this.getAgencyPath(path), - entity, - options - ) - } - - async patch( - path: string, - entity: Entity, - options: PostOptions = {} - ) { - return await this.blutui.patch( - this.getAgencyPath(path), - entity, - options - ) - } - - async delete(path: string, options: PostOptions = {}) { - return await this.blutui.delete(this.getAgencyPath(path), options) + super(blutui) } - /** - * Get the path for the current agency. - */ - private getAgencyPath(path: string): string { + protected getRequestPath(path: string): string { const newPath = path.startsWith('/') ? path.replace('/', '') : path return `/agencies/${this.username}/${newPath}` diff --git a/src/project.ts b/src/project.ts index 61419e3..0c7cea4 100644 --- a/src/project.ts +++ b/src/project.ts @@ -2,53 +2,20 @@ import { Menus } from './resources/project' import type { Blutui } from './blutui' import { Admin } from './admin' -import type { GetOptions, PostOptions } from './types' +import { Request } from './request' -export class Project { +export class Project extends Request { readonly admin = new Admin(this, this.blutui) readonly menus = new Menus(this) constructor( public handle: string, - private readonly blutui: Blutui - ) {} - - async get(path: string, options: GetOptions = {}) { - return await this.blutui.get(this.getProjectPath(path), options) - } - - async post( - path: string, - entity: Entity, - options: PostOptions = {} - ) { - return await this.blutui.post( - this.getProjectPath(path), - entity, - options - ) - } - - async patch( - path: string, - entity: Entity, - options: PostOptions = {} + protected readonly blutui: Blutui ) { - return await this.blutui.patch( - this.getProjectPath(path), - entity, - options - ) - } - - async delete(path: string, options: PostOptions = {}) { - return await this.blutui.delete(this.getProjectPath(path), options) + super(blutui) } - /** - * Get the path for the current agency. - */ - private getProjectPath(path: string): string { + protected getRequestPath(path: string): string { const newPath = path.startsWith('/') ? path.replace('/', '') : path return `https://${this.handle}.blutui.com/api/${newPath}` diff --git a/src/request.ts b/src/request.ts new file mode 100644 index 0000000..d59efef --- /dev/null +++ b/src/request.ts @@ -0,0 +1,45 @@ +import type { Blutui } from './blutui' +import type { GetOptions, PostOptions } from './types' + +export class Request { + constructor(protected readonly blutui: Blutui) {} + + async get(path: string, options: GetOptions = {}) { + return await this.blutui.get(this.getRequestPath(path), options) + } + + async post( + path: string, + entity: Entity, + options: PostOptions = {} + ) { + return await this.blutui.post( + this.getRequestPath(path), + entity, + options + ) + } + + async patch( + path: string, + entity: Entity, + options: PostOptions = {} + ) { + return await this.blutui.patch( + this.getRequestPath(path), + entity, + options + ) + } + + async delete(path: string, options: PostOptions = {}) { + return await this.blutui.delete(this.getRequestPath(path), options) + } + + /** + * Get the path for the current agency. + */ + protected getRequestPath(path: string): string { + return path + } +} From 8e294de724e64940d5451d562428f0f17d89e77c Mon Sep 17 00:00:00 2001 From: Michael-Macbook Date: Fri, 27 Dec 2024 12:04:29 +1300 Subject: [PATCH 13/21] move request to utils --- src/admin.ts | 2 +- src/agency.ts | 2 +- src/project.ts | 2 +- src/{ => utils}/request.ts | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) rename src/{ => utils}/request.ts (90%) diff --git a/src/admin.ts b/src/admin.ts index de0cab7..b46cf6d 100644 --- a/src/admin.ts +++ b/src/admin.ts @@ -2,7 +2,7 @@ import { Menus } from './resources/project/admin' import type { Blutui } from './blutui' import type { Project } from './project' -import { Request } from './request' +import { Request } from './utils/request' export class Admin extends Request { readonly menus = new Menus(this) diff --git a/src/agency.ts b/src/agency.ts index d9e33a3..82ad527 100644 --- a/src/agency.ts +++ b/src/agency.ts @@ -10,7 +10,7 @@ import { import type { Blutui } from './blutui' -import { Request } from './request' +import { Request } from './utils/request' export class Agency extends Request { readonly brand = new Brand(this) diff --git a/src/project.ts b/src/project.ts index 0c7cea4..b392e50 100644 --- a/src/project.ts +++ b/src/project.ts @@ -2,7 +2,7 @@ import { Menus } from './resources/project' import type { Blutui } from './blutui' import { Admin } from './admin' -import { Request } from './request' +import { Request } from './utils/request' export class Project extends Request { readonly admin = new Admin(this, this.blutui) diff --git a/src/request.ts b/src/utils/request.ts similarity index 90% rename from src/request.ts rename to src/utils/request.ts index d59efef..8779867 100644 --- a/src/request.ts +++ b/src/utils/request.ts @@ -1,5 +1,5 @@ -import type { Blutui } from './blutui' -import type { GetOptions, PostOptions } from './types' +import type { Blutui } from '../blutui' +import type { GetOptions, PostOptions } from '../types' export class Request { constructor(protected readonly blutui: Blutui) {} From 4e045e2e1b1892b99da57461c4b3292fa53579ed Mon Sep 17 00:00:00 2001 From: Michael-Macbook Date: Fri, 27 Dec 2024 13:36:12 +1300 Subject: [PATCH 14/21] abstract getRequestPath method --- src/utils/request.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/utils/request.ts b/src/utils/request.ts index 8779867..649282d 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -1,7 +1,7 @@ import type { Blutui } from '../blutui' import type { GetOptions, PostOptions } from '../types' -export class Request { +export abstract class Request { constructor(protected readonly blutui: Blutui) {} async get(path: string, options: GetOptions = {}) { @@ -37,9 +37,7 @@ export class Request { } /** - * Get the path for the current agency. + * Get the path for the current request. */ - protected getRequestPath(path: string): string { - return path - } + protected abstract getRequestPath(path: string): string } From d3629f3ecb300943b996fdbdac81ab5fb6a74cba Mon Sep 17 00:00:00 2001 From: Michael Liu <162056370+chengfang-blutui@users.noreply.github.com> Date: Tue, 7 Jan 2025 10:45:14 +1300 Subject: [PATCH 15/21] Update src/blutui.ts Co-authored-by: Jayan Ratna <30396013+jayan-blutui@users.noreply.github.com> --- src/blutui.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blutui.ts b/src/blutui.ts index f44f9f9..80ba4f3 100644 --- a/src/blutui.ts +++ b/src/blutui.ts @@ -88,7 +88,7 @@ export class Blutui { } /** - * Get a Blutui Project instance for the given agency. + * Get a Blutui Project instance for the given handle. * * @param handle - The project's handle, if the handle is different as the project's subdomain, should pass the subdomain */ From 026f7b2d7552dbedd0de49971de3b278a595395b Mon Sep 17 00:00:00 2001 From: Michael Liu <162056370+chengfang-blutui@users.noreply.github.com> Date: Tue, 7 Jan 2025 10:45:33 +1300 Subject: [PATCH 16/21] Update src/blutui.ts Co-authored-by: Jayan Ratna <30396013+jayan-blutui@users.noreply.github.com> --- src/blutui.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blutui.ts b/src/blutui.ts index 80ba4f3..d91628a 100644 --- a/src/blutui.ts +++ b/src/blutui.ts @@ -90,7 +90,7 @@ export class Blutui { /** * Get a Blutui Project instance for the given handle. * - * @param handle - The project's handle, if the handle is different as the project's subdomain, should pass the subdomain + * @param handle - The project's handle. If the project's handle is different from its subdomain, the subdomain should be used instead. */ project(handle: string): Project { if (!this._projects[handle]) { From 4c201fca76fa51d354db4e1814c1125ab28b3810 Mon Sep 17 00:00:00 2001 From: Chengfang Liu Date: Tue, 7 Jan 2025 10:54:43 +1300 Subject: [PATCH 17/21] is_new_tab to camel case --- .../project/menus/interfaces/create-menu-options.interface.ts | 2 +- .../project/menus/interfaces/update-menu-options.interface.ts | 2 +- src/resources/project/menus/menus.admin.spec.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/resources/project/menus/interfaces/create-menu-options.interface.ts b/src/resources/project/menus/interfaces/create-menu-options.interface.ts index 4a85d76..ef3cb10 100644 --- a/src/resources/project/menus/interfaces/create-menu-options.interface.ts +++ b/src/resources/project/menus/interfaces/create-menu-options.interface.ts @@ -13,7 +13,7 @@ export interface SerializedCreateMenuOptions { export interface CreateMenuItemOptions { label: string url: string - is_new_tab: boolean + isNewTab: boolean active: boolean items?: CreateMenuItemOptions[] } diff --git a/src/resources/project/menus/interfaces/update-menu-options.interface.ts b/src/resources/project/menus/interfaces/update-menu-options.interface.ts index 2ed46dd..df77820 100644 --- a/src/resources/project/menus/interfaces/update-menu-options.interface.ts +++ b/src/resources/project/menus/interfaces/update-menu-options.interface.ts @@ -13,7 +13,7 @@ export interface SerializedUpdateMenuOptions { export interface UpdateMenuItemOptions { label: string url: string - is_new_tab: boolean + isNewTab: boolean active: boolean items?: UpdateMenuItemOptions[] } diff --git a/src/resources/project/menus/menus.admin.spec.ts b/src/resources/project/menus/menus.admin.spec.ts index 6235e8a..3cfda01 100644 --- a/src/resources/project/menus/menus.admin.spec.ts +++ b/src/resources/project/menus/menus.admin.spec.ts @@ -89,7 +89,7 @@ describe('Menu', () => { { label: 'Contact', url: '/contact', - is_new_tab: false, + isNewTab: false, active: true, }, ], @@ -140,7 +140,7 @@ describe('Menu', () => { { label: 'Contact', url: '/contact', - is_new_tab: false, + isNewTab: false, active: true, }, ], From ca446c2efec7cabf8062a9ea01280c845d05ff7f Mon Sep 17 00:00:00 2001 From: Chengfang Liu Date: Tue, 7 Jan 2025 11:30:48 +1300 Subject: [PATCH 18/21] add serialize to menu item --- .../create-menu-options.interface.ts | 16 +++++++++++---- .../update-menu-options.interface.ts | 16 +++++++++++---- .../create-menu-options.serializer.ts | 20 ++++++++++++++++++- .../update-menu-options.serializer.ts | 20 ++++++++++++++++++- 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/resources/project/menus/interfaces/create-menu-options.interface.ts b/src/resources/project/menus/interfaces/create-menu-options.interface.ts index ef3cb10..bce6b59 100644 --- a/src/resources/project/menus/interfaces/create-menu-options.interface.ts +++ b/src/resources/project/menus/interfaces/create-menu-options.interface.ts @@ -4,16 +4,24 @@ export interface CreateMenuOptions { items?: CreateMenuItemOptions[] } +export interface CreateMenuItemOptions { + label: string + url: string + isNewTab: boolean + active: boolean + items?: CreateMenuItemOptions[] +} + export interface SerializedCreateMenuOptions { handle: string name: string - items?: CreateMenuItemOptions[] + items?: SerializedCreateMenuItemOptions[] } -export interface CreateMenuItemOptions { +export interface SerializedCreateMenuItemOptions { label: string url: string - isNewTab: boolean + is_new_tab: boolean active: boolean - items?: CreateMenuItemOptions[] + items?: SerializedCreateMenuItemOptions[] } diff --git a/src/resources/project/menus/interfaces/update-menu-options.interface.ts b/src/resources/project/menus/interfaces/update-menu-options.interface.ts index df77820..94d4f0b 100644 --- a/src/resources/project/menus/interfaces/update-menu-options.interface.ts +++ b/src/resources/project/menus/interfaces/update-menu-options.interface.ts @@ -4,16 +4,24 @@ export interface UpdateMenuOptions { items?: UpdateMenuItemOptions[] } +export interface UpdateMenuItemOptions { + label: string + url: string + isNewTab: boolean + active: boolean + items?: UpdateMenuItemOptions[] +} + export interface SerializedUpdateMenuOptions { handle?: string name?: string - items?: UpdateMenuItemOptions[] + items?: SerializedUpdateMenuItemOptions[] } -export interface UpdateMenuItemOptions { +export interface SerializedUpdateMenuItemOptions { label: string url: string - isNewTab: boolean + is_new_tab: boolean active: boolean - items?: UpdateMenuItemOptions[] + items?: SerializedUpdateMenuItemOptions[] } diff --git a/src/resources/project/menus/serializers/create-menu-options.serializer.ts b/src/resources/project/menus/serializers/create-menu-options.serializer.ts index 27fcb3e..87ccdf4 100644 --- a/src/resources/project/menus/serializers/create-menu-options.serializer.ts +++ b/src/resources/project/menus/serializers/create-menu-options.serializer.ts @@ -1,5 +1,7 @@ import type { + CreateMenuItemOptions, CreateMenuOptions, + SerializedCreateMenuItemOptions, SerializedCreateMenuOptions, } from '../interfaces' @@ -8,5 +10,21 @@ export const serializeCreateMenuOptions = ( ): SerializedCreateMenuOptions => ({ name: options.name, handle: options.handle, - items: options.items, + ...(options.items !== undefined && { + items: serializeCreateMenuItemOptions(options.items), + }), }) + +export const serializeCreateMenuItemOptions = ( + items: CreateMenuItemOptions[] +): SerializedCreateMenuItemOptions[] => { + return items.map((item) => ({ + label: item.label, + url: item.url, + is_new_tab: item.isNewTab, + active: item.active, + ...(item.items !== undefined && { + items: serializeCreateMenuItemOptions(item.items), + }), + })) +} diff --git a/src/resources/project/menus/serializers/update-menu-options.serializer.ts b/src/resources/project/menus/serializers/update-menu-options.serializer.ts index e7e03da..fa6fdd4 100644 --- a/src/resources/project/menus/serializers/update-menu-options.serializer.ts +++ b/src/resources/project/menus/serializers/update-menu-options.serializer.ts @@ -1,5 +1,7 @@ import type { + SerializedUpdateMenuItemOptions, SerializedUpdateMenuOptions, + UpdateMenuItemOptions, UpdateMenuOptions, } from '../interfaces' @@ -8,5 +10,21 @@ export const serializeUpdateMenuOptions = ( ): SerializedUpdateMenuOptions => ({ name: options.name, handle: options.handle, - items: options.items, + ...(options.items !== undefined && { + items: serializeUpdateMenuItemOptions(options.items), + }), }) + +export const serializeUpdateMenuItemOptions = ( + items: UpdateMenuItemOptions[] +): SerializedUpdateMenuItemOptions[] => { + return items.map((item) => ({ + label: item.label, + url: item.url, + is_new_tab: item.isNewTab, + active: item.active, + ...(item.items !== undefined && { + items: serializeUpdateMenuItemOptions(item.items), + }), + })) +} From 1837e1d050107573af8a72d915a6db853cab1a10 Mon Sep 17 00:00:00 2001 From: Chengfang Liu Date: Tue, 7 Jan 2025 11:47:08 +1300 Subject: [PATCH 19/21] refactor admin.ts --- src/admin.ts | 8 ++------ src/project.ts | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/admin.ts b/src/admin.ts index b46cf6d..e431956 100644 --- a/src/admin.ts +++ b/src/admin.ts @@ -1,17 +1,13 @@ import { Menus } from './resources/project/admin' -import type { Blutui } from './blutui' import type { Project } from './project' import { Request } from './utils/request' export class Admin extends Request { readonly menus = new Menus(this) - constructor( - private readonly project: Project, - protected readonly blutui: Blutui - ) { - super(blutui) + constructor(private readonly project: Project) { + super(project.blutui) } protected getRequestPath(path: string): string { diff --git a/src/project.ts b/src/project.ts index b392e50..89d0d2a 100644 --- a/src/project.ts +++ b/src/project.ts @@ -5,12 +5,12 @@ import { Admin } from './admin' import { Request } from './utils/request' export class Project extends Request { - readonly admin = new Admin(this, this.blutui) + readonly admin = new Admin(this) readonly menus = new Menus(this) constructor( public handle: string, - protected readonly blutui: Blutui + readonly blutui: Blutui ) { super(blutui) } From 446e260a4745306686c3bcfbc77514f1537048d1 Mon Sep 17 00:00:00 2001 From: Chengfang Liu Date: Tue, 7 Jan 2025 14:24:23 +1300 Subject: [PATCH 20/21] refactor --- src/admin.ts | 7 ++++++- src/agency.ts | 8 ++++++-- src/project.ts | 8 ++++++-- src/utils/request.ts | 18 ++++++++++++------ 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/admin.ts b/src/admin.ts index e431956..2497ab1 100644 --- a/src/admin.ts +++ b/src/admin.ts @@ -1,5 +1,6 @@ import { Menus } from './resources/project/admin' +import type { Blutui } from './blutui' import type { Project } from './project' import { Request } from './utils/request' @@ -7,7 +8,11 @@ export class Admin extends Request { readonly menus = new Menus(this) constructor(private readonly project: Project) { - super(project.blutui) + super() + } + + protected getBlutui(): Blutui { + return this.project.getBlutui() } protected getRequestPath(path: string): string { diff --git a/src/agency.ts b/src/agency.ts index 82ad527..341a64b 100644 --- a/src/agency.ts +++ b/src/agency.ts @@ -23,9 +23,13 @@ export class Agency extends Request { constructor( public username: string, - protected readonly blutui: Blutui + private readonly blutui: Blutui ) { - super(blutui) + super() + } + + protected getBlutui(): Blutui { + return this.blutui } protected getRequestPath(path: string): string { diff --git a/src/project.ts b/src/project.ts index 89d0d2a..dcb5ebf 100644 --- a/src/project.ts +++ b/src/project.ts @@ -10,9 +10,13 @@ export class Project extends Request { constructor( public handle: string, - readonly blutui: Blutui + private readonly blutui: Blutui ) { - super(blutui) + super() + } + + public getBlutui(): Blutui { + return this.blutui } protected getRequestPath(path: string): string { diff --git a/src/utils/request.ts b/src/utils/request.ts index 649282d..d9a0242 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -2,10 +2,11 @@ import type { Blutui } from '../blutui' import type { GetOptions, PostOptions } from '../types' export abstract class Request { - constructor(protected readonly blutui: Blutui) {} - async get(path: string, options: GetOptions = {}) { - return await this.blutui.get(this.getRequestPath(path), options) + return await this.getBlutui().get( + this.getRequestPath(path), + options + ) } async post( @@ -13,7 +14,7 @@ export abstract class Request { entity: Entity, options: PostOptions = {} ) { - return await this.blutui.post( + return await this.getBlutui().post( this.getRequestPath(path), entity, options @@ -25,7 +26,7 @@ export abstract class Request { entity: Entity, options: PostOptions = {} ) { - return await this.blutui.patch( + return await this.getBlutui().patch( this.getRequestPath(path), entity, options @@ -33,11 +34,16 @@ export abstract class Request { } async delete(path: string, options: PostOptions = {}) { - return await this.blutui.delete(this.getRequestPath(path), options) + return await this.getBlutui().delete( + this.getRequestPath(path), + options + ) } /** * Get the path for the current request. */ protected abstract getRequestPath(path: string): string + + protected abstract getBlutui(): Blutui } From 0d8e9946cb02666fd56f4a3fbb6ce942358333e3 Mon Sep 17 00:00:00 2001 From: Chengfang Liu Date: Tue, 7 Jan 2025 15:03:28 +1300 Subject: [PATCH 21/21] reset request --- src/admin.ts | 11 +++++------ src/agency.ts | 8 ++------ src/project.ts | 11 ++++------- src/utils/request.ts | 18 ++++++------------ 4 files changed, 17 insertions(+), 31 deletions(-) diff --git a/src/admin.ts b/src/admin.ts index 2497ab1..2e43036 100644 --- a/src/admin.ts +++ b/src/admin.ts @@ -7,12 +7,11 @@ import { Request } from './utils/request' export class Admin extends Request { readonly menus = new Menus(this) - constructor(private readonly project: Project) { - super() - } - - protected getBlutui(): Blutui { - return this.project.getBlutui() + constructor( + private readonly project: Project, + blutui: Blutui + ) { + super(blutui) } protected getRequestPath(path: string): string { diff --git a/src/agency.ts b/src/agency.ts index 341a64b..2c8a967 100644 --- a/src/agency.ts +++ b/src/agency.ts @@ -23,13 +23,9 @@ export class Agency extends Request { constructor( public username: string, - private readonly blutui: Blutui + blutui: Blutui ) { - super() - } - - protected getBlutui(): Blutui { - return this.blutui + super(blutui) } protected getRequestPath(path: string): string { diff --git a/src/project.ts b/src/project.ts index dcb5ebf..c96fa2c 100644 --- a/src/project.ts +++ b/src/project.ts @@ -5,18 +5,15 @@ import { Admin } from './admin' import { Request } from './utils/request' export class Project extends Request { - readonly admin = new Admin(this) + readonly admin: Admin readonly menus = new Menus(this) constructor( public handle: string, - private readonly blutui: Blutui + blutui: Blutui ) { - super() - } - - public getBlutui(): Blutui { - return this.blutui + super(blutui) + this.admin = new Admin(this, blutui) } protected getRequestPath(path: string): string { diff --git a/src/utils/request.ts b/src/utils/request.ts index d9a0242..ba5c0b4 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -2,11 +2,10 @@ import type { Blutui } from '../blutui' import type { GetOptions, PostOptions } from '../types' export abstract class Request { + constructor(private readonly blutui: Blutui) {} + async get(path: string, options: GetOptions = {}) { - return await this.getBlutui().get( - this.getRequestPath(path), - options - ) + return await this.blutui.get(this.getRequestPath(path), options) } async post( @@ -14,7 +13,7 @@ export abstract class Request { entity: Entity, options: PostOptions = {} ) { - return await this.getBlutui().post( + return await this.blutui.post( this.getRequestPath(path), entity, options @@ -26,7 +25,7 @@ export abstract class Request { entity: Entity, options: PostOptions = {} ) { - return await this.getBlutui().patch( + return await this.blutui.patch( this.getRequestPath(path), entity, options @@ -34,16 +33,11 @@ export abstract class Request { } async delete(path: string, options: PostOptions = {}) { - return await this.getBlutui().delete( - this.getRequestPath(path), - options - ) + return await this.blutui.delete(this.getRequestPath(path), options) } /** * Get the path for the current request. */ protected abstract getRequestPath(path: string): string - - protected abstract getBlutui(): Blutui }