From 625b047ee6de53bfe802efd30d239ba68de46208 Mon Sep 17 00:00:00 2001 From: Chengfang Liu Date: Fri, 26 Jul 2024 11:07:59 +1200 Subject: [PATCH 1/5] add Role API --- src/agency.ts | 3 +- src/resources/agency/domains/domains.ts | 2 +- src/resources/agency/index.ts | 1 + .../agency/roles/fixtures/role-list.json | 22 +++++ src/resources/agency/roles/fixtures/role.json | 12 +++ .../create-role-options.interface.ts | 15 ++++ .../agency/roles/interfaces/index.ts | 3 + .../agency/roles/interfaces/role.interface.ts | 23 ++++++ .../update-role-options.interface.ts | 15 ++++ src/resources/agency/roles/roles.spec.ts | 82 +++++++++++++++++++ src/resources/agency/roles/roles.ts | 82 +++++++++++++++++++ .../create-role-options.serializer.ts | 12 +++ .../agency/roles/serializers/index.ts | 3 + .../roles/serializers/role.serializer.ts | 38 +++++++++ .../update-role-options.serializer.ts | 12 +++ 15 files changed, 323 insertions(+), 2 deletions(-) create mode 100644 src/resources/agency/roles/fixtures/role-list.json create mode 100644 src/resources/agency/roles/fixtures/role.json create mode 100644 src/resources/agency/roles/interfaces/create-role-options.interface.ts create mode 100644 src/resources/agency/roles/interfaces/index.ts create mode 100644 src/resources/agency/roles/interfaces/role.interface.ts create mode 100644 src/resources/agency/roles/interfaces/update-role-options.interface.ts create mode 100644 src/resources/agency/roles/roles.spec.ts create mode 100644 src/resources/agency/roles/roles.ts create mode 100644 src/resources/agency/roles/serializers/create-role-options.serializer.ts create mode 100644 src/resources/agency/roles/serializers/index.ts create mode 100644 src/resources/agency/roles/serializers/role.serializer.ts create mode 100644 src/resources/agency/roles/serializers/update-role-options.serializer.ts diff --git a/src/agency.ts b/src/agency.ts index 16d17c4..57a7440 100644 --- a/src/agency.ts +++ b/src/agency.ts @@ -1,4 +1,4 @@ -import { Brand, Domains, Projects } from './resources/agency' +import { Brand, Domains, Projects, Roles } from './resources/agency' import type { Blutui } from './blutui' import type { GetOptions, PostOptions } from './types' @@ -7,6 +7,7 @@ export class Agency { readonly brand = new Brand(this) readonly domains = new Domains(this) readonly projects = new Projects(this) + readonly roles = new Roles(this) constructor( public username: string, diff --git a/src/resources/agency/domains/domains.ts b/src/resources/agency/domains/domains.ts index 6363625..3dfcb19 100644 --- a/src/resources/agency/domains/domains.ts +++ b/src/resources/agency/domains/domains.ts @@ -66,7 +66,7 @@ export class Domains { /** * Update a domain for the current agency. * - * @param payload - The values to update the brand + * @param payload - The values to update the domain */ async update(id: string, payload: UpdateDomainOptions): Promise { const { data } = await this.agency.patch< diff --git a/src/resources/agency/index.ts b/src/resources/agency/index.ts index 8b83505..9b212cb 100644 --- a/src/resources/agency/index.ts +++ b/src/resources/agency/index.ts @@ -1,3 +1,4 @@ export { Brand } from './brand/brand' export { Domains } from './domains/domains' export { Projects } from './projects/projects' +export { Roles } from './roles/roles' diff --git a/src/resources/agency/roles/fixtures/role-list.json b/src/resources/agency/roles/fixtures/role-list.json new file mode 100644 index 0000000..03e3ed0 --- /dev/null +++ b/src/resources/agency/roles/fixtures/role-list.json @@ -0,0 +1,22 @@ +{ + "object": "list", + "data": [ + { + "id": "9bfdb42b-1bf0-4510-978e-46aa329f8efa", + "object": "role", + "name": "Owner", + "description": "Owner of the domain", + "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/agency/roles/fixtures/role.json b/src/resources/agency/roles/fixtures/role.json new file mode 100644 index 0000000..af87179 --- /dev/null +++ b/src/resources/agency/roles/fixtures/role.json @@ -0,0 +1,12 @@ +{ + "id": "9bfdb42b-1bf0-4510-978e-46aa329f8efa", + "object": "role", + "name": "Owner", + "description": "Owner of the domain", + "permissions": { + "user.read": true, + "user.write": false + }, + "created_at": 1716170007, + "updated_at": 1716170007 +} diff --git a/src/resources/agency/roles/interfaces/create-role-options.interface.ts b/src/resources/agency/roles/interfaces/create-role-options.interface.ts new file mode 100644 index 0000000..258c970 --- /dev/null +++ b/src/resources/agency/roles/interfaces/create-role-options.interface.ts @@ -0,0 +1,15 @@ +export interface CreateRoleOptions { + name: string + description?: string | null + permissions?: { + [key: string]: boolean + } +} + +export interface SerializedCreateRoleOptions { + name: string + description?: string | null + permissions?: { + [key: string]: boolean + } +} diff --git a/src/resources/agency/roles/interfaces/index.ts b/src/resources/agency/roles/interfaces/index.ts new file mode 100644 index 0000000..014de32 --- /dev/null +++ b/src/resources/agency/roles/interfaces/index.ts @@ -0,0 +1,3 @@ +export * from './role.interface' +export * from './create-role-options.interface' +export * from './update-role-options.interface' diff --git a/src/resources/agency/roles/interfaces/role.interface.ts b/src/resources/agency/roles/interfaces/role.interface.ts new file mode 100644 index 0000000..3e5d348 --- /dev/null +++ b/src/resources/agency/roles/interfaces/role.interface.ts @@ -0,0 +1,23 @@ +export interface Role { + id: string + object: 'role' + name: string + description: string + isSuper: boolean + usersCount?: number + permissions: { [key: string]: boolean } + createdAt: number + updatedAt: number +} + +export interface RoleResponse { + id: string + object: 'role' + name: string + description: string + is_super: boolean + users_count?: number + permissions: { [key: string]: boolean } + created_at: number + updated_at: number +} diff --git a/src/resources/agency/roles/interfaces/update-role-options.interface.ts b/src/resources/agency/roles/interfaces/update-role-options.interface.ts new file mode 100644 index 0000000..954d3c8 --- /dev/null +++ b/src/resources/agency/roles/interfaces/update-role-options.interface.ts @@ -0,0 +1,15 @@ +export interface UpdateRoleOptions { + name?: string + description?: string | null + permissions?: { + [key: string]: boolean + } +} + +export interface SerializedUpdateRoleOptions { + name?: string + description?: string | null + permissions?: { + [key: string]: boolean + } +} diff --git a/src/resources/agency/roles/roles.spec.ts b/src/resources/agency/roles/roles.spec.ts new file mode 100644 index 0000000..e702340 --- /dev/null +++ b/src/resources/agency/roles/roles.spec.ts @@ -0,0 +1,82 @@ +import fetch from 'jest-fetch-mock' +import { Blutui } from '@/blutui' +import { fetchOnce, fetchURL } from '@/utils/testing' + +import roleFixture from './fixtures/role.json' +import roleListFixture from './fixtures/role-list.json' + +const accessToken = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' +const blutui = new Blutui(accessToken) + +describe('Role', () => { + beforeEach(() => fetch.resetMocks()) + + describe('list', () => { + it('can retrieve a list of roles', async () => { + fetchOnce(roleListFixture) + const domains = await blutui.agency('foo').roles.list() + + expect(fetchURL()).toBe(`${blutui.baseURL}/v1/agencies/foo/roles`) + expect(domains).toMatchObject({ + object: 'list', + }) + }) + }) + + describe('get', () => { + it('can retrieve a role information', async () => { + fetchOnce(roleFixture) + const role = await blutui.agency('foo').roles.get(roleFixture.id) + + expect(fetchURL()).toBe( + `${blutui.baseURL}/v1/agencies/foo/roles/${roleFixture.id}` + ) + expect(role).toMatchObject({ + object: 'role', + }) + }) + }) + + describe('create', () => { + it('can create a new role', async () => { + fetchOnce(roleFixture) + const role = await blutui.agency('foo').roles.create({ + name: 'Owner', + }) + + expect(fetchURL()).toBe(`${blutui.baseURL}/v1/agencies/foo/roles`) + expect(role).toMatchObject({ + object: 'role', + name: 'Owner', + }) + }) + }) + + describe('update', () => { + it('can update an agency role', async () => { + fetchOnce(roleFixture) + const role = await blutui + .agency('foo') + .roles.update(roleFixture.id, { name: 'New Role Name' }) + + expect(fetchURL()).toBe( + `${blutui.baseURL}/v1/agencies/foo/roles/${roleFixture.id}` + ) + expect(role).toMatchObject({ + object: 'role', + }) + }) + }) + + describe('remove', () => { + it('can remove an role', async () => { + fetchOnce(roleFixture) + await blutui.agency('foo').roles.remove(roleFixture.id) + + expect(fetchURL()).toBe( + `${blutui.baseURL}/v1/agencies/foo/roles/${roleFixture.id}` + ) + }) + }) +}) diff --git a/src/resources/agency/roles/roles.ts b/src/resources/agency/roles/roles.ts new file mode 100644 index 0000000..67936bb --- /dev/null +++ b/src/resources/agency/roles/roles.ts @@ -0,0 +1,82 @@ +import { + deserializeRole, + deserializeRoleList, + serializeCreateRoleOptions, + serializeUpdateRoleOptions, +} from './serializers' + +import type { Agency } from '@/agency' +import type { + CreateRoleOptions, + Role, + RoleResponse, + SerializedCreateRoleOptions, + SerializedUpdateRoleOptions, + UpdateRoleOptions, +} from './interfaces' +import type { + DeletedResponse, + List, + ListResponse, + PaginationOptions, +} from '@/types' + +export class Roles { + constructor(private readonly agency: Agency) {} + + /** + * Get the roles list for the current agency. + */ + async list(options?: PaginationOptions): Promise> { + const { data } = await this.agency.get>( + 'roles', + { query: options } + ) + + return deserializeRoleList(data) + } + + /** + * Get a role's information by ID. + */ + async get(id: string): Promise { + const { data } = await this.agency.get(`roles/${id}`) + + return deserializeRole(data) + } + + /** + * Add a role to your agency. + */ + async create(payload: CreateRoleOptions): Promise { + const { data } = await this.agency.post< + RoleResponse, + SerializedCreateRoleOptions + >('roles', serializeCreateRoleOptions(payload)) + + return deserializeRole(data) + } + + /** + * Update a rle for the current agency. + * + * @param payload - The values to update the role + */ + async update(id: string, payload: UpdateRoleOptions): Promise { + const { data } = await this.agency.patch< + RoleResponse, + SerializedUpdateRoleOptions + >(`roles/${id}`, serializeUpdateRoleOptions(payload)) + + return deserializeRole(data) + } + + /** + * Remove a role for the current agency. + */ + async remove(id: string): Promise { + const { data } = await this.agency.delete(`roles/${id}`) + + return data + } +} diff --git a/src/resources/agency/roles/serializers/create-role-options.serializer.ts b/src/resources/agency/roles/serializers/create-role-options.serializer.ts new file mode 100644 index 0000000..87f6607 --- /dev/null +++ b/src/resources/agency/roles/serializers/create-role-options.serializer.ts @@ -0,0 +1,12 @@ +import type { + CreateRoleOptions, + SerializedCreateRoleOptions, +} from '../interfaces' + +export const serializeCreateRoleOptions = ( + options: CreateRoleOptions +): SerializedCreateRoleOptions => ({ + name: options.name, + description: options.description, + permissions: options.permissions, +}) diff --git a/src/resources/agency/roles/serializers/index.ts b/src/resources/agency/roles/serializers/index.ts new file mode 100644 index 0000000..5273321 --- /dev/null +++ b/src/resources/agency/roles/serializers/index.ts @@ -0,0 +1,3 @@ +export * from './role.serializer' +export * from './create-role-options.serializer' +export * from './update-role-options.serializer' diff --git a/src/resources/agency/roles/serializers/role.serializer.ts b/src/resources/agency/roles/serializers/role.serializer.ts new file mode 100644 index 0000000..14ac59a --- /dev/null +++ b/src/resources/agency/roles/serializers/role.serializer.ts @@ -0,0 +1,38 @@ +import { deserializePaginationMeta } from '@/utils/serializers' + +import type { List, ListResponse } from '@/types' +import type { Role, RoleResponse } from '../interfaces' + +export const deserializeRole = (role: RoleResponse): Role => { + const { + id, + object, + name, + description, + is_super: isSuper, + permissions, + created_at: createdAt, + updated_at: updatedAt, + users_count: usersCount, + } = role + + return { + id, + object, + name, + description, + isSuper, + permissions, + createdAt, + updatedAt, + ...(usersCount !== undefined && { usersCount }), + } +} + +export const deserializeRoleList = ( + roles: ListResponse +): List => ({ + object: 'list', + data: roles.data.map(deserializeRole), + meta: deserializePaginationMeta(roles.meta), +}) diff --git a/src/resources/agency/roles/serializers/update-role-options.serializer.ts b/src/resources/agency/roles/serializers/update-role-options.serializer.ts new file mode 100644 index 0000000..e1b9193 --- /dev/null +++ b/src/resources/agency/roles/serializers/update-role-options.serializer.ts @@ -0,0 +1,12 @@ +import type { + SerializedUpdateRoleOptions, + UpdateRoleOptions, +} from '../interfaces' + +export const serializeUpdateRoleOptions = ( + options: UpdateRoleOptions +): SerializedUpdateRoleOptions => ({ + name: options.name, + description: options.description, + permissions: options.permissions, +}) From b125af6e3e44387c6c4aeddab113bd1016dbf713 Mon Sep 17 00:00:00 2001 From: Chengfang Liu Date: Fri, 26 Jul 2024 11:38:10 +1200 Subject: [PATCH 2/5] add Permissin type --- .../create-role-options.interface.ts | 6 +++-- .../agency/roles/interfaces/role.interface.ts | 23 +++++++++++++++++-- .../update-role-options.interface.ts | 6 +++-- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/resources/agency/roles/interfaces/create-role-options.interface.ts b/src/resources/agency/roles/interfaces/create-role-options.interface.ts index 258c970..b584d1d 100644 --- a/src/resources/agency/roles/interfaces/create-role-options.interface.ts +++ b/src/resources/agency/roles/interfaces/create-role-options.interface.ts @@ -1,8 +1,10 @@ +import type { Permission } from './role.interface' + export interface CreateRoleOptions { name: string description?: string | null permissions?: { - [key: string]: boolean + [key in Permission]: boolean } } @@ -10,6 +12,6 @@ export interface SerializedCreateRoleOptions { name: string description?: string | null permissions?: { - [key: string]: boolean + [key in Permission]: boolean } } diff --git a/src/resources/agency/roles/interfaces/role.interface.ts b/src/resources/agency/roles/interfaces/role.interface.ts index 3e5d348..3adb533 100644 --- a/src/resources/agency/roles/interfaces/role.interface.ts +++ b/src/resources/agency/roles/interfaces/role.interface.ts @@ -1,3 +1,22 @@ +export type Permission = + | 'domain.delete' + | 'domain.read' + | 'domain.write' + | 'organization.delete' + | 'organization.read' + | 'organization.write' + | 'site.delete' + | 'site.export' + | 'site.publish' + | 'site.read' + | 'site.transfer' + | 'site.write' + | 'stripe' + | 'user.admin' + | 'user.delete' + | 'user.invite' + | 'user.read' + export interface Role { id: string object: 'role' @@ -5,7 +24,7 @@ export interface Role { description: string isSuper: boolean usersCount?: number - permissions: { [key: string]: boolean } + permissions: { [key in Permission]: boolean } createdAt: number updatedAt: number } @@ -17,7 +36,7 @@ export interface RoleResponse { description: string is_super: boolean users_count?: number - permissions: { [key: string]: boolean } + permissions: { [key in Permission]: boolean } created_at: number updated_at: number } diff --git a/src/resources/agency/roles/interfaces/update-role-options.interface.ts b/src/resources/agency/roles/interfaces/update-role-options.interface.ts index 954d3c8..724af01 100644 --- a/src/resources/agency/roles/interfaces/update-role-options.interface.ts +++ b/src/resources/agency/roles/interfaces/update-role-options.interface.ts @@ -1,8 +1,10 @@ +import type { Permission } from './role.interface' + export interface UpdateRoleOptions { name?: string description?: string | null permissions?: { - [key: string]: boolean + [key in Permission]: boolean } } @@ -10,6 +12,6 @@ export interface SerializedUpdateRoleOptions { name?: string description?: string | null permissions?: { - [key: string]: boolean + [key in Permission]: boolean } } From 4fed9c2d58eaa0c828358fc29cd8ff5ef1192533 Mon Sep 17 00:00:00 2001 From: Michael Liu <162056370+chengfang-blutui@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:03:22 +1200 Subject: [PATCH 3/5] Update src/resources/agency/roles/roles.ts Co-authored-by: Jayan Ratna <30396013+jayan-blutui@users.noreply.github.com> --- src/resources/agency/roles/roles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/agency/roles/roles.ts b/src/resources/agency/roles/roles.ts index 67936bb..25e640c 100644 --- a/src/resources/agency/roles/roles.ts +++ b/src/resources/agency/roles/roles.ts @@ -25,7 +25,7 @@ export class Roles { constructor(private readonly agency: Agency) {} /** - * Get the roles list for the current agency. + * Get a list of roles for the current agency. */ async list(options?: PaginationOptions): Promise> { const { data } = await this.agency.get>( From ae00fdae767d9fc6d4ec07ffb0a2772401cc0f29 Mon Sep 17 00:00:00 2001 From: Michael Liu <162056370+chengfang-blutui@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:03:33 +1200 Subject: [PATCH 4/5] Update src/resources/agency/roles/roles.ts Co-authored-by: Jayan Ratna <30396013+jayan-blutui@users.noreply.github.com> --- src/resources/agency/roles/roles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/agency/roles/roles.ts b/src/resources/agency/roles/roles.ts index 25e640c..e44a916 100644 --- a/src/resources/agency/roles/roles.ts +++ b/src/resources/agency/roles/roles.ts @@ -58,7 +58,7 @@ export class Roles { } /** - * Update a rle for the current agency. + * Update a role in the current agency. * * @param payload - The values to update the role */ From 16d3711a5b855cccad9be197663b202e350e5d75 Mon Sep 17 00:00:00 2001 From: Michael Liu <162056370+chengfang-blutui@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:03:43 +1200 Subject: [PATCH 5/5] Update src/resources/agency/roles/roles.ts Co-authored-by: Jayan Ratna <30396013+jayan-blutui@users.noreply.github.com> --- src/resources/agency/roles/roles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/agency/roles/roles.ts b/src/resources/agency/roles/roles.ts index e44a916..c499486 100644 --- a/src/resources/agency/roles/roles.ts +++ b/src/resources/agency/roles/roles.ts @@ -72,7 +72,7 @@ export class Roles { } /** - * Remove a role for the current agency. + * Remove a role from the current agency. */ async remove(id: string): Promise { const { data } = await this.agency.delete(`roles/${id}`)