diff --git a/package.json b/package.json index e76a944..ab92f78 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ } }, "dependencies": { - "axios": "1.6.7", + "axios": "1.7.2", "jsonwebtoken": "9.0.2", "tslib": "2.6.2" }, diff --git a/src/lib/microsoft-partnercenter.spec.ts b/src/lib/microsoft-partnercenter.spec.ts index 055d8dd..ce7b6ae 100644 --- a/src/lib/microsoft-partnercenter.spec.ts +++ b/src/lib/microsoft-partnercenter.spec.ts @@ -174,4 +174,37 @@ describe('Microsoft Partner Center', () => { expect(result).toEqual(licenses) expect(mockAxios.get).toHaveBeenCalledWith('/customers/1/subscribedskus') }) + + it('should create a customer', async () => { + const customer = { id: '1' } + jest.spyOn(mockAxios, 'post').mockResolvedValue({ data: customer }) + const result = await partnerCenter.createCustomer({ id: '1' } as never) + expect(result).toEqual(customer) + expect(mockAxios.post).toHaveBeenCalledWith('/customers', { id: '1' }) + }) + + it('should create a user', async () => { + const user = { id: '1' } + jest.spyOn(mockAxios, 'post').mockResolvedValue({ data: user }) + const result = await partnerCenter.createUser('1', { id: '1' } as never) + expect(result).toEqual(user) + expect(mockAxios.post).toHaveBeenCalledWith('/customers/1/users', { + id: '1', + }) + }) + + it('should set user role', async () => { + jest.spyOn(mockAxios, 'post').mockResolvedValue({ data: {} }) + const result = await partnerCenter.setUserRole('1', '1', { + Id: '1', + DisplayName: 'test', + UserPrincipalName: 'test', + }) + expect(result).toEqual({}) + expect(mockAxios.post).toHaveBeenCalledWith('/customers/1/directoryroles/1/usermembers', { + Id: '1', + DisplayName: 'test', + UserPrincipalName: 'test', + }) + }) }) diff --git a/src/lib/microsoft-partnercenter.ts b/src/lib/microsoft-partnercenter.ts index 2fba455..5fb0672 100644 --- a/src/lib/microsoft-partnercenter.ts +++ b/src/lib/microsoft-partnercenter.ts @@ -1,14 +1,15 @@ import { AxiosInstance } from 'axios' -import { ApplicationConsent } from './types' +import { ApplicationConsent, CreateUser, SetUserRole, SetUserRoleResponse, User } from './types' import { Availability } from './types/availabilities.types' import { IPartnerCenterConfig } from './types/common.types' -import { Customer } from './types/customers.types' +import { CreateCustomer, Customer } from './types/customers.types' import { Invoice } from './types/invoices.types' import { OrderLineItem, OrderLineItemOptions, OrderResponse } from './types/orders.types' import { Sku } from './types/sku.types' import { Subscription } from './types/subscriptions.types' import { TokenManager, initializeHttpAndTokenManager } from './utils/http-token-manager' import { LicenseUsage } from './types/licenses.types' +import { CreateGDAPRelationship, GDAPRelationship } from './types/gdap.types' export class MicrosoftPartnerCenter { private readonly httpAgent: AxiosInstance @@ -66,6 +67,36 @@ export class MicrosoftPartnerCenter { return sub } + async createCustomer(data: CreateCustomer): Promise { + const { data: customer } = await this.httpAgent.post('/customers', data) + return customer + } + + async createUser(customerId: string, data: CreateUser): Promise { + const { data: user } = await this.httpAgent.post(`/customers/${customerId}/users`, data) + return user + } + + async setUserRole( + customerId: string, + roleId: string, + data: SetUserRole, + ): Promise { + const { data: userRole } = await this.httpAgent.post( + `/customers/${customerId}/directoryroles/${roleId}/usermembers`, + data, + ) + return userRole + } + + async createGDAPRelationship(data: CreateGDAPRelationship): Promise { + const { data: gdapRelationship } = await this.httpAgent.post( + '/tenantRelationships/delegatedAdminRelationships', + data, + ) + return gdapRelationship + } + async updateCustomerSubscriptionUsers( customerId: string, subscriptionId: string, diff --git a/src/lib/types/customers.types.ts b/src/lib/types/customers.types.ts index c27bece..5e0e31a 100644 --- a/src/lib/types/customers.types.ts +++ b/src/lib/types/customers.types.ts @@ -29,3 +29,29 @@ export type Links = LinksBase export enum RelationshipToPartner { Reseller = 'reseller', } + +export interface CreateCustomer { + enableGDAPByDefault: boolean + CompanyProfile: { + Domain: string + } + BillingProfile: BillingProfile +} + +export interface BillingProfile { + Culture: string + Email: string + Language: string + CompanyName: string + DefaultAddress: DefaultAddress +} + +export interface DefaultAddress { + FirstName: string + LastName: string + AddressLine1: string + City: string + State: string + PostalCode: string + Country: string +} diff --git a/src/lib/types/gdap.types.ts b/src/lib/types/gdap.types.ts new file mode 100644 index 0000000..8f121d1 --- /dev/null +++ b/src/lib/types/gdap.types.ts @@ -0,0 +1,41 @@ +export interface CreateGDAPRelationship { + displayName: string + duration: string + customer: GDAPCustomer + accessDetails: AccessDetails + autoExtendDuration: string +} + +export interface AccessDetails { + unifiedRoles: UnifiedRole[] +} + +export interface UnifiedRole { + roleDefinitionId: string +} + +export interface GDAPCustomer { + tenantId: string + displayName: string +} + +export interface GDAPRelationship { + '@odata.type': '#microsoft.graph.delegatedAdminRelationship' + '@odata.context': 'https://graph.microsoft.com/v1.0/tenantRelationships/$metadata#delegatedAdminRelationships' + '@odata.etag': string + id: string + displayName: string + duration: string + customer: GDAPCustomer + accessDetails: AccessDetails + status: string + autoExtendDuration: string + createdDateTime: Date + lastModifiedDateTime: Date + activatedDateTime: string + endDateTime: Date +} + +export interface AccessDetails { + unifiedRoles: UnifiedRole[] +} diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts index cdaf3c0..207f836 100644 --- a/src/lib/types/index.ts +++ b/src/lib/types/index.ts @@ -7,3 +7,5 @@ export * from './sku.types' export * from './availabilities.types' export * from './application-consent.types' export * from './licenses.types' +export * from './users.types' +export * from './gdap.types' diff --git a/src/lib/types/users.types.ts b/src/lib/types/users.types.ts new file mode 100644 index 0000000..8a81eb5 --- /dev/null +++ b/src/lib/types/users.types.ts @@ -0,0 +1,53 @@ +export interface CreateUser { + usageLocation: string + userPrincipalName: string + firstName: string + lastName: string + displayName: string + passwordProfile: PasswordProfile +} + +export interface PasswordProfile { + forceChangePassword: boolean + password: string +} + +export interface User { + usageLocation: string + id: string + userPrincipalName: string + firstName: string + lastName: string + displayName: string + immutableId: string + passwordProfile: PasswordProfile + lastDirectorySyncTime: null + userDomainType: string + state: string + softDeletionTime: { objectType: 'CustomerUser' } +} + +export interface SetUserRole { + /** + * The ID of the user + */ + Id: string + /** + * The Display Name of the user + */ + DisplayName: string + /** + * The User's Principal Name + */ + UserPrincipalName: string +} + +export interface SetUserRoleResponse { + displayName: string + userPrincipalName: string + roleId: string + id: string + attributes: { + objectType: 'UserMember' + } +} diff --git a/yarn.lock b/yarn.lock index 5b5a046..cf283a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1698,12 +1698,12 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3" integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg== -axios@1.6.7: - version "1.6.7" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7" - integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA== +axios@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" + integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== dependencies: - follow-redirects "^1.15.4" + follow-redirects "^1.15.6" form-data "^4.0.0" proxy-from-env "^1.1.0" @@ -3160,10 +3160,10 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.1.tgz#bbef080d95fca6709362c73044a1634f7c6e7d05" integrity sha512-OMQjaErSFHmHqZe+PSidH5n8j3O0F2DdnVh8JB4j4eUQ2k6KvB0qGfrKIhapvez5JerBbmWkaLYUYWISaESoXg== -follow-redirects@^1.15.4: - version "1.15.5" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" - integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== +follow-redirects@^1.15.6: + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== foreground-child@^3.1.0: version "3.1.1"