Skip to content

Commit

Permalink
feat(perimeter81): Add users endpoints to Perimeter81 integration (#81)
Browse files Browse the repository at this point in the history
## Describe your changes
Add Perimeter81 integration with the following endpoints:

### Syncs
- users

### Actions
- create-user
- delete-user

**Important Note: No fixtures, nor tests/mocks were generated. I could
not test any of the syncs and actions added to the Perimeter81
integration as it requires an enterprise account, which is not
accessible to me. Hence, once a connection is setup in Nango, these
endpoints need to be tested and validated**

## Issue ticket number and link
N/A

## Checklist before requesting a review (skip if just adding/editing
APIs & templates)
- [ ] I added tests, otherwise the reason is: I could not test any of
the syncs and actions added to the Perimeter81 integration as it
requires an enterprise account, which is not accessible to me. Hence,
once a connection is setup in Nango, these endpoints need to be tested
and validated
- [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
- [ ] 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`
- [X] If the sync is a `full` sync then `track_deletes: true` is set

---------

Co-authored-by: Khaliq <khaliqgant@gmail.com>
Co-authored-by: Khaliq <khaliq@nango.dev>
  • Loading branch information
3 people authored Oct 31, 2024
1 parent deff79d commit 5d68ba9
Show file tree
Hide file tree
Showing 8 changed files with 307 additions and 0 deletions.
49 changes: 49 additions & 0 deletions flows.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5620,6 +5620,55 @@ integrations:
DocumentInput:
threadId: string
attachmentId: string
perimeter81:
actions:
create-user:
description: Creates a user in Perimeter81
input: Perimeter81CreateUser
endpoint: POST /users
output: User
delete-user:
description: Deletes a user in Perimeter81
endpoint: DELETE /users
output: SuccessResponse
input: IdEntity
syncs:
users:
description: |
Fetches the list of users from Perimeter81
endpoint: GET /users
sync_type: full
track_deletes: true
runs: every day
output: User
models:
IdEntity:
id: string
SuccessResponse:
success: boolean
User:
id: string
email: string
firstName: string
lastName: string
CreateUser:
firstName: string
lastName: string
email: string
Perimeter81CreateUser:
firstName: string
lastName: string
email: string
idpType?: string
accessGroups?: string[]
emailVerified?: boolean
inviteMessage?: string
origin?: string
profileData?:
roleName?: string
phone?: string
icon?: string
origin?: string
pipedrive:
syncs:
activities:
Expand Down
56 changes: 56 additions & 0 deletions integrations/perimeter81/actions/create-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { NangoAction, ProxyConfiguration, User, Perimeter81CreateUser } from '../../models';
import { toUser } from '../mappers/to-user.js';
import { perimeter81CreateUserSchema } from '../schema.zod.js';
import type { Perimeter81User } from '../types';

/**
* Creates an Perimeter81 user.
*
* This function validates the input against the defined schema and constructs a request
* to the Perimeter81 API to create a new user. If the input is invalid, it logs the
* errors and throws an ActionError.
*
* @param {NangoAction} nango - The Nango action context, used for logging and making API requests.
* @param {Perimeter81CreateUser} input - The input data for creating a user contact
*
* @returns {Promise<User>} - A promise that resolves to the created User object.
*
* @throws {nango.ActionError} - Throws an error if the input validation fails.
*
* For detailed endpoint documentation, refer to:
* https://support.perimeter81.com/docs/post-new-member
*/
export default async function runAction(nango: NangoAction, input: Perimeter81CreateUser): Promise<User> {
const parsedInput = perimeter81CreateUserSchema.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 { firstName, lastName, profileData = {}, ...data } = parsedInput.data;

const config: ProxyConfiguration = {
// https://support.perimeter81.com/docs/post-new-member
endpoint: `/v1/users`,
data: {
...data,
inviteMessage: parsedInput.data.inviteMessage || 'Welcome to the team!',
profileData: {
...profileData,
firstName,
lastName
}
},
retries: 10
};

const response = await nango.post<Perimeter81User>(config);

return toUser(response.data);
}
46 changes: 46 additions & 0 deletions integrations/perimeter81/actions/delete-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { NangoAction, ProxyConfiguration, SuccessResponse, IdEntity } from '../../models';
import { idEntitySchema } from '../schema.zod.js';

/**
* Deletes a Perimeter81 user.
*
* This function validates the input against the defined schema and constructs a request
* to the Perimeter81 API to delete a user by their ID. If the input is invalid,
* it logs the errors and throws an ActionError.
*
* @param {NangoAction} nango - The Nango action context, used for logging and making API requests.
* @param {IdEntity} input - The input data containing the ID of the user contact to be deleted
*
* @returns {Promise<SuccessResponse>} - A promise that resolves to a SuccessResponse object indicating the result of the deletion.
*
* @throws {nango.ActionError} - Throws an error if the input validation fails.
*
* For detailed endpoint documentation, refer to:
* https://support.perimeter81.com/docs/delete-delete-user
*/
export default async function runAction(nango: NangoAction, input: IdEntity): Promise<SuccessResponse> {
const parsedInput = idEntitySchema.safeParse(input);

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

throw new nango.ActionError({
message: 'Invalid input provided to delete a user'
});
}

const config: ProxyConfiguration = {
// https://support.perimeter81.com/docs/delete-delete-user
endpoint: `/v1/users/${parsedInput.data.id}`,
retries: 10
};

// no body content expected for successful requests
await nango.delete(config);

return {
success: true
};
}
17 changes: 17 additions & 0 deletions integrations/perimeter81/mappers/to-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { User } from '../../models';
import type { Perimeter81User } from '../types';

/**
* Maps a Perimeter81 API user object to a Nango User object.
*
* @param perimeter81User The raw contact object from the Perimeter81 API.
* @returns Mapped User object with essential properties.
*/
export function toUser(perimeter81User: Perimeter81User): User {
return {
id: perimeter81User.id,
email: perimeter81User.email,
firstName: perimeter81User.firstName,
lastName: perimeter81User.lastName
};
}
48 changes: 48 additions & 0 deletions integrations/perimeter81/nango.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
integrations:
perimeter81:
actions:
create-user:
description: Creates a user in Perimeter81
input: Perimeter81CreateUser
endpoint: POST /users
output: User
delete-user:
description: Deletes a user in Perimeter81
endpoint: DELETE /users
output: SuccessResponse
input: IdEntity
syncs:
users:
description: |
Fetches the list of users from Perimeter81
endpoint: GET /users
sync_type: full
track_deletes: true
runs: every day
output: User
models:
IdEntity:
id: string
SuccessResponse:
success: boolean
User:
id: string
email: string
firstName: string
lastName: string
CreateUser:
firstName: string
lastName: string
email: string
Perimeter81CreateUser:
__extends: CreateUser
idpType?: string
accessGroups?: string[]
emailVerified?: boolean
inviteMessage?: string
origin?: string
profileData?:
roleName?: string
phone?: string
icon?: string
origin?: string
42 changes: 42 additions & 0 deletions integrations/perimeter81/schema.zod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Generated by ts-to-zod
import { z } from 'zod';

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

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

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()
});

export const perimeter81CreateUserSchema = z.object({
firstName: z.string(),
lastName: z.string(),
email: z.string(),
idpType: z.string().optional(),
accessGroups: z.array(z.string()).optional(),
emailVerified: z.boolean().optional(),
inviteMessage: z.string().optional(),
origin: z.string().optional(),
profileData: z
.object({
roleName: z.string().optional(),
phone: z.string().optional(),
icon: z.string().optional(),
origin: z.string().optional()
})
.optional()
});
39 changes: 39 additions & 0 deletions integrations/perimeter81/syncs/users.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { NangoSync, ProxyConfiguration, User } from '../../models';
import { toUser } from '../mappers/to-user.js';
import type { Perimeter81User } from '../types';

/**
* Fetches Perimeter81 users, maps them to Nango User objects,
* and saves the processed contacts using NangoSync.
*
* This function handles pagination and ensures that all contacts are fetched,
* transformed, and stored.
*
* For endpoint documentation, refer to:
* https://support.perimeter81.com/docs/get-list-users
*
* @param nango An instance of NangoSync for synchronization tasks.
* @returns Promise that resolves when all users are fetched and saved.
*/
export default async function fetchData(nango: NangoSync): Promise<void> {
const config: ProxyConfiguration = {
// https://support.perimeter81.com/docs/get-list-users
endpoint: '/v1/users',
paginate: {
type: 'offset',
offset_name_in_request: 'page',
offset_start_value: 1,
offset_calculation_method: 'per-page',
limit_name_in_request: 'limit',
response_path: 'data',
limit: 100
},
retries: 10
};

for await (const perimeter81Users of nango.paginate<Perimeter81User>(config)) {
const users = perimeter81Users.map(toUser);

await nango.batchSave<User>(users, 'User');
}
}
10 changes: 10 additions & 0 deletions integrations/perimeter81/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface Perimeter81User {
terminated: boolean;
email: string;
emailVerified: boolean;
initials: string;
roleName: string;
lastName: string;
firstName: string;
id: string;
}

0 comments on commit 5d68ba9

Please sign in to comment.