Skip to content

Commit

Permalink
feat(node): add sdk methods for organization apis (#4826)
Browse files Browse the repository at this point in the history
* feat(node): add methods for organization apis

* docs(node): fix organization docs

* docs(node): fix organization docs
  • Loading branch information
michaldziuba03 authored Dec 19, 2023
1 parent bb18a27 commit 34dad81
Show file tree
Hide file tree
Showing 6 changed files with 342 additions and 0 deletions.
89 changes: 89 additions & 0 deletions packages/node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ Novu provides a single API to manage providers across multiple channels with a s
- [Environments](#environments)
- [Layouts](#layouts)
- [Integrations](#integrations)
- [Organizations](#organizations)


### Subscribers
Expand Down Expand Up @@ -1131,3 +1132,91 @@ await novu.notificationTemplates.getAll({
limit: 20 // optional
})
```

### Organizations

- #### List all organizations

```ts
import { Novu } from '@novu/node';

const novu = new Novu('<NOVU_API_KEY>');

await novu.organizations.list();
```

- #### Create new organization

```ts
import { Novu } from '@novu/node';

const novu = new Novu('<NOVU_API_KEY>');

await novu.organizations.create({ name: 'New Organization' });
```

- #### Rename organization

```ts
import { Novu } from '@novu/node';

const novu = new Novu('<NOVU_API_KEY>');

await novu.organizations.rename({ name: 'Renamed Organization' });
```

- #### Get current organization details

```ts
import { Novu } from '@novu/node';

const novu = new Novu('<NOVU_API_KEY>');

await novu.organizations.getCurrent();
```

- #### Remove member from organization

```ts
import { Novu } from '@novu/node';

const novu = new Novu('<NOVU_API_KEY>');

await novu.organizations.removeMember('memberId');
```

- #### Update organization member role

```ts
import { Novu } from '@novu/node';

const novu = new Novu('<NOVU_API_KEY>');

await novu.organizations.updateMemberRole('memberId', {
role: 'admin';
});
```

- #### Get all members of organization

```ts
import { Novu } from '@novu/node';

const novu = new Novu('<NOVU_API_KEY>');

await novu.organizations.getMembers();
```

- #### Update organization branding details

```ts
import { Novu } from '@novu/node';

const novu = new Novu('<NOVU_API_KEY>');

await novu.organizations.updateBranding({
logo: 'https://s3.us-east-1.amazonaws.com/bucket/image.jpeg',
color: '#000000',
fontFamily: 'Lato',
};);
```
1 change: 1 addition & 0 deletions packages/node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ export * from './lib/feeds/feeds.interface';
export * from './lib/topics/topic.interface';
export * from './lib/integrations/integrations.interface';
export * from './lib/messages/messages.interface';
export * from './lib/organizations/organizations.interface';
export { defaultRetryCondition } from './lib/retry';
3 changes: 3 additions & 0 deletions packages/node/src/lib/novu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Topics } from './topics/topics';
import { Integrations } from './integrations/integrations';
import { Messages } from './messages/messages';
import { Tenants } from './tenants/tenants';
import { Organizations } from './organizations/organizations';
import { makeRetryable } from './retry';

export class Novu extends EventEmitter {
Expand All @@ -30,6 +31,7 @@ export class Novu extends EventEmitter {
readonly integrations: Integrations;
readonly messages: Messages;
readonly tenants: Tenants;
readonly organizations: Organizations;

constructor(apiKey: string, config?: INovuConfiguration) {
super();
Expand Down Expand Up @@ -59,6 +61,7 @@ export class Novu extends EventEmitter {
this.integrations = new Integrations(this.http);
this.messages = new Messages(this.http);
this.tenants = new Tenants(this.http);
this.organizations = new Organizations(this.http);

this.trigger = this.events.trigger;
this.bulkTrigger = this.events.bulkTrigger;
Expand Down
34 changes: 34 additions & 0 deletions packages/node/src/lib/organizations/organizations.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export interface IOrganizations {
list();
create(payload: IOrganizationCreatePayload);
rename(payload: IOrganizationRenamePayload);
getCurrent();
removeMember(memberId: string);
updateMemberRole(
memberId: string,
payload: IOrganizationUpdateMemberRolePayload
);
getMembers();
updateBranding(payload: IOrganizationBrandingPayload);
}

export interface IOrganizationCreatePayload {
name: string;
logo?: string;
}

export interface IOrganizationRenamePayload {
name: string;
}

export interface IOrganizationUpdateMemberRolePayload {
role: string;
}

export interface IOrganizationBrandingPayload {
logo: string;
color: string;
fontColor?: string;
contentBackground?: string;
fontFamily: string;
}
170 changes: 170 additions & 0 deletions packages/node/src/lib/organizations/organizations.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { Novu } from '../novu';
import axios from 'axios';

const mockConfig = {
apiKey: '1234',
};

const mockedOrganization = {
_id: '649070af750b25b4ac8a4704',
__v: 0,
name: 'Test Organization',
branding: {
logo: 'https://s3.us-east-1.amazonaws.com/bucket/key.jpeg',
color: '#ff5517',
fontFamily: 'Lato',
},
createdAt: '2023-06-19T15:13:51.961Z',
updatedAt: '2023-06-19T15:13:51.966Z',
};

const mockedMember = {
_id: '649070af750b25b4ac8a4759',
memberStatus: 'active',
_userId: '649070afaa9e50289df420d8',
roles: ['admin'],
_organizationId: mockedOrganization._id,
createdAt: mockedOrganization.createdAt,
updatedAt: mockedOrganization.createdAt,
__v: 0,
id: '649070af750b25b4ac8a4759',
user: {
_id: '649070afaa9e50289df420d8',
firstName: 'john',
lastName: 'doe',
email: '[email protected]',
profilePicture:
'https://gravatar.com/avatar/fd876f8cd6a58277fc664d47ea10ad19?d=mp',
createdAt: '2023-03-07T13:32:54.573Z',
id: '649070afaa9e50289df420d8',
},
};

jest.mock('axios');

describe('Novu Node.js package - Organizations class', () => {
const mockedAxios = axios as jest.Mocked<typeof axios>;
let novu: Novu;

const methods = ['get', 'post', 'put', 'delete', 'patch'];

beforeEach(() => {
mockedAxios.create.mockReturnThis();
novu = new Novu(mockConfig.apiKey);
});

afterEach(() => {
methods.forEach((method) => {
mockedAxios[method].mockClear();
});
});

it('should list organizations', async () => {
const mockedResponse = {
data: [mockedOrganization],
};
mockedAxios.get.mockResolvedValue(mockedResponse);

const result = await novu.organizations.list();

expect(mockedAxios.get).toBeCalled();
expect(result).toStrictEqual(mockedResponse);
});

it('should create new organization', async () => {
const organizationName = 'New Organization';
const mockedResponse = {
data: {
...mockedOrganization,
name: organizationName,
},
};
mockedAxios.post.mockResolvedValue(mockedResponse);

const payload = { name: organizationName };
const result = await novu.organizations.create(payload);

expect(mockedAxios.post).toBeCalledWith('/organizations', payload);
expect(result).toStrictEqual(mockedResponse);
});

it('should rename current organization', async () => {
const newName = 'Renamed Organization';
const mockedResponse = {
data: {
name: newName,
},
};
mockedAxios.patch.mockResolvedValue(mockedResponse);

const payload = { name: newName };
const result = await novu.organizations.rename(payload);

expect(result).toStrictEqual(mockedResponse);
expect(mockedAxios.patch).toBeCalledWith('/organizations', payload);
});

it('should fetch current organization', async () => {
const mockedResponse = { data: mockedOrganization };
mockedAxios.get.mockResolvedValue(mockedResponse);

const result = await novu.organizations.getCurrent();

expect(result).toStrictEqual(mockedResponse);
expect(mockedAxios.get).toBeCalledWith('/organizations/me');
});

it('should remove member from current organization', async () => {
const mockedResponse = { data: mockedMember };
mockedAxios.delete.mockResolvedValue(mockedResponse);

const result = await novu.organizations.removeMember(mockedMember.id);

expect(result).toStrictEqual(mockedResponse);
expect(mockedAxios.delete).toBeCalledWith(
`/organizations/members/${mockedMember.id}`
);
});

it('should update member role in current organization', async () => {
const mockedResponse = { data: mockedMember };
mockedAxios.put.mockResolvedValue(mockedResponse);

const payload = { role: 'admin' };
const result = await novu.organizations.updateMemberRole(
mockedMember.id,
payload
);

expect(result).toStrictEqual(mockedResponse);
expect(mockedAxios.put).toBeCalledWith(
`/organizations/members/${mockedMember.id}/roles`,
payload
);
});

it('should fetch all members of current organization', async () => {
const mockedResponse = { data: [mockedMember] };
mockedAxios.get.mockResolvedValue(mockedResponse);

const result = await novu.organizations.getMembers();

expect(result).toStrictEqual(mockedResponse);
expect(mockedAxios.get).toBeCalledWith('/organizations/members');
});

it('should update branding details of current organization', async () => {
const payload = {
logo: 'https://s3.us-east-1.amazonaws.com/bucket/key.jpeg',
color: '#000000',
fontFamily: 'Lato',
};
const mockedResponse = { data: payload };
mockedAxios.put.mockResolvedValue(mockedResponse);

const result = await novu.organizations.updateBranding(payload);

expect(result).toStrictEqual(mockedResponse);
expect(mockedAxios.put).toBeCalledWith('/organizations/branding', payload);
});
});
45 changes: 45 additions & 0 deletions packages/node/src/lib/organizations/organizations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { WithHttp } from '../novu.interface';
import {
IOrganizations,
IOrganizationCreatePayload,
IOrganizationRenamePayload,
IOrganizationUpdateMemberRolePayload,
IOrganizationBrandingPayload,
} from './organizations.interface';

export class Organizations extends WithHttp implements IOrganizations {
list() {
return this.http.get('/organizations');
}

create(payload: IOrganizationCreatePayload) {
return this.http.post('/organizations', payload);
}

rename(payload: IOrganizationRenamePayload) {
return this.http.patch('/organizations', payload);
}

getCurrent() {
return this.http.get('/organizations/me');
}

removeMember(memberId: string) {
return this.http.delete(`/organizations/members/${memberId}`);
}

updateMemberRole(
memberId: string,
payload: IOrganizationUpdateMemberRolePayload
) {
return this.http.put(`/organizations/members/${memberId}/roles`, payload);
}

getMembers() {
return this.http.get('/organizations/members');
}

updateBranding(payload: IOrganizationBrandingPayload) {
return this.http.put('/organizations/branding', payload);
}
}

0 comments on commit 34dad81

Please sign in to comment.