Skip to content

Commit

Permalink
feat(aircall): add user operations (#77)
Browse files Browse the repository at this point in the history
## Describe your changes
Add user operations for aircall-basic. Depends on
NangoHQ/nango#2907

## Issue ticket number and link

## Checklist before requesting a review (skip if just adding/editing
APIs & templates)
- [x] I added tests, otherwise the reason is:
- [x] External API requests have `retries`
- [x] Pagination is used where appropriate
- [x] The built in `nango.paginate` call is used instead of a `while
(true)` loop
- [x] Third party requests are NOT parallelized (this can cause issues
with rate limits)
- [ ] If a sync requires metadata the `nango.yaml` has `auto_start:
false`
- [ ] If the sync is a `full` sync then `track_deletes: true` is set
  • Loading branch information
khaliqgant authored Oct 28, 2024
1 parent 12c0218 commit 9ffe0c9
Show file tree
Hide file tree
Showing 46 changed files with 907 additions and 193 deletions.
37 changes: 36 additions & 1 deletion flows.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,39 @@
integrations:
aircall-basic:
actions:
create-user:
description: Creates a user in Aircall.
output: User
endpoint: POST /users
input: CreateUser
delete-user:
description: Deletes a user in Aircall
endpoint: DELETE /users
output: SuccessResponse
input: IdEntity
syncs:
users:
runs: every day
description: |
Fetches a list of users from Aircall.
output: User
track_deletes: true
sync_type: full
endpoint: GET /users
models:
SuccessResponse:
success: boolean
IdEntity:
id: string
User:
id: string
email: string
firstName: string
lastName: string
CreateUser:
firstName: string
lastName: string
email: string
airtable:
syncs:
tables:
Expand Down Expand Up @@ -1996,7 +2031,7 @@ integrations:
description?: string
ExactInvoiceCreateInput:
customerId: string
journal: number
journal?: number
currency?: EUR
description?: string
createdAt?: date
Expand Down
43 changes: 43 additions & 0 deletions integrations/aircall-basic/actions/create-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { NangoAction, ProxyConfiguration, CreateUser, User } from '../../models';
import { createUserSchema } from '../schema.zod.js';
import type { AircallUser } from '../types';

export default async function runAction(nango: NangoAction, input: CreateUser): Promise<User> {
const parsedInput = createUserSchema.safeParse(input);

if (!parsedInput.success) {
for (const error of parsedInput.error.errors) {
await nango.log(`Invalid input provided to create a user: ${error.message} at path ${error.path.join('.')}`, { level: 'error' });
}
throw new nango.ActionError({
message: 'Invalid input provided to create a user'
});
}

const aInput = {
email: input.email,
first_name: input.firstName,
last_name: input.lastName
};

const config: ProxyConfiguration = {
// https://developer.aircall.io/api-references/#create-a-user
endpoint: '/v1/users',
data: aInput,
retries: 10
};

const response = await nango.post<{ user: AircallUser }>(config);

const { data } = response;

const [firstName, lastName] = data.user.name.split(' ');
const user: User = {
id: data.user.id.toString(),
email: data.user.email,
firstName: firstName || '',
lastName: lastName || ''
};

return user;
}
21 changes: 21 additions & 0 deletions integrations/aircall-basic/actions/delete-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { NangoAction, ProxyConfiguration, SuccessResponse, IdEntity } from '../../models';

export default async function runAction(nango: NangoAction, input: IdEntity): Promise<SuccessResponse> {
if (!input || !input.id) {
throw new nango.ActionError({
message: 'Id is required'
});
}

const config: ProxyConfiguration = {
// https://developer.aircall.io/api-references/#delete-a-user
endpoint: `/v1/users/${input.id}`,
retries: 10
};

await nango.delete(config);

return {
success: true
};
}
5 changes: 5 additions & 0 deletions integrations/aircall-basic/fixtures/create-user.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"firstName": "John",
"lastName": "Doe",
"email": "johndoe@test.dev"
}
5 changes: 5 additions & 0 deletions integrations/aircall-basic/mocks/create-user/input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"firstName": "John",
"lastName": "Doe",
"email": "johndoe@test.dev"
}
6 changes: 6 additions & 0 deletions integrations/aircall-basic/mocks/create-user/output.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"id": "1435616",
"email": "johndoe@test.dev",
"firstName": "John",
"lastName": "Doe"
}
3 changes: 3 additions & 0 deletions integrations/aircall-basic/mocks/delete-user/input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"id": "1435616"
}
3 changes: 3 additions & 0 deletions integrations/aircall-basic/mocks/delete-user/output.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"success": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
""
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"users": [
{
"id": 1434499,
"direct_link": "https://api.aircall.io/v1/users/1434499",
"name": "Johnny Doeseph",
"email": "johnny@some-company.com",
"available": false,
"availability_status": "available",
"created_at": "2024-10-25T12:59:36.000Z",
"time_zone": "Etc/UTC",
"language": "en-US",
"state": "always_opened",
"wrap_up_time": 0
},
{
"id": 1435616,
"direct_link": "https://api.aircall.io/v1/users/1435616",
"name": "John Doe",
"email": "johndoe@test.dev",
"available": false,
"availability_status": "available",
"created_at": "2024-10-28T09:24:52.000Z",
"time_zone": "Etc/UTC",
"language": "en-US",
"state": "always_opened",
"wrap_up_time": 0
}
],
"meta": {
"count": 2,
"total": 2,
"current_page": 1,
"per_page": 20,
"next_page_link": null,
"previous_page_link": null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"user": {
"id": 1435616,
"direct_link": "https://api.aircall.io/v1/users/1435616",
"name": "John Doe",
"email": "johndoe@test.dev",
"available": false,
"availability_status": "available",
"created_at": "2024-10-28T09:24:52.000Z",
"time_zone": "Etc/UTC",
"language": "en-US",
"state": "always_opened",
"wrap_up_time": 0,
"default_number_id": null,
"numbers": [],
"substatus": "always_opened"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
14 changes: 14 additions & 0 deletions integrations/aircall-basic/mocks/users/User/batchSave.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[
{
"id": "1434499",
"firstName": "Johnny",
"lastName": "Doeseph",
"email": "johnny@some-company.com"
},
{
"id": "1435616",
"firstName": "John",
"lastName": "Doe",
"email": "johndoe@test.dev"
}
]
40 changes: 40 additions & 0 deletions integrations/aircall-basic/nango.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
integrations:
aircall-basic:
actions:
create-user:
description: Creates a user in Aircall.
output: User
endpoint: POST /users
input: CreateUser
delete-user:
description: Deletes a user in Aircall
endpoint: DELETE /users
output: SuccessResponse
input: IdEntity
syncs:
users:
runs: every day
description: |
Fetches a list of users from Aircall.
output: User
track_deletes: true
sync_type: full
endpoint: GET /users
models:
# Generic
SuccessResponse:
success: boolean
IdEntity:
id: string

# User
User:
id: string
email: string
firstName: string
lastName: string

CreateUser:
firstName: string
lastName: string
email: string
23 changes: 23 additions & 0 deletions integrations/aircall-basic/schema.zod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Generated by ts-to-zod
import { z } from 'zod';

export const successResponseSchema = z.object({
success: z.boolean()
});

export const idEntitySchema = z.object({
id: z.string()
});

export const userSchema = z.object({
id: z.string(),
email: z.string(),
firstName: z.string(),
lastName: z.string()
});

export const createUserSchema = z.object({
firstName: z.string(),
lastName: z.string(),
email: z.string()
});
29 changes: 29 additions & 0 deletions integrations/aircall-basic/syncs/users.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { NangoSync, ProxyConfiguration, User } from '../../models';
import type { AircallUser } from '../types';

export default async function fetchData(nango: NangoSync) {
const config: ProxyConfiguration = {
// https://developer.aircall.io/api-references/#list-all-users
endpoint: '/v1/users',
retries: 10,
paginate: {
response_path: 'users'
}
};

for await (const aUsers of nango.paginate<AircallUser>(config)) {
const users: User[] = aUsers.map((aUser: AircallUser) => {
const [firstName, lastName] = aUser.name.split(' ');
const user: User = {
id: aUser.id.toString(),
firstName: firstName || '',
lastName: lastName || '',
email: aUser.email
};

return user;
});

await nango.batchSave(users, 'User');
}
}
19 changes: 19 additions & 0 deletions integrations/aircall-basic/tests/aircall-basic-create-user.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { vi, expect, it, describe } from "vitest";

import runAction from "../actions/create-user.js";

describe("aircall-basic create-user tests", () => {
const nangoMock = new global.vitest.NangoActionMock({
dirname: __dirname,
name: "create-user",
Model: "User"
});

it('should output the action output that is expected', async () => {
const input = await nangoMock.getInput();
const response = await runAction(nangoMock, input);
const output = await nangoMock.getOutput();

expect(response).toEqual(output);
});
});
19 changes: 19 additions & 0 deletions integrations/aircall-basic/tests/aircall-basic-delete-user.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { vi, expect, it, describe } from "vitest";

import runAction from "../actions/delete-user.js";

describe("aircall-basic delete-user tests", () => {
const nangoMock = new global.vitest.NangoActionMock({
dirname: __dirname,
name: "delete-user",
Model: "SuccessResponse"
});

it('should output the action output that is expected', async () => {
const input = await nangoMock.getInput();
const response = await runAction(nangoMock, input);
const output = await nangoMock.getOutput();

expect(response).toEqual(output);
});
});
Loading

0 comments on commit 9ffe0c9

Please sign in to comment.